mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
Merge pull request #1661 from retrocpugeek/fix/cpu-exception-sigill
Terminate the emulated process on an illegal-instruction CPU exception
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -12,6 +12,8 @@ from qiling import Qiling
|
||||
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 @@ class QlOsLinux(QlOsPosix):
|
||||
# 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 @@ class QlOsLinux(QlOsPosix):
|
||||
|
||||
# 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 @@ class QlOsLinux(QlOsPosix):
|
||||
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)
|
||||
|
||||
@@ -123,6 +123,29 @@ class TestShellcode(unittest.TestCase):
|
||||
ql.os.set_syscall('execve', graceful_execve, QL_INTERCEPT.EXIT)
|
||||
ql.run()
|
||||
|
||||
# the tests above end the emulation from the execve hook, so they never reach
|
||||
# the shellcode's trailing '/bin/sh' string. the two below deliberately let
|
||||
# execve return and fall through into it: those bytes are not valid code, so
|
||||
# the cpu raises an undefined/reserved instruction exception. the kernel would
|
||||
# deliver a fatal SIGILL and terminate the process, and qiling must do the
|
||||
# same. before this fix nothing hooked those exceptions, so the unhandled
|
||||
# interrupt was turned into QlErrorCoreHook and ql.run() blew up.
|
||||
def _run_past_execve(self, code: bytes, archtype: QL_ARCH):
|
||||
def returning_execve(ql: Qiling, pathname: int, argv: int, envp: int, retval: int):
|
||||
assert retval != 0, 'execve is not expected to return on success'
|
||||
|
||||
ql = Qiling(code=code, archtype=archtype, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.OFF)
|
||||
ql.os.set_syscall('execve', returning_execve, QL_INTERCEPT.EXIT)
|
||||
ql.run()
|
||||
|
||||
def test_linux_arm64_illegal_instruction(self):
|
||||
print("Linux ARM 64bit illegal instruction")
|
||||
self._run_past_execve(ARM64_LIN, QL_ARCH.ARM64)
|
||||
|
||||
def test_linux_mips32_illegal_instruction(self):
|
||||
print("Linux MIPS 32bit EL illegal instruction")
|
||||
self._run_past_execve(MIPS32EL_LIN, QL_ARCH.MIPS)
|
||||
|
||||
# #This shellcode needs to be changed to something simpler not requiring rootfs
|
||||
# def test_windows_x86(self):
|
||||
# print("Windows X86 32bit Shellcode")
|
||||
|
||||
Reference in New Issue
Block a user