mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
@@ -118,6 +118,8 @@ CALL :collect_dll64 shlwapi.dll
|
||||
CALL :collect_dll64 user32.dll
|
||||
CALL :collect_dll64 vcruntime140.dll
|
||||
CALL :collect_dll64 vcruntime140d.dll
|
||||
CALL :collect_dll64 vcruntime140_1.dll
|
||||
CALL :collect_dll64 vcruntime140_1d.dll
|
||||
CALL :collect_dll64 win32u.dll
|
||||
CALL :collect_dll64 winhttp.dll
|
||||
CALL :collect_dll64 wininet.dll
|
||||
|
||||
+21
-11
@@ -32,10 +32,10 @@ from .core_hooks import QlCoreHooks
|
||||
class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
def __init__(
|
||||
self,
|
||||
argv: Sequence[str] = None,
|
||||
argv: Sequence[str] = [],
|
||||
rootfs: str = r'.',
|
||||
env: MutableMapping[AnyStr, AnyStr] = {},
|
||||
code: bytes = None,
|
||||
code: Optional[bytes] = None,
|
||||
ostype: Union[str, QL_OS] = None,
|
||||
archtype: Union[str, QL_ARCH] = None,
|
||||
verbose: QL_VERBOSE = QL_VERBOSE.DEFAULT,
|
||||
@@ -90,18 +90,26 @@ class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
##############
|
||||
# argv setup #
|
||||
##############
|
||||
if argv is None:
|
||||
argv = ['qilingcode']
|
||||
if argv:
|
||||
if code:
|
||||
raise AttributeError('argv and code are mutually execlusive')
|
||||
|
||||
elif not os.path.exists(argv[0]):
|
||||
raise QlErrorFileNotFound(f'Target binary not found: "{argv[0]}"')
|
||||
target = argv[0]
|
||||
|
||||
if not os.path.isfile(target):
|
||||
raise QlErrorFileNotFound(f'Target binary not found: "{target}"')
|
||||
else:
|
||||
# an empty argv list means we are going to execute a shellcode. to keep
|
||||
# the 'path' api compatible, we insert a dummy placeholder
|
||||
|
||||
argv = ['']
|
||||
|
||||
self._argv = argv
|
||||
|
||||
################
|
||||
# rootfs setup #
|
||||
################
|
||||
if not os.path.exists(rootfs):
|
||||
if not os.path.isdir(rootfs):
|
||||
raise QlErrorFileNotFound(f'Target rootfs not found: "{rootfs}"')
|
||||
|
||||
self._rootfs = rootfs
|
||||
@@ -697,11 +705,11 @@ class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
|
||||
# Map "ql_path" to any objects which implements QlFsMappedObject.
|
||||
def add_fs_mapper(self, ql_path: Union["PathLike", str], real_dest):
|
||||
self.os.fs_mapper.add_fs_mapping(ql_path, real_dest)
|
||||
self.os.fs_mapper.add_mapping(ql_path, real_dest)
|
||||
|
||||
# Remove "ql_path" mapping.
|
||||
def remove_fs_mapper(self, ql_path: Union["PathLike", str]):
|
||||
self.os.fs_mapper.remove_fs_mapping(ql_path)
|
||||
self.os.fs_mapper.remove_mapping(ql_path)
|
||||
|
||||
# push to stack bottom, and update stack register
|
||||
def stack_push(self, data):
|
||||
@@ -757,14 +765,16 @@ class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
if getattr(self.arch, '_init_thumb', False):
|
||||
begin |= 0b1
|
||||
|
||||
self._state = QL_STATE.STARTED
|
||||
|
||||
# reset exception status before emulation starts
|
||||
self._internal_exception = None
|
||||
|
||||
self._state = QL_STATE.STARTED
|
||||
|
||||
# effectively start the emulation. this returns only after uc.emu_stop is called
|
||||
self.uc.emu_start(begin, end, timeout, count)
|
||||
|
||||
self._state = QL_STATE.STOPPED
|
||||
|
||||
# if an exception was raised during emulation, propagate it up
|
||||
if self.internal_exception is not None:
|
||||
raise self.internal_exception
|
||||
|
||||
+46
-38
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -8,22 +8,53 @@
|
||||
# handling hooks #
|
||||
##############################################
|
||||
|
||||
import functools
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, MutableMapping, MutableSequence, Protocol
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from unicorn import Uc
|
||||
from unicorn.unicorn_const import *
|
||||
from unicorn.unicorn_const import (
|
||||
UC_HOOK_INTR,
|
||||
UC_HOOK_INSN,
|
||||
UC_HOOK_CODE,
|
||||
UC_HOOK_BLOCK,
|
||||
|
||||
UC_HOOK_MEM_READ_UNMAPPED, # attempt to read from an unmapped memory location
|
||||
UC_HOOK_MEM_WRITE_UNMAPPED, # attempt to write to an unmapped memory location
|
||||
UC_HOOK_MEM_FETCH_UNMAPPED, # attempt to fetch from an unmapped memory location
|
||||
UC_HOOK_MEM_UNMAPPED, # any of the 3 above
|
||||
|
||||
UC_HOOK_MEM_READ_PROT, # attempt to read from a non-readable memory location
|
||||
UC_HOOK_MEM_WRITE_PROT, # attempt to write to a write-protected memory location
|
||||
UC_HOOK_MEM_FETCH_PROT, # attempt to fetch from a non-executable memory location
|
||||
UC_HOOK_MEM_PROT, # any of the 3 above
|
||||
|
||||
UC_HOOK_MEM_READ_INVALID, # UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_READ_PROT
|
||||
UC_HOOK_MEM_WRITE_INVALID, # UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_WRITE_INVALID
|
||||
UC_HOOK_MEM_FETCH_INVALID, # UC_HOOK_MEM_FETCH_UNMAPPED | UC_HOOK_MEM_FETCH_INVALID
|
||||
UC_HOOK_MEM_INVALID, # any of the 3 above
|
||||
|
||||
UC_HOOK_MEM_READ, # valid memory read
|
||||
UC_HOOK_MEM_WRITE, # valid memory write
|
||||
UC_HOOK_MEM_FETCH, # valid instruction fetch
|
||||
UC_HOOK_MEM_VALID, # any of the 3 above
|
||||
|
||||
UC_HOOK_MEM_READ_AFTER,
|
||||
UC_HOOK_INSN_INVALID
|
||||
)
|
||||
|
||||
from .core_hooks_types import Hook, HookAddr, HookIntr, HookRet
|
||||
from .const import QL_HOOK_BLOCK
|
||||
from .exception import QlErrorCoreHook
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from unicorn import Uc
|
||||
from qiling import Qiling
|
||||
|
||||
|
||||
class MemHookCallback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', __access: int, __address: int, __size: int, __value: int, *__context: Any) -> Any:
|
||||
def __call__(self, __ql: Qiling, __access: int, __address: int, __size: int, __value: int, *__context: Any) -> Any:
|
||||
"""Memory access hook callback.
|
||||
|
||||
Args:
|
||||
@@ -40,8 +71,9 @@ class MemHookCallback(Protocol):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TraceHookCalback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', __address: int, __size: int, *__context: Any) -> Any:
|
||||
def __call__(self, __ql: Qiling, __address: int, __size: int, *__context: Any) -> Any:
|
||||
"""Execution hook callback.
|
||||
|
||||
Args:
|
||||
@@ -56,8 +88,9 @@ class TraceHookCalback(Protocol):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AddressHookCallback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', *__context: Any) -> Any:
|
||||
def __call__(self, __ql: Qiling, *__context: Any) -> Any:
|
||||
"""Address hook callback.
|
||||
|
||||
Args:
|
||||
@@ -70,8 +103,9 @@ class AddressHookCallback(Protocol):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class InterruptHookCallback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', intno: int, *__context: Any) -> Any:
|
||||
def __call__(self, __ql: Qiling, intno: int, *__context: Any) -> Any:
|
||||
"""Interrupt hook callback.
|
||||
|
||||
Args:
|
||||
@@ -86,9 +120,8 @@ class InterruptHookCallback(Protocol):
|
||||
pass
|
||||
|
||||
|
||||
def hookcallback(ql: 'Qiling', callback: Callable):
|
||||
|
||||
functools.wraps(callback)
|
||||
def hookcallback(ql: Qiling, callback: Callable):
|
||||
@wraps(callback)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return callback(*args, **kwargs)
|
||||
@@ -113,10 +146,10 @@ class QlCoreHooks:
|
||||
self._addr_hook: MutableMapping[int, MutableSequence[HookAddr]] = {}
|
||||
self._addr_hook_fuc: MutableMapping[int, int] = {}
|
||||
|
||||
|
||||
########################
|
||||
# Callback definitions #
|
||||
########################
|
||||
|
||||
def _hook_intr_cb(self, uc: Uc, intno: int, pack_data) -> None:
|
||||
"""Interrupt hooks dispatcher.
|
||||
"""
|
||||
@@ -142,7 +175,6 @@ class QlCoreHooks:
|
||||
if not handled:
|
||||
raise QlErrorCoreHook("_hook_intr_cb : not handled")
|
||||
|
||||
|
||||
def _hook_insn_cb(self, uc: Uc, *args):
|
||||
"""Instruction hooks dispatcher.
|
||||
"""
|
||||
@@ -166,7 +198,6 @@ class QlCoreHooks:
|
||||
# use the last return value received
|
||||
return retval
|
||||
|
||||
|
||||
def _hook_trace_cb(self, uc: Uc, addr: int, size: int, pack_data) -> None:
|
||||
"""Code and block hooks dispatcher.
|
||||
"""
|
||||
@@ -183,7 +214,6 @@ class QlCoreHooks:
|
||||
if type(ret) is int and ret & QL_HOOK_BLOCK:
|
||||
break
|
||||
|
||||
|
||||
def _hook_mem_cb(self, uc: Uc, access: int, addr: int, size: int, value: int, pack_data):
|
||||
"""Memory access hooks dispatcher.
|
||||
"""
|
||||
@@ -207,7 +237,6 @@ class QlCoreHooks:
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _hook_insn_invalid_cb(self, uc: Uc, pack_data) -> None:
|
||||
"""Invalid instruction hooks dispatcher.
|
||||
"""
|
||||
@@ -228,7 +257,6 @@ class QlCoreHooks:
|
||||
if not handled:
|
||||
raise QlErrorCoreHook("_hook_insn_invalid_cb : not handled")
|
||||
|
||||
|
||||
def _hook_addr_cb(self, uc: Uc, addr: int, size: int, pack_data):
|
||||
"""Address hooks dispatcher.
|
||||
"""
|
||||
@@ -247,18 +275,17 @@ class QlCoreHooks:
|
||||
###############
|
||||
# Class Hooks #
|
||||
###############
|
||||
|
||||
def _ql_hook_internal(self, hook_type: int, callback: Callable, context: Any, *args) -> int:
|
||||
_callback = hookcallback(self, callback)
|
||||
|
||||
return self._h_uc.hook_add(hook_type, _callback, (self, context), 1, 0, *args)
|
||||
|
||||
|
||||
def _ql_hook_addr_internal(self, callback: Callable, address: int) -> int:
|
||||
_callback = hookcallback(self, callback)
|
||||
|
||||
return self._h_uc.hook_add(UC_HOOK_CODE, _callback, self, address, address)
|
||||
|
||||
|
||||
def _ql_hook(self, hook_type: int, h: Hook, *args) -> None:
|
||||
|
||||
def __handle_intr(t: int) -> None:
|
||||
@@ -330,7 +357,6 @@ class QlCoreHooks:
|
||||
if hook_type & t:
|
||||
handler(t)
|
||||
|
||||
|
||||
def ql_hook(self, hook_type: int, callback: Callable, user_data: Any = None, begin: int = 1, end: int = 0, *args) -> HookRet:
|
||||
"""Intercept certain emulation events within a specified range.
|
||||
|
||||
@@ -355,7 +381,6 @@ class QlCoreHooks:
|
||||
|
||||
return HookRet(self, hook_type, hook)
|
||||
|
||||
|
||||
def hook_code(self, callback: TraceHookCalback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept assembly instructions before they get executed.
|
||||
|
||||
@@ -374,12 +399,10 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_CODE, callback, user_data, begin, end)
|
||||
|
||||
|
||||
# TODO: remove; this is a special case of hook_intno(-1)
|
||||
def hook_intr(self, callback, user_data=None, begin=1, end=0):
|
||||
return self.ql_hook(UC_HOOK_INTR, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_block(self, callback: TraceHookCalback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept landings in new basic blocks in a specified range.
|
||||
|
||||
@@ -398,7 +421,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_BLOCK, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_unmapped(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept illegal accesses to unmapped memory in a specified range.
|
||||
|
||||
@@ -417,7 +439,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_UNMAPPED, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_read_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept illegal reading attempts from a specified range.
|
||||
|
||||
@@ -436,7 +457,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_READ_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_write_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept illegal writing attempts to a specified range.
|
||||
|
||||
@@ -455,7 +475,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_WRITE_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_fetch_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept illegal code fetching attempts from a specified range.
|
||||
|
||||
@@ -474,7 +493,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_FETCH_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_valid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept benign memory accesses within a specified range.
|
||||
This is equivalent to hooking memory reads, writes and fetches.
|
||||
@@ -494,7 +512,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_VALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept invalid memory accesses within a specified range.
|
||||
This is equivalent to hooking invalid memory reads, writes and fetches.
|
||||
@@ -514,7 +531,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_address(self, callback: AddressHookCallback, address: int, user_data: Any = None) -> HookRet:
|
||||
"""Intercept execution from a certain memory address.
|
||||
|
||||
@@ -540,7 +556,6 @@ class QlCoreHooks:
|
||||
# note: assuming 0 is not a valid hook type
|
||||
return HookRet(self, 0, hook)
|
||||
|
||||
|
||||
def hook_intno(self, callback: InterruptHookCallback, intno: int, user_data: Any = None) -> HookRet:
|
||||
"""Intercept interrupts.
|
||||
|
||||
@@ -558,7 +573,6 @@ class QlCoreHooks:
|
||||
|
||||
return HookRet(self, UC_HOOK_INTR, hook)
|
||||
|
||||
|
||||
def hook_mem_read(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept benign memory reads from a specified range.
|
||||
|
||||
@@ -577,7 +591,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_READ, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_write(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept benign memory writes to a specified range.
|
||||
|
||||
@@ -596,7 +609,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_WRITE, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_fetch(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept benign code fetches from a specified range.
|
||||
|
||||
@@ -615,7 +627,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_FETCH, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_insn(self, callback, insn_type: int, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept execution of a certain instruction type within a specified range.
|
||||
|
||||
@@ -637,7 +648,6 @@ class QlCoreHooks:
|
||||
|
||||
return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, insn_type)
|
||||
|
||||
|
||||
def hook_del(self, hret: HookRet) -> None:
|
||||
"""Unregister an existing hook and release its resources.
|
||||
|
||||
@@ -692,7 +702,6 @@ class QlCoreHooks:
|
||||
if hook_type & t:
|
||||
handler(t)
|
||||
|
||||
|
||||
def clear_hooks(self):
|
||||
for ptr in self._hook_fuc.values():
|
||||
self._h_uc.hook_del(ptr)
|
||||
@@ -705,7 +714,6 @@ class QlCoreHooks:
|
||||
|
||||
self.clear_ql_hooks()
|
||||
|
||||
|
||||
def clear_ql_hooks(self):
|
||||
self._hook = {}
|
||||
self._hook_fuc = {}
|
||||
|
||||
+40
-34
@@ -78,36 +78,42 @@ class QlGdb(QlDebugger):
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
|
||||
if ql.baremetal:
|
||||
load_address = ql.loader.load_address
|
||||
exit_point = load_address + os.path.getsize(ql.path)
|
||||
elif ql.code:
|
||||
load_address = ql.os.entry_point
|
||||
exit_point = load_address + len(ql.code)
|
||||
else:
|
||||
load_address = ql.loader.load_address
|
||||
exit_point = load_address + os.path.getsize(ql.path)
|
||||
def __get_attach_addr() -> int:
|
||||
if ql.baremetal:
|
||||
entry_point = ql.loader.entry_point
|
||||
|
||||
if ql.baremetal:
|
||||
entry_point = ql.loader.entry_point
|
||||
elif ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD) and not ql.code:
|
||||
entry_point = ql.os.elf_entry
|
||||
else:
|
||||
entry_point = ql.os.entry_point
|
||||
elif ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD) and not ql.code:
|
||||
entry_point = ql.os.elf_entry
|
||||
|
||||
# though linkers set the entry point LSB to indicate arm thumb mode, the
|
||||
# effective entry point address is aligned. make sure we have it aligned
|
||||
if hasattr(ql.arch, 'is_thumb'):
|
||||
entry_point &= ~0b1
|
||||
else:
|
||||
entry_point = ql.os.entry_point
|
||||
|
||||
# Only part of the binary file will be debugged.
|
||||
if ql.entry_point is not None:
|
||||
entry_point = ql.entry_point
|
||||
# though linkers set the entry point LSB to indicate arm thumb mode, the
|
||||
# effective entry point address is aligned. make sure we have it aligned
|
||||
if hasattr(ql.arch, 'is_thumb'):
|
||||
entry_point &= ~0b1
|
||||
|
||||
if ql.exit_point is not None:
|
||||
exit_point = ql.exit_point
|
||||
return entry_point
|
||||
|
||||
self.gdb = QlGdbUtils(ql, entry_point, exit_point)
|
||||
def __get_detach_addr() -> int:
|
||||
if ql.baremetal:
|
||||
base = ql.loader.load_address
|
||||
size = os.path.getsize(ql.path)
|
||||
|
||||
elif ql.code:
|
||||
base = ql.os.entry_point
|
||||
size = len(ql.code)
|
||||
|
||||
else:
|
||||
base = ql.loader.load_address
|
||||
size = os.path.getsize(ql.path)
|
||||
|
||||
return base + size
|
||||
|
||||
attach_addr = __get_attach_addr() if ql.entry_point is None else ql.entry_point
|
||||
detach_addr = __get_detach_addr() if ql.exit_point is None else ql.exit_point
|
||||
|
||||
self.gdb = QlGdbUtils(ql, attach_addr, detach_addr)
|
||||
|
||||
self.features = QlGdbFeatures(self.ql.arch.type, self.ql.os.type)
|
||||
self.regsmap = self.features.regsmap
|
||||
@@ -293,11 +299,11 @@ class QlGdb(QlDebugger):
|
||||
addr, size = (int(p, 16) for p in subcmd.split(','))
|
||||
|
||||
try:
|
||||
data = self.ql.mem.read(addr, size).hex()
|
||||
except UcError:
|
||||
return 'E14'
|
||||
data = self.ql.mem.read(addr, size)
|
||||
except UcError as ex:
|
||||
return f'E{ex.errno:02d}'
|
||||
else:
|
||||
return data
|
||||
return data.hex()
|
||||
|
||||
def handle_M(subcmd: str) -> Reply:
|
||||
"""Write target memory.
|
||||
@@ -313,8 +319,8 @@ class QlGdb(QlDebugger):
|
||||
|
||||
try:
|
||||
self.ql.mem.write(addr, data)
|
||||
except UcError:
|
||||
return 'E01'
|
||||
except UcError as ex:
|
||||
return f'E{ex.errno:02d}'
|
||||
else:
|
||||
return REPLY_OK
|
||||
|
||||
@@ -693,8 +699,8 @@ class QlGdb(QlDebugger):
|
||||
try:
|
||||
if data:
|
||||
self.ql.mem.write(addr, data.encode(ENCODING))
|
||||
except UcError:
|
||||
return 'E01'
|
||||
except UcError as ex:
|
||||
return f'E{ex.errno:02d}'
|
||||
else:
|
||||
return REPLY_OK
|
||||
|
||||
@@ -837,7 +843,7 @@ class GdbSerialConn:
|
||||
buffer += incoming
|
||||
|
||||
# discard incoming acks
|
||||
if buffer[0:1] == REPLY_ACK:
|
||||
if buffer.startswith(REPLY_ACK):
|
||||
del buffer[0]
|
||||
|
||||
packet = pattern.match(buffer)
|
||||
|
||||
@@ -899,7 +899,7 @@ class QlEmuQiling:
|
||||
elffile = ELFFile(f)
|
||||
elf_header = elffile.header
|
||||
if elf_header['e_type'] == 'ET_EXEC':
|
||||
self.baseaddr = self.ql.os.elf_mem_start
|
||||
self.baseaddr = self.ql.loader.images[0].base
|
||||
elif elf_header['e_type'] == 'ET_DYN':
|
||||
if self.ql.arch.bits == 32:
|
||||
self.baseaddr = int(self.ql.os.profile.get("OS32", "load_address"), 16)
|
||||
|
||||
@@ -84,7 +84,7 @@ class QlLoaderDOS(QlLoader):
|
||||
|
||||
# https://en.wikipedia.org/wiki/Master_boot_record#BIOS_to_MBR_interface
|
||||
if not self.ql.os.fs_mapper.has_mapping(0x80):
|
||||
self.ql.os.fs_mapper.add_fs_mapping(0x80, QlDisk(path, 0x80))
|
||||
self.ql.os.fs_mapper.add_mapping(0x80, QlDisk(path, 0x80))
|
||||
|
||||
# 0x80 -> first drive
|
||||
self.ql.arch.regs.dx = 0x80
|
||||
|
||||
+10
-6
@@ -230,10 +230,15 @@ class QlLoaderELF(QlLoader):
|
||||
|
||||
# load the interpreter, if there is one
|
||||
if interp_path:
|
||||
interp_local_path = os.path.normpath(self.ql.rootfs + interp_path)
|
||||
self.ql.log.debug(f'Interpreter path: {interp_local_path}')
|
||||
interp_vpath = self.ql.os.path.virtual_abspath(interp_path)
|
||||
interp_hpath = self.ql.os.path.virtual_to_host_path(interp_path)
|
||||
|
||||
with open(interp_local_path, 'rb') as infile:
|
||||
self.ql.log.debug(f'Interpreter path: {interp_vpath}')
|
||||
|
||||
if not self.ql.os.path.is_safe_host_path(interp_hpath):
|
||||
raise PermissionError(f'unsafe path: {interp_hpath}')
|
||||
|
||||
with open(interp_hpath, 'rb') as infile:
|
||||
interp = ELFFile(infile)
|
||||
min_vaddr = min(seg['p_vaddr'] for seg in interp.iter_segments(type='PT_LOAD'))
|
||||
|
||||
@@ -244,10 +249,10 @@ class QlLoaderELF(QlLoader):
|
||||
self.ql.log.debug(f'Interpreter addr: {interp_address:#x}')
|
||||
|
||||
# load interpreter segments data to memory
|
||||
interp_start, interp_end = load_elf_segments(interp, interp_address, interp_local_path)
|
||||
interp_start, interp_end = load_elf_segments(interp, interp_address, interp_vpath)
|
||||
|
||||
# add interpreter to the loaded images list
|
||||
self.images.append(Image(interp_start, interp_end, os.path.abspath(interp_local_path)))
|
||||
self.images.append(Image(interp_start, interp_end, interp_hpath))
|
||||
|
||||
# determine entry point
|
||||
entry_point = interp_address + interp['e_entry']
|
||||
@@ -353,7 +358,6 @@ class QlLoaderELF(QlLoader):
|
||||
self.init_sp = self.ql.arch.regs.arch_sp
|
||||
|
||||
self.ql.os.entry_point = self.entry_point = entry_point
|
||||
self.ql.os.elf_mem_start = mem_start
|
||||
self.ql.os.elf_entry = self.elf_entry
|
||||
self.ql.os.function_hook = FunctionHook(self.ql, elf_phdr, elf_phnum, elf_phent, load_address, mem_end)
|
||||
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Mapping, MutableSequence, NamedTuple, Optional
|
||||
from typing import TYPE_CHECKING, Any, Mapping, MutableSequence, NamedTuple, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
from qiling import Qiling
|
||||
|
||||
class Image(NamedTuple):
|
||||
base: int
|
||||
end: int
|
||||
path: str
|
||||
|
||||
|
||||
class QlLoader:
|
||||
def __init__(self, ql: Qiling):
|
||||
self.ql = ql
|
||||
|
||||
+13
-3
@@ -3,13 +3,18 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import os, pefile, pickle, secrets, ntpath
|
||||
from typing import Any, Dict, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pefile
|
||||
import pickle
|
||||
import secrets
|
||||
import ntpath
|
||||
from typing import TYPE_CHECKING, Any, Dict, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union
|
||||
|
||||
from unicorn import UcError
|
||||
from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.arch.x86_const import FS_SEGMENT_ADDR, GS_SEGMENT_ADDR
|
||||
from qiling.const import QL_ARCH, QL_STATE
|
||||
from qiling.exception import QlErrorArch
|
||||
@@ -20,6 +25,10 @@ from qiling.os.windows.utils import has_lib_ext
|
||||
from qiling.os.windows.structs import *
|
||||
from .loader import QlLoader, Image
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
|
||||
class QlPeCacheEntry(NamedTuple):
|
||||
ba: int
|
||||
data: bytearray
|
||||
@@ -859,6 +868,7 @@ class QlLoaderPE(QlLoader, Process):
|
||||
self.ql.os.entry_point = self.entry_point
|
||||
self.init_sp = self.ql.arch.regs.arch_sp
|
||||
|
||||
|
||||
class ShowProgress:
|
||||
"""Display a progress animation while performing a time
|
||||
consuming task.
|
||||
|
||||
+43
-19
@@ -3,32 +3,39 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import weakref
|
||||
|
||||
from typing import Optional, TextIO
|
||||
from typing import TYPE_CHECKING, Optional, TextIO
|
||||
from logging import Filter, Formatter, LogRecord, Logger, NullHandler, StreamHandler, FileHandler
|
||||
|
||||
from qiling.const import QL_VERBOSE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
|
||||
QL_INSTANCE_ID = 114514
|
||||
|
||||
FMT_STR = '%(levelname)s\t%(message)s'
|
||||
|
||||
|
||||
class COLOR:
|
||||
WHITE = '\033[37m'
|
||||
CRIMSON = '\033[31m'
|
||||
RED = '\033[91m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
BLUE = '\033[94m'
|
||||
MAGENTA = '\033[95m'
|
||||
CYAN = '\033[96m'
|
||||
ENDC = '\033[0m'
|
||||
DEFAULT = '\033[39m'
|
||||
|
||||
class QlBaseFormatter(logging.Formatter):
|
||||
|
||||
class QlBaseFormatter(Formatter):
|
||||
__level_tag = {
|
||||
'WARNING' : '[!]',
|
||||
'INFO' : '[=]',
|
||||
@@ -37,9 +44,10 @@ class QlBaseFormatter(logging.Formatter):
|
||||
'ERROR' : '[x]'
|
||||
}
|
||||
|
||||
def __init__(self, ql, *args, **kwargs):
|
||||
def __init__(self, ql: Qiling, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.ql = weakref.proxy(ql)
|
||||
|
||||
self.ql: Qiling = weakref.proxy(ql)
|
||||
|
||||
def get_level_tag(self, level: str) -> str:
|
||||
return self.__level_tag[level]
|
||||
@@ -47,7 +55,7 @@ class QlBaseFormatter(logging.Formatter):
|
||||
def get_thread_tag(self, thread: str) -> str:
|
||||
return thread
|
||||
|
||||
def format(self, record: logging.LogRecord):
|
||||
def format(self, record: LogRecord):
|
||||
# In case we have multiple formatters, we have to keep a copy of the record.
|
||||
record = copy.copy(record)
|
||||
|
||||
@@ -64,6 +72,7 @@ class QlBaseFormatter(logging.Formatter):
|
||||
|
||||
return super().format(record)
|
||||
|
||||
|
||||
class QlColoredFormatter(QlBaseFormatter):
|
||||
__level_color = {
|
||||
'WARNING' : COLOR.YELLOW,
|
||||
@@ -76,22 +85,24 @@ class QlColoredFormatter(QlBaseFormatter):
|
||||
def get_level_tag(self, level: str) -> str:
|
||||
s = super().get_level_tag(level)
|
||||
|
||||
return f'{self.__level_color[level]}{s}{COLOR.ENDC}'
|
||||
return f'{self.__level_color[level]}{s}{COLOR.DEFAULT}'
|
||||
|
||||
def get_thread_tag(self, tid: str) -> str:
|
||||
s = super().get_thread_tag(tid)
|
||||
|
||||
return f'{COLOR.GREEN}{s}{COLOR.ENDC}'
|
||||
return f'{COLOR.GREEN}{s}{COLOR.DEFAULT}'
|
||||
|
||||
class RegexFilter(logging.Filter):
|
||||
|
||||
class RegexFilter(Filter):
|
||||
def update_filter(self, regexp: str):
|
||||
self._filter = re.compile(regexp)
|
||||
|
||||
def filter(self, record: logging.LogRecord):
|
||||
def filter(self, record: LogRecord):
|
||||
msg = record.getMessage()
|
||||
|
||||
return self._filter.match(msg) is not None
|
||||
|
||||
|
||||
def resolve_logger_level(verbose: QL_VERBOSE) -> int:
|
||||
return {
|
||||
QL_VERBOSE.DISABLED : logging.CRITICAL,
|
||||
@@ -102,6 +113,7 @@ def resolve_logger_level(verbose: QL_VERBOSE) -> int:
|
||||
QL_VERBOSE.DUMP : logging.DEBUG
|
||||
}[verbose]
|
||||
|
||||
|
||||
def __is_color_terminal(stream: TextIO) -> bool:
|
||||
"""Determine whether standard output is attached to a color terminal.
|
||||
|
||||
@@ -142,7 +154,8 @@ def __is_color_terminal(stream: TextIO) -> bool:
|
||||
|
||||
return handler(stream.fileno())
|
||||
|
||||
def setup_logger(ql, log_file: Optional[str], console: bool, log_override: Optional[logging.Logger], log_plain: bool):
|
||||
|
||||
def setup_logger(ql: Qiling, log_file: Optional[str], console: bool, log_override: Optional[Logger], log_plain: bool):
|
||||
global QL_INSTANCE_ID
|
||||
|
||||
# If there is an override for our logger, then use it.
|
||||
@@ -155,15 +168,19 @@ def setup_logger(ql, log_file: Optional[str], console: bool, log_override: Optio
|
||||
|
||||
# Disable propagation to avoid duplicate output.
|
||||
log.propagate = False
|
||||
|
||||
# Clear all handlers and filters.
|
||||
log.handlers = []
|
||||
log.filters = []
|
||||
log.handlers.clear()
|
||||
log.filters.clear()
|
||||
|
||||
# Do we have console output?
|
||||
if console:
|
||||
handler = logging.StreamHandler()
|
||||
handler = StreamHandler()
|
||||
|
||||
if log_plain or not __is_color_terminal(handler.stream):
|
||||
# adhere to the NO_COLOR convention (see: https://no-color.org/)
|
||||
no_color = os.getenv('NO_COLOR', False)
|
||||
|
||||
if no_color or log_plain or not __is_color_terminal(handler.stream):
|
||||
formatter = QlBaseFormatter(ql, FMT_STR)
|
||||
else:
|
||||
formatter = QlColoredFormatter(ql, FMT_STR)
|
||||
@@ -171,18 +188,25 @@ def setup_logger(ql, log_file: Optional[str], console: bool, log_override: Optio
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
else:
|
||||
handler = logging.NullHandler()
|
||||
handler = NullHandler()
|
||||
log.addHandler(handler)
|
||||
|
||||
# Do we have to write log to a file?
|
||||
if log_file is not None:
|
||||
handler = logging.FileHandler(log_file)
|
||||
handler = FileHandler(log_file)
|
||||
formatter = QlBaseFormatter(ql, FMT_STR)
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
# optimize logging speed by avoiding the collection of unnecesary logging properties
|
||||
logging._srcfile = None
|
||||
logging.logThreads = False
|
||||
logging.logProcesses = False
|
||||
logging.logMultiprocessing = False
|
||||
|
||||
return log
|
||||
|
||||
|
||||
__all__ = ['RegexFilter', 'setup_logger', 'resolve_logger_level']
|
||||
|
||||
+29
-12
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import os
|
||||
from typing import AnyStr
|
||||
from typing import AnyStr, Optional
|
||||
|
||||
from qiling.exception import *
|
||||
from qiling.os.posix.stat import *
|
||||
@@ -14,25 +14,28 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class ql_file:
|
||||
def __init__(self, path: AnyStr, fd: int):
|
||||
self.__path = path
|
||||
self.__fd = fd
|
||||
self.__closed = False
|
||||
|
||||
# information for syscall mmap
|
||||
self._is_map_shared = False
|
||||
self._mapped_offset = -1
|
||||
self._close_on_exec = 0
|
||||
self.close_on_exec = False
|
||||
|
||||
@classmethod
|
||||
def open(cls, open_path: AnyStr, open_flags: int, open_mode: int, dir_fd: int = None):
|
||||
open_mode &= 0x7fffffff
|
||||
def open(cls, path: AnyStr, flags: int, mode: int, dir_fd: Optional[int] = None):
|
||||
mode &= 0x7fffffff
|
||||
|
||||
try:
|
||||
fd = os.open(open_path, open_flags, open_mode, dir_fd=dir_fd)
|
||||
fd = os.open(path, flags, mode, dir_fd=dir_fd)
|
||||
except OSError as e:
|
||||
raise QlSyscallError(e.errno, e.args[1] + ' : ' + e.filename)
|
||||
|
||||
return cls(open_path, fd)
|
||||
return cls(path, fd)
|
||||
|
||||
def read(self, read_len: int) -> bytes:
|
||||
return os.read(self.__fd, read_len)
|
||||
@@ -52,6 +55,8 @@ class ql_file:
|
||||
def close(self) -> None:
|
||||
os.close(self.__fd)
|
||||
|
||||
self.__closed = True
|
||||
|
||||
def fstat(self):
|
||||
return Fstat(self.__fd)
|
||||
|
||||
@@ -88,9 +93,21 @@ class ql_file:
|
||||
return self.__path
|
||||
|
||||
@property
|
||||
def close_on_exec(self) -> int:
|
||||
return self._close_on_exec
|
||||
def closed(self) -> bool:
|
||||
return self.__closed
|
||||
|
||||
@close_on_exec.setter
|
||||
def close_on_exec(self, value: int) -> None:
|
||||
self._close_on_exec = value
|
||||
|
||||
class PersistentQlFile(ql_file):
|
||||
"""A persistent variation of the ql_file class, which silently drops
|
||||
attempts to close its udnerlying file. This is useful when using host
|
||||
environment resources, which should not be closed when their wrapping
|
||||
ql_file gets closed.
|
||||
|
||||
For example, stdout and stderr might be closed by the emulated program
|
||||
by calling POSIX dup2 or dup3 system calls, and then replaced by another
|
||||
file or socket. this class prevents the emulated program from closing
|
||||
shared resources on the hosting system.
|
||||
"""
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -10,16 +10,15 @@ from qiling.arch.x86_utils import GDTManager, SegmentManager86
|
||||
from qiling.const import QL_OS
|
||||
from qiling.os.posix.posix import QlOsPosix
|
||||
|
||||
|
||||
class QlOsFreebsd(QlOsPosix):
|
||||
type = QL_OS.FREEBSD
|
||||
|
||||
def __init__(self, ql):
|
||||
super(QlOsFreebsd, self).__init__(ql)
|
||||
|
||||
self.elf_mem_start = 0x0
|
||||
self.load()
|
||||
|
||||
|
||||
def load(self):
|
||||
gdtm = GDTManager(self.ql)
|
||||
|
||||
@@ -29,16 +28,14 @@ class QlOsFreebsd(QlOsPosix):
|
||||
|
||||
self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL)
|
||||
|
||||
|
||||
def hook_syscall(self, ql):
|
||||
return self.load_syscall()
|
||||
|
||||
|
||||
def run(self):
|
||||
if self.ql.exit_point is not None:
|
||||
self.exit_point = self.ql.exit_point
|
||||
|
||||
if self.ql.entry_point is not None:
|
||||
if self.ql.entry_point is not None:
|
||||
self.ql.loader.elf_entry = self.ql.entry_point
|
||||
|
||||
try:
|
||||
|
||||
@@ -48,7 +48,6 @@ class QlOsLinux(QlOsPosix):
|
||||
self.futexm = None
|
||||
self.fh = None
|
||||
self.function_after_load_list = []
|
||||
self.elf_mem_start = 0x0
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
@@ -118,15 +117,22 @@ class QlOsLinux(QlOsPosix):
|
||||
|
||||
# on fork or execve, do not inherit opened files tagged as 'close on exec'
|
||||
for i in range(len(self.fd)):
|
||||
if getattr(self.fd[i], 'close_on_exec', 0):
|
||||
if getattr(self.fd[i], 'close_on_exec', False):
|
||||
self.fd[i] = None
|
||||
|
||||
def setup_procfs(self):
|
||||
self.fs_mapper.add_fs_mapping(r'/proc/self/auxv', partial(QlProcFS.self_auxv, self))
|
||||
self.fs_mapper.add_fs_mapping(r'/proc/self/cmdline', partial(QlProcFS.self_cmdline, self))
|
||||
self.fs_mapper.add_fs_mapping(r'/proc/self/environ', partial(QlProcFS.self_environ, self))
|
||||
self.fs_mapper.add_fs_mapping(r'/proc/self/exe', partial(QlProcFS.self_exe, self))
|
||||
self.fs_mapper.add_fs_mapping(r'/proc/self/maps', partial(QlProcFS.self_map, self.ql.mem))
|
||||
files = (
|
||||
(r'/proc/self/auxv', lambda: partial(QlProcFS.self_auxv, self)),
|
||||
(r'/proc/self/cmdline', lambda: partial(QlProcFS.self_cmdline, self)),
|
||||
(r'/proc/self/environ', lambda: partial(QlProcFS.self_environ, self)),
|
||||
(r'/proc/self/exe', lambda: partial(QlProcFS.self_exe, self)),
|
||||
(r'/proc/self/maps', lambda: partial(QlProcFS.self_map, self.ql.mem))
|
||||
)
|
||||
|
||||
for filename, wrapper in files:
|
||||
# add mapping only if the user has not already mapped it
|
||||
if not self.fs_mapper.has_mapping(filename):
|
||||
self.fs_mapper.add_mapping(filename, wrapper())
|
||||
|
||||
def hook_syscall(self, ql, intno = None):
|
||||
return self.load_syscall()
|
||||
@@ -161,12 +167,14 @@ class QlOsLinux(QlOsPosix):
|
||||
if self.ql.entry_point is not None:
|
||||
self.ql.loader.elf_entry = self.ql.entry_point
|
||||
|
||||
# do we have an interp?
|
||||
elif self.ql.loader.elf_entry != self.ql.loader.entry_point:
|
||||
entry_address = self.ql.loader.elf_entry
|
||||
|
||||
if self.ql.arch.type == QL_ARCH.ARM:
|
||||
entry_address &= ~1
|
||||
|
||||
# start running interp, but stop when elf entry point is reached
|
||||
self.ql.emu_start(self.ql.loader.entry_point, entry_address, self.ql.timeout)
|
||||
self.ql.do_lib_patch()
|
||||
self.run_function_after_load()
|
||||
|
||||
@@ -16,6 +16,10 @@ class FsMappedStream(io.BytesIO):
|
||||
def __init__(self, fname: str, *args) -> None:
|
||||
super().__init__(*args)
|
||||
|
||||
# note that the name property should reflect the actual file name
|
||||
# on the host file system, and here we get a virtual file name
|
||||
# instead. we should be fine, however, since there is no file
|
||||
# backing this object anyway
|
||||
self.name = fname
|
||||
|
||||
|
||||
|
||||
+182
-100
@@ -4,11 +4,15 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from os import PathLike
|
||||
from typing import Any, Callable, MutableMapping, Union
|
||||
|
||||
from .path import QlOsPath
|
||||
from .filestruct import ql_file
|
||||
|
||||
QlPath = Union['PathLike[str]', str, 'PathLike[bytes]', bytes]
|
||||
|
||||
|
||||
# All mapped objects should inherit this class.
|
||||
# Note this object is compatible with ql_file.
|
||||
# Q: Why not derive from ql_file directly?
|
||||
@@ -63,115 +67,193 @@ class QlFsMapper:
|
||||
self._mapping: MutableMapping[str, Any] = {}
|
||||
self.path = path
|
||||
|
||||
def _open_mapping_ql_file(self, ql_path: str, openflags: int, openmode: int):
|
||||
real_dest = self._mapping[ql_path]
|
||||
def __contains__(self, vpath: str) -> bool:
|
||||
# canonicalize the path first
|
||||
absvpath = self.path.virtual_abspath(vpath)
|
||||
|
||||
if isinstance(real_dest, str):
|
||||
obj = ql_file.open(real_dest, openflags, openmode)
|
||||
return absvpath in self._mapping
|
||||
|
||||
elif callable(real_dest):
|
||||
obj = real_dest()
|
||||
|
||||
else:
|
||||
obj = real_dest
|
||||
|
||||
return obj
|
||||
|
||||
def _open_mapping(self, ql_path: str, openmode: str):
|
||||
real_dest = self._mapping[ql_path]
|
||||
|
||||
if isinstance(real_dest, str):
|
||||
obj = open(real_dest, openmode)
|
||||
|
||||
elif callable(real_dest):
|
||||
obj = real_dest()
|
||||
|
||||
else:
|
||||
obj = real_dest
|
||||
|
||||
return obj
|
||||
|
||||
def has_mapping(self, fm: str) -> bool:
|
||||
return fm in self._mapping
|
||||
|
||||
def mapping_count(self) -> int:
|
||||
return len(self._mapping)
|
||||
|
||||
def open_ql_file(self, path: str, openflags: int, openmode: int):
|
||||
if self.has_mapping(path):
|
||||
return self._open_mapping_ql_file(path, openflags, openmode)
|
||||
|
||||
host_path = self.path.virtual_to_host_path(path)
|
||||
|
||||
if not self.path.is_safe_host_path(host_path):
|
||||
raise PermissionError(f'unsafe path: {host_path}')
|
||||
|
||||
return ql_file.open(host_path, openflags, openmode)
|
||||
def file_exists(self, path:str) -> bool:
|
||||
# check if file exists
|
||||
if self.has_mapping(path):
|
||||
return True
|
||||
|
||||
host_path = self.path.virtual_to_host_path(path)
|
||||
if not self.path.is_safe_host_path(host_path):
|
||||
raise PermissionError(f'unsafe path: {host_path}')
|
||||
return os.path.isfile(host_path)
|
||||
|
||||
def create_empty_file(self, path:str)->bool:
|
||||
if not self.file_exists(path):
|
||||
try:
|
||||
f = self.open(path, "w+")
|
||||
f.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# for some reason, we could not create an empty file.
|
||||
return False
|
||||
return True
|
||||
|
||||
def open(self, path: str, openmode: str):
|
||||
if self.has_mapping(path):
|
||||
return self._open_mapping(path, openmode)
|
||||
|
||||
host_path = self.path.virtual_to_host_path(path)
|
||||
|
||||
if not self.path.is_safe_host_path(host_path):
|
||||
raise PermissionError(f'unsafe path: {host_path}')
|
||||
|
||||
return open(host_path, openmode)
|
||||
|
||||
def _parse_path(self, p: Union[os.PathLike, str]) -> str:
|
||||
fspath = getattr(p, '__fspath__', None)
|
||||
|
||||
# p is an `os.PathLike` object
|
||||
if fspath is not None:
|
||||
p = fspath()
|
||||
|
||||
if isinstance(p, bytes): # os.PathLike.__fspath__ may return bytes.
|
||||
p = p.decode("utf-8")
|
||||
|
||||
return p
|
||||
|
||||
def add_fs_mapping(self, ql_path: Union[os.PathLike, str], real_dest: Union[str, QlFsMappedObject, Callable]) -> None:
|
||||
"""Map an object to Qiling emulated file system.
|
||||
def has_mapping(self, vpath: str) -> bool:
|
||||
"""Check whether a specific virtrual path has a binding.
|
||||
|
||||
Args:
|
||||
ql_path: Emulated path which should be convertable to a string or a hashable object. e.g. pathlib.Path
|
||||
real_dest: Mapped object, can be a string, an object or a callable(class).
|
||||
string: mapped path in the host machine, e.g. '/dev/urandom' -> '/dev/urandom'
|
||||
object: mapped object, will be returned each time the emulated path has been opened
|
||||
class: mapped callable, will be used to create a new instance each time the emulated path has been opened
|
||||
vpath: virtual path name to check
|
||||
|
||||
Returns: `True` if the specified virtual path has been bound, `False` otherwise.
|
||||
"""
|
||||
|
||||
ql_path = self._parse_path(ql_path)
|
||||
real_dest = self._parse_path(real_dest)
|
||||
return vpath in self
|
||||
|
||||
self._mapping[ql_path] = real_dest
|
||||
def __len__(self) -> int:
|
||||
return len(self._mapping)
|
||||
|
||||
def remove_fs_mapping(self, ql_path: Union[os.PathLike, str]):
|
||||
def mapping_count(self) -> int:
|
||||
"""Count of currently existing bindings.
|
||||
"""
|
||||
|
||||
return len(self)
|
||||
|
||||
def __open_mapped(self, absvpath: str, opener: Callable, *args) -> Any:
|
||||
"""Internal method user for opening an existing mapped object.
|
||||
|
||||
Args:
|
||||
absvpath: absolute virtual path name
|
||||
opener: a method to use to open the target host path
|
||||
*args: arguments to the opener method
|
||||
"""
|
||||
|
||||
mapped = self._mapping[absvpath]
|
||||
|
||||
# mapped to a file name on the host file system
|
||||
if isinstance(mapped, str):
|
||||
obj = opener(mapped, *args)
|
||||
|
||||
# mapped to a class or a method
|
||||
elif callable(mapped):
|
||||
obj = mapped()
|
||||
|
||||
# mapped to another kind of object
|
||||
else:
|
||||
obj = mapped
|
||||
|
||||
return obj
|
||||
|
||||
def __open_new(self, absvpath: str, opener: Callable, *args) -> Any:
|
||||
hpath = self.path.virtual_to_host_path(absvpath)
|
||||
|
||||
if not self.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
return opener(hpath, *args)
|
||||
|
||||
def open_ql_file(self, vpath: str, flags: int, mode: int):
|
||||
absvpath = self.path.virtual_abspath(vpath)
|
||||
opener = self.__open_mapped if self.has_mapping(absvpath) else self.__open_new
|
||||
|
||||
return opener(absvpath, ql_file.open, flags, mode)
|
||||
|
||||
def open(self, vpath: str, mode: str):
|
||||
absvpath = self.path.virtual_abspath(vpath)
|
||||
opener = self.__open_mapped if self.has_mapping(absvpath) else self.__open_new
|
||||
|
||||
return opener(absvpath, open, mode)
|
||||
|
||||
def file_exists(self, vpath: str) -> bool:
|
||||
"""Check whether a file exists on the virtual file system.
|
||||
|
||||
Args:
|
||||
vpath: virtual path name to check
|
||||
|
||||
Returns: `True` if the specified virtual path has an existing mapping or
|
||||
resolves to an existing file on the virtual file system. `False` otherwise.
|
||||
"""
|
||||
|
||||
if self.has_mapping(vpath):
|
||||
return True
|
||||
|
||||
hpath = self.path.virtual_to_host_path(vpath)
|
||||
|
||||
if not self.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
return os.path.isfile(hpath)
|
||||
|
||||
def create_empty_file(self, vpath: str) -> bool:
|
||||
if not self.file_exists(vpath):
|
||||
try:
|
||||
f = self.open(vpath, "w+")
|
||||
except OSError:
|
||||
# for some reason, we could not create an empty file.
|
||||
return False
|
||||
else:
|
||||
f.close()
|
||||
|
||||
return True
|
||||
|
||||
def __fspath(self, path: QlPath) -> str:
|
||||
"""Similar to os.fspath, this method takes a path-like object and returns
|
||||
its string representation.
|
||||
"""
|
||||
|
||||
if isinstance(path, PathLike):
|
||||
path = path.__fspath__()
|
||||
|
||||
if isinstance(path, str):
|
||||
return path
|
||||
|
||||
elif isinstance(path, bytes):
|
||||
return path.decode('utf-8')
|
||||
|
||||
raise TypeError(path)
|
||||
|
||||
def add_mapping(self, vpath: QlPath, binding: Union[QlPath, QlFsMappedObject, Callable], *, force: bool = False) -> None:
|
||||
"""Create a new mapping in the virtual filesystem.
|
||||
|
||||
Args:
|
||||
vpath: a virtual path to bind
|
||||
|
||||
binding: a target to use whenever the bound virtual path is referenced. such a target can be
|
||||
either a path on the host filesystem, an object instance or a class. the behavior of the mapping
|
||||
is determined by the bound object type:
|
||||
[*] a string: bind a path on the host filesystem (e.g. "/dev/urandom"). use with caution!
|
||||
[*] an object: bind an object instance which will be returned each time the virtual path is opened
|
||||
[*] a class: bind a class that will be instantiated each time the virtual path is opened
|
||||
|
||||
force: when set to `True`, re-mapping an existing vpath becomes possible. In such case, the
|
||||
old mapping will be discarded
|
||||
|
||||
Raises:
|
||||
`KeyError`: in case the specified vpath has already been mapped (default behavior).
|
||||
"""
|
||||
|
||||
vpath = self.__fspath(vpath)
|
||||
absvpath = self.path.virtual_abspath(vpath)
|
||||
|
||||
if self.has_mapping(absvpath) and not force:
|
||||
raise KeyError(f'mapping already exists: "{absvpath}"')
|
||||
|
||||
if isinstance(binding, (str, bytes, PathLike)):
|
||||
binding = self.__fspath(binding)
|
||||
|
||||
self._mapping[absvpath] = binding
|
||||
|
||||
def remove_mapping(self, vpath: QlPath) -> None:
|
||||
"""Remove a mapping from the fs mapper.
|
||||
|
||||
Args:
|
||||
ql_path (Union[os.PathLike, str]): The mapped path.
|
||||
vpath: bound virtual path to remove
|
||||
|
||||
Raises:
|
||||
`KeyError`: in case the specified vpath has no mapping
|
||||
"""
|
||||
del self._mapping[self._parse_path(ql_path)]
|
||||
|
||||
vpath = self.__fspath(vpath)
|
||||
absvpath = self.path.virtual_abspath(vpath)
|
||||
|
||||
if not self.has_mapping(absvpath):
|
||||
raise KeyError(absvpath)
|
||||
|
||||
del self._mapping[absvpath]
|
||||
|
||||
def rename_mapping(self, old_vpath: str, new_vpath: str) -> None:
|
||||
old_absvpath = self.path.virtual_abspath(old_vpath)
|
||||
|
||||
# vpath to rename does not exist
|
||||
if not self.has_mapping(old_absvpath):
|
||||
raise KeyError(old_vpath)
|
||||
|
||||
new_absvpath = self.path.virtual_abspath(new_vpath)
|
||||
|
||||
# new vpath already exists
|
||||
if self.has_mapping(new_absvpath):
|
||||
raise KeyError(new_vpath)
|
||||
|
||||
# avoid renaming to the same vapth
|
||||
if old_absvpath == new_absvpath:
|
||||
return
|
||||
|
||||
binding = self._mapping[old_absvpath]
|
||||
|
||||
# remove old mapping and add a new one instead
|
||||
self._mapping[new_absvpath] = binding
|
||||
del self._mapping[old_absvpath]
|
||||
|
||||
+22
-13
@@ -4,7 +4,8 @@
|
||||
#
|
||||
|
||||
import sys
|
||||
from typing import Any, Hashable, Iterable, Optional, Callable, Mapping, Sequence, TextIO, Tuple
|
||||
from io import UnsupportedOperation
|
||||
from typing import Any, Dict, Iterable, Optional, Callable, Mapping, Sequence, TextIO, Tuple, Union
|
||||
|
||||
from unicorn import UcError
|
||||
|
||||
@@ -13,7 +14,7 @@ from qiling.const import QL_OS, QL_STATE, QL_INTERCEPT, QL_OS_POSIX
|
||||
from qiling.os.const import STRING, WSTRING, GUID
|
||||
from qiling.os.fcall import QlFunctionCall, TypedArg
|
||||
|
||||
from .filestruct import ql_file
|
||||
from .filestruct import PersistentQlFile
|
||||
from .mapper import QlFsMapper
|
||||
from .stats import QlOsStats
|
||||
from .utils import QlOsUtils
|
||||
@@ -47,23 +48,31 @@ class QlOs:
|
||||
self.path = QlOsPath(ql.rootfs, cwd, self.type)
|
||||
self.fs_mapper = QlFsMapper(self.path)
|
||||
|
||||
self.user_defined_api = {
|
||||
QL_INTERCEPT.CALL : {},
|
||||
self.user_defined_api: Dict[QL_INTERCEPT, Dict[Union[int, str], Callable]] = {
|
||||
QL_INTERCEPT.CALL: {},
|
||||
QL_INTERCEPT.ENTER: {},
|
||||
QL_INTERCEPT.EXIT : {}
|
||||
QL_INTERCEPT.EXIT: {}
|
||||
}
|
||||
|
||||
# IDAPython has some hack on standard io streams and thus they don't have corresponding fds.
|
||||
try:
|
||||
import ida_idaapi
|
||||
except ImportError:
|
||||
self._stdin = ql_file('stdin', sys.stdin.fileno())
|
||||
self._stdout = ql_file('stdout', sys.stdout.fileno())
|
||||
self._stderr = ql_file('stderr', sys.stderr.fileno())
|
||||
else:
|
||||
# Qiling may be used on interactive shells (ex: IDLE) or embedded python
|
||||
# interpreters (ex: IDA Python). such environments use their own version
|
||||
# for the standard streams which usually do not support certain operations,
|
||||
# such as fileno(). here we use this to determine how we are going to use
|
||||
# the environment standard streams
|
||||
sys.stdin.fileno()
|
||||
except UnsupportedOperation:
|
||||
# Qiling is used on an interactive shell or embedded python interpreter.
|
||||
# if the internal stream buffer is accessible, we should use it
|
||||
self._stdin = getattr(sys.stdin, 'buffer', sys.stdin)
|
||||
self._stdout = getattr(sys.stdout, 'buffer', sys.stdout)
|
||||
self._stderr = getattr(sys.stderr, 'buffer', sys.stderr)
|
||||
else:
|
||||
# Qiling is used in a script, or on an environment that supports ordinary
|
||||
# stanard streams
|
||||
self._stdin = PersistentQlFile('stdin', sys.stdin.fileno())
|
||||
self._stdout = PersistentQlFile('stdout', sys.stdout.fileno())
|
||||
self._stderr = PersistentQlFile('stderr', sys.stderr.fileno())
|
||||
|
||||
# defult exit point
|
||||
self.exit_point = {
|
||||
@@ -207,7 +216,7 @@ class QlOs:
|
||||
|
||||
return retval
|
||||
|
||||
def set_api(self, target: Hashable, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL):
|
||||
def set_api(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL):
|
||||
"""Either hook or replace an OS API with a custom one.
|
||||
|
||||
Args:
|
||||
|
||||
+18
-2
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -10,6 +10,7 @@ from qiling.const import QL_OS, QL_OS_POSIX
|
||||
|
||||
AnyPurePath = Union[PurePosixPath, PureWindowsPath]
|
||||
|
||||
|
||||
class QlOsPath:
|
||||
"""Virtual to host path manipulations helper.
|
||||
"""
|
||||
@@ -60,6 +61,10 @@ class QlOsPath:
|
||||
|
||||
return path
|
||||
|
||||
@property
|
||||
def root(self) -> str:
|
||||
return str(self._cwd_anchor)
|
||||
|
||||
@property
|
||||
def cwd(self) -> str:
|
||||
return str(self._cwd_anchor / self._cwd_vpath)
|
||||
@@ -274,6 +279,18 @@ class QlOsPath:
|
||||
|
||||
return str(virtpath)
|
||||
|
||||
def is_virtual_abspath(self, virtpath: str) -> bool:
|
||||
"""Determine whether a given virtual path is absolute.
|
||||
|
||||
Args:
|
||||
virtpath : virtual path to query
|
||||
|
||||
Returns: `True` if `virtpath` is an absolute path, `False` if relative
|
||||
"""
|
||||
|
||||
vpath = self.PureVirtualPath(virtpath)
|
||||
|
||||
return vpath.is_absolute()
|
||||
|
||||
def virtual_abspath(self, virtpath: str) -> str:
|
||||
"""Convert a relative virtual path to an absolute virtual path based
|
||||
@@ -346,4 +363,3 @@ class QlOsPath:
|
||||
return QlOsPath.__host_casefold_path(hostpath)
|
||||
|
||||
return hostpath
|
||||
|
||||
|
||||
@@ -1003,11 +1003,38 @@ errors = {
|
||||
}
|
||||
|
||||
# shm syscall
|
||||
IPC_CREAT = 8**3
|
||||
IPC_EXCL = 2*(8**3)
|
||||
IPC_NOWAIT = 4*(8**3)
|
||||
IPC_PRIVATE = 0
|
||||
|
||||
SHM_RDONLY = 8**4
|
||||
SHM_RND = 2*(8**4)
|
||||
SHM_REMAP= 4*(8**4)
|
||||
SHM_EXEC = 1*(8**5)
|
||||
# see: https://elixir.bootlin.com/linux/v5.19.17/source/include/uapi/linux/ipc.h
|
||||
IPC_CREAT = 0o0001000 # create if key is nonexistent
|
||||
IPC_EXCL = 0o0002000 # fail if key exists
|
||||
IPC_NOWAIT = 0o0004000 # return error on wait
|
||||
|
||||
# see: https://elixir.bootlin.com/linux/v5.19.17/source/include/uapi/linux/shm.h
|
||||
SHM_W = 0o000200
|
||||
SHM_R = 0o000400
|
||||
SHM_HUGETLB = 0o004000 # segment will use huge TLB pages
|
||||
SHM_RDONLY = 0o010000 # read-only access
|
||||
SHM_RND = 0o020000 # round attach address to SHMLBA boundary
|
||||
SHM_REMAP = 0o040000 # take-over region on attach
|
||||
SHM_EXEC = 0o100000 # execution access
|
||||
|
||||
SHMMNI = 4096 # max num of segs system wide
|
||||
|
||||
# see: https://elixir.bootlin.com/linux/v5.19.17/source/include/uapi/asm-generic/hugetlb_encode.h
|
||||
HUGETLB_FLAG_ENCODE_SHIFT = 26
|
||||
HUGETLB_FLAG_ENCODE_MASK = 0x3f
|
||||
|
||||
# ipc syscall
|
||||
SEMOP = 1
|
||||
SEMGET = 2
|
||||
SEMCTL = 3
|
||||
SEMTIMEDOP = 4
|
||||
MSGSND = 11
|
||||
MSGRCV = 12
|
||||
MSGGET = 13
|
||||
MSGCTL = 14
|
||||
SHMAT = 21
|
||||
SHMDT = 22
|
||||
SHMGET = 23
|
||||
SHMCTL = 24
|
||||
|
||||
@@ -156,12 +156,13 @@ def socket_domain_mapping(p: int, archtype: QL_ARCH, ostype: QL_OS) -> str:
|
||||
|
||||
def socket_tcp_option_mapping(t: int, archtype: QL_ARCH) -> str:
|
||||
socket_option_map = {
|
||||
QL_ARCH.X86: linux_socket_tcp_options,
|
||||
QL_ARCH.X86: linux_socket_tcp_options,
|
||||
QL_ARCH.X8664: linux_socket_tcp_options,
|
||||
QL_ARCH.ARM: linux_socket_tcp_options,
|
||||
QL_ARCH.ARM: linux_socket_tcp_options,
|
||||
QL_ARCH.ARM64: linux_socket_tcp_options,
|
||||
QL_ARCH.MIPS: linux_socket_tcp_options,
|
||||
QL_ARCH.MIPS: linux_socket_tcp_options,
|
||||
}[archtype]
|
||||
|
||||
return _constant_mapping(t, socket_option_map)
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from inspect import signature, Parameter
|
||||
from typing import TextIO, Union, Callable, IO, List, Optional
|
||||
from typing import Dict, TextIO, Tuple, Union, Callable, IO, List, Optional
|
||||
|
||||
from unicorn.arm64_const import UC_ARM64_REG_X8, UC_ARM64_REG_X16
|
||||
from unicorn.arm_const import (
|
||||
@@ -72,7 +72,7 @@ class QlFileDes:
|
||||
def __len__(self):
|
||||
return len(self.__fds)
|
||||
|
||||
def __getitem__(self, idx: Union[slice, int]):
|
||||
def __getitem__(self, idx: int):
|
||||
return self.__fds[idx]
|
||||
|
||||
def __setitem__(self, idx: int, val: Optional[IO]):
|
||||
@@ -91,6 +91,51 @@ class QlFileDes:
|
||||
self.__fds = fds
|
||||
|
||||
|
||||
# vaguely reflects a shmid64_ds structure
|
||||
class QlShmId:
|
||||
|
||||
def __init__(self, key: int, uid: int, gid: int, mode: int, segsz: int) -> None:
|
||||
# ipc64_perm
|
||||
self.key = key
|
||||
self.uid = uid
|
||||
self.gid = gid
|
||||
self.mode = mode
|
||||
|
||||
self.segsz = segsz
|
||||
|
||||
# track the memory locations this segment is currently attached to
|
||||
self.attach: List[int] = []
|
||||
|
||||
|
||||
class QlShm:
|
||||
def __init__(self) -> None:
|
||||
self.__shm: Dict[int, QlShmId] = {}
|
||||
self.__id: int = 0x0F000000
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__shm)
|
||||
|
||||
def add(self, shm: QlShmId) -> int:
|
||||
shmid = self.__id
|
||||
self.__shm[shmid] = shm
|
||||
|
||||
self.__id += 0x1000
|
||||
|
||||
return shmid
|
||||
|
||||
def remove(self, shmid: int) -> None:
|
||||
del self.__shm[shmid]
|
||||
|
||||
def get_by_key(self, key: int) -> Tuple[int, Optional[QlShmId]]:
|
||||
return next(((shmid, shmobj) for shmid, shmobj in self.__shm.items() if shmobj.key == key), (-1, None))
|
||||
|
||||
def get_by_id(self, shmid: int) -> Optional[QlShmId]:
|
||||
return self.__shm.get(shmid, None)
|
||||
|
||||
def get_by_attaddr(self, shmaddr: int) -> Optional[QlShmId]:
|
||||
return next((shmobj for shmobj in self.__shm.values() if shmobj.attach.count(shmaddr) > 0), None)
|
||||
|
||||
|
||||
class QlOsPosix(QlOs):
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
@@ -110,9 +155,9 @@ class QlOsPosix(QlOs):
|
||||
self.ifrname_ovr = conf.get('ifrname_override')
|
||||
|
||||
self.posix_syscall_hooks = {
|
||||
QL_INTERCEPT.CALL : {},
|
||||
QL_INTERCEPT.CALL: {},
|
||||
QL_INTERCEPT.ENTER: {},
|
||||
QL_INTERCEPT.EXIT : {}
|
||||
QL_INTERCEPT.EXIT: {}
|
||||
}
|
||||
|
||||
self.__syscall_id_reg = {
|
||||
@@ -157,7 +202,7 @@ class QlOsPosix(QlOs):
|
||||
self.stdout = self._stdout
|
||||
self.stderr = self._stderr
|
||||
|
||||
self._shms = {}
|
||||
self._shm = QlShm()
|
||||
|
||||
def __get_syscall_mapper(self, archtype: QL_ARCH):
|
||||
qlos_path = f'.os.{self.type.name.lower()}.map_syscall'
|
||||
@@ -191,7 +236,7 @@ class QlOsPosix(QlOs):
|
||||
self.euid = 0 if enabled else self.uid
|
||||
self.egid = 0 if enabled else self.gid
|
||||
|
||||
def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT=QL_INTERCEPT.CALL):
|
||||
def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL):
|
||||
"""Either hook or replace a system call with a custom one.
|
||||
|
||||
Args:
|
||||
@@ -298,7 +343,7 @@ class QlOsPosix(QlOs):
|
||||
raise e
|
||||
|
||||
# print out log entry
|
||||
syscall_basename = syscall_name[len(SYSCALL_PREF) if syscall_name.startswith(SYSCALL_PREF) else 0:]
|
||||
syscall_basename = syscall_name[len(SYSCALL_PREF) if syscall_name.startswith(SYSCALL_PREF) else 0:]
|
||||
|
||||
args = []
|
||||
|
||||
@@ -349,3 +394,7 @@ class QlOsPosix(QlOs):
|
||||
@property
|
||||
def fd(self):
|
||||
return self._fd
|
||||
|
||||
@property
|
||||
def shm(self):
|
||||
return self._shm
|
||||
@@ -16,10 +16,12 @@ from .resource import *
|
||||
from .sched import *
|
||||
from .select import *
|
||||
from .sendfile import *
|
||||
from .shm import *
|
||||
from .signal import *
|
||||
from .socket import *
|
||||
from .stat import *
|
||||
from .sysctl import *
|
||||
from .syscall import *
|
||||
from .sysinfo import *
|
||||
from .time import *
|
||||
from .types import *
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_OS, QL_ARCH
|
||||
@@ -13,119 +12,96 @@ from qiling.os.posix.const import *
|
||||
from qiling.os.posix.const_mapping import ql_open_flag_mapping
|
||||
from qiling.os.posix.filestruct import ql_socket
|
||||
|
||||
from .unistd import virtual_abspath_at, get_opened_fd
|
||||
|
||||
|
||||
def __do_open(ql: Qiling, absvpath: str, flags: int, mode: int) -> int:
|
||||
flags &= 0xffffffff
|
||||
mode &= 0xffffffff
|
||||
|
||||
# look for the next available fd slot
|
||||
idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1)
|
||||
|
||||
if idx == -1:
|
||||
return -EMFILE
|
||||
|
||||
if ql.arch.type is QL_ARCH.ARM and ql.os.type is not QL_OS.QNX:
|
||||
mode = 0
|
||||
|
||||
# translate emulated os open flags into host os open flags
|
||||
flags = ql_open_flag_mapping(ql, flags)
|
||||
|
||||
try:
|
||||
ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(absvpath, flags, mode)
|
||||
except QlSyscallError:
|
||||
return -1
|
||||
|
||||
return idx
|
||||
|
||||
|
||||
def ql_syscall_open(ql: Qiling, filename: int, flags: int, mode: int):
|
||||
path = ql.os.utils.read_cstring(filename)
|
||||
real_path = ql.os.path.transform_to_real_path(path)
|
||||
relative_path = ql.os.path.transform_to_relative_path(path)
|
||||
vpath = ql.os.utils.read_cstring(filename)
|
||||
absvpath = ql.os.path.virtual_abspath(vpath)
|
||||
|
||||
flags &= 0xffffffff
|
||||
mode &= 0xffffffff
|
||||
regreturn = __do_open(ql, absvpath, flags, mode)
|
||||
|
||||
idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1)
|
||||
|
||||
if idx == -1:
|
||||
regreturn = -EMFILE
|
||||
else:
|
||||
try:
|
||||
if ql.arch.type == QL_ARCH.ARM and ql.os.type != QL_OS.QNX:
|
||||
mode = 0
|
||||
|
||||
flags = ql_open_flag_mapping(ql, flags)
|
||||
ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(path, flags, mode)
|
||||
regreturn = idx
|
||||
except QlSyscallError as e:
|
||||
regreturn = - e.errno
|
||||
|
||||
|
||||
ql.log.debug("open(%s, 0o%o) = %d" % (relative_path, mode, regreturn))
|
||||
|
||||
if regreturn >= 0 and regreturn != 2:
|
||||
ql.log.debug(f'File found: {real_path:s}')
|
||||
else:
|
||||
ql.log.debug(f'File not found {real_path:s}')
|
||||
ql.log.debug(f'open("{absvpath}", {flags:#x}, 0{mode:o}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
def ql_syscall_creat(ql: Qiling, filename: int, mode: int):
|
||||
flags = posix_open_flags["O_WRONLY"] | posix_open_flags["O_CREAT"] | posix_open_flags["O_TRUNC"]
|
||||
|
||||
path = ql.os.utils.read_cstring(filename)
|
||||
real_path = ql.os.path.transform_to_real_path(path)
|
||||
relative_path = ql.os.path.transform_to_relative_path(path)
|
||||
|
||||
flags &= 0xffffffff
|
||||
mode &= 0xffffffff
|
||||
|
||||
idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1)
|
||||
|
||||
if idx == -1:
|
||||
regreturn = -ENOMEM
|
||||
else:
|
||||
try:
|
||||
if ql.arch.type == QL_ARCH.ARM:
|
||||
mode = 0
|
||||
|
||||
flags = ql_open_flag_mapping(ql, flags)
|
||||
ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(path, flags, mode)
|
||||
regreturn = idx
|
||||
except QlSyscallError as e:
|
||||
regreturn = -e.errno
|
||||
|
||||
ql.log.debug("creat(%s, 0o%o) = %d" % (relative_path, mode, regreturn))
|
||||
|
||||
if regreturn >= 0 and regreturn != 2:
|
||||
ql.log.debug(f'File found: {real_path:s}')
|
||||
else:
|
||||
ql.log.debug(f'File not found {real_path:s}')
|
||||
|
||||
return regreturn
|
||||
|
||||
def ql_syscall_openat(ql: Qiling, fd: int, path: int, flags: int, mode: int):
|
||||
file_path = ql.os.utils.read_cstring(path)
|
||||
# real_path = ql.os.path.transform_to_real_path(path)
|
||||
# relative_path = ql.os.path.transform_to_relative_path(path)
|
||||
vpath = ql.os.utils.read_cstring(path)
|
||||
absvpath = virtual_abspath_at(ql, vpath, fd)
|
||||
|
||||
flags &= 0xffffffff
|
||||
regreturn = -1 if absvpath is None else __do_open(ql, absvpath, flags, mode)
|
||||
|
||||
ql.log.debug(f'openat({fd:d}, "{vpath}", {flags:#x}, 0{mode:o}) = {regreturn:d}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_creat(ql: Qiling, filename: int, mode: int):
|
||||
vpath = ql.os.utils.read_cstring(filename)
|
||||
|
||||
# FIXME: this is broken
|
||||
flags = posix_open_flags["O_WRONLY"] | posix_open_flags["O_CREAT"] | posix_open_flags["O_TRUNC"]
|
||||
mode &= 0xffffffff
|
||||
|
||||
idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1)
|
||||
|
||||
if idx == -1:
|
||||
regreturn = -EMFILE
|
||||
regreturn = -ENOMEM
|
||||
else:
|
||||
if ql.arch.type == QL_ARCH.ARM:
|
||||
mode = 0
|
||||
|
||||
try:
|
||||
if ql.arch.type == QL_ARCH.ARM:
|
||||
mode = 0
|
||||
|
||||
flags = ql_open_flag_mapping(ql, flags)
|
||||
fd = ql.unpacks(ql.pack(fd))
|
||||
|
||||
if 0 <= fd < NR_OPEN:
|
||||
fobj = ql.os.fd[fd]
|
||||
# ql_file object or QlFsMappedObject
|
||||
if hasattr(fobj, "fileno") and hasattr(fobj, "name"):
|
||||
if not Path.is_absolute(Path(file_path)):
|
||||
file_path = Path(fobj.name) / Path(file_path)
|
||||
|
||||
ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(file_path, flags, mode)
|
||||
|
||||
regreturn = idx
|
||||
ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(vpath, flags, mode)
|
||||
except QlSyscallError as e:
|
||||
regreturn = -e.errno
|
||||
|
||||
ql.log.debug(f'openat(fd = {fd:d}, path = {file_path}, mode = {mode:#o}) = {regreturn:d}')
|
||||
else:
|
||||
regreturn = idx
|
||||
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
absvpath = ql.os.path.virtual_abspath(vpath)
|
||||
|
||||
ql.log.debug(f'creat("{absvpath}", {mode:#o}) = {regreturn}')
|
||||
|
||||
if regreturn >= 0 and regreturn != 2:
|
||||
ql.log.debug(f'File found: {hpath:s}')
|
||||
else:
|
||||
ql.log.debug(f'File not found {hpath:s}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
return -EBADF
|
||||
return -1
|
||||
|
||||
if cmd == F_DUPFD:
|
||||
if arg not in range(NR_OPEN):
|
||||
@@ -140,10 +116,10 @@ def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int):
|
||||
regreturn = -EMFILE
|
||||
|
||||
elif cmd == F_GETFD:
|
||||
regreturn = getattr(f, "close_on_exec", 0)
|
||||
regreturn = int(getattr(f, "close_on_exec", False))
|
||||
|
||||
elif cmd == F_SETFD:
|
||||
f.close_on_exec = 1 if arg & FD_CLOEXEC else 0
|
||||
f.close_on_exec = bool(arg & FD_CLOEXEC)
|
||||
regreturn = 0
|
||||
|
||||
elif cmd == F_GETFL:
|
||||
@@ -209,28 +185,36 @@ def ql_syscall_flock(ql: Qiling, fd: int, operation: int):
|
||||
|
||||
|
||||
def ql_syscall_rename(ql: Qiling, oldname_buf: int, newname_buf: int):
|
||||
"""
|
||||
rename(const char *oldpath, const char *newpath)
|
||||
description: change the name or location of a file
|
||||
ret value: On success, zero is returned. On error, -1 is returned
|
||||
"""
|
||||
regreturn = 0 # default value is success
|
||||
oldpath = ql.os.utils.read_cstring(oldname_buf)
|
||||
newpath = ql.os.utils.read_cstring(newname_buf)
|
||||
old_vpath = ql.os.utils.read_cstring(oldname_buf)
|
||||
new_vpath = ql.os.utils.read_cstring(newname_buf)
|
||||
|
||||
ql.log.debug(f"rename() path: {oldpath} -> {newpath}")
|
||||
old_absvpath = ql.os.path.virtual_abspath(old_vpath)
|
||||
|
||||
old_realpath = ql.os.path.transform_to_real_path(oldpath)
|
||||
new_realpath = ql.os.path.transform_to_real_path(newpath)
|
||||
# if has a mapping, rename the mapped vpath
|
||||
if ql.os.fs_mapper.has_mapping(old_absvpath):
|
||||
try:
|
||||
ql.os.fs_mapper.rename_mapping(old_vpath, new_vpath)
|
||||
except KeyError:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
if old_realpath == new_realpath:
|
||||
# do nothing, just return success
|
||||
return regreturn
|
||||
# otherwise, rename the actual files
|
||||
else:
|
||||
old_hpath = ql.os.path.virtual_to_host_path(old_vpath)
|
||||
new_hpath = ql.os.path.virtual_to_host_path(new_vpath)
|
||||
|
||||
try:
|
||||
os.rename(old_realpath, new_realpath)
|
||||
except OSError:
|
||||
ql.log.exception(f"rename(): {newpath} exists!")
|
||||
regreturn = -1
|
||||
# if source and target paths are identical, do nothing
|
||||
if old_hpath == new_hpath:
|
||||
return 0
|
||||
|
||||
return regreturn
|
||||
try:
|
||||
os.rename(old_hpath, new_hpath)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'rename("{old_vpath}", "{new_vpath}") = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
@@ -225,33 +225,3 @@ def ql_syscall_mmap2(ql: Qiling, addr: int, length: int, prot: int, flags: int,
|
||||
pgoffset *= ql.mem.pagesize
|
||||
|
||||
return syscall_mmap_impl(ql, addr, length, prot, flags, fd, pgoffset, 2)
|
||||
|
||||
|
||||
def ql_syscall_shmget(ql: Qiling, key: int, size: int, shmflg: int):
|
||||
if shmflg & IPC_CREAT:
|
||||
if shmflg & IPC_EXCL:
|
||||
if key in ql.os._shms:
|
||||
return EEXIST
|
||||
else:
|
||||
#addr = ql.mem.map_anywhere(size)
|
||||
ql.os._shms[key] = (key, size)
|
||||
return key
|
||||
else:
|
||||
if key not in ql.os._shms:
|
||||
return ENOENT
|
||||
|
||||
|
||||
def ql_syscall_shmat(ql: Qiling, shmid: int, shmaddr: int, shmflg: int):
|
||||
# shmid == key
|
||||
# dummy implementation
|
||||
if shmid not in ql.os._shms:
|
||||
return EINVAL
|
||||
|
||||
key, size = ql.os._shms[shmid]
|
||||
|
||||
if shmaddr == 0:
|
||||
addr = ql.mem.map_anywhere(size)
|
||||
else:
|
||||
addr = ql.mem.map(shmaddr, size, info="[shm]")
|
||||
|
||||
return addr
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from unicorn.unicorn_const import UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_ARCH
|
||||
from qiling.exception import QlMemoryMappedError
|
||||
from qiling.os.posix.const import *
|
||||
from qiling.os.posix.posix import QlShmId
|
||||
|
||||
|
||||
def ql_syscall_shmget(ql: Qiling, key: int, size: int, shmflg: int):
|
||||
|
||||
def __create_shm(key: int, size: int, flags: int) -> int:
|
||||
"""Create a new shared memory segment for the specified key.
|
||||
|
||||
Returns: shmid of the newly created segment, -1 if an error has occured
|
||||
"""
|
||||
|
||||
if len(ql.os.shm) >= SHMMNI:
|
||||
return -1 # ENOSPC
|
||||
|
||||
mode = flags & ((1 << 9) - 1)
|
||||
|
||||
# determine size alignment: either normal or huge page
|
||||
if flags & SHM_HUGETLB:
|
||||
shiftsize = (flags >> HUGETLB_FLAG_ENCODE_SHIFT) & HUGETLB_FLAG_ENCODE_MASK
|
||||
alignment = (1 << shiftsize)
|
||||
else:
|
||||
alignment = ql.mem.pagesize
|
||||
|
||||
shm_size = ql.mem.align_up(size, alignment)
|
||||
|
||||
shmid = ql.os.shm.add(QlShmId(key, ql.os.uid, ql.os.gid, mode, shm_size))
|
||||
|
||||
ql.log.debug(f'created a new shm: key = {key:#x}, mode = 0{mode:o}, size = {shm_size:#x}. assigned id: {shmid:#x}')
|
||||
|
||||
return shmid
|
||||
|
||||
# create new shared memory segment
|
||||
if key == IPC_PRIVATE:
|
||||
shmid = __create_shm(key, size, shmflg)
|
||||
|
||||
else:
|
||||
shmid, shm = ql.os.shm.get_by_key(key)
|
||||
|
||||
# a shm with the specified key does not exist
|
||||
if shm is None:
|
||||
# the user asked to create a new one?
|
||||
if shmflg & IPC_CREAT:
|
||||
shmid = __create_shm(key, size, shmflg)
|
||||
|
||||
else:
|
||||
return -1 # ENOENT
|
||||
|
||||
# a shm with the specified key exists
|
||||
else:
|
||||
# the user asked to create a new one?
|
||||
if shmflg & (IPC_CREAT | IPC_EXCL):
|
||||
return -1 # EEXIST
|
||||
|
||||
# check whether the user has permissions to access this shm
|
||||
# FIXME: should probably use ql.os.cuid instead, but we don't support it yet
|
||||
if (ql.os.uid == shm.uid) and (shm.mode & (SHM_W | SHM_R)):
|
||||
return shmid
|
||||
|
||||
else:
|
||||
return -1 # EACCES
|
||||
|
||||
return shmid
|
||||
|
||||
|
||||
def ql_syscall_shmat(ql: Qiling, shmid: int, shmaddr: int, shmflg: int):
|
||||
shm = ql.os.shm.get_by_id(shmid)
|
||||
|
||||
# a shm with the specified key does not exist
|
||||
if shm is None:
|
||||
return -1 # EINVAL
|
||||
|
||||
if shmaddr == 0:
|
||||
# system may choose any suitable page-aligned address
|
||||
attaddr = ql.mem.find_free_space(shm.segsz, ql.loader.mmap_address)
|
||||
|
||||
elif shmflg & SHM_RND:
|
||||
# select the appropriate SHMLBA value, based on the platform
|
||||
shmlba = {
|
||||
QL_ARCH.MIPS: 0x40000,
|
||||
QL_ARCH.ARM: ql.mem.pagesize * 4,
|
||||
QL_ARCH.ARM64: ql.mem.pagesize * 4,
|
||||
QL_ARCH.X86: ql.mem.pagesize,
|
||||
QL_ARCH.X8664: ql.mem.pagesize
|
||||
}
|
||||
|
||||
# align the address specified by shmaddr to platform SHMLBA
|
||||
attaddr = ql.mem.align(shmaddr, shmlba[ql.arch.type])
|
||||
|
||||
else:
|
||||
# shmaddr is expected to be aligned
|
||||
if shmaddr & (ql.mem.pagesize - 1):
|
||||
return -1 # EINVAL
|
||||
|
||||
attaddr = shmaddr
|
||||
|
||||
perms = UC_PROT_READ
|
||||
|
||||
if shmflg & SHM_RDONLY == 0:
|
||||
perms |= UC_PROT_WRITE
|
||||
|
||||
if shmflg & SHM_EXEC:
|
||||
perms |= UC_PROT_EXEC
|
||||
|
||||
# user asked to attached the seg as readable; is it allowed?
|
||||
if (perms & UC_PROT_READ) and not (shm.mode & SHM_R):
|
||||
return -1 # EACCES
|
||||
|
||||
# user asked to attached the seg as writable; is it allowed?
|
||||
if (perms & UC_PROT_WRITE) and not (shm.mode & SHM_W):
|
||||
return -1 # EACCES
|
||||
|
||||
# TODO: if segment is already attached, there is no need to map another memory for it.
|
||||
# if we do, data changes will not be reflected between the segment attachments. we could
|
||||
# use a mmio map for additional attachments, and have writes and reads directed to the
|
||||
# first attachment mapping
|
||||
|
||||
try:
|
||||
# attach the segment at shmaddr
|
||||
ql.mem.map(attaddr, shm.segsz, perms, '[shm]')
|
||||
except QlMemoryMappedError:
|
||||
return -1 # EINVAL
|
||||
|
||||
# track attachment
|
||||
shm.attach.append(attaddr)
|
||||
|
||||
ql.log.debug(f'shm {shmid:#x} attached at {attaddr:#010x}')
|
||||
|
||||
return attaddr
|
||||
|
||||
|
||||
def ql_syscall_shmdt(ql: Qiling, shmaddr: int):
|
||||
shm = ql.os.shm.get_by_attaddr(shmaddr)
|
||||
|
||||
if shm is None:
|
||||
return -1 # EINVAL
|
||||
|
||||
shm.attach.remove(shmaddr)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ql_syscall_shmget',
|
||||
'ql_syscall_shmdt',
|
||||
'ql_syscall_shmat'
|
||||
]
|
||||
@@ -27,10 +27,10 @@ def inet_aton(ipaddr: str) -> int:
|
||||
return int.from_bytes(ipdata, byteorder='big')
|
||||
|
||||
|
||||
def inet6_aton(ipaddr: str) -> int:
|
||||
ipdata = ipaddress.IPv6Address(ipaddr).packed
|
||||
def inet6_aton(ipaddr: str) -> Tuple[int, ...]:
|
||||
abytes = ipaddress.IPv6Address(ipaddr).packed
|
||||
|
||||
return int.from_bytes(ipdata, byteorder='big')
|
||||
return tuple(abytes)
|
||||
|
||||
|
||||
def inet_htoa(ql: Qiling, addr: int) -> str:
|
||||
@@ -52,6 +52,10 @@ def inet6_htoa(ql: Qiling, addr: bytes) -> str:
|
||||
|
||||
|
||||
def inet6_ntoa(addr: bytes) -> str:
|
||||
# if addr arg is not strictly a bytes object, convert it to bytes
|
||||
if not isinstance(addr, bytes):
|
||||
addr = bytes(addr)
|
||||
|
||||
return ipaddress.IPv6Address(addr).compressed
|
||||
|
||||
|
||||
@@ -260,13 +264,13 @@ def ql_syscall_connect(ql: Qiling, sockfd: int, addr: int, addrlen: int):
|
||||
dest = (host, port)
|
||||
|
||||
elif sock.family == AF_INET6 and ql.os.ipv6:
|
||||
sockaddr_in6 = make_sockaddr_in(abits, endian)
|
||||
sockaddr_in6 = make_sockaddr_in6(abits, endian)
|
||||
sockaddr_obj = sockaddr_in6.from_buffer(data)
|
||||
|
||||
port = ntohs(ql, sockaddr_obj.sin_port)
|
||||
port = ntohs(ql, sockaddr_obj.sin6_port)
|
||||
host = inet6_htoa(ql, sockaddr_obj.sin6_addr.s6_addr)
|
||||
|
||||
ql.log.debug(f'Conecting to {host}:{port}')
|
||||
ql.log.debug(f'Connecting to {host}:{port}')
|
||||
dest = (host, port)
|
||||
|
||||
if dest is not None:
|
||||
@@ -409,10 +413,10 @@ def ql_syscall_bind(ql: Qiling, sockfd: int, addr: int, addrlen: int):
|
||||
dest = (host, port)
|
||||
|
||||
elif sa_family == AF_INET6 and ql.os.ipv6:
|
||||
sockaddr_in6 = make_sockaddr_in(abits, endian)
|
||||
sockaddr_in6 = make_sockaddr_in6(abits, endian)
|
||||
sockaddr_obj = sockaddr_in6.from_buffer(data)
|
||||
|
||||
port = ntohs(ql, sockaddr_obj.sin_port)
|
||||
port = ntohs(ql, sockaddr_obj.sin6_port)
|
||||
host = inet6_ntoa(sockaddr_obj.sin6_addr.s6_addr)
|
||||
|
||||
if ql.os.bindtolocalhost:
|
||||
@@ -879,10 +883,10 @@ def ql_syscall_sendto(ql: Qiling, sockfd: int, buf: int, length: int, flags: int
|
||||
dest = (host, port)
|
||||
|
||||
elif sa_family == AF_INET6 and ql.os.ipv6:
|
||||
sockaddr_in6 = make_sockaddr_in(abits, endian)
|
||||
sockaddr_in6 = make_sockaddr_in6(abits, endian)
|
||||
sockaddr_obj = sockaddr_in6.from_buffer(data)
|
||||
|
||||
port = ntohs(ql, sockaddr_obj.sin_port)
|
||||
port = ntohs(ql, sockaddr_obj.sin6_port)
|
||||
host = inet6_ntoa(sockaddr_obj.sin6_addr.s6_addr)
|
||||
|
||||
ql.log.debug(f'Sending to {host}:{port}')
|
||||
|
||||
@@ -1225,27 +1225,45 @@ def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0):
|
||||
|
||||
|
||||
def ql_syscall_chmod(ql: Qiling, filename: int, mode: int):
|
||||
file_path = ql.os.utils.read_cstring(filename)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
vpath = ql.os.utils.read_cstring(filename)
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
try:
|
||||
os.chmod(real_path, mode)
|
||||
regreturn = 0
|
||||
except:
|
||||
os.chmod(hpath, mode)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
ql.log.debug(f'chmod("{ql.os.utils.read_cstring(filename)}", {mode:d}) = 0')
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'chmod("{vpath}", 0{mode:o}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_fchmod(ql: Qiling, fd: int, mode: int):
|
||||
if fd not in range(NR_OPEN) or ql.os.fd[fd] is None:
|
||||
return -EBADF
|
||||
if fd not in range(NR_OPEN):
|
||||
return -1
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
|
||||
if f is None:
|
||||
return -1
|
||||
|
||||
try:
|
||||
os.fchmod(ql.os.fd[fd].fileno(), mode)
|
||||
regreturn = 0
|
||||
except:
|
||||
os.fchmod(f.fileno(), mode)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
ql.log.debug("fchmod(%d, %d) = %d" % (fd, mode, regreturn))
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'fchmod({fd}, 0{mode:o}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int):
|
||||
dirfd, real_path = transform_path(ql, dirfd, path, flags)
|
||||
|
||||
@@ -1476,32 +1494,45 @@ def ql_syscall_mknodat(ql: Qiling, dirfd: int, path: int, mode: int, dev: int):
|
||||
|
||||
|
||||
def ql_syscall_mkdir(ql: Qiling, pathname: int, mode: int):
|
||||
file_path = ql.os.utils.read_cstring(pathname)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
regreturn = 0
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
try:
|
||||
if not os.path.exists(real_path):
|
||||
os.mkdir(real_path, mode)
|
||||
except:
|
||||
if not os.path.exists(hpath):
|
||||
os.mkdir(hpath, mode)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'mkdir("{vpath}", 0{mode:o}) = {regreturn}')
|
||||
|
||||
ql.log.debug("mkdir(%s, 0%o) = %d" % (real_path, mode, regreturn))
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_rmdir(ql: Qiling, pathname: int):
|
||||
file_path = ql.os.utils.read_cstring(pathname)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
regreturn = 0
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
try:
|
||||
if os.path.exists(real_path):
|
||||
os.rmdir(real_path)
|
||||
except:
|
||||
if os.path.exists(hpath):
|
||||
os.rmdir(hpath)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'rmdir("{vpath}") = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_fstatfs(ql: Qiling, fd: int, buf: int):
|
||||
data = b"0" * (12 * 8) # for now, just return 0s
|
||||
regreturn = 0
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.os.posix.const import *
|
||||
|
||||
from .shm import *
|
||||
|
||||
|
||||
def ql_syscall_ipc(ql: Qiling, call: int, first: int, second: int, third: int, ptr: int, fifth: int):
|
||||
version = call >> 16 # hi word
|
||||
call &= 0xffff # lo word
|
||||
|
||||
# FIXME: this is an incomplete implementation.
|
||||
# see: https://elixir.bootlin.com/linux/v5.19.17/source/ipc/syscall.c
|
||||
|
||||
def __call_shmat(*args: int) -> int:
|
||||
if version == 1:
|
||||
return -1 # EINVAL
|
||||
|
||||
return ql_syscall_shmat(ql, args[0], args[3], args[1])
|
||||
|
||||
def __call_shmdt(*args: int) -> int:
|
||||
return ql_syscall_shmdt(ql, args[3])
|
||||
|
||||
def __call_shmget(*args: int) -> int:
|
||||
return ql_syscall_shmget(ql, args[0], args[1], args[2])
|
||||
|
||||
ipc_call = {
|
||||
SHMAT: __call_shmat,
|
||||
SHMDT: __call_shmdt,
|
||||
SHMGET: __call_shmget
|
||||
}
|
||||
|
||||
if call not in ipc_call:
|
||||
return -1 # ENOSYS
|
||||
|
||||
return ipc_call[call](first, second, third, ptr, fifth)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ql_syscall_ipc'
|
||||
]
|
||||
+372
-260
@@ -3,23 +3,26 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import stat
|
||||
import itertools
|
||||
import pathlib
|
||||
|
||||
from typing import Iterator
|
||||
from multiprocessing import Process
|
||||
from typing import TYPE_CHECKING, Iterator, Optional
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_ARCH, QL_OS
|
||||
from qiling.os.posix.filestruct import ql_pipe
|
||||
from qiling.os.posix.const import *
|
||||
from qiling.os.posix.stat import Stat
|
||||
from qiling.core_hooks import QlCoreHooks
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling.os.posix.posix import QlOsPosix
|
||||
|
||||
|
||||
def ql_syscall_exit(ql: Qiling, code: int):
|
||||
if ql.os.child_processes == True:
|
||||
if ql.os.child_processes:
|
||||
os._exit(0)
|
||||
|
||||
if ql.multithread:
|
||||
@@ -37,7 +40,7 @@ def ql_syscall_exit(ql: Qiling, code: int):
|
||||
|
||||
|
||||
def ql_syscall_exit_group(ql: Qiling, code: int):
|
||||
if ql.os.child_processes == True:
|
||||
if ql.os.child_processes:
|
||||
os._exit(0)
|
||||
|
||||
if ql.multithread:
|
||||
@@ -135,99 +138,163 @@ def ql_syscall_setgroups(ql: Qiling, gidsetsize: int, grouplist: int):
|
||||
def ql_syscall_setresuid(ql: Qiling):
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_setresgid(ql: Qiling):
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_capget(ql: Qiling, hdrp: int, datap: int):
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_capset(ql: Qiling, hdrp: int, datap: int):
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_kill(ql: Qiling, pid: int, sig: int):
|
||||
return 0
|
||||
|
||||
|
||||
def get_opened_fd(os: QlOsPosix, fd: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
# TODO: set errno to EBADF
|
||||
return None
|
||||
|
||||
f = os.fd[fd]
|
||||
|
||||
if f is None:
|
||||
# TODO: set errno to EBADF
|
||||
return None
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def ql_syscall_fsync(ql: Qiling, fd: int):
|
||||
try:
|
||||
os.fsync(ql.os.fd[fd].fileno())
|
||||
regreturn = 0
|
||||
except:
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
regreturn = -1
|
||||
ql.log.debug("fsync(%d) = %d" % (fd, regreturn))
|
||||
|
||||
else:
|
||||
try:
|
||||
os.fsync(f.fileno())
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'fsync({fd:d}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_fdatasync(ql: Qiling, fd: int):
|
||||
try:
|
||||
os.fdatasync(ql.os.fd[fd].fileno())
|
||||
regreturn = 0
|
||||
except:
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
ql.log.debug("fdatasync(%d) = %d" % (fd, regreturn))
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'fdatasync({fd:d}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int):
|
||||
access_path = ql.os.utils.read_cstring(filename)
|
||||
real_path = ql.os.path.transform_to_real_path(access_path)
|
||||
def virtual_abspath_at(ql: Qiling, vpath: str, dirfd: int) -> Optional[str]:
|
||||
if ql.os.path.is_virtual_abspath(vpath):
|
||||
return vpath
|
||||
|
||||
if not os.path.exists(real_path):
|
||||
# <WORKAROUND>
|
||||
def __as_signed(value: int, nbits: int) -> int:
|
||||
msb = (1 << (nbits - 1))
|
||||
|
||||
return -(((value & msb) << 1) - value)
|
||||
|
||||
# syscall params are read as unsigned int by default. until we fix that
|
||||
# broadly, this is a workaround to turn fd into a signed value
|
||||
dirfd = __as_signed(dirfd, ql.arch.bits)
|
||||
# </WORKAROUND>
|
||||
|
||||
if dirfd == AT_FDCWD:
|
||||
basedir = ql.os.path.cwd
|
||||
|
||||
else:
|
||||
f = get_opened_fd(ql.os, dirfd)
|
||||
|
||||
if f is None or not hasattr(f, 'name'):
|
||||
# EBADF
|
||||
return None
|
||||
|
||||
hpath = f.name
|
||||
|
||||
if not os.path.isdir(hpath):
|
||||
# ENOTDIR
|
||||
return None
|
||||
|
||||
basedir = ql.os.path.host_to_virtual_path(hpath)
|
||||
|
||||
return str(ql.os.path.PureVirtualPath(basedir, vpath))
|
||||
|
||||
|
||||
def ql_syscall_faccessat(ql: Qiling, dirfd: int, filename: int, mode: int):
|
||||
vpath = ql.os.utils.read_cstring(filename)
|
||||
vpath_at = virtual_abspath_at(ql, vpath, dirfd)
|
||||
|
||||
if vpath_at is None:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
if regreturn == -1:
|
||||
ql.log.debug(f'File not found or skipped: {access_path}')
|
||||
else:
|
||||
ql.log.debug(f'File found: {access_path}')
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath_at)
|
||||
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
regreturn = 0 if os.path.exists(hpath) else -1
|
||||
|
||||
ql.log.debug(f'faccessat({dirfd:d}, "{vpath}", {mode:d}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_lseek(ql: Qiling, fd: int, offset: int, origin: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
|
||||
if f is None:
|
||||
return -EBADF
|
||||
|
||||
offset = ql.unpacks(ql.pack(offset))
|
||||
|
||||
try:
|
||||
regreturn = f.seek(offset, origin)
|
||||
except OSError:
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
regreturn = -1
|
||||
|
||||
# ql.log.debug("lseek(fd = %d, ofset = 0x%x, origin = 0x%x) = %d" % (fd, offset, origin, regreturn))
|
||||
else:
|
||||
try:
|
||||
regreturn = f.seek(offset, origin)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
|
||||
ql.log.debug(f'lseek({fd:d}, {offset:#x}, {origin}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall__llseek(ql: Qiling, fd: int, offset_high: int, offset_low: int, result: int, whence: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
|
||||
if f is None:
|
||||
return -EBADF
|
||||
|
||||
# treat offset as a signed value
|
||||
offset = ql.unpack64s(ql.pack64((offset_high << 32) | offset_low))
|
||||
origin = whence
|
||||
|
||||
try:
|
||||
ret = f.seek(offset, origin)
|
||||
except OSError:
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
regreturn = -1
|
||||
else:
|
||||
ql.mem.write_ptr(result, ret, 8)
|
||||
regreturn = 0
|
||||
|
||||
# ql.log.debug("_llseek(%d, 0x%x, 0x%x, 0x%x) = %d" % (fd, offset_high, offset_low, origin, regreturn))
|
||||
else:
|
||||
try:
|
||||
ret = f.seek(offset, whence)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
else:
|
||||
ql.mem.write_ptr(result, ret, 8)
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug(f'_llseek({fd:d}, {offset_high:#x}, {offset_low:#x}, {result:#x}, {whence}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
@@ -251,75 +318,68 @@ def ql_syscall_brk(ql: Qiling, inp: int):
|
||||
|
||||
|
||||
def ql_syscall_access(ql: Qiling, path: int, mode: int):
|
||||
file_path = ql.os.utils.read_cstring(path)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
relative_path = ql.os.path.transform_to_relative_path(file_path)
|
||||
vpath = ql.os.utils.read_cstring(path)
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
|
||||
regreturn = 0 if os.path.exists(real_path) else -1
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
# ql.log.debug("access(%s, 0x%x) = %d " % (relative_path, access_mode, regreturn))
|
||||
regreturn = 0 if os.path.exists(hpath) else -1
|
||||
|
||||
if regreturn == 0:
|
||||
ql.log.debug(f'File found: {relative_path}')
|
||||
else:
|
||||
ql.log.debug(f'No such file or directory: {relative_path}')
|
||||
ql.log.debug(f'access("{vpath}", 0{mode:o}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_close(ql: Qiling, fd: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
return -1
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
return -1
|
||||
regreturn = -1
|
||||
|
||||
f.close()
|
||||
ql.os.fd[fd] = None
|
||||
else:
|
||||
f.close()
|
||||
ql.os.fd[fd] = None
|
||||
regreturn = 0
|
||||
|
||||
return 0
|
||||
ql.log.debug(f'close({fd:d}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_pread64(ql: Qiling, fd: int, buf: int, length: int, offt: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
return -1
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
return -1
|
||||
|
||||
# https://chromium.googlesource.com/linux-syscall-support/+/2c73abf02fd8af961e38024882b9ce0df6b4d19b
|
||||
# https://chromiumcodereview.appspot.com/10910222
|
||||
if ql.arch.type == QL_ARCH.MIPS:
|
||||
offt = ql.mem.read_ptr(ql.arch.regs.arch_sp + 0x10, 8)
|
||||
|
||||
try:
|
||||
pos = f.tell()
|
||||
f.seek(offt)
|
||||
|
||||
data = f.read(length)
|
||||
f.seek(pos)
|
||||
|
||||
ql.mem.write(buf, data)
|
||||
except:
|
||||
regreturn = -1
|
||||
|
||||
else:
|
||||
regreturn = len(data)
|
||||
# https://chromium.googlesource.com/linux-syscall-support/+/2c73abf02fd8af961e38024882b9ce0df6b4d19b
|
||||
# https://chromiumcodereview.appspot.com/10910222
|
||||
if ql.arch.type == QL_ARCH.MIPS:
|
||||
offt = ql.mem.read_ptr(ql.arch.regs.arch_sp + 0x10, 8)
|
||||
|
||||
try:
|
||||
pos = f.tell()
|
||||
f.seek(offt)
|
||||
|
||||
data = f.read(length)
|
||||
f.seek(pos)
|
||||
except OSError:
|
||||
regreturn = -1
|
||||
else:
|
||||
ql.mem.write(buf, data)
|
||||
|
||||
regreturn = len(data)
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_read(ql: Qiling, fd, buf: int, length: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
return -EBADF
|
||||
return -1
|
||||
|
||||
try:
|
||||
data = f.read(length)
|
||||
@@ -334,13 +394,10 @@ def ql_syscall_read(ql: Qiling, fd, buf: int, length: int):
|
||||
|
||||
|
||||
def ql_syscall_write(ql: Qiling, fd: int, buf: int, count: int):
|
||||
if fd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
if f is None:
|
||||
return -EBADF
|
||||
return -1
|
||||
|
||||
try:
|
||||
data = ql.mem.read(buf, count)
|
||||
@@ -357,90 +414,95 @@ def ql_syscall_write(ql: Qiling, fd: int, buf: int, count: int):
|
||||
ql.log.warning(f'write failed since fd {fd:d} does not have a write method')
|
||||
regreturn = -1
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def __do_readlink(ql: Qiling, absvpath: str, outbuf: int) -> int:
|
||||
target = None
|
||||
|
||||
# cover a few procfs pseudo files first
|
||||
if absvpath == r'/proc/self/exe':
|
||||
# note this would raise an exception if the binary is not under rootfs
|
||||
target = ql.os.path.host_to_virtual_path(ql.path)
|
||||
|
||||
elif absvpath == r'/proc/self/cwd':
|
||||
target = ql.os.path.cwd
|
||||
|
||||
elif absvpath == r'/proc/self/root':
|
||||
target = ql.os.path.root
|
||||
|
||||
else:
|
||||
hpath = ql.os.path.virtual_to_host_path(absvpath)
|
||||
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
# FIXME: we do not really know how to emulated links, so we do not read them
|
||||
if os.path.exists(hpath):
|
||||
target = ''
|
||||
|
||||
if target is None:
|
||||
return -1
|
||||
|
||||
cstr = target.encode('utf-8')
|
||||
|
||||
if cstr:
|
||||
ql.mem.write(outbuf, cstr + b'\x00')
|
||||
|
||||
return len(cstr)
|
||||
|
||||
|
||||
def ql_syscall_readlink(ql: Qiling, pathname: int, buf: int, bufsize: int):
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
absvpath = ql.os.path.virtual_abspath(vpath)
|
||||
|
||||
regreturn = __do_readlink(ql, absvpath, buf)
|
||||
|
||||
ql.log.debug(f'readlink("{vpath}", {buf:#x}, {bufsize:#x}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_readlink(ql: Qiling, path_name: int, path_buff: int, path_buffsize: int):
|
||||
pathname = ql.os.utils.read_cstring(path_name)
|
||||
# pathname = str(pathname, 'utf-8', errors="ignore")
|
||||
host_path = ql.os.path.virtual_to_host_path(pathname)
|
||||
virt_path = ql.os.path.virtual_abspath(pathname)
|
||||
def ql_syscall_readlinkat(ql: Qiling, dirfd: int, pathname: int, buf: int, bufsize: int):
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
absvpath = virtual_abspath_at(ql, vpath, dirfd)
|
||||
|
||||
# cover procfs psaudo files first
|
||||
# TODO: /proc/self/root, /proc/self/cwd
|
||||
if virt_path == r'/proc/self/exe':
|
||||
p = ql.os.path.host_to_virtual_path(ql.path)
|
||||
p = p.encode('utf-8')
|
||||
regreturn = -1 if absvpath is None else __do_readlink(ql, absvpath, buf)
|
||||
|
||||
ql.mem.write(path_buff, p + b'\x00')
|
||||
regreturn = len(p)
|
||||
|
||||
elif os.path.exists(host_path):
|
||||
regreturn = 0
|
||||
|
||||
else:
|
||||
regreturn = -1
|
||||
|
||||
ql.log.debug('readlink("%s", 0x%x, 0x%x) = %d' % (virt_path, path_buff, path_buffsize, regreturn))
|
||||
ql.log.debug(f'readlinkat({dirfd:d}, "{vpath}", {buf:#x}, {bufsize:#x}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_getcwd(ql: Qiling, path_buff: int, path_buffsize: int):
|
||||
localpath = ql.os.path.transform_to_relative_path('./')
|
||||
localpath = bytes(localpath, 'utf-8') + b'\x00'
|
||||
cwd = ql.os.path.cwd
|
||||
|
||||
ql.mem.write(path_buff, localpath)
|
||||
regreturn = len(localpath)
|
||||
cwd_bytes = cwd.encode('utf-8') + b'\x00'
|
||||
ql.mem.write(path_buff, cwd_bytes)
|
||||
regreturn = len(cwd_bytes)
|
||||
|
||||
pathname = ql.os.utils.read_cstring(path_buff)
|
||||
# pathname = str(pathname, 'utf-8', errors="ignore")
|
||||
|
||||
ql.log.debug("getcwd(%s, 0x%x) = %d" % (pathname, path_buffsize, regreturn))
|
||||
ql.log.debug(f'getcwd("{cwd}", {path_buffsize}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_chdir(ql: Qiling, path_name: int):
|
||||
pathname = ql.os.utils.read_cstring(path_name)
|
||||
host_path = ql.os.path.virtual_to_host_path(pathname)
|
||||
virt_path = ql.os.path.virtual_abspath(pathname)
|
||||
vpath = ql.os.utils.read_cstring(path_name)
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
|
||||
if os.path.exists(host_path) and os.path.isdir(host_path):
|
||||
ql.os.path.cwd = virt_path
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
absvpath = ql.os.path.virtual_abspath(vpath)
|
||||
|
||||
if os.path.isdir(hpath):
|
||||
ql.os.path.cwd = absvpath
|
||||
|
||||
regreturn = 0
|
||||
ql.log.debug("chdir(%s) = %d"% (virt_path, regreturn))
|
||||
else:
|
||||
regreturn = -1
|
||||
ql.log.warning("chdir(%s) = %d : not found" % (virt_path, regreturn))
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_readlinkat(ql: Qiling, dfd: int, path: int, buf: int, bufsize: int):
|
||||
pathname = ql.os.utils.read_cstring(path)
|
||||
# pathname = str(pathname, 'utf-8', errors="ignore")
|
||||
host_path = ql.os.path.virtual_to_host_path(pathname)
|
||||
virt_path = ql.os.path.virtual_abspath(pathname)
|
||||
|
||||
# cover procfs psaudo files first
|
||||
# TODO: /proc/self/root, /proc/self/cwd
|
||||
if virt_path == r'/proc/self/exe':
|
||||
p = ql.os.path.host_to_virtual_path(ql.path)
|
||||
p = p.encode('utf-8')
|
||||
|
||||
ql.mem.write(buf, p + b'\x00')
|
||||
regreturn = len(p)
|
||||
|
||||
elif os.path.exists(host_path):
|
||||
regreturn = 0
|
||||
|
||||
else:
|
||||
regreturn = -1
|
||||
|
||||
ql.log.debug('readlinkat(%d, "%s", 0x%x, 0x%x) = %d' % (dfd, virt_path, buf, bufsize, regreturn))
|
||||
ql.log.debug(f'chdir("{absvpath}") = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
@@ -455,6 +517,8 @@ def ql_syscall_getppid(ql: Qiling):
|
||||
|
||||
def ql_syscall_vfork(ql: Qiling):
|
||||
if ql.host.os == QL_OS.WINDOWS:
|
||||
from multiprocessing import Process
|
||||
|
||||
try:
|
||||
pid = Process()
|
||||
pid = 0
|
||||
@@ -479,15 +543,24 @@ def ql_syscall_vfork(ql: Qiling):
|
||||
def ql_syscall_fork(ql: Qiling):
|
||||
return ql_syscall_vfork(ql)
|
||||
|
||||
|
||||
def ql_syscall_setsid(ql: Qiling):
|
||||
return os.getpid()
|
||||
|
||||
|
||||
def ql_syscall_execve(ql: Qiling, pathname: int, argv: int, envp: int):
|
||||
file_path = ql.os.utils.read_cstring(pathname)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
|
||||
def __read_str_array(addr: int) -> Iterator[str]:
|
||||
# is it safe to run?
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
return -1 # EACCES
|
||||
|
||||
# is it a file? does it exist?
|
||||
if not os.path.isfile(hpath):
|
||||
return -1 # EACCES
|
||||
|
||||
def __read_ptr_array(addr: int) -> Iterator[int]:
|
||||
if addr:
|
||||
while True:
|
||||
elem = ql.mem.read_ptr(addr)
|
||||
@@ -495,25 +568,28 @@ def ql_syscall_execve(ql: Qiling, pathname: int, argv: int, envp: int):
|
||||
if elem == 0:
|
||||
break
|
||||
|
||||
yield ql.os.utils.read_cstring(elem)
|
||||
yield elem
|
||||
addr += ql.arch.pointersize
|
||||
|
||||
args = [s for s in __read_str_array(argv)]
|
||||
def __read_str_array(addr: int) -> Iterator[str]:
|
||||
yield from (ql.os.utils.read_cstring(ptr) for ptr in __read_ptr_array(addr))
|
||||
|
||||
args = list(__read_str_array(argv))
|
||||
|
||||
env = {}
|
||||
for s in __read_str_array(envp):
|
||||
k, _, v = s.partition('=')
|
||||
env[k] = v
|
||||
|
||||
ql.emu_stop()
|
||||
ql.stop()
|
||||
ql.clear_ql_hooks()
|
||||
ql.mem.unmap_all()
|
||||
|
||||
ql.log.debug(f'execve({file_path}, [{", ".join(args)}], [{", ".join(f"{k}={v}" for k, v in env.items())}])')
|
||||
ql.log.debug(f'execve("{vpath}", [{", ".join(args)}], [{", ".join(f"{k}={v}" for k, v in env.items())}])')
|
||||
|
||||
ql.loader.argv = args
|
||||
ql.loader.env = env
|
||||
ql._argv = [real_path] + args
|
||||
ql.mem.map_info = []
|
||||
ql.clear_ql_hooks()
|
||||
ql._argv = [hpath] + args
|
||||
|
||||
# Clean debugger to prevent port conflicts
|
||||
# ql.debugger = None
|
||||
@@ -534,56 +610,86 @@ def ql_syscall_execve(ql: Qiling, pathname: int, argv: int, envp: int):
|
||||
QlCoreHooks.__init__(ql, uc)
|
||||
|
||||
ql.os.load()
|
||||
|
||||
# close all open fd marked with 'close_on_exec'
|
||||
for i in range(NR_OPEN):
|
||||
f = ql.os.fd[i]
|
||||
|
||||
if f and getattr(f, 'close_on_exec', False) and not f.closed:
|
||||
f.close()
|
||||
ql.os.fd[i] = None
|
||||
|
||||
ql.loader.run()
|
||||
ql.run()
|
||||
|
||||
|
||||
def ql_syscall_dup(ql: Qiling, oldfd: int):
|
||||
if oldfd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
f = ql.os.fd[oldfd]
|
||||
f = get_opened_fd(ql.os, oldfd)
|
||||
|
||||
if f is None:
|
||||
return -EBADF
|
||||
return -1
|
||||
|
||||
idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1)
|
||||
newfd = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1)
|
||||
|
||||
if idx == -1:
|
||||
if newfd == -1:
|
||||
return -EMFILE
|
||||
|
||||
ql.os.fd[idx] = f.dup()
|
||||
|
||||
return idx
|
||||
|
||||
|
||||
def ql_syscall_dup2(ql: Qiling, fd: int, newfd: int):
|
||||
if fd not in range(NR_OPEN) or newfd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
|
||||
if f is None:
|
||||
return -EBADF
|
||||
|
||||
ql.os.fd[newfd] = f.dup()
|
||||
|
||||
ql.log.debug(f'dup({oldfd:d}) = {newfd:d}')
|
||||
|
||||
return newfd
|
||||
|
||||
|
||||
def ql_syscall_dup3(ql: Qiling, fd: int, newfd: int, flags: int):
|
||||
if fd not in range(NR_OPEN) or newfd not in range(NR_OPEN):
|
||||
return -1
|
||||
|
||||
f = ql.os.fd[fd]
|
||||
def ql_syscall_dup2(ql: Qiling, oldfd: int, newfd: int):
|
||||
f = get_opened_fd(ql.os, oldfd)
|
||||
|
||||
if f is None:
|
||||
return -1
|
||||
|
||||
if newfd not in range(NR_OPEN):
|
||||
return -1
|
||||
|
||||
newslot = ql.os.fd[newfd]
|
||||
|
||||
if newslot is not None:
|
||||
newslot.close()
|
||||
|
||||
ql.os.fd[newfd] = f.dup()
|
||||
|
||||
ql.log.debug(f'dup2({oldfd:d}, {newfd:d}) = {newfd:d}')
|
||||
|
||||
return newfd
|
||||
|
||||
|
||||
def ql_syscall_dup3(ql: Qiling, oldfd: int, newfd: int, flags: int):
|
||||
O_CLOEXEC = 0o2000000
|
||||
|
||||
f = get_opened_fd(ql.os, oldfd)
|
||||
|
||||
if f is None:
|
||||
return -1
|
||||
|
||||
if newfd not in range(NR_OPEN):
|
||||
return -1
|
||||
|
||||
newslot = ql.os.fd[newfd]
|
||||
|
||||
if newslot is not None:
|
||||
newslot.close()
|
||||
|
||||
newf = f.dup()
|
||||
|
||||
if flags & O_CLOEXEC:
|
||||
newf.close_on_exec = True
|
||||
|
||||
ql.os.fd[newfd] = newf
|
||||
|
||||
ql.log.debug(f'dup3({oldfd:d}, {newfd:d}, 0{flags:o}) = {newfd:d}')
|
||||
|
||||
return newfd
|
||||
|
||||
|
||||
def ql_syscall_set_tid_address(ql: Qiling, tidptr: int):
|
||||
if ql.os.thread_management:
|
||||
regreturn = ql.os.thread_management.cur_thread.id
|
||||
@@ -621,102 +727,107 @@ def ql_syscall_nice(ql: Qiling, inc: int):
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_truncate(ql: Qiling, path: int, length: int):
|
||||
file_path = ql.os.utils.read_cstring(path)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
st_size = Stat(real_path).st_size
|
||||
def __do_truncate(ql: Qiling, hpath: str, length: int) -> int:
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
try:
|
||||
if st_size >= length:
|
||||
os.truncate(real_path, length)
|
||||
st_size = os.path.getsize(hpath)
|
||||
|
||||
else:
|
||||
if st_size > length:
|
||||
os.truncate(hpath, length)
|
||||
|
||||
elif st_size < length:
|
||||
padding = length - st_size
|
||||
|
||||
with open(real_path, 'a+b') as ofile:
|
||||
with open(hpath, 'a+b') as ofile:
|
||||
ofile.write(b'\x00' * padding)
|
||||
except:
|
||||
regreturn = -1
|
||||
except OSError:
|
||||
return -1
|
||||
else:
|
||||
regreturn = 0
|
||||
return 0
|
||||
|
||||
ql.log.debug('truncate(%s, 0x%x) = %d' % (file_path, length, regreturn))
|
||||
|
||||
def ql_syscall_truncate(ql: Qiling, path: int, length: int):
|
||||
vpath = ql.os.utils.read_cstring(path)
|
||||
hpath = ql.os.path.virtual_to_host_path(vpath)
|
||||
|
||||
regreturn = __do_truncate(ql, hpath, length)
|
||||
|
||||
ql.log.debug(f'truncate("{vpath}", {length:#x}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_ftruncate(ql: Qiling, fd: int, length: int):
|
||||
real_path = ql.os.fd[fd].name
|
||||
st_size = Stat(real_path).st_size
|
||||
f = get_opened_fd(ql.os, fd)
|
||||
|
||||
try:
|
||||
if st_size >= length:
|
||||
os.truncate(real_path, length)
|
||||
regreturn = -1 if f is None else __do_truncate(ql, f.name, length)
|
||||
|
||||
else:
|
||||
padding = length - st_size
|
||||
|
||||
with open(real_path, 'a+b') as ofile:
|
||||
ofile.write(b'\x00' * padding)
|
||||
except:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug("ftruncate(%d, 0x%x) = %d" % (fd, length, regreturn))
|
||||
ql.log.debug(f'ftruncate({fd}, {length:#x}) = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def __do_unlink(ql: Qiling, absvpath: str) -> int:
|
||||
|
||||
def __has_opened_fd(hpath: str) -> bool:
|
||||
opened_fds = (ql.os.fd[i] for i in range(NR_OPEN) if ql.os.fd[i] is not None)
|
||||
f = next((fd for fd in opened_fds if getattr(fd, 'name', '') == hpath), None)
|
||||
|
||||
return f is not None and f.closed
|
||||
|
||||
hpath = ql.os.path.virtual_to_host_path(absvpath)
|
||||
|
||||
if ql.os.fs_mapper.has_mapping(absvpath):
|
||||
if __has_opened_fd(hpath):
|
||||
return -1
|
||||
|
||||
ql.os.fs_mapper.remove_mapping(absvpath)
|
||||
|
||||
else:
|
||||
if not ql.os.path.is_safe_host_path(hpath):
|
||||
raise PermissionError(f'unsafe path: {hpath}')
|
||||
|
||||
# NOTE: no idea why these are always ok to remove
|
||||
def __ok_to_remove(hpath: str) -> bool:
|
||||
path = pathlib.Path(hpath)
|
||||
|
||||
return any((path.is_block_device(), path.is_fifo(), path.is_socket(), path.is_symlink()))
|
||||
|
||||
if __has_opened_fd(hpath) and not __ok_to_remove(hpath):
|
||||
return -1
|
||||
|
||||
try:
|
||||
os.unlink(hpath)
|
||||
except OSError:
|
||||
return -1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_unlink(ql: Qiling, pathname: int):
|
||||
file_path = ql.os.utils.read_cstring(pathname)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
absvpath = ql.os.path.virtual_abspath(vpath)
|
||||
|
||||
opened_fds = [getattr(ql.os.fd[i], 'name', None) for i in range(NR_OPEN) if ql.os.fd[i] is not None]
|
||||
path = pathlib.Path(real_path)
|
||||
regreturn = __do_unlink(ql, absvpath)
|
||||
|
||||
if any((real_path not in opened_fds, path.is_block_device(), path.is_fifo(), path.is_socket(), path.is_symlink())):
|
||||
try:
|
||||
os.unlink(real_path)
|
||||
except FileNotFoundError:
|
||||
ql.log.debug('No such file or directory')
|
||||
regreturn = -1
|
||||
except:
|
||||
regreturn = -1
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
else:
|
||||
regreturn = -1
|
||||
|
||||
ql.log.debug("unlink(%s) = %d" % (file_path, regreturn))
|
||||
ql.log.debug(f'unlink("{vpath}") = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_unlinkat(ql: Qiling, fd: int, pathname: int):
|
||||
file_path = ql.os.utils.read_cstring(pathname)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
def ql_syscall_unlinkat(ql: Qiling, dirfd: int, pathname: int, flags: int):
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
absvpath = virtual_abspath_at(ql, vpath, dirfd)
|
||||
|
||||
try:
|
||||
dir_fd = ql.os.fd[fd].fileno()
|
||||
except:
|
||||
dir_fd = None
|
||||
regreturn = -1 if absvpath is None else __do_unlink(ql, absvpath)
|
||||
|
||||
try:
|
||||
if dir_fd is None:
|
||||
os.unlink(real_path)
|
||||
else:
|
||||
os.unlink(file_path, dir_fd=dir_fd)
|
||||
except OSError as e:
|
||||
regreturn = -e.errno
|
||||
else:
|
||||
regreturn = 0
|
||||
|
||||
ql.log.debug("unlinkat(fd = %d, path = '%s') = %d" % (fd, file_path, regreturn))
|
||||
ql.log.debug(f'unlinkat({dirfd}, "{vpath}") = {regreturn}')
|
||||
|
||||
return regreturn
|
||||
|
||||
|
||||
# https://man7.org/linux/man-pages/man2/getdents.2.html
|
||||
# struct linux_dirent {
|
||||
# unsigned long d_ino; /* Inode number */
|
||||
@@ -827,5 +938,6 @@ def __getdents_common(ql: Qiling, fd: int, dirp: int, count: int, *, is_64: bool
|
||||
def ql_syscall_getdents(ql: Qiling, fd: int, dirp: int, count: int):
|
||||
return __getdents_common(ql, fd, dirp, count, is_64=False)
|
||||
|
||||
|
||||
def ql_syscall_getdents64(ql: Qiling, fd: int, dirp: int, count: int):
|
||||
return __getdents_common(ql, fd, dirp, count, is_64=True)
|
||||
|
||||
+13
-17
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from unicorn import UcError
|
||||
|
||||
from qiling import Qiling
|
||||
@@ -20,6 +18,7 @@ from qiling.os.fcall import QlFunctionCall
|
||||
from qiling.os.const import *
|
||||
from qiling.os.posix.posix import QlOsPosix
|
||||
|
||||
|
||||
class QlOsQnx(QlOsPosix):
|
||||
type = QL_OS.QNX
|
||||
|
||||
@@ -45,9 +44,8 @@ class QlOsQnx(QlOsPosix):
|
||||
self.futexm = None
|
||||
self.fh = None
|
||||
self.function_after_load_list = []
|
||||
self.elf_mem_start = 0x0
|
||||
self.load()
|
||||
|
||||
|
||||
# use counters to get free Ids
|
||||
self.channel_id = 1
|
||||
# TODO: replace 0x400 with NR_OPEN from Qiling 1.25
|
||||
@@ -75,37 +73,35 @@ class QlOsQnx(QlOsPosix):
|
||||
'get_tls': 0xffff0fe0
|
||||
})
|
||||
|
||||
|
||||
def hook_syscall(self, ql, intno):
|
||||
return self.load_syscall()
|
||||
|
||||
|
||||
def register_function_after_load(self, function):
|
||||
if function not in self.function_after_load_list:
|
||||
self.function_after_load_list.append(function)
|
||||
|
||||
|
||||
def run_function_after_load(self):
|
||||
for f in self.function_after_load_list:
|
||||
f()
|
||||
|
||||
|
||||
def run(self):
|
||||
if self.ql.exit_point is not None:
|
||||
self.exit_point = self.ql.exit_point
|
||||
|
||||
if self.ql.entry_point is not None:
|
||||
if self.ql.entry_point is not None:
|
||||
self.ql.loader.elf_entry = self.ql.entry_point
|
||||
|
||||
self.cpupage_addr = int(self.ql.os.profile.get("OS32", "cpupage_address"), 16)
|
||||
self.cpupage_tls_addr = int(self.ql.os.profile.get("OS32", "cpupage_tls_address"), 16)
|
||||
self.tls_data_addr = int(self.ql.os.profile.get("OS32", "tls_data_address"), 16)
|
||||
self.syspage_addr = int(self.ql.os.profile.get("OS32", "syspage_address"), 16)
|
||||
syspage_path = os.path.join(self.ql.rootfs, "syspage.bin")
|
||||
profile = self.ql.os.profile['OS32']
|
||||
|
||||
self.cpupage_addr = profile.getint('cpupage_address')
|
||||
self.cpupage_tls_addr = profile.getint('cpupage_tls_address')
|
||||
self.tls_data_addr = profile.getint('tls_data_address')
|
||||
self.syspage_addr = profile.getint('syspage_address')
|
||||
|
||||
self.ql.mem.map(self.syspage_addr, 0x4000, info="[syspage_mem]")
|
||||
|
||||
with open(syspage_path, "rb") as sp:
|
||||
syspage_hpath = self.ql.os.path.virtual_to_host_path("/syspage.bin")
|
||||
|
||||
with open(syspage_hpath, "rb") as sp:
|
||||
self.ql.mem.write(self.syspage_addr, sp.read())
|
||||
|
||||
# Address of struct _thread_local_storage for our thread
|
||||
|
||||
@@ -52,21 +52,13 @@ def hook_FindFirstFileA(ql: Qiling, address: int, params):
|
||||
if len(filename) >= MAX_PATH:
|
||||
return ERROR_INVALID_PARAMETER
|
||||
|
||||
host_path = ql.os.path.virtual_to_host_path(filename)
|
||||
|
||||
# Verify the directory is in ql.rootfs to ensure no path traversal has taken place
|
||||
if not ql.os.path.is_safe_host_path(host_path):
|
||||
ql.os.last_error = ERROR_FILE_NOT_FOUND
|
||||
|
||||
return INVALID_HANDLE_VALUE
|
||||
|
||||
# Check if path exists
|
||||
filesize = 0
|
||||
|
||||
try:
|
||||
f = ql.os.fs_mapper.open(host_path, "r")
|
||||
f = ql.os.fs_mapper.open(filename, "r")
|
||||
|
||||
filesize = os.path.getsize(host_path)
|
||||
filesize = os.path.getsize(f.name)
|
||||
except FileNotFoundError:
|
||||
ql.os.last_error = ERROR_FILE_NOT_FOUND
|
||||
|
||||
|
||||
+40
-19
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -8,11 +8,13 @@ This module is intended for general purpose functions that can be used
|
||||
thoughout the qiling framework
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
import importlib, inspect, os
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from functools import partial
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Tuple, TypeVar, Union
|
||||
|
||||
@@ -38,27 +40,32 @@ def __name_to_enum(name: str, mapping: Mapping[str, T], aliases: Mapping[str, st
|
||||
|
||||
return mapping.get(aliases.get(key) or key)
|
||||
|
||||
|
||||
def os_convert(os: str) -> Optional[QL_OS]:
|
||||
alias_map = {
|
||||
'darwin' : 'macos'
|
||||
'darwin': 'macos'
|
||||
}
|
||||
|
||||
return __name_to_enum(os, os_map, alias_map)
|
||||
|
||||
|
||||
def arch_convert(arch: str) -> Optional[QL_ARCH]:
|
||||
alias_map = {
|
||||
'x86_64' : 'x8664',
|
||||
'riscv32' : 'riscv'
|
||||
'x86_64': 'x8664',
|
||||
'riscv32': 'riscv'
|
||||
}
|
||||
|
||||
return __name_to_enum(arch, arch_map, alias_map)
|
||||
|
||||
|
||||
def debugger_convert(debugger: str) -> Optional[QL_DEBUGGER]:
|
||||
return __name_to_enum(debugger, debugger_map)
|
||||
|
||||
|
||||
def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]:
|
||||
return arch_os_map.get(arch)
|
||||
|
||||
|
||||
def ql_get_module(module_name: str) -> ModuleType:
|
||||
try:
|
||||
module = importlib.import_module(module_name, 'qiling')
|
||||
@@ -67,6 +74,7 @@ def ql_get_module(module_name: str) -> ModuleType:
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def ql_get_module_function(module_name: str, member_name: str):
|
||||
module = ql_get_module(module_name)
|
||||
|
||||
@@ -77,6 +85,7 @@ def ql_get_module_function(module_name: str, member_name: str):
|
||||
|
||||
return member
|
||||
|
||||
|
||||
def __emu_env_from_pathname(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]:
|
||||
if os.path.isdir(path) and path.endswith('.kext'):
|
||||
return QL_ARCH.X8664, QL_OS.MACOS, QL_ENDIAN.EL
|
||||
@@ -89,6 +98,7 @@ def __emu_env_from_pathname(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_O
|
||||
|
||||
return None, None, None
|
||||
|
||||
|
||||
def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]:
|
||||
# instead of using full-blown elffile parsing, we perform a simple parsing to avoid
|
||||
# external dependencies for target systems that do not need them.
|
||||
@@ -99,7 +109,7 @@ def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], O
|
||||
ELFCLASS32 = 1 # 32-bit
|
||||
ELFCLASS64 = 2 # 64-bit
|
||||
|
||||
#ei_data
|
||||
# ei_data
|
||||
ELFDATA2LSB = 1 # little-endian
|
||||
ELFDATA2MSB = 2 # big-endian
|
||||
|
||||
@@ -121,8 +131,8 @@ def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], O
|
||||
EM_PPC = 20
|
||||
|
||||
endianess = {
|
||||
ELFDATA2LSB : (QL_ENDIAN.EL, 'little'),
|
||||
ELFDATA2MSB : (QL_ENDIAN.EB, 'big')
|
||||
ELFDATA2LSB: (QL_ENDIAN.EL, 'little'),
|
||||
ELFDATA2MSB: (QL_ENDIAN.EB, 'big')
|
||||
}
|
||||
|
||||
machines32 = {
|
||||
@@ -140,8 +150,8 @@ def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], O
|
||||
}
|
||||
|
||||
classes = {
|
||||
ELFCLASS32 : machines32,
|
||||
ELFCLASS64 : machines64
|
||||
ELFCLASS32: machines32,
|
||||
ELFCLASS64: machines64
|
||||
}
|
||||
|
||||
abis = {
|
||||
@@ -192,6 +202,7 @@ def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], O
|
||||
|
||||
return archtype, ostype, archendian
|
||||
|
||||
|
||||
def __emu_env_from_macho(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]:
|
||||
macho_macos_sig64 = b'\xcf\xfa\xed\xfe'
|
||||
macho_macos_sig32 = b'\xce\xfa\xed\xfe'
|
||||
@@ -220,6 +231,7 @@ def __emu_env_from_macho(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS],
|
||||
|
||||
return arch, ostype, endian
|
||||
|
||||
|
||||
def __emu_env_from_pe(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]:
|
||||
import pefile
|
||||
|
||||
@@ -260,6 +272,7 @@ def __emu_env_from_pe(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Op
|
||||
|
||||
return arch, ostype, archendian
|
||||
|
||||
|
||||
def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]:
|
||||
guessing_methods = (
|
||||
__emu_env_from_pathname,
|
||||
@@ -278,9 +291,10 @@ def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Opt
|
||||
|
||||
return arch, ostype, endian
|
||||
|
||||
|
||||
def select_loader(ostype: QL_OS, libcache: bool) -> QlClassInit['QlLoader']:
|
||||
if ostype == QL_OS.WINDOWS:
|
||||
kwargs = {'libcache' : libcache}
|
||||
kwargs = {'libcache': libcache}
|
||||
|
||||
else:
|
||||
kwargs = {}
|
||||
@@ -305,6 +319,7 @@ def select_loader(ostype: QL_OS, libcache: bool) -> QlClassInit['QlLoader']:
|
||||
|
||||
return partial(obj, **kwargs)
|
||||
|
||||
|
||||
def select_component(component_type: str, component_name: str, **kwargs) -> QlClassInit[Any]:
|
||||
component_path = f'.{component_type}.{component_name}'
|
||||
component_class = f'Ql{component_name.capitalize()}Manager'
|
||||
@@ -313,6 +328,7 @@ def select_component(component_type: str, component_name: str, **kwargs) -> QlCl
|
||||
|
||||
return partial(obj, **kwargs)
|
||||
|
||||
|
||||
def select_debugger(options: Union[str, bool]) -> Optional[QlClassInit['QlDebugger']]:
|
||||
if options is True:
|
||||
options = 'gdb'
|
||||
@@ -353,14 +369,15 @@ def select_debugger(options: Union[str, bool]) -> Optional[QlClassInit['QlDebugg
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassInit['QlArch']:
|
||||
# set endianess and thumb mode for arm-based archs
|
||||
if archtype == QL_ARCH.ARM:
|
||||
kwargs = {'endian' : endian, 'thumb' : thumb}
|
||||
if archtype is QL_ARCH.ARM:
|
||||
kwargs = {'endian': endian, 'thumb': thumb}
|
||||
|
||||
# set endianess for mips arch
|
||||
elif archtype == QL_ARCH.MIPS:
|
||||
kwargs = {'endian' : endian}
|
||||
elif archtype is QL_ARCH.MIPS:
|
||||
kwargs = {'endian': endian}
|
||||
|
||||
else:
|
||||
kwargs = {}
|
||||
@@ -386,6 +403,7 @@ def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassIni
|
||||
|
||||
return partial(obj, **kwargs)
|
||||
|
||||
|
||||
def select_os(ostype: QL_OS) -> QlClassInit['QlOs']:
|
||||
qlos_name = ostype.name
|
||||
qlos_path = f'.os.{qlos_name.lower()}.{qlos_name.lower()}'
|
||||
@@ -395,14 +413,15 @@ def select_os(ostype: QL_OS) -> QlClassInit['QlOs']:
|
||||
|
||||
return partial(obj)
|
||||
|
||||
|
||||
def profile_setup(ostype: QL_OS, user_config: Optional[Union[str, dict]]):
|
||||
# mcu uses a yaml-based config
|
||||
if ostype == QL_OS.MCU:
|
||||
if ostype is QL_OS.MCU:
|
||||
import yaml
|
||||
|
||||
if user_config:
|
||||
with open(user_config) as f:
|
||||
config = yaml.load(f, Loader=yaml.Loader)
|
||||
config = yaml.load(f, Loader=yaml.SafeLoader)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
@@ -426,6 +445,7 @@ def profile_setup(ostype: QL_OS, user_config: Optional[Union[str, dict]]):
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# verify if emulator returns properly
|
||||
def verify_ret(ql: 'Qiling', err):
|
||||
# init_sp location is not consistent; this is here to work around that
|
||||
@@ -463,6 +483,7 @@ def verify_ret(ql: 'Qiling', err):
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
__all__ = [
|
||||
'os_convert',
|
||||
'arch_convert',
|
||||
|
||||
+446
-237
@@ -3,353 +3,550 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import http.client, platform, socket, sys, os, threading, time, unittest
|
||||
import http.client
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from typing import List
|
||||
|
||||
sys.path.append("..")
|
||||
from qiling import Qiling
|
||||
from qiling.const import *
|
||||
from qiling.exception import *
|
||||
from qiling.os.filestruct import ql_file
|
||||
from qiling.os.stats import QlOsNullStats
|
||||
|
||||
|
||||
BASE_ROOTFS = r'../examples/rootfs'
|
||||
X86_LINUX_ROOTFS = fr'{BASE_ROOTFS}/x86_linux'
|
||||
X64_LINUX_ROOTFS = fr'{BASE_ROOTFS}/x8664_linux'
|
||||
ARM_LINUX_ROOTFS = fr'{BASE_ROOTFS}/arm_linux'
|
||||
ARMEB_LINUX_ROOTFS = fr'{BASE_ROOTFS}/armeb_linux'
|
||||
ARM64_LINUX_ROOTFS = fr'{BASE_ROOTFS}/arm64_linux'
|
||||
MIPSEB_LINUX_ROOTFS = fr'{BASE_ROOTFS}/mips32_linux'
|
||||
MIPSEL_LINUX_ROOTFS = fr'{BASE_ROOTFS}/mips32el_linux'
|
||||
|
||||
|
||||
class ELFTest(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(platform.system() == "Darwin" and platform.machine() == "arm64", 'darwin host')
|
||||
def test_elf_linux_execve_x8664(self):
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/posix_syscall_execve"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG)
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/posix_syscall_execve'], X64_LINUX_ROOTFS, verbose=QL_VERBOSE.DEBUG)
|
||||
ql.run()
|
||||
|
||||
for key, value in ql.loader.env.items():
|
||||
QL_TEST=value
|
||||
env = ql.loader.env
|
||||
|
||||
self.assertEqual("TEST_QUERY", QL_TEST)
|
||||
self.assertEqual("child", ql.loader.argv[0])
|
||||
|
||||
del QL_TEST
|
||||
del ql
|
||||
self.assertIn('QL_TEST', env)
|
||||
self.assertEqual('TEST_QUERY', env['QL_TEST'])
|
||||
self.assertEqual('child', ql.loader.argv[0])
|
||||
|
||||
def test_elf_linux_cloexec_x8664(self):
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_cloexec_test"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, multithread=True)
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/x8664_cloexec_test'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
filename = 'output.txt'
|
||||
err = ql_file.open(filename, os.O_RDWR | os.O_CREAT, 0o777)
|
||||
filename = 'stderr.txt'
|
||||
err = ql_file.open(filename, os.O_RDWR | os.O_CREAT, 0o644)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.stderr = err
|
||||
|
||||
ql.run()
|
||||
err.close()
|
||||
|
||||
with open(filename, 'rb') as f:
|
||||
content = f.read()
|
||||
with open(filename, 'r') as f:
|
||||
contents = f.readlines()
|
||||
|
||||
# cleanup
|
||||
os.remove(filename)
|
||||
|
||||
self.assertIn(b'fail', content)
|
||||
|
||||
del ql
|
||||
self.assertGreaterEqual(len(contents), 4)
|
||||
self.assertIn('Operation not permitted', contents[-2])
|
||||
self.assertIn('Operation not permitted', contents[-1])
|
||||
|
||||
def test_multithread_elf_linux_x86(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
nonlocal buf_out
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
buf_out = buf
|
||||
except:
|
||||
pass
|
||||
buf_out = None
|
||||
ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_multithreading"], "../examples/rootfs/x86_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{X86_LINUX_ROOTFS}/bin/x86_multithreading'], X86_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertTrue("thread 2 ret val is" in buf_out)
|
||||
|
||||
del ql
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('thread 1 ret val is'))
|
||||
self.assertTrue(logged[-1].startswith('thread 2 ret val is'))
|
||||
|
||||
def test_multithread_elf_linux_arm64(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
nonlocal buf_out
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
buf_out = buf
|
||||
except:
|
||||
pass
|
||||
buf_out = None
|
||||
ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_multithreading"], "../examples/rootfs/arm64_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{ARM64_LINUX_ROOTFS}/bin/arm64_multithreading'], ARM64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertTrue("thread 2 ret val is" in buf_out)
|
||||
|
||||
del ql
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('thread 1 ret val is'))
|
||||
self.assertTrue(logged[-1].startswith('thread 2 ret val is'))
|
||||
|
||||
def test_multithread_elf_linux_x8664(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
nonlocal buf_out
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
buf_out = buf
|
||||
except:
|
||||
pass
|
||||
buf_out = None
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_multithreading"], "../examples/rootfs/x8664_linux", multithread=True)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/x8664_multithreading'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertTrue("thread 2 ret val is" in buf_out)
|
||||
|
||||
del ql
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('thread 1 ret val is'))
|
||||
self.assertTrue(logged[-1].startswith('thread 2 ret val is'))
|
||||
|
||||
def test_multithread_elf_linux_mips32eb(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
nonlocal buf_out
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
buf_out = buf
|
||||
except:
|
||||
pass
|
||||
buf_out = None
|
||||
ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_multithreading"], "../examples/rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG, multithread=True)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{MIPSEB_LINUX_ROOTFS}/bin/mips32_multithreading'], MIPSEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertTrue("thread 2 ret val is" in buf_out)
|
||||
|
||||
del ql
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('thread 1 ret val is'))
|
||||
self.assertTrue(logged[-1].startswith('thread 2 ret val is'))
|
||||
|
||||
def test_multithread_elf_linux_mips32el(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
nonlocal buf_out
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
buf_out = buf
|
||||
except:
|
||||
pass
|
||||
buf_out = None
|
||||
ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_multithreading"], "../examples/rootfs/mips32el_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{MIPSEL_LINUX_ROOTFS}/bin/mips32el_multithreading'], MIPSEL_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertTrue("thread 2 ret val is" in buf_out)
|
||||
|
||||
del ql
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('thread 1 ret val is'))
|
||||
self.assertTrue(logged[-1].startswith('thread 2 ret val is'))
|
||||
|
||||
def test_multithread_elf_linux_arm(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
nonlocal buf_out
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
buf_out = buf
|
||||
except:
|
||||
pass
|
||||
buf_out = None
|
||||
ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_multithreading"], "../examples/rootfs/arm_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{ARM_LINUX_ROOTFS}/bin/arm_multithreading'], ARM_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertTrue("thread 2 ret val is" in buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('thread 1 ret val is'))
|
||||
self.assertTrue(logged[-1].startswith('thread 2 ret val is'))
|
||||
|
||||
del ql
|
||||
@unittest.skip('broken: unicorn.unicorn.UcError: Invalid instruction (UC_ERR_INSN_INVALID)')
|
||||
def test_multithread_elf_linux_armeb(self):
|
||||
logged: List[str] = []
|
||||
|
||||
# unicorn.unicorn.UcError: Invalid instruction (UC_ERR_INSN_INVALID)
|
||||
# def test_multithread_elf_linux_armeb(self):
|
||||
# def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
# nonlocal buf_out
|
||||
# try:
|
||||
# buf = ql.mem.read(write_buf, write_count)
|
||||
# buf = buf.decode()
|
||||
# buf_out = buf
|
||||
# except:
|
||||
# pass
|
||||
# buf_out = None
|
||||
# ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_multithreading"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
# ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
# ql.run()
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
# self.assertTrue("thread 2 ret val is" in buf_out)
|
||||
logged.append(content.decode())
|
||||
|
||||
# del ql
|
||||
ql = Qiling([fr'{ARMEB_LINUX_ROOTFS}/bin/armeb_multithreading'], ARMEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('thread 1 ret val is'))
|
||||
self.assertTrue(logged[-1].startswith('thread 2 ret val is'))
|
||||
|
||||
def test_tcp_elf_linux_x86(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server send()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_tcp_test", "20001"], "../examples/rootfs/x86_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{X86_LINUX_ROOTFS}/bin/x86_tcp_test', '20000'], X86_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server send() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recv()'))
|
||||
self.assertTrue(logged[-1].startswith('server send()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_tcp_elf_linux_x8664(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server send()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_tcp_test", "20002"], "../examples/rootfs/x8664_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/x8664_tcp_test', '20001'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server send() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recv()'))
|
||||
self.assertTrue(logged[-1].startswith('server send()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_tcp_elf_linux_arm(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server write()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_tcp_test", "20003"], "../examples/rootfs/arm_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{ARM_LINUX_ROOTFS}/bin/arm_tcp_test', '20002'], ARM_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server write() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server read()'))
|
||||
self.assertTrue(logged[-1].startswith('server write()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server read() return 14.\n'
|
||||
# 'server write() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_tcp_elf_linux_arm64(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server send()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_tcp_test", "20004"], "../examples/rootfs/arm64_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{ARM64_LINUX_ROOTFS}/bin/arm64_tcp_test', '20003'], ARM64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server send() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recv()'))
|
||||
self.assertTrue(logged[-1].startswith('server send()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_tcp_elf_linux_armeb(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server send()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_tcp_test", "20003"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{ARMEB_LINUX_ROOTFS}/bin/armeb_tcp_test', '20004'], ARMEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server send() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recv()'))
|
||||
self.assertTrue(logged[-1].startswith('server send()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_tcp_elf_linux_mips32eb(self):
|
||||
ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_tcp_test", "20005"], "../examples/rootfs/mips32_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql.run()
|
||||
del ql
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{MIPSEB_LINUX_ROOTFS}/bin/mips32_tcp_test', '20005'], MIPSEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recv()'))
|
||||
self.assertTrue(logged[-1].startswith('server send()'))
|
||||
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_tcp_elf_linux_mips32el(self):
|
||||
ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_tcp_test", "20005"], "../examples/rootfs/mips32el_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql.run()
|
||||
del ql
|
||||
logged: List[str] = []
|
||||
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{MIPSEL_LINUX_ROOTFS}/bin/mips32el_tcp_test', '20006'], MIPSEL_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server read()'))
|
||||
self.assertTrue(logged[-1].startswith('server write()'))
|
||||
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server read() return 14.\n'
|
||||
# 'server write() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_udp_elf_linux_x86(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server sendto()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
logged: List[str] = []
|
||||
|
||||
ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_udp_test", "20007"], "../examples/rootfs/x86_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{X86_LINUX_ROOTFS}/bin/x86_udp_test', '20010'], X86_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recvfrom()'))
|
||||
self.assertTrue(logged[-1].startswith('server sendto()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_udp_elf_linux_x8664(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server sendto()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
logged: List[str] = []
|
||||
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_udp_test", "20008"], "../examples/rootfs/x8664_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/x8664_udp_test', '20011'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recvfrom()'))
|
||||
self.assertTrue(logged[-1].startswith('server sendto()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_udp_elf_linux_arm64(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server sendto()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
logged: List[str] = []
|
||||
|
||||
ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_udp_test", "20009"], "../examples/rootfs/arm64_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{ARM64_LINUX_ROOTFS}/bin/arm64_udp_test', '20013'], ARM64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recvfrom()'))
|
||||
self.assertTrue(logged[-1].startswith('server sendto()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_udp_elf_linux_armeb(self):
|
||||
def check_write(ql, write_fd, write_buf, write_count, *args, **kw):
|
||||
try:
|
||||
buf = ql.mem.read(write_buf, write_count)
|
||||
buf = buf.decode()
|
||||
if buf.startswith("server sendto()"):
|
||||
ql.buf_out = buf
|
||||
except:
|
||||
pass
|
||||
logged: List[str] = []
|
||||
|
||||
ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_udp_test", "20010"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
def check_write(ql: Qiling, fd: int, write_buf, count: int):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
|
||||
ql = Qiling([fr'{ARMEB_LINUX_ROOTFS}/bin/armeb_udp_test', '20014'], ARMEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
ql.os.stats = QlOsNullStats()
|
||||
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
|
||||
ql.run()
|
||||
|
||||
self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out)
|
||||
self.assertGreaterEqual(len(logged), 2)
|
||||
self.assertTrue(logged[-2].startswith('server recvfrom()'))
|
||||
self.assertTrue(logged[-1].startswith('server sendto()'))
|
||||
|
||||
del ql
|
||||
# the server is expected to send the value it received, for example:
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
self.assertTrue(msg.endswith(f'{num} return {num}.'))
|
||||
|
||||
def test_http_elf_linux_x8664(self):
|
||||
PORT = 20020
|
||||
|
||||
def picohttpd():
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/picohttpd", "12911"], "../examples/rootfs/x8664_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/picohttpd', f'{PORT:d}'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql.run()
|
||||
|
||||
picohttpd_therad = threading.Thread(target=picohttpd, daemon=True)
|
||||
@@ -357,14 +554,18 @@ class ELFTest(unittest.TestCase):
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
f = http.client.HTTPConnection('localhost', 12911, timeout=10)
|
||||
f.request("GET", "/")
|
||||
response = f.getresponse()
|
||||
self.assertEqual("httpd_test_successful", response.read().decode())
|
||||
conn = http.client.HTTPConnection('localhost', PORT, timeout=10)
|
||||
conn.request('GET', '/')
|
||||
|
||||
response = conn.getresponse()
|
||||
feedback = response.read()
|
||||
self.assertEqual('httpd_test_successful', feedback.decode())
|
||||
|
||||
def test_http_elf_linux_arm(self):
|
||||
PORT = 20021
|
||||
|
||||
def picohttpd():
|
||||
ql = Qiling(["../examples/rootfs/arm_linux/bin/picohttpd", "12912"], "../examples/rootfs/arm_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql = Qiling([fr'{ARM_LINUX_ROOTFS}/bin/picohttpd', f'{PORT:d}'], ARM_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql.run()
|
||||
|
||||
picohttpd_therad = threading.Thread(target=picohttpd, daemon=True)
|
||||
@@ -372,14 +573,18 @@ class ELFTest(unittest.TestCase):
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
f = http.client.HTTPConnection('localhost', 12912, timeout=10)
|
||||
f.request("GET", "/")
|
||||
response = f.getresponse()
|
||||
self.assertEqual("httpd_test_successful", response.read().decode())
|
||||
conn = http.client.HTTPConnection('localhost', PORT, timeout=10)
|
||||
conn.request('GET', '/')
|
||||
|
||||
response = conn.getresponse()
|
||||
feedback = response.read()
|
||||
self.assertEqual('httpd_test_successful', feedback.decode())
|
||||
|
||||
def test_http_elf_linux_armeb(self):
|
||||
PORT = 20022
|
||||
|
||||
def picohttpd():
|
||||
ql = Qiling(["../examples/rootfs/armeb_linux/bin/picohttpd", "12913"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql = Qiling([fr'{ARMEB_LINUX_ROOTFS}/bin/picohttpd', f'{PORT:d}'], ARMEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
ql.run()
|
||||
|
||||
picohttpd_thread = threading.Thread(target=picohttpd, daemon=True)
|
||||
@@ -387,19 +592,23 @@ class ELFTest(unittest.TestCase):
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.connect(("localhost", 12913))
|
||||
s.sendall(b"GET / HTTP/1.1\r\nHost: 127.0.0.1:12913\r\nUser-Agent: curl/7.74.0\r\nAccept: */*\r\n\r\n")
|
||||
data = s.recv(1024)
|
||||
# armeb libc uses statx to query stdout stats, but fails because 'stdout' is not a valid
|
||||
# path on the hosting paltform. it prints out the "Server started" message, but stdout is
|
||||
# not found and the message is kept buffered in.
|
||||
#
|
||||
# later on, picohttpd dups the client socket into stdout fd and uses ordinary printf to
|
||||
# send data out. however, when the "successful" message is sent, it is sent along with
|
||||
# the buffered message, which arrives first and raises a http.client.BadStatusLine exception
|
||||
# as it reads as a malformed http response.
|
||||
#
|
||||
# here we use a raw 'recv' method instead of 'getresponse' to work around that.
|
||||
|
||||
res = data.decode("UTF-8",'replace')
|
||||
self.assertIn("httpd_test_successful", res)
|
||||
conn = http.client.HTTPConnection('localhost', PORT, timeout=10)
|
||||
conn.request('GET', '/')
|
||||
|
||||
feedback = conn.sock.recv(96).decode()
|
||||
self.assertTrue(feedback.endswith('httpd_test_successful'))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+103
-38
@@ -1,97 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import sys, unittest
|
||||
from binascii import unhexlify
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.append("..")
|
||||
from qiling import *
|
||||
from qiling.exception import *
|
||||
from qiling.const import QL_VERBOSE
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_INTERCEPT, QL_VERBOSE
|
||||
|
||||
|
||||
# test = bytes.fromhex('cccc')
|
||||
|
||||
X86_LIN = bytes.fromhex('31c050682f2f7368682f62696e89e3505389e1b00bcd80')
|
||||
X8664_LIN = bytes.fromhex('31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05')
|
||||
|
||||
MIPS32EL_LIN = bytes.fromhex('''
|
||||
ffff0628ffffd004ffff05280110e4270ff08424ab0f02240c0101012f62696e
|
||||
2f7368
|
||||
''')
|
||||
|
||||
X86_WIN = bytes.fromhex('''
|
||||
fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c
|
||||
617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b5920
|
||||
01d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475
|
||||
e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ff
|
||||
e05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd5
|
||||
3c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300
|
||||
''')
|
||||
|
||||
X8664_WIN = bytes.fromhex('''
|
||||
fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52
|
||||
183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1
|
||||
c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0
|
||||
746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d
|
||||
31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b
|
||||
40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e
|
||||
595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7
|
||||
c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd5
|
||||
4831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373
|
||||
616765426f7800
|
||||
''')
|
||||
|
||||
ARM_LIN = bytes.fromhex('''
|
||||
01308fe213ff2fe178460e300190491a921a0827c251033701df2f62696e2f2f
|
||||
7368
|
||||
''')
|
||||
|
||||
ARM_THUMB = bytes.fromhex('401c01464fea011200bf')
|
||||
|
||||
ARM64_LIN = bytes.fromhex('''
|
||||
420002ca210080d2400080d2c81880d2010000d4e60300aa01020010020280d2
|
||||
681980d2010000d4410080d2420002cae00306aa080380d2010000d4210400f1
|
||||
65ffff54e0000010420002ca210001caa81b80d2010000d4020004d27f000001
|
||||
2f62696e2f736800
|
||||
''')
|
||||
|
||||
X8664_FBSD = bytes.fromhex('''
|
||||
6a61586a025f6a015e990f054897baff02aaaa80f2ff524889e699046680c210
|
||||
0f05046a0f05041e4831f6990f0548976a035852488d7424f080c2100f0548b8
|
||||
523243427730637257488d3e48af74084831c048ffc00f055f4889d04889fe48
|
||||
ffceb05a0f0575f799043b48bb2f62696e2f2f73685253545f5257545e0f05
|
||||
''')
|
||||
|
||||
X8664_MACOS = bytes.fromhex('''
|
||||
4831f65648bf2f2f62696e2f7368574889e74831d24831c0b00248c1c828b03b
|
||||
0f05
|
||||
''')
|
||||
|
||||
|
||||
# some shellcodes call execve, which under normal circumstences, does not return.
|
||||
# however, those shellcodes attempt to run a non-exsting '/bin/sh' binary and do
|
||||
# not bother to handle failures gracefully.
|
||||
#
|
||||
# the execution then continues to the next bytes, which are usually the '/bin/sh'
|
||||
# string and not valid code. that causes Qiling to raise an exception, and this is
|
||||
# why we need a way to thwart those execve failures and end the emulation gracefully
|
||||
def graceful_execve(ql: Qiling, pathname: int, argv: int, envp: int, retval: int):
|
||||
assert retval != 0, f'execve is not expected to return on success'
|
||||
|
||||
vpath = ql.os.utils.read_cstring(pathname)
|
||||
|
||||
ql.log.debug(f'failed to call execve("{vpath}"), ending emulation gracefully')
|
||||
ql.stop()
|
||||
|
||||
test = unhexlify('cccc')
|
||||
X86_LIN = unhexlify('31c050682f2f7368682f62696e89e3505389e1b00bcd80')
|
||||
X8664_LIN = unhexlify('31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05')
|
||||
MIPS32EL_LIN = unhexlify('ffff0628ffffd004ffff05280110e4270ff08424ab0f02240c0101012f62696e2f7368')
|
||||
X86_WIN = unhexlify('fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300')
|
||||
X8664_WIN = unhexlify('fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd54831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373616765426f7800')
|
||||
ARM_LIN = unhexlify('01308fe213ff2fe178460e300190491a921a0827c251033701df2f62696e2f2f7368')
|
||||
ARM_THUMB = unhexlify('401c01464fea011200bf')
|
||||
ARM64_LIN = unhexlify('420002ca210080d2400080d2c81880d2010000d4e60300aa01020010020280d2681980d2010000d4410080d2420002cae00306aa080380d2010000d4210400f165ffff54e0000010420002ca210001caa81b80d2010000d4020004d27f0000012f62696e2f736800')
|
||||
X8664_FBSD = unhexlify('6a61586a025f6a015e990f054897baff02aaaa80f2ff524889e699046680c2100f05046a0f05041e4831f6990f0548976a035852488d7424f080c2100f0548b8523243427730637257488d3e48af74084831c048ffc00f055f4889d04889fe48ffceb05a0f0575f799043b48bb2f62696e2f2f73685253545f5257545e0f05')
|
||||
X8664_macos = unhexlify('4831f65648bf2f2f62696e2f7368574889e74831d24831c0b00248c1c828b03b0f05')
|
||||
|
||||
class TestShellcode(unittest.TestCase):
|
||||
def test_linux_x86(self):
|
||||
print("Linux X86 32bit Shellcode")
|
||||
ql = Qiling(code = X86_LIN, archtype = "x86", ostype = "linux", verbose=QL_VERBOSE.OFF)
|
||||
ql = Qiling(code=X86_LIN, archtype="x86", ostype="linux", verbose=QL_VERBOSE.OFF)
|
||||
ql.run()
|
||||
|
||||
def test_linux_x64(self):
|
||||
print("Linux X86 64bit Shellcode")
|
||||
ql = Qiling(code = X8664_LIN, archtype = "x8664", ostype = "linux", verbose=QL_VERBOSE.OFF)
|
||||
ql = Qiling(code=X8664_LIN, archtype="x8664", ostype="linux", verbose=QL_VERBOSE.OFF)
|
||||
ql.run()
|
||||
|
||||
def test_linux_mips32(self):
|
||||
print("Linux MIPS 32bit EL Shellcode")
|
||||
ql = Qiling(code = MIPS32EL_LIN, archtype = "mips", ostype = "linux", verbose=QL_VERBOSE.OFF)
|
||||
ql = Qiling(code=MIPS32EL_LIN, archtype="mips", ostype="linux", 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
|
||||
# This shellcode needs to be changed to something non-blocking
|
||||
def test_linux_arm(self):
|
||||
print("Linux ARM 32bit Shellcode")
|
||||
ql = Qiling(code = ARM_LIN, archtype = "arm", ostype = "linux", verbose=QL_VERBOSE.OFF)
|
||||
ql.run()
|
||||
|
||||
print("Linux ARM 32bit Shellcode")
|
||||
ql = Qiling(code=ARM_LIN, archtype="arm", ostype="linux", verbose=QL_VERBOSE.OFF)
|
||||
ql.run()
|
||||
|
||||
def test_linux_arm_thumb(self):
|
||||
print("Linux ARM Thumb Shllcode")
|
||||
ql = Qiling(code = ARM_THUMB, archtype = "arm", ostype = "linux", verbose=QL_VERBOSE.OFF, thumb = True)
|
||||
ql = Qiling(code=ARM_THUMB, archtype="arm", ostype="linux", verbose=QL_VERBOSE.OFF, thumb=True)
|
||||
ql.run()
|
||||
|
||||
|
||||
def test_linux_arm64(self):
|
||||
print("Linux ARM 64bit Shellcode")
|
||||
ql = Qiling(code = ARM64_LIN, archtype = "arm64", ostype = "linux", verbose=QL_VERBOSE.OFF)
|
||||
ql = Qiling(code=ARM64_LIN, archtype="arm64", ostype="linux", verbose=QL_VERBOSE.OFF)
|
||||
|
||||
ql.os.set_syscall('execve', graceful_execve, QL_INTERCEPT.EXIT)
|
||||
ql.run()
|
||||
|
||||
# #This shellcode needs to be changed to something simpler not requiring rootfs
|
||||
# def test_windows_x86(self):
|
||||
# print("Windows X86 32bit Shellcode")
|
||||
# ql = Qiling(code = X86_WIN, archtype = "x86", ostype = "windows", rootfs="../examples/rootfs/x86_reactos", verbose=QL_VERBOSE.OFF)
|
||||
# ql = Qiling(code=X86_WIN, archtype="x86", ostype="windows", rootfs="../examples/rootfs/x86_reactos", verbose=QL_VERBOSE.OFF)
|
||||
# ql.run()
|
||||
|
||||
# #This shellcode needs to be changed to something simpler not requiring rootfs
|
||||
# def test_windows_x64(self):
|
||||
# print("\nWindows X8664 64bit Shellcode")
|
||||
# ql = Qiling(code = X8664_WIN, archtype = "x8664", ostype = "windows", rootfs="../examples/rootfs/x86_reactos", verbose=QL_VERBOSE.OFF)
|
||||
# ql = Qiling(code=X8664_WIN, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x86_reactos", verbose=QL_VERBOSE.OFF)
|
||||
# ql.run()
|
||||
|
||||
#This shellcode needs to be changed to something simpler, listen is blocking
|
||||
#def test_freebsd_x64(self):
|
||||
# #This shellcode needs to be changed to something simpler, listen is blocking
|
||||
# def test_freebsd_x64(self):
|
||||
# print("FreeBSD X86 64bit Shellcode")
|
||||
# ql = Qiling(code = X8664_FBSD, archtype = "x8664", ostype = "freebsd", verbose=QL_VERBOSE.OFF)
|
||||
# ql = Qiling(code=X8664_FBSD, archtype="x8664", ostype="freebsd", verbose=QL_VERBOSE.OFF)
|
||||
# ql.run()
|
||||
|
||||
# def test_macos_x64(self):
|
||||
# print("macos X86 64bit Shellcode")
|
||||
# ql = Qiling(code = X8664_macos, archtype = "x8664", ostype = "macos", verbose=QL_VERBOSE.OFF)
|
||||
# ql = Qiling(code=X8664_macos, archtype="x8664", ostype="macos", verbose=QL_VERBOSE.OFF)
|
||||
# ql.run()
|
||||
|
||||
# def test_invalid_os(self):
|
||||
# print("Testing Unknown OS")
|
||||
# self.assertRaises(QlErrorOsType, Qiling, code = test, archtype = "arm64", ostype = "qilingos", verbose=QL_VERBOSE.DEFAULT )
|
||||
# self.assertRaises(QlErrorOsType, Qiling, code=test, archtype="arm64", ostype="qilingos", verbose=QL_VERBOSE.DEFAULT )
|
||||
|
||||
# def test_invalid_arch(self):
|
||||
# print("Testing Unknown Arch")
|
||||
# self.assertRaises(QlErrorArch, Qiling, code = test, archtype = "qilingarch", ostype = "linux", verbose=QL_VERBOSE.DEFAULT )
|
||||
# self.assertRaises(QlErrorArch, Qiling, code=test, archtype="qilingarch", ostype="linux", verbose=QL_VERBOSE.DEFAULT )
|
||||
|
||||
# def test_invalid_output(self):
|
||||
# print("Testing Invalid output")
|
||||
# self.assertRaises(QlErrorOutput, Qiling, code = test, archtype = "arm64", ostype = "linux", verbose=QL_VERBOSE.DEFAULT )
|
||||
|
||||
# self.assertRaises(QlErrorOutput, Qiling, code=test, archtype="arm64", ostype="linux", verbose=QL_VERBOSE.DEFAULT )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user