diff --git a/examples/shellcode_run.py b/examples/shellcode_run.py index c7e189696..1507fdab6 100644 --- a/examples/shellcode_run.py +++ b/examples/shellcode_run.py @@ -3,12 +3,23 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import os import sys sys.path.append("..") from qiling import Qiling from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE + +def windows_rootfs_ready(rootfs: str) -> bool: + # The Windows examples need genuine Windows system DLLs, which cannot be + # redistributed and are therefore not shipped with Qiling. They must be + # collected from a licensed Windows host using the helper script at + # examples/scripts/dllscollector.bat. Probe for ntdll.dll so we can skip + # these stages with a helpful message instead of crashing on an unmapped + # read when the DLLs are absent. + return os.path.isfile(os.path.join(rootfs, 'Windows', 'System32', 'ntdll.dll')) + X86_LIN = bytes.fromhex('31c050682f2f7368682f62696e89e3505389e1b00bcd80') X8664_LIN = bytes.fromhex('31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05') @@ -87,12 +98,20 @@ ql.run() print("\nWindows x86 Shellcode") - ql = Qiling(code=X86_WIN, archtype=QL_ARCH.X86, ostype=QL_OS.WINDOWS, rootfs=r'rootfs/x86_windows') - ql.run() + if windows_rootfs_ready(r'rootfs/x86_windows'): + ql = Qiling(code=X86_WIN, archtype=QL_ARCH.X86, ostype=QL_OS.WINDOWS, rootfs=r'rootfs/x86_windows') + ql.run() + else: + print(" [skipped] Windows system DLLs not found under rootfs/x86_windows/Windows/System32.") + print(" Collect them from a licensed Windows host with examples/scripts/dllscollector.bat.") print("\nWindows x86-64 Shellcode") - ql = Qiling(code=X8664_WIN, archtype=QL_ARCH.X8664, ostype=QL_OS.WINDOWS, rootfs=r'rootfs/x8664_windows') - ql.run() + if windows_rootfs_ready(r'rootfs/x8664_windows'): + ql = Qiling(code=X8664_WIN, archtype=QL_ARCH.X8664, ostype=QL_OS.WINDOWS, rootfs=r'rootfs/x8664_windows') + ql.run() + else: + print(" [skipped] Windows system DLLs not found under rootfs/x8664_windows/Windows/System32.") + print(" Collect them from a licensed Windows host with examples/scripts/dllscollector.bat.") # FIXME: freebsd sockets are currently broken. # diff --git a/qiling/arch/mips_const.py b/qiling/arch/mips_const.py index c7f1a5722..a56c7e0b4 100644 --- a/qiling/arch/mips_const.py +++ b/qiling/arch/mips_const.py @@ -3,8 +3,17 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from enum import IntEnum + from unicorn.mips_const import * + +class EXCP(IntEnum): + # subset of QEMU's MIPS exception codes, as reported to unicorn interrupt hooks + SYSCALL = 17 # system call + BREAK = 18 # breakpoint + RI = 20 # reserved (illegal) instruction + reg_map = { "r0": UC_MIPS_REG_0, "r1": UC_MIPS_REG_1, diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 0313218d6..cbba81fb1 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -12,6 +12,8 @@ from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.arch import arm_utils +from qiling.arch.cortex_m_const import EXCP +from qiling.arch.mips_const import EXCP as MIPS_EXCP from qiling.cc import QlCC, intel, arm, mips, riscv, ppc from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall @@ -56,7 +58,8 @@ def load(self): # ARM if self.ql.arch.type == QL_ARCH.ARM: self.ql.arch.enable_vfp() - self.ql.hook_intno(self.hook_syscall, 2) + self.ql.hook_intno(self.hook_syscall, EXCP.SWI) + self.ql.hook_intno(self.hook_cpu_exception, EXCP.UDEF) self.thread_class = thread.QlLinuxARMThread arm_utils.init_linux_traps(self.ql, { 'memory_barrier': 0xffff0fa0, @@ -66,13 +69,15 @@ def load(self): # MIPS32 elif self.ql.arch.type == QL_ARCH.MIPS: - self.ql.hook_intno(self.hook_syscall, 17) + self.ql.hook_intno(self.hook_syscall, MIPS_EXCP.SYSCALL) + self.ql.hook_intno(self.hook_cpu_exception, MIPS_EXCP.RI) self.thread_class = thread.QlLinuxMIPS32Thread # ARM64 elif self.ql.arch.type == QL_ARCH.ARM64: self.ql.arch.enable_vfp() - self.ql.hook_intno(self.hook_syscall, 2) + self.ql.hook_intno(self.hook_syscall, EXCP.SWI) + self.ql.hook_intno(self.hook_cpu_exception, EXCP.UDEF) self.thread_class = thread.QlLinuxARM64Thread # X86 @@ -137,6 +142,24 @@ def setup_procfs(self): def hook_syscall(self, ql, intno = None): return self.load_syscall() + def hook_cpu_exception(self, ql, intno = None): + # A cpu exception the kernel would turn into a fatal signal that + # terminates the process (e.g. SIGILL on an undefined instruction). + # Emulate that termination by stopping cleanly instead of letting the + # unhandled-interrupt dispatcher raise QlErrorCoreHook. This commonly + # happens with shellcode that falls through into trailing data once a + # terminal syscall (e.g. a denied execve) returns instead of replacing + # the image. + signame = { + EXCP.UDEF: 'SIGILL', # ARM / ARM64 undefined instruction + MIPS_EXCP.RI: 'SIGILL', # MIPS reserved (illegal) instruction + }.get(intno, f'exception {intno:#x}') + + pc = ql.arch.regs.arch_pc + + ql.log.debug(f'CPU raised {signame} at {pc:#x}; terminating emulated process') + ql.stop() + def register_function_after_load(self, function): if function not in self.function_after_load_list: self.function_after_load_list.append(function) diff --git a/tests/test_shellcode.py b/tests/test_shellcode.py index 9b2b4e054..34428f1cf 100644 --- a/tests/test_shellcode.py +++ b/tests/test_shellcode.py @@ -9,7 +9,7 @@ sys.path.append("..") from qiling import Qiling -from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT, QL_VERBOSE +from qiling.const import QL_ARCH, QL_OS, QL_ENDIAN, QL_INTERCEPT, QL_VERBOSE # test = bytes.fromhex('cccc') @@ -22,6 +22,13 @@ 2f7368 ''') +# big-endian counterpart of MIPS32EL_LIN: the instruction words are byte-swapped +# while the trailing '/bin/sh' string is left as-is +MIPS32EB_LIN = bytes.fromhex(''' + 2806ffff04d0ffff2805ffff27e410012484f00f24020fab0101010c2f62696e + 2f7368 +''') + X86_WIN = bytes.fromhex(''' fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c 617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b5920 @@ -105,6 +112,13 @@ def test_linux_mips32(self): ql.os.set_syscall('execve', graceful_execve, QL_INTERCEPT.EXIT) ql.run() + def test_linux_mips32eb(self): + print("Linux MIPS 32bit EB Shellcode") + ql = Qiling(code=MIPS32EB_LIN, archtype=QL_ARCH.MIPS, ostype=QL_OS.LINUX, endian=QL_ENDIAN.EB, verbose=QL_VERBOSE.OFF) + + ql.os.set_syscall('execve', graceful_execve, QL_INTERCEPT.EXIT) + ql.run() + # This shellcode needs to be changed to something non-blocking def test_linux_arm(self): print("Linux ARM 32bit Shellcode")