71's blog

宏願縱未了 奮鬥總不太晚

0%

uClibc | embedded_heap-0ctf2019final

有点老的题目——19年的 house of prime。算是 MIPS 入门题。环境为 uLibc,是用于嵌入式系统和移动设备的基于 Linux 内核的操作系统(摘自wiki)。说白了就是小型设备都用它,我理解为精简版的 glibc,有一点点变化,但相似度还是蛮高的en,但是 qemu user调着好像有点问题,先鸽一会会😭😰,后续补上

参考链接(🙏):

题目分析

chunk 结构大致如下:

程序一开始使用上面的函数分配了若干个(随机数)chunk,大小也是 0~0x100 随机。接着打印出每个 chunk 的 content 大小(非实际 chunk 大小)。

菜单题:

  • update:往已分配的 chunk 中(+8 ptr 位置)写任意 size 的 content。
  • view:打印 content 处 size 个字符。
  • pwn:执行完后退出程序。其中 delete 两次,update 一次。

只有一个堆溢出漏洞,但足以使 chunk 的所有字段可控。

接下来看题目给的 libc 和 ld 文件。显然,使用的是 uClibc 的 0.9.33.2 版本。但是这里给了四个文件,不难看出它们之间的链接关系:

我们试着用 qemu 运行一下,发现在 lib 中有两套链接的情况下,默认选择的是 ld-uClibc.so.0。但是报错说,文件架构不匹配。

1
2
ch331n@ubuntu:~/cs/practices/embedded_heap$ qemu-mips -L ./ ./embedded_heap
/lib/ld-uClibc.so.0: Invalid ELF image for this architecture

也许真正匹配的是 0.9.33.2 版本的 uClibc,用 mv 命令把默认的 ld-uClibc.so.0 替换成 ld-uClibc-0.9.33.2.so 就可以了,libc 的替换同理。换好后再运行就正常了。

1
2
ch331n@ubuntu:~/cs/practices/embedded_heap/lib$ mv ld-uClibc-0.9.33.2.so ld-uClibc.so.0
ch331n@ubuntu:~/cs/practices/embedded_heap/lib$ mv libuClibc-0.9.33.2.so libc.so.0

堆管理机制分析

堆溢出具体要修改哪里、改成什么,还得看 uClibc 的管理机制是怎么写的。从官网上下载源码:https://www.uclibc.org/。malloc 的位置:/(your_path)/uClibc-0.9.33.2/libc/stdlib/malloc-standard

malloc_state 结构体定义如下:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct malloc_state {

/* The maximum chunk size to be eligible for fastbin */
size_t max_fast; /* low 2 bits used as flags */

/* Fastbins */
mfastbinptr fastbins[NFASTBINS];// struct malloc_chunk*

/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;// struct malloc_chunk*

/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;// struct malloc_chunk*

/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2]; // #define NBINS 96

/* Bitmap of bins. Trailing zero map handles cases of largest binned size */
unsigned int binmap[BINMAPSIZE+1];
/* 其中 BINMAPSIZE 在 malloc.h 中的定义为:
Conservatively use 32 bits per map word, even if on 64bit system
#define BINMAPSHIFT 5
#define BITSPERMAP (1U << BINMAPSHIFT)
#define BINMAPSIZE (NBINS / BITSPERMAP)
*/


/* Tunable parameters */
unsigned long trim_threshold;
size_t top_pad;
size_t mmap_threshold;

/* Memory map support */
int n_mmaps;
int n_mmaps_max;
int max_n_mmaps;

/* Cache malloc_getpagesize */
unsigned int pagesize;

/* Track properties of MORECORE */
unsigned int morecore_properties;

/* Statistics */
size_t mmapped_mem;
size_t sbrked_mem;
size_t max_sbrked_mem;
size_t max_mmapped_mem;
size_t max_total_mem;
};

其中,关于 malloc_chunk 的定义,跟 glibc 是相似的:

1
2
3
4
5
6
7
8
9
10
11
struct malloc_chunk {

size_t prev_size; /* Size of previous chunk (if free). */
size_t size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
};

typedef struct malloc_chunk* mchunkptr;
typedef struct malloc_chunk* mfastbinptr;

不同的是,uClibc 把 max_fast 写进了管理所有堆布局的 malloc_state 中,就在 fastbin 的上方。如果想修改 max_fast 的大小,最直接的应该是通过其相邻的 fastbin 进行修改,当然不排除也有其他的做法。

以下是 free.c 中关于 fastbin 的代码。可以看到,当准备 free 的 chunk 符合 fastbin 的大小时,会以它的 size 大小去寻找合适的 fastbin 链,也就是 fastbin_index(size) 这一步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/

if ((unsigned long)(size) <= (unsigned long)(av->max_fast)

#if TRIM_FASTBINS
/* If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins */
&& (chunk_at_offset(p, size) != av->top)
#endif
) {

set_fastchunks(av);
fb = &(av->fastbins[fastbin_index(size)]);//查找合适的下标
p->fd = *fb;
*fb = p;
}

关于 fastbin 下标的计算,在 malloc.h 中有定义:

1
2
/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2)

前面说到在这个题目中,chunk 的所有字段都可控。 size 可控,通过 fastbin_index(sz) 计算出来的下标也可控。巧就巧在当 size==8 时,(8 >> 3) -2 = -1 ,而在 malloc_state 中,fastbin[-1] 就是 max_fast 的位置。从而执行完 free 后, max_fast 存储的是堆地址,相当于扩大了 fastbin 的范围。

正常情况下 size 不可能为 8,该手法的利用前提之一就是 size 可修改。free 完 这个 size 为 8 的 chunk 后, max_fast 的值变成了 0x5555……..(chunk 地址,十二位 hex 数,很大)。之后再 free 一个 “huge size” (在 chunk 地址范围内)的 chunk,也会被合法地当作 fastbin 处理,放入由 fastbin_index(size) 算出来的合适偏移处。由于fastbin + huge idx 的范围也很广,现在也算能实现任意地址写了。因此利用的第二个前提是,能实现两次 free。至于往任意地址写什么,因题而异。

漏洞利用

刚好这个题目,size可控,有两次 free。现在的问题是往什么地址写什么。考虑到程序执行完 pwn 函数后直接结束,打 exit 流。

en 大家貌似都是用 qemu-system 调试的,都只是提了下 qemu-user 的调试方法,没有人用 user 来调?貌似有点问题,调了几天都没调好, 待我找下问题在哪……

关于 uClibc

uClibc 的初始发行都是 2000 年的事了。这道题目 embedded_heap 所用的 0.9.33.2 版本,是 2012年发布的最终版本。此后没再发布新版本,官方给出的理由是维护者缺乏任何沟通 (?