House Of Force

本文最后更新于 2025年12月30日 凌晨

参考

CTF-WIKI

前置知识

malloc请求大小与实际大小

  1. 请求大小我们用req来标识
  2. 实际大小我们用nb来标识
  3. malloc(req)后req会转为无符号型整数
    • void *__libc_malloc(size_t bytes)
  4. 请求大小与实际大小的转换
    • REQUEST_OUT_OF_RANGE(req)越界检查
    • request2size(req)实现转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
Check if a request is so large that it would wrap around zero when
padded and aligned. To simplify some other code, the bound is made
low enough so that adding MINSIZE will also not wrap around zero.
*/

#define REQUEST_OUT_OF_RANGE(req) \
((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE))
/* pad request bytes into a usable size -- internal version */
//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) \
? MINSIZE \
: ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/* Same, except also perform argument check */

#define checked_request2size(req, sz) \
if (REQUEST_OUT_OF_RANGE(req)) { \
__set_errno(ENOMEM); \
return 0; \
} \
(sz) = request2size(req);

REQUEST_OUT_OF_RANGE(req)越界检查

越界检查很好理解,就是我们req的大小不能大于(unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE)

(unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE)

  • x86_32:MINSIZE = 0x10 所以边界为 0xffffffe0
  • x86_64:MINSIZE = 0x20 所以边界为 0xffffffffffffffc0

也就是说我们的req要小于边界大小即可

request2size(req)实现转换

核心

  • MINSIZE:最小堆的大小
    • prev_size
    • size
    • fd
    • bk
  • MALLOC_ALIGN_MASK:用来对齐的x86_64下对齐0x10 x86_32下对齐0x8,也可以称为对齐冗余
  • SIZE_SZ:size字段的大小
    • 为什么没有prev_size呢,因为在实际chunk中prev_size是归属于物理相邻的低地址chunk的
1
2
3
4
5
//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) \
? MINSIZE \
: ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

转换流程

  1. malloc(req)
  2. nb = MINSIZE : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

举个例子[x86_64]

正数

req = 0x10

(req) + SIZE_SZ + MALLOC_ALIGN_MASK = 0x27 > MINSIZE

那么nb = 0x20 [((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)]

负数

req = -0x10

-0x10 > -0x40[-2 * MINSIZE] –> 拒绝申请

req = -0x60 == 0xffffffffffffffa0

-0x60 < -0x40[-2 * MINSIZE]

(req) + SIZE_SZ + MALLOC_ALIGN_MASK = 0xffffffffffffffb7 > MINSIZE

nb = 0xffffffffffffffb0 [((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)]

可以看到req和nb之间的差别在于SIZE_SZ + 对齐

那么我们要求req == nb呢(这个很重要,关乎于我们控制top_chunk)

只要提前 - (SIZE_SZ + MALLOC_ALIGN_MASK),并保证对齐即可

malloc(req)与top_chunk

给出场景top_chunk = 0x3955020

req = 0x10[Add(0x10)];nb = 0x20

top_chunk = 0x3955040

0x3955040 - 0x3955020 = 0x20 == nb

那么也就是说当我们malloc(req)的时候top_chunk -= nb

Exploit条件

  1. 能够以溢出等方式控制到 top chunk 的 size 域
  2. 能够自由地控制堆分配尺寸的大小

top_chunk的地址存放在main_arena+0x58(88)上,也就是我们Unsorted bin leak出来的地址

无Top chunk

通过req来控制我们Top chunk的位置

  • malloc(req)
  • nb = request2size(req):依据需求对req进行手动转换nb,避免更新Top chunk地址错误
  • Top chunk -= nb

局限

  • Top chunk的地址不可控

知晓Top chunk

通过计算Top chunk与目标地址(Tag_Address), 实现在目的地址附近创建堆块

  • offset = Tag_Address - Top chunk - (SIZE_SZ * 2)

    • (SIZE_SZ * 2) : 预留prev_size size
  • malloc(offset):依据需求对offset进行手动转换nb,避免更新Top chunk地址错误

    • 注意size域会被更新
  • malloc(req):获取Tag_Address堆块


House Of Force
https://tforevery.github.io/PWN/HEAP/House-Of-Force/
作者
TY
发布于
2025年12月24日
许可协议