首页
社区
课程
招聘
[原创]musl1.1.24 源码分析+利用+例题
发表于: 2021-12-10 00:02 16273

[原创]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对应一个大小,后面是对应多个大小

image-20211208171035885

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

image-20211209160637275

在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)

image-20211208203841534

建议下载源码进行编译可以进行源码级别的调试,运行程序的话,先进行patchelf --set-interpreter libc.so xxxx,然后直接运行文件就可以。

题目中UAF,堆块固定大小0x200,只能分配5个,show完之后会把showFlags设置为非零。理论上只能show一次。

image-20211208193732141

程序存在静态堆内存区,先查看一下静态堆内存区的位置。
image-20211208194141064

申请chunk的话会优先从大于申请大小的bin中选最小的来,0x7ffff7ffe3b0这个区域会被首先分配,利用UAF就可以泄露libc地址。申请完第一个chunk之后,原本0x7ffff7ffe3b0的chunk会被分割。

这个题目中exit没有执行,下面代码中前3个函数,直接执行_Exit(),没办法利用exit。只能修改返回地址进行ROP。

进行ROP的话需要泄露程序基址和栈地址,发现mal结构体中程序段的静态内存和noteList结构体距离很近,利用unbin修改mal结构体中bin.head的最低字节,从而控制noteList数组,这样也可以修改showFlag进行多次泄露。

image-20211208195510399

修改的时候注意要把前面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;

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

收藏
免费 4
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//