基础知识
论坛上 师傅写的 很好,研究一下午就大致能了解透彻musl 1.2.2的内存管理结构和机制。美中不足的是没讲讲怎么编译一个用musl libc的程序。本文就简单介绍介绍如何编译一个musl libc下的程序,并通过它简单调试调试musl libc 的一些特性。
安装musl环境&&符号表
这里直接用0xRGz师傅的做法
下载.deb安装包(0xRGz师傅的链接在笔者的电脑上貌似失效了):
安装:
1
|
sudo dpkg
-
i musl_1.
2.2
-
1_amd64
.deb
|
安装调试符号:
下载安装包
安装:
1
|
sudo dpkg
-
i musl
-
dbgsym_1.
2.2
-
1_amd64
.ddeb
|
安装gdb小插件(from xf1les 师傅):
1
2
|
git clone https:
/
/
github.com
/
xf1les
/
muslheap.git
echo
"source /path/to/muslheap.py"
>> ~
/
.gdbinit
|
(这里的/path/to是需要修改的,也就是你git的muslheap的路径。安装了这个就能使用mheap和mchunk等一些非常好用的辅助调试的命令)
手动编译第一个musl程序
下载&&安装
1
2
3
4
5
|
curl
-
LO http:
/
/
musl.libc.org
/
releases
/
musl
-
1.2
.
2.tar
.gz
tar vxf musl
-
1.2
.
2.tar
.gz
cd musl
-
1.2
.
2
.
/
configure
-
-
prefix
=
/
usr
/
local
/
musl CFLAGS
=
'-O2 -v'
make && sudo make install
|
源码&&编译
(在musl-1.2.2目录下的操作)
main.c:
1
2
3
4
5
6
|
int
main() {
printf(
"Hello musl libc\n"
);
return
0
;
}
|
编译:
1
|
.
/
obj
/
musl
-
gcc main.c
-
o test
|
链接:
1
|
patchelf
-
-
set
-
interpreter .
/
libc.so .
/
test
|
运行
测试
demo1
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
|
void init()
{
setbuf(stdin,
0
);
setbuf(stdout,
0
);
setbuf(stderr,
0
);
}
int
main() {
init();
size_t
*
p1,
*
p2,
*
p3;
p1
=
(size_t
*
)malloc(
0x20
);
p2
=
(size_t
*
)malloc(
0x30
);
p3
=
(size_t
*
)malloc(
0x40
);
read(
0
,p1,
0x10
);
read(
0
,p2,
0x20
);
read(
0
,p3,
0x30
);
free(p1);
free(p2);
free(p3);
return
0
;
}
|
通过read我们就能定位到分配的chunk的地址,从而通过mchunk看出对应的group和meta。然后通过mheap中的active看出对应的meta有没有被dequeue
初始状态
malloc p1后
发现多了一个meta(其实是先多一个chunk,然后多一个group再多一个meta)
malloc p3后
多了两个meta
mchunk p1
到read看见p1的addr:
mchunk它:
可以看见它的group的首地址是它的地址减0x10,和文章里写的一样,并且group的首地址存了meta的地址
p (struct meta ) 这个地址看看:
发现这个meta此时是指向自己的,并且mem存的是group的地址(发现meta有东西指向group,group也有东西指向meta)
avail_mask为1022是因为它的二进制表示为:
freed_mask记录group中已经被free释放的堆块,当前没有任何被释放的堆块,所以它为0
last_idx为9表示group中最多10个同大小的堆块(0~9)
free_able为1表示当前有一个堆块能free
sizeclass为2 表示由0x2
这个group进行管理这一类的大小的chunk
maplen为0说明这个group不是通过mmap分配的
看看group中的chunk
0x70本来是chunk头,结果被last_idx和meta_addr占位了,从0x80开始是我们读入的数据“nameless”
free chunk过后看看:
发现active[2]不见了。这其实触发了meta dequeue的第一个条件——在free的时候,group中只有一个堆块处于使用状态中即free它过后,group中的所有堆块都处于未使用状态。这个时候还留它干嘛呢,直接将该meta dequeue了
demo2
通过demo1简单了解了gdb下musl 的chunk、group和meta怎么调试,以及meta dequeue的第一种触发方式。接下来我们调试调试meta dequeue第二种触发方式——malloc的时候
源码:
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
|
void init()
{
setbuf(stdin,
0
);
setbuf(stdout,
0
);
setbuf(stderr,
0
);
}
int
main() {
init();
size_t
*
p1;
p1
=
(size_t
*
)malloc(
0x20
);
/
/
1
p1
=
(size_t
*
)malloc(
0x20
);
/
/
2
p1
=
(size_t
*
)malloc(
0x20
);
/
/
3
p1
=
(size_t
*
)malloc(
0x20
);
/
/
4
p1
=
(size_t
*
)malloc(
0x20
);
/
/
5
p1
=
(size_t
*
)malloc(
0x20
);
/
/
6
p1
=
(size_t
*
)malloc(
0x20
);
/
/
7
p1
=
(size_t
*
)malloc(
0x20
);
/
/
8
p1
=
(size_t
*
)malloc(
0x20
);
/
/
9
p1
=
(size_t
*
)malloc(
0x20
);
/
/
10
p1
=
(size_t
*
)malloc(
0x20
);
/
/
11
p1
=
(size_t
*
)malloc(
0x20
);
/
/
12
p1
=
(size_t
*
)malloc(
0x20
);
/
/
13
p1
=
(size_t
*
)malloc(
0x20
);
/
/
14
return
0
;
}
|
测试
发现当malloc第10个堆块过后,active[2]就从0x298变成0x2c0即发生了meta dequeue
而且group的位置也从0xc70变成了0xc50。这说明在这种dequeue的时候,会产生一个新grep,以及对应的meta
学meta dequeue有啥用呢?
做题!(即答)
别别别,ctf to learn,not learn to ctf
我们从p (struct meta ) 能看出来meta是一个双链表结构,dequeue的过程就牵扯到一个类似UB或者large bin chunk 的unlink的操作。就有可能通过这里的dequeue实现unlink 任意地址写,但具体怎么操作就留给读者思考或者看其它大师傅博客复现赛题了QAQ(笔者写这篇的时候还没做任何一道musl 1.2.2的题,虽然确实是奔着做题去学的,不过搞嵌入式的话不会arm,不会musl怎么行呢?)
一些话
纸上得来终觉浅,绝知此事要躬行,觉得在正式做题之前还是要自己写个demo简单调调,检验检验自己是否学懂了musl 1.2.2的特性。不能总想着做题.......
参考文章