-
-
[原创]musl1.1.24 源码分析+利用+例题
-
发表于: 2021-12-10 00:02 16273
-
musl1.1.24 malloc基础定义在malloc_impl.h文件中。
堆块基本结构。psize是前一个堆块的size,csize是当前堆块的size,通过next,prev把堆块用双向链表链接。不同于glibc,psize一直有效。
bin的基本结构,head指针指向bin中第一个堆块,tail指针指向最后一个堆块,第一个堆块的prev指向bin,最后一个堆块的next也指向bin。当head=next==0,或者是head=next\==&bin的时候说明bin为空。
mal的结构,类似glibc中的arena
一些宏定义
musl中堆块的大小,0-31是每个bin对应一个大小,后面是对应多个大小
musl存在特殊的静态堆内存区,在glibc中,堆从一开始就动态申请和释放,而在musl中,程序开始的时候musl会把程序和libc空闲的区域当做堆内存(静态内存),优先从这里面划分空间,这部分空间用完之后再通过mmap进行分配。
这个性质可以用来泄露libc地址或者是程序基址。
流程如下:
musl1.1.24采用unbin函数从bin中取出chunk,对应glibc中的unlink,但是unbin中检查不足没有检查链表完整性,可以进行利用实现任意地址写。如果泄露了堆地址还可以写rop链进行ROP。
exit函数调用的第一个函数就是__funcs_on_exit()。
在__funcs_on_exit函数中,根据head指针指向的fl结构体中的f[],a[]数组来确定调用的函数和参数。我们可以修改head指针,指向可写的区域,进行伪造fl结构体或者是对head指针指向的区域进行修改,从而执行我们想执行的函数。存在while循环,可以多次执行指定函数
需要特别注意的是,slot变量为static类型,初始为0,所以for循环第一次的时候while(slot-->0)会直接提出,for循环第二次head=head->next的时候赋值slot=COUNT,才会开始执行f[slot],因此其实伪造的fl结构体要放到head->next的位置,直接把伪造结构体的next指针设置为自身的地址就可以。
下面是我写的demo
在musl中,FILE结构体没有vtable,只有3个函数指针,根据在glibc中的经验,如果我们能修改其中的函数指针,并且找到一条函数调用链,控制其中一些参数,就可以攻击利用。
这条利用链就在exit函数中exit()->__stdio_exit()->close_file()
先看exit函数的源码:
关键在下面的函数close_file中,如果f->wpos != f->wbase
,那么就会通过函数指针调用f->write(f,0,0)
。并且FILE结构体指针被放到了rdi寄存器中,如果把write函数指针修改为system,把FILE结构体中的flags修改为binsh,就可以获得shell。
FILE结构体中wpos偏移为0x28, wbase偏移为0x38,write函数指针的偏移为0x48。
下面是我写的demo,输入字符串为/bin/sh的时候程序会执行system(/bin/sh)
建议下载源码进行编译可以进行源码级别的调试,运行程序的话,先进行patchelf --set-interpreter libc.so xxxx
,然后直接运行文件就可以。
题目中UAF,堆块固定大小0x200,只能分配5个,show完之后会把showFlags设置为非零。理论上只能show一次。
程序存在静态堆内存区,先查看一下静态堆内存区的位置。
申请chunk的话会优先从大于申请大小的bin中选最小的来,0x7ffff7ffe3b0
这个区域会被首先分配,利用UAF就可以泄露libc地址。申请完第一个chunk之后,原本0x7ffff7ffe3b0
的chunk会被分割。
这个题目中exit没有执行,下面代码中前3个函数,直接执行_Exit(),没办法利用exit。只能修改返回地址进行ROP。
进行ROP的话需要泄露程序基址和栈地址,发现mal结构体中程序段的静态内存和noteList结构体距离很近,利用unbin修改mal结构体中bin.head的最低字节,从而控制noteList数组,这样也可以修改showFlag进行多次泄露。
修改的时候注意要把前面bin的head,tail指针设置为0,就会从这个bin中进行切割。
通过environ泄露栈地址,堆地址泄露piebase,修改返回地址进行ROP。禁用了execve,要进行orw。
struct chunk {
size_t psize, csize;
struct chunk
*
next
,
*
prev;
};
struct chunk {
size_t psize, csize;
struct chunk
*
next
,
*
prev;
};
struct
bin
{
volatile
int
lock[
2
];
/
/
链表头,用来链接空闲堆块
struct chunk
*
head;
struct chunk
*
tail;
};
struct
bin
{
volatile
int
lock[
2
];
/
/
链表头,用来链接空闲堆块
struct chunk
*
head;
struct chunk
*
tail;
};
static struct {
/
/
binmap用来表示对应
bin
是否为空
volatile uint64_t binmap;
/
/
bin
的数组
struct
bin
bins[
64
];
volatile
int
free_lock[
2
];
} mal;
static struct {
/
/
binmap用来表示对应
bin
是否为空
volatile uint64_t binmap;
/
/
bin
的数组
struct
bin
bins[
64
];
volatile
int
free_lock[
2
];
} mal;
/
/
4size_t
对齐,
64
位是
0x20
#define SIZE_ALIGN (4*sizeof(size_t))
#define SIZE_MASK (-SIZE_ALIGN)
/
/
chunk头部大小为
2
*
size_t,
64
位是
0x10
#define OVERHEAD (2*sizeof(size_t))
/
/
获得堆块的csize和psize,去除标志位的影响
#define CHUNK_SIZE(c) ((c)->csize & -2)
#define CHUNK_PSIZE(c) ((c)->psize & -2)
/
/
获得物理上前一个堆块和后一个堆块,csize定位nextchunk,psize定位prevchunk
#define PREV_CHUNK(c) ((struct chunk *)((char *)(c) - CHUNK_PSIZE(c)))
#define NEXT_CHUNK(c) ((struct chunk *)((char *)(c) + CHUNK_SIZE(c)))
/
/
mem是指向存放内容位置,chunk是指向head
#define MEM_TO_CHUNK(p) (struct chunk *)((char *)(p) - OVERHEAD)
#define CHUNK_TO_MEM(c) (void *)((char *)(c) + OVERHEAD)
#define BIN_TO_CHUNK(i) (MEM_TO_CHUNK(&mal.bins[i].head))
/
/
标志位inuse,如果设置了inuse说明堆块正在使用,否则说明堆块已经被释放或者通过mmap释放需要通过psize进一步判断
#define C_INUSE ((size_t)1)
#define IS_MMAPPED(c) !((c)->csize & (C_INUSE))
/
/
4size_t
对齐,
64
位是
0x20
#define SIZE_ALIGN (4*sizeof(size_t))
#define SIZE_MASK (-SIZE_ALIGN)
/
/
chunk头部大小为
2
*
size_t,
64
位是
0x10
#define OVERHEAD (2*sizeof(size_t))
/
/
获得堆块的csize和psize,去除标志位的影响
#define CHUNK_SIZE(c) ((c)->csize & -2)
#define CHUNK_PSIZE(c) ((c)->psize & -2)
/
/
获得物理上前一个堆块和后一个堆块,csize定位nextchunk,psize定位prevchunk
#define PREV_CHUNK(c) ((struct chunk *)((char *)(c) - CHUNK_PSIZE(c)))
#define NEXT_CHUNK(c) ((struct chunk *)((char *)(c) + CHUNK_SIZE(c)))
/
/
mem是指向存放内容位置,chunk是指向head
#define MEM_TO_CHUNK(p) (struct chunk *)((char *)(p) - OVERHEAD)
#define CHUNK_TO_MEM(c) (void *)((char *)(c) + OVERHEAD)
#define BIN_TO_CHUNK(i) (MEM_TO_CHUNK(&mal.bins[i].head))
/
/
标志位inuse,如果设置了inuse说明堆块正在使用,否则说明堆块已经被释放或者通过mmap释放需要通过psize进一步判断
#define C_INUSE ((size_t)1)
#define IS_MMAPPED(c) !((c)->csize & (C_INUSE))
void
*
malloc(size_t n)
{
struct chunk
*
c;
int
i, j;
/
/
判断n是否符合条件,并且转化为
0x20
对齐
if
(adjust_size(&n) <
0
)
return
0
;
/
/
n大于
0x1c00
*
SIZE_ALIGN,采用mmap直接分配
if
(n > MMAP_THRESHOLD) {
size_t
len
=
n
+
OVERHEAD
+
PAGE_SIZE
-
1
&
-
PAGE_SIZE;
char
*
base
=
__mmap(
0
,
len
, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,
-
1
,
0
);
if
(base
=
=
(void
*
)
-
1
)
return
0
;
c
=
(void
*
)(base
+
SIZE_ALIGN
-
OVERHEAD);
c
-
>csize
=
len
-
(SIZE_ALIGN
-
OVERHEAD);
c
-
>psize
=
SIZE_ALIGN
-
OVERHEAD;
return
CHUNK_TO_MEM(c);
}
/
/
获得对应
bin
的索引
i
=
bin_index_up(n);
for
(;;) {
/
/
找到所有大于等于i的
bin
,小于i的
bin
会被去除
uint64_t mask
=
mal.binmap &
-
(
1ULL
<<i);
/
/
如果没有合适的
bin
,通过扩展堆进行分配
if
(!mask) {
c
=
expand_heap(n);
if
(!c)
return
0
;
if
(alloc_rev(c)) {
struct chunk
*
x
=
c;
c
=
PREV_CHUNK(c);
NEXT_CHUNK(x)
-
>psize
=
c
-
>csize
=
x
-
>csize
+
CHUNK_SIZE(c);
}
break
;
}
/
/
找到符合条件最小的
bin
/
/
内嵌汇编指令bsf,找到值为
1
的最低的位
j
=
first_set(mask);
lock_bin(j);
c
=
mal.bins[j].head;
/
/
判断头结点的head指针是不是指向自己,指向自己的话说明
bin
为空
if
(c !
=
BIN_TO_CHUNK(j)) {
/
/
分割堆块
if
(!pretrim(c, n, i, j)) unbin(c, j);
/
/
从
bin
中去除,堆块先进先出
unlock_bin(j);
break
;
}
unlock_bin(j);
}
/
*
Now patch up
in
case we over
-
allocated
*
/
/
/
回收切割剩余的堆块
trim(c, n);
return
CHUNK_TO_MEM(c);
}
void
*
malloc(size_t n)
{
struct chunk
*
c;
int
i, j;
/
/
判断n是否符合条件,并且转化为
0x20
对齐
if
(adjust_size(&n) <
0
)
return
0
;
/
/
n大于
0x1c00
*
SIZE_ALIGN,采用mmap直接分配
if
(n > MMAP_THRESHOLD) {
size_t
len
=
n
+
OVERHEAD
+
PAGE_SIZE
-
1
&
-
PAGE_SIZE;
char
*
base
=
__mmap(
0
,
len
, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,
-
1
,
0
);
if
(base
=
=
(void
*
)
-
1
)
return
0
;
c
=
(void
*
)(base
+
SIZE_ALIGN
-
OVERHEAD);
c
-
>csize
=
len
-
(SIZE_ALIGN
-
OVERHEAD);
c
-
>psize
=
SIZE_ALIGN
-
OVERHEAD;
return
CHUNK_TO_MEM(c);
}
/
/
获得对应
bin
的索引
i
=
bin_index_up(n);
for
(;;) {
/
/
找到所有大于等于i的
bin
,小于i的
bin
会被去除
uint64_t mask
=
mal.binmap &
-
(
1ULL
<<i);
/
/
如果没有合适的
bin
,通过扩展堆进行分配
if
(!mask) {
c
=
expand_heap(n);
if
(!c)
return
0
;
if
(alloc_rev(c)) {
struct chunk
*
x
=
c;
c
=
PREV_CHUNK(c);
NEXT_CHUNK(x)
-
>psize
=
c
-
>csize
=
x
-
>csize
+
CHUNK_SIZE(c);
}
break
;
}
/
/
找到符合条件最小的
bin
/
/
内嵌汇编指令bsf,找到值为
1
的最低的位
j
=
first_set(mask);
lock_bin(j);
c
=
mal.bins[j].head;
/
/
判断头结点的head指针是不是指向自己,指向自己的话说明
bin
为空
if
(c !
=
BIN_TO_CHUNK(j)) {
/
/
分割堆块
if
(!pretrim(c, n, i, j)) unbin(c, j);
/
/
从
bin
中去除,堆块先进先出
unlock_bin(j);
break
;
}
unlock_bin(j);
}
/
*
Now patch up
in
case we over
-
allocated
*
/
/
/
回收切割剩余的堆块
trim(c, n);
return
CHUNK_TO_MEM(c);
}
void free(void
*
p)
{
if
(!p)
return
;
struct chunk
*
self
=
MEM_TO_CHUNK(p);
/
/
如果通过mmap分配的,调用unmap,inuse位
=
0
if
(IS_MMAPPED(
self
))
unmap_chunk(
self
);
/
/
否则调用__bin_chunk
else
__bin_chunk(
self
);
}
static void unmap_chunk(struct chunk
*
self
)
{
size_t extra
=
self
-
>psize;
char
*
base
=
(char
*
)
self
-
extra;
size_t
len
=
CHUNK_SIZE(
self
)
+
extra;
/
*
Crash on double free
*
/
/
/
检测double free
if
(extra &
1
) a_crash();
__munmap(base,
len
);
}
void __bin_chunk(struct chunk
*
self
)
{
struct chunk
*
next
=
NEXT_CHUNK(
self
);
size_t final_size, new_size, size;
int
reclaim
=
0
;
int
i;
/
/
finalsize为合并空闲块之后的size
final_size
=
new_size
=
CHUNK_SIZE(
self
);
/
*
Crash on corrupted footer (likely
from
buffer
overflow)
*
/
/
/
检测nextchunk的psize和chunk的size是否相等
if
(
next
-
>psize !
=
self
-
>csize) a_crash();
for
(;;) {
/
/
前后堆块的inuse都为
1
,没法合并
if
(
self
-
>psize &
next
-
>csize & C_INUSE) {
self
-
>csize
=
final_size | C_INUSE;
next
-
>psize
=
final_size | C_INUSE;
i
=
bin_index(final_size);
lock_bin(i);
lock(mal.free_lock);
if
(
self
-
>psize &
next
-
>csize & C_INUSE)
break
;
unlock(mal.free_lock);
unlock_bin(i);
}
/
/
向前合并
if
(alloc_rev(
self
)) {
self
=
PREV_CHUNK(
self
);
size
=
CHUNK_SIZE(
self
);
/
/
更新size
final_size
+
=
size;
if
(new_size
+
size > RECLAIM && (new_size
+
size^size) > size)
reclaim
=
1
;
}
/
/
向后合并
if
(alloc_fwd(
next
)) {
size
=
CHUNK_SIZE(
next
);
/
/
更新size
final_size
+
=
size;
if
(new_size
+
size > RECLAIM && (new_size
+
size^size) > size)
reclaim
=
1
;
/
/
更新
next
指针
next
=
NEXT_CHUNK(
next
);
}
}
if
(!(mal.binmap &
1ULL
<<i))
a_or_64(&mal.binmap,
1ULL
<<i);
/
/
更新
self
-
>csize,
next
-
>psize
self
-
>csize
=
final_size;
next
-
>psize
=
final_size;
unlock(mal.free_lock);
/
/
放在对应
bin
的双向链表的最后
self
-
>
next
=
BIN_TO_CHUNK(i);
self
-
>prev
=
mal.bins[i].tail;
self
-
>
next
-
>prev
=
self
;
self
-
>prev
-
>
next
=
self
;
/
*
Replace middle of large chunks with fresh zero pages
*
/
if
(reclaim) {
uintptr_t a
=
(uintptr_t)
self
+
SIZE_ALIGN
+
PAGE_SIZE
-
1
&
-
PAGE_SIZE;
uintptr_t b
=
(uintptr_t)
next
-
SIZE_ALIGN &
-
PAGE_SIZE;
#if 1
__madvise((void
*
)a, b
-
a, MADV_DONTNEED);
#else
__mmap((void
*
)a, b
-
a, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED,
-
1
,
0
);
#endif
}
unlock_bin(i);
}
void free(void
*
p)
{
if
(!p)
return
;
struct chunk
*
self
=
MEM_TO_CHUNK(p);
/
/
如果通过mmap分配的,调用unmap,inuse位
=
0
if
(IS_MMAPPED(
self
))
unmap_chunk(
self
);
/
/
否则调用__bin_chunk
else
__bin_chunk(
self
);
}
static void unmap_chunk(struct chunk
*
self
)
{
size_t extra
=
self
-
>psize;
char
*
base
=
(char
*
)
self
-
extra;
size_t
len
=
CHUNK_SIZE(
self
)
+
extra;
/
*
Crash on double free
*
/
/
/
检测double free
if
(extra &
1
) a_crash();
__munmap(base,
len
);
}
void __bin_chunk(struct chunk
*
self
)
{
struct chunk
*
next
=
NEXT_CHUNK(
self
);
size_t final_size, new_size, size;
int
reclaim
=
0
;
int
i;
/
/
finalsize为合并空闲块之后的size
final_size
=
new_size
=
CHUNK_SIZE(
self
);
/
*
Crash on corrupted footer (likely
from
buffer
overflow)
*
/
/
/
检测nextchunk的psize和chunk的size是否相等
if
(
next
-
>psize !
=
self
-
>csize) a_crash();
for
(;;) {
/
/
前后堆块的inuse都为
1
,没法合并
if
(
self
-
>psize &
next
-
>csize & C_INUSE) {
self
-
>csize
=
final_size | C_INUSE;
next
-
>psize
=
final_size | C_INUSE;
i
=
bin_index(final_size);
lock_bin(i);
lock(mal.free_lock);
if
(
self
-
>psize &
next
-
>csize & C_INUSE)
break
;
unlock(mal.free_lock);
unlock_bin(i);
}
/
/
向前合并
if
(alloc_rev(
self
)) {
self
=
PREV_CHUNK(
self
);
size
=
CHUNK_SIZE(
self
);
/
/
更新size
final_size
+
=
size;
if
(new_size
+
size > RECLAIM && (new_size
+
size^size) > size)
reclaim
=
1
;
}
/
/
向后合并
if
(alloc_fwd(
next
)) {
size
=
CHUNK_SIZE(
next
);
/
/
更新size
final_size
+
=
size;
if
(new_size
+
size > RECLAIM && (new_size
+
size^size) > size)
reclaim
=
1
;
/
/
更新
next
指针
next
=
NEXT_CHUNK(
next
);
}
}
if
(!(mal.binmap &
1ULL
<<i))
a_or_64(&mal.binmap,
1ULL
<<i);
/
/
更新
self
-
>csize,
next
-
>psize
self
-
>csize
=
final_size;
next
-
>psize
=
final_size;
unlock(mal.free_lock);
/
/
放在对应
bin
的双向链表的最后
self
-
>
next
=
BIN_TO_CHUNK(i);
self
-
>prev
=
mal.bins[i].tail;
self
-
>
next
-
>prev
=
self
;
self
-
>prev
-
>
next
=
self
;
/
*
Replace middle of large chunks with fresh zero pages
*
/
if
(reclaim) {
uintptr_t a
=
(uintptr_t)
self
+
SIZE_ALIGN
+
PAGE_SIZE
-
1
&
-
PAGE_SIZE;
uintptr_t b
=
(uintptr_t)
next
-
SIZE_ALIGN &
-
PAGE_SIZE;
#if 1
__madvise((void
*
)a, b
-
a, MADV_DONTNEED);
#else
__mmap((void
*
)a, b
-
a, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED,
-
1
,
0
);
#endif
}
unlock_bin(i);
}
static void unbin(struct chunk
*
c,
int
i)
{
/
/
如果
bin
中只有一个堆块的话,unbin之后清除binmap
if
(c
-
>prev
=
=
c
-
>
next
)
a_and_64(&mal.binmap, ~(
1ULL
<<i));
/
/
链表操作,可以伪造c
-
>
next
,需要保证c
-
>
next
-
>prev可写
c
-
>prev
-
>
next
=
c
-
>
next
;
c
-
>
next
-
>prev
=
c
-
>prev;
c
-
>csize |
=
C_INUSE;
NEXT_CHUNK(c)
-
>psize |
=
C_INUSE;
}
static void unbin(struct chunk
*
c,
int
i)
{
/
/
如果
bin
中只有一个堆块的话,unbin之后清除binmap
if
(c
-
>prev
=
=
c
-
>
next
)
a_and_64(&mal.binmap, ~(
1ULL
<<i));
/
/
链表操作,可以伪造c
-
>
next
,需要保证c
-
>
next
-
>prev可写
c
-
>prev
-
>
next
=
c
-
>
next
;
c
-
>
next
-
>prev
=
c
-
>prev;