Merge pull request #1336 from elicn/dev-improv

Periodic maintenance PR
This commit is contained in:
xwings
2023-05-10 21:19:19 +08:00
committed by GitHub
33 changed files with 1897 additions and 1030 deletions
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -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)
+8 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -6
View File
@@ -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:
+15 -7
View File
@@ -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()
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+34 -7
View File
@@ -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
+4 -3
View File
@@ -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)
+57 -8
View File
@@ -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
+2
View File
@@ -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 *
+94 -110
View File
@@ -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
-30
View File
@@ -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
+157
View File
@@ -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'
]
+14 -10
View File
@@ -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}')
+56 -25
View File
@@ -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
+45
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -10
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()