House of Orange
我不中了我放弃了我低头了
freerabbit
chunk
https://www.yuque.com/xswlhhh/ctf/dn1h48dsw8fd6apm?language=zh-cn#
在程序的执行过程中,我们称由 malloc 申请的内存为 chunk 。这块内存在 ptmalloc 内部用 malloc_chunk 结构体来表示。当程序申请的 chunk 被 free 后,会被加入到相应的空闲管理列表中。非常无论一个 chunk 的大小如何,处于分配状态还是释放状态,它们都使用一个统一的结构。虽然它们使用了同一个数据结构,但是根据是否被释放,它们的表现形式会有所不同。
malloc_chunk的结构
1 | struct malloc_chunk { |
chunk 处于分配状态时,从 fd 字段开始是用户的数据。
也就是相当于prev_size(8字节)+ size(8字节)+ data
GLIBC 为了节省空间,把 size 字段的最后 3 位借用来当标志位(因为块大小永远是 16 的倍数,最后 4 位二进制永远是 0,空着也是浪费)。
- 第 0 位 (PREV_INUSE): 值为
1时,表示前一个块正在被使用。 - 第 1 位 (IS_MMAPPED): 表示这个块是不是通过
mmap分配的。 - 第 2 位 (NON_MAIN_ARENA): 表示这个块是否属于主分配区。
当你看到 0x21 时:
0x20是真实的物理大小(32 字节)。+ 1是因为前一个块(即 Intro 块)正在被使用。- 所以 0x20+1=0x21。
Top Chunk
程序第一次进行 malloc 的时候,heap 会被分为两块,一块给用户,剩下的那块就是 top chunk。其实,所谓的 top chunk 就是处于当前堆的物理地址最高的 chunk。这个 chunk 不属于任何一个 bin,它的作用在于当所有的 bin 都无法满足用户请求的大小时,如果其大小不小于指定的大小,就进行分配,并将剩下的部分作为新的 top chunk。否则,就对 heap 进行扩展后再进行分配。在 main arena 中通过 sbrk 扩展 heap,而在 thread arena 中通过 mmap 分配新的 heap。需要注意的是,top chunk 的 prev_inuse 比特位始终为 1,否则其前面的 chunk 就会被合并到 top chunk 中。初始情况下,我们可以将 unsorted chunk 作为 top chunk。
main_arena
main_arena 是 Libc 里的一个结构体,负责管理所有的堆块。它里面有一个数组叫 bins,存放着各种空闲链表的表头。
在 GLIBC 2.23 中,bins 数组的第一个元素就是 Unsorted Bin。
main_arena的起始地址到Unsorted Bin表头位置的距离刚好是 88 字节。
- 当你修改 Top Chunk Size 为
0xbb1并申请0xc00时,malloc发现不够分,于是调用sysmalloc。 sysmalloc会将这个旧的、被缩减的 Top Chunk “丢进” Unsorted Bin。- 在双向链表中,如果链表是空的,新加入的块的
fd(前向指针)和bk(后向指针)都会指向链表的管理者(也就是main_arena里的那个表头位置)。
结论: 此时旧 Top Chunk 的数据区(Addr + 0x10)里就躺着 main_arena + 88 的真实地址。因为 main_arena 在 Libc 内部,你只要读出这个地址,减去固定的偏移,就能算出 Libc 的基地址。

这是一道典型的 Heap(堆) 类型题目,主要考察在没有 free 函数的情况下,如何利用**堆溢出(Heap Overflow)**实现泄露地址(Leak)和获取 Shell(Exploit)。
fastbin/tcache需要free函数
泄露libc基址:House of Orange
改写内存:House of Force
需要的信息:当前 Top Chunk 的地址,目标地址,或者偏移
步骤:
利用 House of Orange 泄露 Libc,算出 __malloc_hook 的地址。
利用 House of Force 强行申请到 __malloc_hook 所在的内存。
把 system 的地址写入 __malloc_hook。
下一次程序调用 malloc 时(比如 coffee2 里的 malloc(0x10)),它就会自动变成 system("/bin/sh")。
其他保护措施:
新建和修改的次数有限(n2 n3计数)
程序运行 30 秒后会自动自杀,会报Kami sama is angry!
获取libc版本
1 | strings libc.so.6 | grep GLIBC |
找到类似于这样的一行。这题是2.23
1 | GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.2) stable release version 2.23 |
coffee1,新建一杯咖啡
有三次malloc操作,一次存intro的长度(大小固定是16),一次存intro,一次存name(大小固定是16)。intro长度不能超过4096
1 | qword_202020 = (__int64)malloc(0x10u); |
查看top chunk地址
需要在gdb中设置glibc版本,然后让程序运行并至少malloc一个块,然后输入heap
断点要用rebase(b *($rebase(0xa47)))

top chunk地址是0x5555556032f0
申请一杯新咖啡之后,第二个chunk是存introduction的,第三个chunk是存name的
Intro 数据区开始: 0x5555556032c0 (Chunk 地址 + 0x10 Header)。
Top Chunk Size 位置: 0x5555556032f8 (Top Chunk 地址 + 0x8)。
所以padding长度是0x38
coffee3,也就是修改选项对应的函数中存在越界写漏洞。指针还是指向之前分配好的coffee,但是可以修改introduction的长度,并且没有检查有没有超过之前分配的大小
1 | sub_9A6(*(_QWORD *)qword_202020, 16); |
第一步改小top chunk。
1 | from pwn import * |
倒是改小了,但是top chunk直接没了。说是因为gdb解析最后一个chunk,发现这个size太大了,超出了内存范围,就不继续看了。然后系统检查下一个块在哪,发现下一个块地址都非法了。所以说溢出的时候不要过于破坏name那个chunk了,顺手修一修

把name块的size部分修一下,能识别出来了。top chunk的size也改好了

第二步申请大空间,泄露libc
这个错误是因为没有满足页对齐检查,需要满足:
1 | (Top Chunk 起始地址+Top Chunk Size)(mod0x1000)==0 |

所以说笛老大的top chunk的size也不是随便设的,应该是为了好对齐(额啊啊啊啊啊好麻烦)
我这的top chunk地址是 0x556b0a03d470,算一下size就是0x1000 - (0x470)。对齐之后改成功了

这是想作甚呢
1 | /build/gdb-Dh0pdX/gdb-12.1/gdb/nat/x86-linux-dregs.c:146: internal-error: x86_linux_update_debug_registers: Assertion `lwp_is_stopped (lwp)' failed. |

1 | /build/gdb-Dh0pdX/gdb-12.1/gdb/nat/x86-linux-dregs.c:146: internal-error: x86_linux_update_debug_registers: Assertion `lwp_is_stopped (lwp)' failed. |