<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】借助DefCon Quals 2021的mooosl學習musl mallocng(源碼審計篇)

    VSole2022-03-17 08:35:55

    關于musl libc的資料比賽期間找到過一篇從一次 CTF 出題談 musl libc 堆漏洞利用,礙于musl libc在1.2.x之后的堆管理機制有較大的改版,因而有了該文章。本次文章分上下兩篇,從musl libc 1.2.2的源碼審計、調試,以及其中的利用機會,再到mooosl這道題的解題過程做一個分析。

    musl libc 1.2.2的源碼可以從[此處],(https://musl.libc.org/releases/musl-1.2.2.tar.gz)下載獲得。1.2.x采用src/malloc/mallocng內的代碼,其堆管理結構與早期版本幾乎完全不同,而早期的堆管理器則放入了src/malloc/oldmalloc中。

    調試帶符號的musl libc

    0x01源碼編譯

    題目提供的libc.so不帶符號,很難通過調試去理解musl堆管理器的數據結構,可以通過源碼編譯,生成一份帶調試符號的libc.so,進行源碼級debug。

    tar -xzvf ./musl-1.2.2.tar.gz
    cd musl-1.2.2
    mkdir build x64
    cd build
    CC="gcc" CXX="g++" \             
        CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
        CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
        ../configure --prefix=/home/sung3r/workspace/sharefd/glibc/glibc-2.32/x64 --disable-werror
    make
    make install
    

    /src/x64/下找到編譯好的libc.so

    通過patchelf將ld.so改成libc.so即可,gdb調試時加上dir /path/to/musl-1.2.2/src/malloc/dir /path/to/musl-1.2.2/src/malloc/mallocng便可源碼調試。

    0x02安裝調試符號

    此方法要在ubuntu 20.04下才能成功

    下載musl_1.2.2-1_amd64.deb、musl-dbgsym_1.2.2-1_amd64.ddeb

    在ubuntu20.04安裝

    sudo dpkg -i musl_1.2.2-1_amd64.deb
    sudo dpkg -i musl-dbgsym_1.2.2-1_amd64.ddeb
    

    gdb調試時通過dir加載源碼即可。推薦此方法,比較簡單,而且該deb里的libc.so與題目提供的libc.so md5一致。

    源碼審計

    meta.h

    //line:124~127
    static inline int get_slot_index(const unsigned char *p)
    {
        //chunk地址往前的第3個byte就是該chunk的下標
        return p[-3] & 31;
    }
    //line:129~157
    static inline struct meta *get_meta(const unsigned char *p)
    {
        assert(!((uintptr_t)p & 15));//16字節對齊
        //獲取slot的偏移offset,offset*0x10才是真實偏移
        int offset = *(const uint16_t *)(p - 2);
        //獲取slot的下標,這里的slot就是我們習慣中理解的chunk
        int index = get_slot_index(p);
        if (p[-4]) {
            //如果offset不為0,表示不是group里的首個chunk,拋出異常
            assert(!offset);
            offset = *(uint32_t *)(p - 8);
            assert(offset > 0xffff);
        }
        //獲取group首地址,也即`meta->mem`這個地址
        const struct group *base = (const void *)(p - UNIT*offset - UNIT);
        //獲取meta地址,group首地址指向meta結構的地址
        const struct meta *meta = base->meta;
        assert(meta->mem == base);
        assert(index <= meta->last_idx);
        assert(!(meta->avail_mask & (1u<    assert(!(meta->freed_mask & (1u<    const struct meta_area *area = (void *)((uintptr_t)meta & -4096);
        //校驗Page的secret是否正確,防止偽造Page
        assert(area->check == ctx.secret);
        if (meta->sizeclass < 48) {//一般都為48個sizeclass
            assert(offset >= size_classes[meta->sizeclass]*index);
            assert(offset < size_classes[meta->sizeclass]*(index+1));
        } else {
            assert(meta->sizeclass == 63);
        }
        if (meta->maplen) {
            assert(offset <= meta->maplen*4096UL/UNIT - 1);
        }
        return (struct meta *)meta;
    }
    //line:229~238
    //16字節對齊向上取整,最后換算成size_classes的下標,對group進行分類
    static inline int size_to_class(size_t n)
    {
        n = (n+IB-1)>>4;
        if (n<10) return n;
        n++;
        int i = (28-a_clz_32(n))*4 + 8;
        if (n>size_classes[i+1]) i+=2;
        if (n>size_classes[i]) i++;
        return i;
    }
    

    mallocng/malloc.c:

    //line:174~284
    static struct meta *alloc_group(int sc, size_t req)
    {
    ...
        } else {///通過brk分配
            int j = size_to_class(UNIT+cnt*size-IB);
            int idx = alloc_slot(j, UNIT+cnt*size-IB);
            if (idx < 0) {
                free_meta(m);
                return 0;
            }
            struct meta *g = ctx.active[j];
            p = enframe(g, idx, UNIT*size_classes[j]-IB, ctx.mmap_counter);
            m->maplen = 0;
            p[-3] = (p[-3]&31) | (6<<5);
            for (int i=0; i<=cnt; i++)
                p[UNIT+i*size-4] = 0;///根據size清零mem
            active_idx = cnt-1;
        }
    ...
    }
    //line:300~381
    //malloc的實現,lite_malloc調這里
    void *malloc(size_t n)
    {
        if (size_overflows(n)) return 0;
        struct meta *g;
        uint32_t mask, first;
        int sc;
        int idx;
        int ctr;
        //大于某一個閾值,通過mmap分配
        if (n >= MMAP_THRESHOLD) {///p MMAP_THRESHOLD; $10 = 0x1ffec
            size_t needed = n + IB + UNIT;
            void *p = mmap(0, needed, PROT_READ|PROT_WRITE,
                MAP_PRIVATE|MAP_ANON, -1, 0);
            if (p==MAP_FAILED) return 0;
            wrlock();
            step_seq();
            g = alloc_meta();
            if (!g) {
                unlock();
                munmap(p, needed);
                return 0;
            }
            g->mem = p;
            g->mem->meta = g;
            g->last_idx = 0;
            g->freeable = 1;
            g->sizeclass = 63;
            g->maplen = (needed+4095)/4096;
            g->avail_mask = g->freed_mask = 0;
            // use a global counter to cycle offset in
            // individually-mmapped allocations.
            ctx.mmap_counter++;
            idx = 0;
            goto success;
        }
        //否則通過brk分配
        //根據傳入size,轉換成size_classes的下標,根據sc申請相對應group的chunk
        sc = size_to_class(n);
        rdlock();
        //根據sc,獲取存放著對應size group的meta,如果還沒申請過這類group,對應ctx.active[sc]為0
        g = ctx.active[sc];
        // use coarse size classes initially when there are not yet
        // any groups of desired size. this allows counts of 2 or 3
        // to be allocated at first rather than having to start with
        // 7 or 5, the min counts for even size classes.
        if (!g && sc>=4 && sc<32 && sc!=6 && !(sc&1) && !ctx.usage_by_class[sc]) {
            size_t usage = ctx.usage_by_class[sc|1];
            // if a new group may be allocated, count it toward
            // usage in deciding if we can use coarse class.
            if (!ctx.active[sc|1] || (!ctx.active[sc|1]->avail_mask
                && !ctx.active[sc|1]->freed_mask))
                usage += 3;
            if (usage <= 12)
                sc |= 1;
            g = ctx.active[sc];
        }
        for (;;) {
            mask = g ? g->avail_mask : 0;
            first = mask&-mask;
            if (!first) break;
            if (RDLOCK_IS_EXCLUSIVE || !MT)
                g->avail_mask = mask-first;
            else if (a_cas(&g->avail_mask, mask, mask-first)!=mask)
                continue;
            idx = a_ctz_32(first);
            goto success;
        }
        upgradelock();
        //申請分配sc類別的chunk,size為n
        idx = alloc_slot(sc, n);
        if (idx < 0) {
            unlock();
            return 0;
        }
        g = ctx.active[sc];
    success:
        ctr = ctx.mmap_counter;
        unlock();
        return enframe(g, idx, n, ctr);
    }
    //line:286~298
    //申請chunk
    static int alloc_slot(int sc, size_t req)
    {
        uint32_t first = try_avail(&ctx.active[sc]);
        if (first) return a_ctz_32(first);
        //申請group,group信息存放于meta結構
        struct meta *g = alloc_group(sc, req);
        if (!g) return -1;
        g->avail_mask--;
        queue(&ctx.acti
        ve[sc], g);
        return 0;
    }
    

    free.c

    //line:101~143
    void free(void *p)
    {
        if (!p) return;//地址為0,直接返回
        //獲取meta結構,以及做一些檢查
        struct meta *g = get_meta(p);
        //獲取chunk的下標
        int idx = get_slot_index(p);
        size_t stride = get_stride(g);
        unsigned char *start = g->mem->storage + stride*idx;
        unsigned char *end = start + stride - IB;
        get_nominal_size(p, end);
        uint32_t self = 1u<2u<last_idx)-1;
        //將對應chunk的下標置0xff
        ((unsigned char *)p)[-3] = 255;
        // invalidate offset to group header, and cycle offset of
        // used region within slot if current offset is zero.
        //將chunk的offset清0
        *(uint16_t *)((char *)p-2) = 0;
        // release any whole pages contained in the slot to be freed
        // unless it's a single-slot group that will be unmapped.
        if (((uintptr_t)(start-1) ^ (uintptr_t)end) >= 2*PGSZ && g->last_idx) {
            unsigned char *base = start + (-(uintptr_t)start & (PGSZ-1));
            size_t len = (end-base) & -PGSZ;
            if (len) madvise(base, len, MADV_FREE);
        }
        // atomic free without locking if this is neither first or last slot
        //設置meta的avail_mask`freed_mask
        for (;;) {
            uint32_t freed = g->freed_mask;
            uint32_t avail = g->avail_mask;
            uint32_t mask = freed | avail;
            assert(!(mask&self));
            if (!freed || mask+self==all) break;
            if (!MT)
                g->freed_mask = freed+self;
            else if (a_cas(&g->freed_mask, freed, freed+self)!=freed)
                continue;
            return;
        }
        wrlock();
        struct mapinfo mi = nontrivial_free(g, idx);
        unlock();
        if (mi.len) munmap(mi.base, mi.len);
    }
    

    meta、group、chunk的具體結構,以下通過debug進行分析。

    分配釋放

    store('a0a0', 'b0b0')
    store('a1a11', 'b1b1111')
    delete('a0a0')
    

    __malloc_context是musl libc的全局管理結構指針,存放在libc.so的bss段

    gef?  p __malloc_context
    $1 = {
      secret = 0x69448097523526a7,
      init_done = 0x1,
      mmap_counter = 0x0,
      free_meta_head = 0x0,
      avail_meta = 0x56042ee901f8,
      avail_meta_count = 0x59,
      avail_meta_area_count = 0x0,
      meta_alloc_shift = 0x0,
      meta_area_head = 0x56042ee90000,
      meta_area_tail = 0x56042ee90000,
      avail_meta_areas = 0x56042ee91000 ,
      active = {0x56042ee901d0, 0x0, 0x0, 0x56042ee901a8, 0x0, 0x0, 0x0, 0x56042ee900b8, 0x0, 0x0, 0x0, 0x56042ee90090, 0x0, 0x0, 0x0, 0x56042ee90068, 0x0, 0x0, 0x0, 0x56042ee90040, 0x0, 0x0, 0x0, 0x56042ee90018, 0x0 times>},
      usage_by_class = {0x1e, 0x0, 0x0, 0x7, 0x0 times>},
      unmap_seq = '\000' times>,
      bounces = '\000' times>,
      seq = 0x0,
      brk = 0x56042ee91000
    }
    

    active = {0x56042ee901d0,0,0...:堆管理器依據申請的size,將chunk分成48類chunk,由sizeclass指定。每類chunk由一個meta結構管理,meta管理的chunk個數有限,由small_cnt_tab指定。當申請個數超出一個meta所能管理的最大數量,堆管理器會再申請同類型meta管理更多的chunk,并且以雙向鏈表結構管理這些相同類型的meta。

    usage_by_class = {0x1e, 0x0, 0x0, 0x7,...:表示當前各meta管理著的chunk個數。

    secret = 0x69448097523526a7:在meta域每個page大小的首8個byte,都會存在一個校驗key。

    musl libc用以下的結構管理著meta、group以及chunk

    分配了兩個0x30的chunk,未釋放。

    gef?  p *(struct meta*)0x56042ee901a8
    $2 = {
      prev = 0x56042ee901a8,
      next = 0x56042ee901a8,
      mem = 0x7f79e1df5c50,
      avail_mask = 0x7c,
      freed_mask = 0x0,
      last_idx = 0x6,
      freeable = 0x1,
      sizeclass = 0x3,
      maplen = 0x0
    }
    

    prevnext都指向本身,表示只有一個meta頁,meta頁由一個雙向鏈表進行維護;

    0x7f79e1df5c50user data域;

    avail_mask = 0x7c = 0b1111100表示第0、1個chunk不可用(已經被使用);

    freed_mask = 0x0表示沒有chunk被釋放;

    last_idx = 0x6表示最后一個chunk的下標是0x6,總數是0x7個

    sizeclass = 0x3表示由0x3這個group進行管理。

    0x000056042ee901a8指向meta結構的地址;

    后面8個byte表示chunk的頭部結構:

    0x00000x0001表示當前chunk,距離group首地址0x00007f79e1df5c58的偏移為00x40

    0xa00xa1表示當前chunk是group中的第0和1個chunk;

    再往后0x28個byte就是user data域,最多接收輸入0x28+4個byte,占用下一個chunk的前4個byte。

    同時,也分配了四個0x10的chunk,未釋放

    gef?  p *(struct meta*)0x56042ee901d0
    $3 = {
      prev = 0x56042ee901d0,
      next = 0x56042ee901d0,
      mem = 0x56042db99c50,
      avail_mask = 0x3ffffff0,
      freed_mask = 0x0,
      last_idx = 0x1d,
      freeable = 0x1,
      sizeclass = 0x0,
      maplen = 0x0
    }
    

    prevnext都指向本身,表示只有一個meta頁,meta頁由一個雙向鏈表進行維護;

    0x56042db99c50user data域;

    avail_mask = 0x3ffffff0 = 0b111111111111111111111111110000表示第0、1、2、3個chunk不可用(已經被使用);

    freed_mask = 0x0表示沒有chunk被釋放;

    last_idx = 0x1d表示最后一個chunk的下標是0x1d,總數是0x1e個

    sizeclass = 0x3表示由0x3這個group進行管理。

    0x00000x00010x00020x0003表示距離group首地址偏移為00x100x200x30byte;

    0xa00xa10xa20xa3表示group中的chunk下標;

    往后8byte是user data,user data最多接收輸入8+4個byte,占用下一個chunk header的前4個byte(與x86的glibc類似)

    釋放兩個0x10的chunk

    gef?  p *(struct meta*)0x56042ee901d0
    $9 = {
      prev = 0x56042ee901d0,
      next = 0x56042ee901d0,
      mem = 0x56042db99c50,
      avail_mask = 0x3fffffe0,
      freed_mask = 0x3,
      last_idx = 0x1d,
      freeable = 0x1,
      sizeclass = 0x0,
      maplen = 0x0
    }
    

    freed_mask = 0x3 = 0b11表示前兩個chunk被釋放;

    avail_mask = 0x3fffffe0 = 0b111111111111111111111111100000可以發現,此時前兩個chunk仍然為不可分配的狀態;

    已釋放的chunk會將chunk header的offset清零,并且將chunk下標置成0xff,不清空user data域。

    釋放一個0x30的chunk

    gef?  p *(struct meta*)0x56042ee901a8
    $13 = {
      prev = 0x56042ee901a8,
      next = 0x56042ee901a8,
      mem = 0x7f79e1df5c50,
      avail_mask = 0x7c,
      freed_mask = 0x1,
      last_idx = 0x6,
      freeable = 0x1,
      sizeclass = 0x3,
      maplen = 0x0
    }
    

    freed_mask = 0x1表示有1個已被釋放的chunk。

    同樣,chunk header的offset清零,且chunk下標置0xff

    const uint16_t size_classes[] = {
        1, 2, 3, 4, 5, 6, 7, 8,
        9, 10, 12, 15,
        18, 20, 25, 31,
        36, 42, 50, 63,
        72, 84, 102, 127,
        146, 170, 204, 255,
        292, 340, 409, 511,
        584, 682, 818, 1023,
        1169, 1364, 1637, 2047,
        2340, 2730, 3276, 4095,
        4680, 5460, 6552, 8191,
    };
    static const uint8_t small_cnt_tab[][3] = {
        { 30, 30, 30 },
        { 31, 15, 15 },
        { 20, 10, 10 },
        { 31, 15, 7 },
        { 25, 12, 6 },
        { 21, 10, 5 },
        { 18, 8, 4 },
        { 31, 15, 7 },
        { 28, 14, 6 },
    };
    static struct meta *alloc_group(int sc, size_t req)
    {
        size_t size = UNIT*size_classes[sc];
        int i = 0, cnt;
        unsigned char *p;
        struct meta *m = alloc_meta();///分配內存,用于建立一個group
        if (!m) return 0;
        size_t usage = ctx.usage_by_class[sc];
        size_t pagesize = PGSZ;
        int active_idx;
        if (sc < 9) {
            while (i<2 && 4*small_cnt_tab[sc][i] > usage)
                i++;
            cnt = small_cnt_tab[sc][i];
        } else {
    ...
        ctx.usage_by_class[sc] += cnt;
    ...
    

    幾個有用的結構

    group分類表,由sc指定由哪個group管理:usage_by_class = {0,0,0,…}

    要申請的chunk大小,由這個大小計算出sc:req = 0x30 -> sc = 0x3

    malloc的chunk大小:UNITsize_classes = 0x10 0x3 = 0x30

    設定該group最多有多少個chunk:ctx.usage_by_class[sc] = 30 = 0x1e

    漏洞點(Info Leak)

    0x30 chunk, malloc 6次,free 5次

    store('A', 'A')
    for _ in range(5):
        query('A' * 0x30)
    

    avail_mask = 0x40 = 0b1000000除了最后一個chunk,其余chunk不可分配;

    freed_mask = 0x3e = 0b111110除第一個以及最后一個chunk,其余chunk已被釋放

    gef?  p *(struct meta*)0x55b9b0b551a8
    $2 = {
      prev = 0x55b9b0b551a8,
      next = 0x55b9b0b551a8,
      mem = 0x7fccf5fdcc50,
      avail_mask = 0x40,
      freed_mask = 0x3e,
      last_idx = 0x6,
      freeable = 0x1,
      sizeclass = 0x3,
      maplen = 0x0
    }
    

    可以發現,free掉的chunk不會優先分配

    chunk在被free后不會清空user data域

    增加到malloc 8次,free 7次

    store('A', 'A')
    for _ in range(5):
        query('A' * 0x30)
    query('A' * 0x30)
    query('B' * 0x30)
    

    avail_mask = 0x7c = 0b1111100被釋放的chunk重新分配,也就是當耗盡該group的7個chunk時,堆管理器才會檢查是否有已被free掉的chunk,將這些chunk的avail_mask置1,再重新分配。

    gef?  p *(struct meta*)0x5575a83401a8
    $2 = {
      prev = 0x5575a83401a8,
      next = 0x5575a83401a8,
      mem = 0x7f54fbdeec50,
      avail_mask = 0x7c,
      freed_mask = 0x2,
      last_idx = 0x6,
      freeable = 0x1,
      sizeclass = 0x3,
      maplen = 0x0
    }
    

    現在可以分配回先前已被釋放的chunk,這樣就有了uaf的利用機會。通過重新將帶指針的結構體chunk分配回來,可leak出內存信息。

    漏洞點(Hijack)

    meta.h

    //line:90~100
    static inline void dequeue(struct meta **phead, struct meta *m)
    {
        if (m->next != m) {
            m->prev->next = m->next;
            m->next->prev = m->prev;
            if (*phead == m) *phead = m->next;
        } else {
            *phead = 0;
        }
        m->prev = m->next = 0;
    }
    

    在審計源碼時,可以發現這個經典的unsafe-unlink漏洞,跟早期glibc版本unlink宏出現的問題十分類似。

    通過偽造fake meta,在刪除該meta時,便會產生一次任意寫,那么就有了劫持的機會。關于mooosl這道題的完整利用過程會在下篇文章中分析。

    metaassert
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    本題來源于DefCon Quals 2021的mooosl,考察點是最新版本musl libc 1.2.2利用。
    musl libc 是一個專門為嵌入式系統開發的輕量級 libc 庫,以簡單、輕量和高效率為特色。
    溯源排查中比較重要的一環是web突破口排查,攻擊者通過web突破口入侵時,有極大的概率會寫入webshell,本文介紹下常見的webshell排查方法和流程。
    漏洞評級:高危影響版本:Django 3.2、Django 3.1安全版本:Django >= 3.2.5、Django >= 3.1.13漏洞分析2.1 order_by()order_by是QuerySet下的一種查詢方法,作用是將查詢的結果根據某個字段進行排序,在字段前面加一個符號,結果會倒序輸出。
    Thymeleaf SSTI漏洞分析
    2021-11-11 12:56:34
    要了解SSTI漏洞,首先要對模板引擎有所了解。下面是模板引擎的幾個相關概念。 模板引擎(這里特指用于Web開發的模板引擎)是為了使用戶界面與業務數據(內容)分離而產生的,它可以生成特定格式的文檔,用于網站的模板引擎就會生成一個標準的文檔。 模板引擎的本質是將模板文件和數據通過模板引擎生成最終的HTML代碼。 模板引擎不屬于特定技術領域,它是跨領域跨平臺的概念。 模板引擎的出現是為了解決前后端分離
    跟php pwn一樣,以前遇到這樣的pwn直接都不看的,經過了解之后發現,老版本的Musl libc和新版本之間差距還比較大。結合最近幾次比賽中出現的Musl pwn,學習一下新老版本的Musl libc姿勢。
    Java安全中Groovy組件從反序列化到命令注入及繞過和在白盒中的排查方法
    隨著Web應用攻擊手段變得復雜,基于請求特征的防護手段,已經不能滿足企業安全防護需求。在2012年的時候,Gartner引入了“Runtime application self-protection”一詞,簡稱為RASP,屬于一種新型應用安全保護技術,它將防護功能“ 注入”到應用程序中,與應用程序融為一體,使應用程序具備自我防護能力,當應用程序遭受到實際攻擊傷害時,能實時檢測和阻斷安全攻擊,而不需要進行人工干預。實現了在攻擊鏈路最關鍵的地方阻斷攻擊。
    強網杯-WriteUp
    2022-08-02 08:02:30
    然后使用 admin/123登錄管理員賬戶即可,登錄后存在購買頁面,經過測試,使用如下 payload 可以繞過檢查,再訪問主頁面即可獲得 flag
    .NET下的反Dump手段比較單一,無非是在運行后對PE頭中的.NET部分進行抹除。由于CLR在加載程序集時已經保存了所有.NET元數據的偏移和大小,抹除這部分.NET頭對程序的運行沒有任何影響。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类