dig into musl

dig into musl

1.数据结构

1.malloc_context

struct malloc_context {
	uint64_t secret;//用于检查meta的合法性
#ifndef PAGESIZE
	size_t pagesize;//0x1000
#endif
	int init_done;//是否初始化
	unsigned mmap_counter;//用mmap开辟空间的数量
	struct meta *free_meta_head;//free掉的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];//active的meta链表头指针的数组
	size_t usage_by_class[48];//对应大小meta的group管理的chunk数
	uint8_t unmap_seq[32], bounces[32];
	uint8_t seq;
	uintptr_t brk;
};

2.meta_area

struct meta_area {
	uint64_t check;//与secret对应,检查meta_area的合法性
	struct meta_area *next;//下一个meta_area
	int nslots;//管理的meta数量
	struct meta slots[];
};

3.meta

struct meta {
	struct meta *prev, *next;//meta链表指针
	struct group *mem;//管理的group指针
	volatile int avail_mask, freed_mask;//bitmap,用一个bit表示是与否
	uintptr_t last_idx:5;//占5bit,表示最后一个chunk的索引(从左往右数)
	uintptr_t freeable:1;//占1bit,表示是否可以free
	uintptr_t sizeclass:6;//占6bit,表示此meta对应chunk的大小索引
	uintptr_t maplen:8*sizeof(uintptr_t)-12;//如果是由mmap分配的,则为内存页数,否则为0
};

4.group

struct group {
	struct meta *meta;//对应meta指针
	unsigned char active_idx:5;//5bit,还多少chunk可用
	char pad[UNIT - sizeof(struct meta *) - 1];//对齐0x10byte
	unsigned char storage[];
};

5.chunk

struct chunk{
 char prev_user_data[];
    uint8_t idx;  //低5bit为idx第几个chunk
    uint16_t offset; //group_addr = chunk_addr - offset*0x10 - 0x10
    char data[];
};

2.利用思路

在nontrivial_free函数中存在dequeue函数

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; // 清理m(meta)的头尾指针
}

存在next指针和prev指针互写,且没有检查

free()–>nontrival_free()–>dequeue()

我们需要伪造chunk,甚至group,meta等等才能实现两个地址互写.

并且想要到达dequeue()函数,也要经过一系列检查.


free中调用的get_meta函数

(/src/malloc/mallocng/meta.h, line 129)
static inline struct meta *get_meta(const unsigned char *p)
{
	assert(!((uintptr_t)p & 15));
	int offset = *(const uint16_t *)(p - 2);
	int index = get_slot_index(p);
	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;
}

1.meta->mem == base : 检查meta的mem指针是否是原来通过group->meta找到自己的group,即检查双向闭合

2.index <= meta->last_idx : 检查idx的合法性

3.area->check == ctx.secret : 检查area的合法性

4.offset >= size_classes[meta->sizeclass]*index

5.offset < size_classes[meta->sizeclass]*(index+1) : 检查offset和chunk大小是否匹配

6.assert(offset <= meta->maplen*4096UL/UNIT - 1) : 检查offset是否越界


然后再看nontrivial_free

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)) {
		// any multi-slot group is necessarily on an active list
		// here, but single-slot groups might or might not be.
		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);
		// might still be active if there were no allocations
		// after last available slot was taken.
		if (ctx.active[sc] != g) {
			queue(&ctx.active[sc], g);
		}
	}
	a_or(&g->freed_mask, self);
	return (struct mapinfo){ 0 };
}

这里要求mask+self == (2u<last_idx)-1 && okay_to_free(g), 因此要合理设置meta的两个mask的值

(1) avail_mask 表示只有一个chunk 被使用 ,freed_mask=0,free这个chunk

(2) avail_mask=0, freed_mask 表示只有1个chunk没被释放,即当前这个chunk没释放,free这个chunk

tips: activate_group(ctx.active[sc])建议不要让这条语句执行,因为它在dequeue之后,相当于把next指针的区域链入active[]并且还要activate这个区域,但是这个区域一般是stdout_used是无法控制,来伪造成meta,所以很可能会报错. 所以为了避免执行,我们把sc改掉即sizeclass,使得ctx.active[sc]!=g,activate_new为0.


之后调用free_group

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;
		// not checking size/reserved here; it's intentionally invalid
		mi = nontrivial_free(m, idx);
	}
	free_meta(g);
	return mi;
}

我们不能进入else分支,这样会再一次调用nontrivial_free,因此要使得maplen不为0


3.控制程序流

想控制musl题的程序流,一般都是打IO

IO_FILE

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;
};

IO_FILE结构体中有4个函数指针,一般利用stdinstdoutstderr

exit()调用链

_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);
}

在close_file函数中当 f->wpos != f->wbase 就会调用f->write函数

1.getshell

1.利用dequeue函数,将可控地址写到stdout_used(__stdout_FILE结构体链表头)中

2.再在FILE结构体头几字节写入"/bin/sh"

3.再修改write指针为system,

4.以及f->wpos != f->wbase

5.触发exit()

2.orw

maigic_gadget

mov rsp, qword ptr [rdi + 0x30] ; jmp qword ptr [rdi + 0x38]

1.利用dequeue函数,将可控地址写到stdout_used(__stdout_FILE结构体链表头)中

2.再rdi+0x30也就是FILE+0x30写入orw的首地址,在rdi+0x38(也是f->wbase)处写入ret

3.再在write指针处写入magic_gagdet

4.构造好orw链

5.触发exit()


reference

musl 1.2.2 总结+源码分析

musl pwn 入门 (1)

musl pwn 入门 (2)

musl pwn 入门 (3)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值