mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
@@ -115,7 +115,6 @@ if __name__ == "__main__":
|
||||
parser.add_argument("-t", "--trace", help="Enable full trace", action='store_true', default=False)
|
||||
parser.add_argument("-R", "--root", help="rootfs", default=None)
|
||||
parser.add_argument("-d", "--dump", help="Directory to dump memory regions to", default="dump")
|
||||
#parser.add_argument("-a", "--automatize_input", help="Automatize writes on standard input", default=False)
|
||||
parser.add_argument("-p ", "--profile", help="customized profile",
|
||||
default="qiling/profiles/windows.ql")
|
||||
parser.add_argument('input', nargs='*')
|
||||
|
||||
@@ -4,7 +4,7 @@ from abc import abstractmethod
|
||||
from qiling import Qiling
|
||||
from qiling.arch.x86 import QlArchIntel
|
||||
from qiling.arch.x86_const import *
|
||||
from qiling.exception import QlGDTError
|
||||
from qiling.exception import QlGDTError, QlMemoryMappedError
|
||||
from qiling.os.memory import QlMemoryManager
|
||||
|
||||
class GDTArray:
|
||||
@@ -50,8 +50,10 @@ class GDTManager:
|
||||
def __init__(self, ql: Qiling, base = QL_X86_GDT_ADDR, limit = QL_X86_GDT_LIMIT, num_entries = 16):
|
||||
ql.log.debug(f'Mapping GDT at {base:#x} with limit {limit:#x}')
|
||||
|
||||
if not ql.mem.is_mapped(base, limit):
|
||||
ql.mem.map(base, limit, info="[GDT]")
|
||||
if not ql.mem.is_available(base, limit):
|
||||
raise QlMemoryMappedError('cannot map GDT, memory location is taken')
|
||||
|
||||
ql.mem.map(base, limit, info="[GDT]")
|
||||
|
||||
# setup GDT by writing to GDTR
|
||||
ql.arch.regs.write(UC_X86_REG_GDTR, (0, base, limit, 0x0))
|
||||
|
||||
+3
-3
@@ -45,7 +45,7 @@ class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
filter = None,
|
||||
stop: QL_STOP = QL_STOP.NONE,
|
||||
*,
|
||||
endian: QL_ENDIAN = None,
|
||||
endian: Optional[QL_ENDIAN] = None,
|
||||
thumb: bool = False,
|
||||
libcache: bool = False
|
||||
):
|
||||
@@ -414,8 +414,8 @@ class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
return self._debug_stop
|
||||
|
||||
@debug_stop.setter
|
||||
def debug_stop(self, ds):
|
||||
self._debug_stop = ds
|
||||
def debug_stop(self, enabled: bool):
|
||||
self._debug_stop = enabled
|
||||
|
||||
@property
|
||||
def debugger(self) -> bool:
|
||||
|
||||
+345
-31
@@ -8,7 +8,8 @@
|
||||
# handling hooks #
|
||||
##############################################
|
||||
|
||||
from typing import Callable, MutableMapping, MutableSequence
|
||||
from typing import Any, Callable, MutableMapping, MutableSequence, Protocol
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from unicorn import Uc
|
||||
from unicorn.unicorn_const import *
|
||||
@@ -18,6 +19,73 @@ from .utils import catch_KeyboardInterrupt
|
||||
from .const import QL_HOOK_BLOCK
|
||||
from .exception import QlErrorCoreHook
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
class MemHookCallback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', __access: int, __address: int, __size: int, __value: int, *__context: Any) -> Any:
|
||||
"""Memory access hook callback.
|
||||
|
||||
Args:
|
||||
__ql : the associated qiling instance
|
||||
__access : the intercepted memory access type, one of UC_HOOK_MEM_* constants
|
||||
__addr : the target memory location
|
||||
__size : size of intercepted memory access
|
||||
__value : the value to write, for write operations, 0 for others
|
||||
__context : additional context passed on hook creation. if no context was passed, this argument should be omitted
|
||||
|
||||
Returns:
|
||||
an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks
|
||||
(if any) or `None`
|
||||
"""
|
||||
pass
|
||||
|
||||
class TraceHookCalback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', __address: int, __size: int, *__context: Any) -> Any:
|
||||
"""Execution hook callback.
|
||||
|
||||
Args:
|
||||
__ql : the associated qiling instance
|
||||
__address : address of the instruction to be executed
|
||||
__size : instruction size
|
||||
__context : additional context passed on hook creation. if no context was passed, this argument should be omitted
|
||||
|
||||
Returns:
|
||||
an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks
|
||||
(if any) or `None`
|
||||
"""
|
||||
pass
|
||||
|
||||
class AddressHookCallback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', *__context: Any) -> Any:
|
||||
"""Address hook callback.
|
||||
|
||||
Args:
|
||||
__ql : the associated qiling instance
|
||||
__context : additional context passed on hook creation. if no context was passed, this argument should be omitted
|
||||
|
||||
Returns:
|
||||
an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks
|
||||
(if any) or `None`
|
||||
"""
|
||||
pass
|
||||
|
||||
class InterruptHookCallback(Protocol):
|
||||
def __call__(self, __ql: 'Qiling', intno: int, *__context: Any) -> Any:
|
||||
"""Interrupt hook callback.
|
||||
|
||||
Args:
|
||||
__ql : the associated qiling instance
|
||||
__intno : the intercepted interrupt number
|
||||
__context : additional context passed on hook creation. if no context was passed, this argument should be omitted
|
||||
|
||||
Returns:
|
||||
an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks
|
||||
(if any) or `None`
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Don't assume self is Qiling.
|
||||
class QlCoreHooks:
|
||||
def __init__(self, uc: Uc):
|
||||
@@ -37,6 +105,9 @@ class QlCoreHooks:
|
||||
# Callback definitions #
|
||||
########################
|
||||
def _hook_intr_cb(self, uc: Uc, intno: int, pack_data) -> None:
|
||||
"""Interrupt hooks dispatcher.
|
||||
"""
|
||||
|
||||
ql, hook_type = pack_data
|
||||
handled = False
|
||||
|
||||
@@ -60,15 +131,18 @@ class QlCoreHooks:
|
||||
|
||||
|
||||
def _hook_insn_cb(self, uc: Uc, *args):
|
||||
ql, hook_type = args[-1]
|
||||
"""Instruction hooks dispatcher.
|
||||
"""
|
||||
|
||||
*hook_args, (ql, insn_type) = args
|
||||
retval = None
|
||||
|
||||
if hook_type in self._insn_hook:
|
||||
hooks_list = self._insn_hook[hook_type]
|
||||
if insn_type in self._insn_hook:
|
||||
hooks_list = self._insn_hook[insn_type]
|
||||
|
||||
for hook in hooks_list:
|
||||
if hook.bound_check(ql.arch.regs.arch_pc):
|
||||
ret = hook.call(ql, *args[:-1])
|
||||
ret = hook.call(ql, *hook_args)
|
||||
|
||||
if type(ret) is tuple:
|
||||
ret, retval = ret
|
||||
@@ -81,13 +155,16 @@ class QlCoreHooks:
|
||||
|
||||
|
||||
def _hook_trace_cb(self, uc: Uc, addr: int, size: int, pack_data) -> None:
|
||||
"""Code and block hooks dispatcher.
|
||||
"""
|
||||
|
||||
ql, hook_type = pack_data
|
||||
|
||||
if hook_type in self._hook:
|
||||
hooks_list = self._hook[hook_type]
|
||||
|
||||
for hook in hooks_list:
|
||||
if hook.bound_check(ql.arch.regs.arch_pc):
|
||||
if hook.bound_check(addr, size):
|
||||
ret = hook.call(ql, addr, size)
|
||||
|
||||
if type(ret) is int and ret & QL_HOOK_BLOCK:
|
||||
@@ -95,6 +172,9 @@ class QlCoreHooks:
|
||||
|
||||
|
||||
def _hook_mem_cb(self, uc: Uc, access: int, addr: int, size: int, value: int, pack_data):
|
||||
"""Memory access hooks dispatcher.
|
||||
"""
|
||||
|
||||
ql, hook_type = pack_data
|
||||
handled = False
|
||||
|
||||
@@ -116,6 +196,9 @@ class QlCoreHooks:
|
||||
|
||||
|
||||
def _hook_insn_invalid_cb(self, uc: Uc, pack_data) -> None:
|
||||
"""Invalid instruction hooks dispatcher.
|
||||
"""
|
||||
|
||||
ql, hook_type = pack_data
|
||||
handled = False
|
||||
|
||||
@@ -134,6 +217,9 @@ class QlCoreHooks:
|
||||
|
||||
|
||||
def _hook_addr_cb(self, uc: Uc, addr: int, size: int, pack_data):
|
||||
"""Address hooks dispatcher.
|
||||
"""
|
||||
|
||||
ql = pack_data
|
||||
|
||||
if addr in self._addr_hook:
|
||||
@@ -148,10 +234,10 @@ class QlCoreHooks:
|
||||
###############
|
||||
# Class Hooks #
|
||||
###############
|
||||
def _ql_hook_internal(self, hook_type, callback, user_data=None, *args) -> int:
|
||||
def _ql_hook_internal(self, hook_type: int, callback: Callable, context: Any, *args) -> int:
|
||||
_callback = catch_KeyboardInterrupt(self, callback)
|
||||
# pack user_data & callback for wrapper _callback
|
||||
return self._h_uc.hook_add(hook_type, _callback, (self, user_data), 1, 0, *args)
|
||||
|
||||
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:
|
||||
@@ -175,7 +261,7 @@ class QlCoreHooks:
|
||||
ins_t = args[0]
|
||||
|
||||
if ins_t not in self._insn_hook_fuc:
|
||||
self._insn_hook_fuc[ins_t] = self._ql_hook_internal(t, self._hook_insn_cb, ins_t, *args)
|
||||
self._insn_hook_fuc[ins_t] = self._ql_hook_internal(t, self._hook_insn_cb, ins_t, ins_t)
|
||||
|
||||
if ins_t not in self._insn_hook:
|
||||
self._insn_hook[ins_t] = []
|
||||
@@ -232,51 +318,202 @@ class QlCoreHooks:
|
||||
handler(t)
|
||||
|
||||
|
||||
def ql_hook(self, hook_type: int, callback: Callable, user_data=None, begin=1, end=0, *args) -> HookRet:
|
||||
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.
|
||||
|
||||
Args:
|
||||
hook_type : event type to intercept; this argument is used as a bitmap and may encode multiple
|
||||
events to hook with the same calback. see UC_HOOK_* constants for available events
|
||||
callback : a method to call upon interception; callback signature may vary
|
||||
depending on the hooked event type
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
hook = Hook(callback, user_data, begin, end)
|
||||
self._ql_hook(hook_type, hook, *args)
|
||||
|
||||
return HookRet(self, hook_type, hook)
|
||||
|
||||
|
||||
def hook_code(self, callback, user_data=None, begin=1, end=0):
|
||||
def hook_code(self, callback: TraceHookCalback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet:
|
||||
"""Intercept assembly instructions before they get executed.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
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, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_BLOCK, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_unmapped(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_UNMAPPED, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_read_invalid(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_READ_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_write_invalid(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_WRITE_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_fetch_invalid(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_FETCH_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_valid(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_VALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_invalid(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_INVALID, callback, user_data, begin, end)
|
||||
|
||||
|
||||
# a convenient API to set callback for a single address
|
||||
def hook_address(self, callback, address, user_data=None):
|
||||
def hook_address(self, callback: AddressHookCallback, address: int, user_data: Any = None) -> HookRet:
|
||||
"""Intercept execution from a certain memory address.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
address : memory location to watch
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
hook = HookAddr(callback, address, user_data)
|
||||
|
||||
if address not in self._addr_hook_fuc:
|
||||
@@ -287,37 +524,114 @@ class QlCoreHooks:
|
||||
|
||||
self._addr_hook[address].append(hook)
|
||||
|
||||
return HookRet(self, None, hook)
|
||||
# note: assuming 0 is not a valid hook type
|
||||
return HookRet(self, 0, hook)
|
||||
|
||||
|
||||
def get_hook_address(self, address):
|
||||
return self._addr_hook.get(address, [])
|
||||
def hook_intno(self, callback: InterruptHookCallback, intno: int, user_data: Any = None) -> HookRet:
|
||||
"""Intercept interrupts.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
intono : interrupt vector number to intercept, or -1 for any
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
def hook_intno(self, callback, intno, user_data=None):
|
||||
hook = HookIntr(callback, intno, user_data)
|
||||
self._ql_hook(UC_HOOK_INTR, hook)
|
||||
|
||||
return HookRet(self, UC_HOOK_INTR, hook)
|
||||
|
||||
|
||||
def hook_mem_read(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_READ, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_write(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_WRITE, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_mem_fetch(self, callback, user_data=None, begin=1, end=0):
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_MEM_FETCH, callback, user_data, begin, end)
|
||||
|
||||
|
||||
def hook_insn(self, callback, arg1, user_data=None, begin=1, end=0):
|
||||
return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, arg1)
|
||||
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.
|
||||
|
||||
Args:
|
||||
callback : a method to call upon interception; the callback arguments list differs
|
||||
based on the instruction type
|
||||
insn_type : instruction type to intercept
|
||||
user_data : an additional context to pass to callback (default: `None`)
|
||||
begin : start of memory range to watch
|
||||
end : end of memory range to watch
|
||||
|
||||
Notes:
|
||||
- The set of supported instruction types is very limited and defined by unicorn.
|
||||
- If `begin` and `end` are not specified, the entire memory space will be watched.
|
||||
|
||||
Returns:
|
||||
Hook handle
|
||||
"""
|
||||
|
||||
return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, insn_type)
|
||||
|
||||
|
||||
def hook_del(self, hret: HookRet):
|
||||
def hook_del(self, hret: HookRet) -> None:
|
||||
"""Unregister an existing hook and release its resources.
|
||||
|
||||
Args:
|
||||
hret : hook handle
|
||||
"""
|
||||
|
||||
h = hret.obj
|
||||
hook_type = hret.type
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from unicorn.unicorn_const import (
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS
|
||||
from qiling.debugger import QlDebugger
|
||||
from qiling.debugger.gdb import xmlregs
|
||||
from qiling.debugger.gdb.xmlregs import QlGdbFeatures
|
||||
from qiling.debugger.gdb.utils import QlGdbUtils
|
||||
|
||||
# gdb logging prompt
|
||||
@@ -98,7 +98,8 @@ class QlGdb(QlDebugger):
|
||||
|
||||
self.gdb = QlGdbUtils(ql, entry_point, exit_point)
|
||||
|
||||
self.regsmap = xmlregs.load_regsmap(self.ql.arch.type)
|
||||
self.features = QlGdbFeatures(self.ql.arch.type, self.ql.os.type)
|
||||
self.regsmap = self.features.regsmap
|
||||
|
||||
def run(self):
|
||||
server = GdbSerialConn(self.ip, self.port, self.ql.log)
|
||||
@@ -451,22 +452,12 @@ class QlGdb(QlDebugger):
|
||||
offset, length = (int(p, 16) for p in params.split(','))
|
||||
|
||||
if feature == 'features' and op == 'read':
|
||||
xfercmd_abspath = os.path.dirname(os.path.abspath(__file__))
|
||||
xml_folder = self.ql.arch.type.name.lower()
|
||||
xfercmd_file = os.path.join(xfercmd_abspath, 'xml', xml_folder, annex)
|
||||
|
||||
if self.ql.os.type == QL_OS.WINDOWS:
|
||||
self.ql.log.info(f'{PROMPT} Qiling does not support XML for this platform yet')
|
||||
content = ''
|
||||
|
||||
elif not os.path.exists(xfercmd_file):
|
||||
self.ql.log.info(f'{PROMPT} XML file not found: "{xfercmd_file}"')
|
||||
content = ''
|
||||
if annex == r'target.xml':
|
||||
content = self.features.tostring()[offset:offset + length]
|
||||
|
||||
else:
|
||||
with open(xfercmd_file, 'r') as f:
|
||||
f.seek(offset, os.SEEK_SET)
|
||||
content = f.read(length)
|
||||
self.ql.log.info(f'{PROMPT} did not expect "{annex}" here')
|
||||
content = ''
|
||||
|
||||
return f'{"l" if len(content) < length else "m"}{content}'
|
||||
|
||||
@@ -604,7 +595,7 @@ class QlGdb(QlDebugger):
|
||||
if os.path.exists(host_path) and not path.startswith(r'/proc'):
|
||||
fd = os.open(host_path, flags, mode)
|
||||
|
||||
return f'F{fd}'
|
||||
return f'F{fd:x}'
|
||||
|
||||
elif op == 'pread':
|
||||
fd, count, offset = (int(p, 16) for p in params)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from typing import Iterator, Mapping, Optional, Sequence, Tuple
|
||||
from pathlib import PurePath
|
||||
from xml.etree import ElementTree, ElementInclude
|
||||
|
||||
from qiling.arch.arm_const import reg_map as arm_regs
|
||||
from qiling.arch.arm_const import reg_vfp as arm_regs_vfp
|
||||
@@ -12,7 +13,6 @@ from qiling.arch.arm64_const import reg_map as arm64_regs
|
||||
from qiling.arch.arm64_const import reg_map_v as arm64_regs_v
|
||||
from qiling.arch.mips_const import reg_map as mips_regs_gpr
|
||||
from qiling.arch.mips_const import reg_map_fpu as mips_regs_fpu
|
||||
from qiling.arch.x86_const import reg_map_16 as x86_regs_16
|
||||
from qiling.arch.x86_const import reg_map_32 as x86_regs_32
|
||||
from qiling.arch.x86_const import reg_map_64 as x86_regs_64
|
||||
from qiling.arch.x86_const import reg_map_misc as x86_regs_misc
|
||||
@@ -21,92 +21,130 @@ from qiling.arch.x86_const import reg_map_st as x86_regs_st
|
||||
from qiling.arch.x86_const import reg_map_xmm as x86_regs_xmm
|
||||
from qiling.arch.x86_const import reg_map_ymm as x86_regs_ymm
|
||||
|
||||
from qiling.const import QL_ARCH
|
||||
from qiling.const import QL_ARCH, QL_OS
|
||||
|
||||
RegEntry = Tuple[Optional[int], int, int]
|
||||
|
||||
# define a local dummy function to let us reference this module
|
||||
__anchor__ = lambda x: x
|
||||
class QlGdbFeatures:
|
||||
def __init__(self, archtype: QL_ARCH, ostype: QL_OS):
|
||||
xmltree = QlGdbFeatures.__load_target_xml(archtype, ostype)
|
||||
regsmap = QlGdbFeatures.__load_regsmap(archtype, xmltree)
|
||||
|
||||
def __get_xml_path(archtype: QL_ARCH) -> Tuple[str, PurePath]:
|
||||
import inspect
|
||||
self.xmltree = xmltree
|
||||
self.regsmap = regsmap
|
||||
|
||||
p = PurePath(inspect.getfile(__anchor__))
|
||||
basedir = p.parent / 'xml' / archtype.name.lower()
|
||||
filename = basedir / 'target.xml'
|
||||
def tostring(self) -> str:
|
||||
root = self.xmltree.getroot()
|
||||
|
||||
return str(filename), basedir
|
||||
return ElementTree.tostring(root, encoding='unicode', xml_declaration=True)
|
||||
|
||||
def __walk_xml_regs(filename: str, base_url: PurePath) -> Iterator[Tuple[int, str, int]]:
|
||||
from xml.etree import ElementTree, ElementInclude
|
||||
@staticmethod
|
||||
def __get_xml_path(archtype: QL_ARCH) -> Tuple[str, PurePath]:
|
||||
import inspect
|
||||
|
||||
tree = ElementTree.parse(filename)
|
||||
root = tree.getroot()
|
||||
p = PurePath(inspect.getfile(QlGdbFeatures))
|
||||
basedir = p.parent / 'xml' / archtype.name.lower()
|
||||
filename = basedir / 'target.xml'
|
||||
|
||||
# NOTE: this is needed to load xinclude hrefs relative to the main xml file. starting
|
||||
# from python 3.9 ElementInclude.include has an argument for that called 'base_url'.
|
||||
# this is a workaround for earlier python versions such as 3.8
|
||||
return str(filename), basedir
|
||||
|
||||
def my_loader(base: PurePath):
|
||||
def __wrapped(href: str, parse, encoding=None):
|
||||
abshref = base / href
|
||||
@staticmethod
|
||||
def __load_target_xml(archtype: QL_ARCH, ostype: QL_OS) -> ElementTree.ElementTree:
|
||||
filename, base_url = QlGdbFeatures.__get_xml_path(archtype)
|
||||
|
||||
return ElementInclude.default_loader(str(abshref), parse, encoding)
|
||||
tree = ElementTree.parse(filename)
|
||||
|
||||
return __wrapped
|
||||
# NOTE: this is needed to load xinclude hrefs relative to the main xml file. starting
|
||||
# from python 3.9 ElementInclude.include has an argument for that called 'base_url'.
|
||||
# this is a workaround for earlier python versions such as 3.8
|
||||
|
||||
ElementInclude.include(root, loader=my_loader(base_url))
|
||||
# <WORKAROUND>
|
||||
def my_loader(base: PurePath):
|
||||
def __wrapped(href: str, parse, encoding=None):
|
||||
abshref = base / href
|
||||
|
||||
regnum = -1
|
||||
return ElementInclude.default_loader(str(abshref), parse, encoding)
|
||||
|
||||
for reg in root.iter('reg'):
|
||||
# if regnum is not specified, assume it follows the previous one
|
||||
regnum = int(reg.get('regnum', regnum + 1))
|
||||
return __wrapped
|
||||
# </WORKAROUND>
|
||||
|
||||
name = reg.attrib['name']
|
||||
bitsize = reg.attrib['bitsize']
|
||||
# inline all xi:include elements
|
||||
ElementInclude.include(tree.getroot(), loader=my_loader(base_url))
|
||||
|
||||
yield regnum, name, int(bitsize)
|
||||
# patch xml osabi element with the appropriate abi tag
|
||||
osabi = tree.find('osabi')
|
||||
|
||||
def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]:
|
||||
"""Initialize registers map using available target XML files.
|
||||
if osabi is not None:
|
||||
# NOTE: the 'Windows' abi tag is supported starting from gdb 10.
|
||||
# earlier gdb versions use 'Cygwin' instead
|
||||
|
||||
Args:
|
||||
archtype: target architecture type
|
||||
abitag = {
|
||||
QL_OS.LINUX : 'GNU/Linux',
|
||||
QL_OS.FREEBSD : 'FreeBSD',
|
||||
QL_OS.MACOS : 'Darwin',
|
||||
QL_OS.WINDOWS : 'Windows',
|
||||
QL_OS.UEFI : 'Windows',
|
||||
QL_OS.DOS : 'Windows',
|
||||
QL_OS.QNX : 'QNX-Neutrino'
|
||||
}.get(ostype, 'unknown')
|
||||
|
||||
Returns: a list representing registers data
|
||||
"""
|
||||
osabi.text = abitag
|
||||
|
||||
# retreive the relevant set of registers; their order of appearance is not
|
||||
# important as it is determined by the info read from the xml files
|
||||
ucregs: Mapping[str, int] = {
|
||||
QL_ARCH.A8086 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st),
|
||||
QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm),
|
||||
QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm),
|
||||
QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp),
|
||||
QL_ARCH.CORTEX_M : arm_regs,
|
||||
QL_ARCH.ARM64 : dict(**arm64_regs, **arm64_regs_v),
|
||||
QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu)
|
||||
}[archtype]
|
||||
return tree
|
||||
|
||||
xmlpath = __get_xml_path(archtype)
|
||||
regsinfo = sorted(__walk_xml_regs(*xmlpath))
|
||||
@staticmethod
|
||||
def __walk_xml_regs(xmltree: ElementTree.ElementTree) -> Iterator[Tuple[int, str, int]]:
|
||||
regnum = -1
|
||||
|
||||
# pre-allocate regmap and occupy it with null entries
|
||||
last_regnum = regsinfo[-1][0]
|
||||
regmap: Sequence[RegEntry] = [(None, 0, 0)] * (last_regnum + 1)
|
||||
for reg in xmltree.iter('reg'):
|
||||
# if regnum is not specified, assume it follows the previous one
|
||||
regnum = int(reg.get('regnum', regnum + 1))
|
||||
|
||||
pos = 0
|
||||
name = reg.attrib['name']
|
||||
bitsize = reg.attrib['bitsize']
|
||||
|
||||
for regnum, name, bitsize in sorted(regsinfo):
|
||||
# reg value size in nibbles
|
||||
nibbles = bitsize // 4
|
||||
yield regnum, name, int(bitsize)
|
||||
|
||||
regmap[regnum] = (ucregs.get(name), pos, nibbles)
|
||||
@staticmethod
|
||||
def __load_regsmap(archtype: QL_ARCH, xmltree: ElementTree.ElementTree) -> Sequence[RegEntry]:
|
||||
"""Initialize registers map using available target XML files.
|
||||
|
||||
# value position of next reg
|
||||
pos += nibbles
|
||||
Args:
|
||||
archtype: target architecture type
|
||||
|
||||
return regmap
|
||||
Returns: a list representing registers data
|
||||
"""
|
||||
|
||||
__all__ = ['RegEntry', 'load_regsmap']
|
||||
# retreive the relevant set of registers; their order of appearance is not
|
||||
# important as it is determined by the info read from the xml files
|
||||
ucregs: Mapping[str, int] = {
|
||||
QL_ARCH.A8086 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st),
|
||||
QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm),
|
||||
QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm),
|
||||
QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp),
|
||||
QL_ARCH.CORTEX_M : arm_regs,
|
||||
QL_ARCH.ARM64 : dict(**arm64_regs, **arm64_regs_v),
|
||||
QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu)
|
||||
}[archtype]
|
||||
|
||||
regsinfo = sorted(QlGdbFeatures.__walk_xml_regs(xmltree))
|
||||
|
||||
# pre-allocate regmap and occupy it with null entries
|
||||
last_regnum = regsinfo[-1][0]
|
||||
regmap: Sequence[RegEntry] = [(None, 0, 0)] * (last_regnum + 1)
|
||||
|
||||
pos = 0
|
||||
|
||||
for regnum, name, bitsize in sorted(regsinfo):
|
||||
# reg value size in nibbles
|
||||
nibbles = bitsize // 4
|
||||
|
||||
regmap[regnum] = (ucregs.get(name), pos, nibbles)
|
||||
|
||||
# value position of next reg
|
||||
pos += nibbles
|
||||
|
||||
return regmap
|
||||
|
||||
|
||||
__all__ = ['RegEntry', 'QlGdbFeatures']
|
||||
|
||||
@@ -353,7 +353,9 @@ class QlQdb(cmd.Cmd, QlDebugger):
|
||||
show some runtime information
|
||||
"""
|
||||
|
||||
self.ql.mem.show_mapinfo()
|
||||
for info_line in self.ql.mem.get_formatted_mapinfo():
|
||||
self.ql.log.info(info_line)
|
||||
|
||||
qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}")
|
||||
if self.rr:
|
||||
qdb_print(QDB_MSG.INFO, f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}")
|
||||
|
||||
@@ -368,7 +368,7 @@ class QlLoaderELF(QlLoader):
|
||||
_vsyscall_addr = int(self.profile.get('vsyscall_address'), 0)
|
||||
_vsyscall_size = int(self.profile.get('vsyscall_size'), 0)
|
||||
|
||||
if not self.ql.mem.is_mapped(_vsyscall_addr, _vsyscall_size):
|
||||
if self.ql.mem.is_available(_vsyscall_addr, _vsyscall_size):
|
||||
# initialize with int3 instructions then insert syscall entry
|
||||
# each syscall should be 1KiB away
|
||||
self.ql.mem.map(_vsyscall_addr, _vsyscall_size, info="[vsyscall]")
|
||||
|
||||
+2
-2
@@ -31,10 +31,10 @@ class QlPeCacheEntry(NamedTuple):
|
||||
class QlPeCache:
|
||||
@staticmethod
|
||||
def cache_filename(path: str) -> str:
|
||||
dirname, basename = ntpath.split(path)
|
||||
dirname, basename = os.path.split(path)
|
||||
|
||||
# canonicalize basename while preserving the path
|
||||
path = ntpath.join(dirname, basename.casefold())
|
||||
path = os.path.join(dirname, basename.casefold())
|
||||
|
||||
return f'{path}.cache2'
|
||||
|
||||
|
||||
+72
-52
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
import os, re
|
||||
from typing import Any, Callable, List, Mapping, MutableSequence, Optional, Sequence, Tuple
|
||||
from typing import Any, Callable, Iterator, List, Mapping, MutableSequence, Optional, Pattern, Sequence, Tuple, Union
|
||||
|
||||
from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL
|
||||
|
||||
@@ -127,8 +127,8 @@ class QlMemoryManager:
|
||||
|
||||
self.map_info = tmp_map_info
|
||||
|
||||
def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: int = None, mem_info: str = None):
|
||||
tmp_map_info: MapInfoEntry = None
|
||||
def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, mem_info: Optional[str] = None):
|
||||
tmp_map_info: Optional[MapInfoEntry] = None
|
||||
info_idx: int = None
|
||||
|
||||
for idx, map_info in enumerate(self.map_info):
|
||||
@@ -149,12 +149,12 @@ class QlMemoryManager:
|
||||
if mem_info is not None:
|
||||
self.map_info[info_idx] = (tmp_map_info[0], tmp_map_info[1], tmp_map_info[2], mem_info, tmp_map_info[4])
|
||||
|
||||
def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, Optional[str]]]:
|
||||
def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, str]]:
|
||||
"""Get memory map info.
|
||||
|
||||
Returns: A sequence of 5-tuples representing the memory map entries. Each
|
||||
tuple contains range start, range end, permissions, range label and path of
|
||||
containing image (or None if not contained by any image)
|
||||
containing image (or an empty string if not contained by any image)
|
||||
"""
|
||||
|
||||
def __perms_mapping(ps: int) -> str:
|
||||
@@ -166,21 +166,21 @@ class QlMemoryManager:
|
||||
|
||||
return ''.join(val if idx & ps else '-' for idx, val in perms_d.items())
|
||||
|
||||
def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, Optional[str]]:
|
||||
def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, str]:
|
||||
perms_str = __perms_mapping(perms)
|
||||
|
||||
if hasattr(self.ql, 'loader'):
|
||||
image = self.ql.loader.find_containing_image(lbound)
|
||||
container = image.path if image and not is_mmio else None
|
||||
container = image.path if image and not is_mmio else ''
|
||||
else:
|
||||
container = None
|
||||
container = ''
|
||||
|
||||
return (lbound, ubound, perms_str, label, container)
|
||||
|
||||
return tuple(__process(*entry) for entry in self.map_info)
|
||||
|
||||
def show_mapinfo(self):
|
||||
"""Emit memory map info in a nicely formatted table.
|
||||
def get_formatted_mapinfo(self) -> Sequence[str]:
|
||||
"""Get memory map info in a nicely formatted table.
|
||||
"""
|
||||
|
||||
mapinfo = self.get_mapinfo()
|
||||
@@ -192,12 +192,17 @@ class QlMemoryManager:
|
||||
len_addr = max(grouped[0])
|
||||
len_label = max(grouped[1])
|
||||
|
||||
# emit title row
|
||||
self.ql.log.info(f'{"Start":{len_addr}s} {"End":{len_addr}s} {"Perm":5s} {"Label":{len_label}s} {"Image"}')
|
||||
# pre-allocate table
|
||||
table = [''] * (len(mapinfo) + 1)
|
||||
|
||||
# emit table rows
|
||||
for lbound, ubound, perms, label, container in mapinfo:
|
||||
self.ql.log.info(f'{lbound:0{len_addr}x} - {ubound:0{len_addr}x} {perms:5s} {label:{len_label}s} {container or ""}')
|
||||
# add title row
|
||||
table[0] = f'{"Start":{len_addr}s} {"End":{len_addr}s} {"Perm":5s} {"Label":{len_label}s} {"Image"}'
|
||||
|
||||
# add table rows
|
||||
for i, (lbound, ubound, perms, label, container) in enumerate(mapinfo, 1):
|
||||
table[i] = f'{lbound:0{len_addr}x} - {ubound:0{len_addr}x} {perms:5s} {label:{len_label}s} {container}'
|
||||
|
||||
return table
|
||||
|
||||
# TODO: relying on the label string is risky; find a more reliable method
|
||||
def get_lib_base(self, filename: str) -> Optional[int]:
|
||||
@@ -210,7 +215,7 @@ class QlMemoryManager:
|
||||
|
||||
return next((lbound for lbound, info in stripped if os.path.basename(info) == filename), None)
|
||||
|
||||
def align(self, value: int, alignment: int = None) -> int:
|
||||
def align(self, value: int, alignment: Optional[int] = None) -> int:
|
||||
"""Align a value down to the specified alignment boundary. If `value` is already
|
||||
aligned, the same value is returned. Commonly used to determine the base address
|
||||
of the enclosing page.
|
||||
@@ -232,7 +237,7 @@ class QlMemoryManager:
|
||||
# round down to nearest alignment
|
||||
return value & ~(alignment - 1)
|
||||
|
||||
def align_up(self, value: int, alignment: int = None) -> int:
|
||||
def align_up(self, value: int, alignment: Optional[int] = None) -> int:
|
||||
"""Align a value up to the specified alignment boundary. If `value` is already
|
||||
aligned, the same value is returned. Commonly used to determine the end address
|
||||
of the enlosing page.
|
||||
@@ -280,7 +285,7 @@ class QlMemoryManager:
|
||||
self.ql.log.debug(f'restoring memory range: {lbound:#08x} {ubound:#08x} {label}')
|
||||
|
||||
size = ubound - lbound
|
||||
if not self.is_mapped(lbound, size):
|
||||
if self.is_available(lbound, size):
|
||||
self.ql.log.debug(f'mapping {lbound:#08x} {ubound:#08x}, mapsize = {size:#x}')
|
||||
self.map(lbound, size, perms, label)
|
||||
|
||||
@@ -305,13 +310,13 @@ class QlMemoryManager:
|
||||
|
||||
return self.ql.uc.mem_read(addr, size)
|
||||
|
||||
def read_ptr(self, addr: int, size: int=None) -> int:
|
||||
def read_ptr(self, addr: int, size: int = 0) -> int:
|
||||
"""Read an integer value from a memory address.
|
||||
Bytes read will be unpacked using emulated architecture properties.
|
||||
|
||||
Args:
|
||||
addr: memory address to read
|
||||
size: pointer size (in bytes): either 1, 2, 4, 8, or None for arch native size
|
||||
size: pointer size (in bytes): either 1, 2, 4, 8, or 0 for arch native size
|
||||
|
||||
Returns: integer value stored at the specified memory address
|
||||
"""
|
||||
@@ -341,14 +346,14 @@ class QlMemoryManager:
|
||||
|
||||
self.ql.uc.mem_write(addr, data)
|
||||
|
||||
def write_ptr(self, addr: int, value: int, size: int=None) -> None:
|
||||
def write_ptr(self, addr: int, value: int, size: int = 0) -> None:
|
||||
"""Write an integer value to a memory address.
|
||||
Bytes written will be packed using emulated architecture properties.
|
||||
|
||||
Args:
|
||||
addr: target memory address
|
||||
value: integer value to write
|
||||
size: pointer size (in bytes): either 1, 2, 4, 8, or None for arch native size
|
||||
size: pointer size (in bytes): either 1, 2, 4, 8, or 0 for arch native size
|
||||
"""
|
||||
|
||||
if not size:
|
||||
@@ -366,18 +371,18 @@ class QlMemoryManager:
|
||||
|
||||
self.write(addr, __pack(value))
|
||||
|
||||
def search(self, needle: bytes, begin: int = None, end: int = None) -> Sequence[int]:
|
||||
def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = None, end: Optional[int] = None) -> Sequence[int]:
|
||||
"""Search for a sequence of bytes in memory.
|
||||
|
||||
Args:
|
||||
needle: bytes sequence to look for
|
||||
needle: bytes sequence or regex pattern to look for
|
||||
begin: search starting address (or None to start at lowest avaiable address)
|
||||
end: search ending address (or None to end at highest avaiable address)
|
||||
|
||||
Returns: addresses of all matches
|
||||
"""
|
||||
|
||||
# if starting point not set, search from the first mapped region
|
||||
# if starting point not set, search from the first mapped region
|
||||
if begin is None:
|
||||
begin = self.map_info[0][0]
|
||||
|
||||
@@ -391,6 +396,10 @@ class QlMemoryManager:
|
||||
ranges = [(max(begin, lbound), min(ubound, end)) for lbound, ubound, _, _, is_mmio in self.map_info if not (end < lbound or ubound < begin or is_mmio)]
|
||||
results = []
|
||||
|
||||
# if needle is a bytes sequence use it verbatim, not as a pattern
|
||||
if type(needle) is bytes:
|
||||
needle = re.escape(needle)
|
||||
|
||||
for lbound, ubound in ranges:
|
||||
haystack = self.read(lbound, ubound - lbound)
|
||||
local_results = (match.start(0) + lbound for match in re.finditer(needle, haystack))
|
||||
@@ -413,16 +422,41 @@ class QlMemoryManager:
|
||||
if (addr, addr + size) in self.mmio_cbs:
|
||||
del self.mmio_cbs[(addr, addr+size)]
|
||||
|
||||
def unmap_all(self):
|
||||
def unmap_all(self) -> None:
|
||||
"""Reclaim the entire memory space.
|
||||
"""
|
||||
|
||||
for begin, end, _ in self.ql.uc.mem_regions():
|
||||
self.unmap(begin, end - begin + 1)
|
||||
|
||||
def __mapped_regions(self) -> Iterator[Tuple[int, int]]:
|
||||
"""Iterate through all mapped memory regions, consolidating adjacent regions
|
||||
together to a continuous one. Protection bits and labels are ignored.
|
||||
"""
|
||||
|
||||
if not self.map_info:
|
||||
return
|
||||
|
||||
iter_memmap = iter(self.map_info)
|
||||
|
||||
p_lbound, p_ubound, _, _, _ = next(iter_memmap)
|
||||
|
||||
# map_info is assumed to contain non-overlapping regions sorted by lbound
|
||||
for lbound, ubound, _, _, _ in iter_memmap:
|
||||
if lbound == p_ubound:
|
||||
p_ubound = ubound
|
||||
else:
|
||||
yield (p_lbound, p_ubound)
|
||||
|
||||
p_lbound = lbound
|
||||
p_ubound = ubound
|
||||
|
||||
yield (p_lbound, p_ubound)
|
||||
|
||||
|
||||
def is_available(self, addr: int, size: int) -> bool:
|
||||
"""Query whether the memory range starting at `addr` and is of length of `size` bytes
|
||||
can be allocated.
|
||||
is available for allocation.
|
||||
|
||||
Returns: True if it can be allocated, False otherwise
|
||||
"""
|
||||
@@ -433,37 +467,23 @@ class QlMemoryManager:
|
||||
end = addr + size
|
||||
|
||||
# make sure neither begin nor end are enclosed within a mapped range, or entirely enclosing one
|
||||
return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound, _, _, _ in self.map_info)
|
||||
return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound in self.__mapped_regions())
|
||||
|
||||
def is_mapped(self, addr: int, size: int) -> bool:
|
||||
"""Query whether the memory range starting at `addr` and is of length of `size` bytes
|
||||
is mapped, either partially or entirely.
|
||||
is fully mapped.
|
||||
|
||||
Returns: True if any part of the specified memory range is taken, False otherwise
|
||||
Returns: True if the specified memory range is taken fully, False otherwise
|
||||
"""
|
||||
|
||||
return not self.is_available(addr, size)
|
||||
assert size > 0, 'expected a positive size value'
|
||||
|
||||
def is_free(self, address, size):
|
||||
'''
|
||||
The main function of is_free first must fufull is_mapped condition.
|
||||
then, check for is the mapped range empty, either fill with 0xFF or 0x00
|
||||
Returns true if mapped range is empty else return Flase
|
||||
If not not mapped, map it and return true
|
||||
'''
|
||||
if self.is_mapped(address, size) == True:
|
||||
address_end = (address + size)
|
||||
while address < address_end:
|
||||
mem_read = self.ql.mem.read(address, 0x1)
|
||||
if (mem_read[0] != 0x00) and (mem_read[0] != 0xFF):
|
||||
return False
|
||||
address += 1
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
begin = addr
|
||||
end = addr + size
|
||||
|
||||
return any((lbound <= begin < end <= ubound) for lbound, ubound in self.__mapped_regions())
|
||||
|
||||
def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None) -> int:
|
||||
def find_free_space(self, size: int, minaddr: Optional[int] = None, maxaddr: Optional[int] = None, align: Optional[int] = None) -> int:
|
||||
"""Locate an unallocated memory that is large enough to contain a range in size of
|
||||
`size` and based at `minaddr`.
|
||||
|
||||
@@ -508,7 +528,7 @@ class QlMemoryManager:
|
||||
|
||||
raise QlOutOfMemory('Out Of Memory')
|
||||
|
||||
def map_anywhere(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None, perms: int = UC_PROT_ALL, info: str = None) -> int:
|
||||
def map_anywhere(self, size: int, minaddr: Optional[int] = None, maxaddr: Optional[int] = None, align: Optional[int] = None, perms: int = UC_PROT_ALL, info: Optional[str] = None) -> int:
|
||||
"""Map a region anywhere in memory.
|
||||
|
||||
Args:
|
||||
@@ -543,7 +563,7 @@ class QlMemoryManager:
|
||||
self.change_mapinfo(aligned_address, aligned_address + aligned_size, mem_p = perms)
|
||||
|
||||
|
||||
def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: str = None):
|
||||
def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None):
|
||||
"""Map a new memory range.
|
||||
|
||||
Args:
|
||||
@@ -722,7 +742,7 @@ class QlMemoryHeap:
|
||||
self.current_alloc = 0
|
||||
self.current_use = 0
|
||||
|
||||
def _find(self, addr: int, inuse: bool = None) -> Optional[Chunk]:
|
||||
def _find(self, addr: int, inuse: Optional[bool] = None) -> Optional[Chunk]:
|
||||
"""Find a chunk starting at a specified address.
|
||||
|
||||
Args:
|
||||
|
||||
+3
-2
@@ -252,5 +252,6 @@ class QlOs:
|
||||
finally:
|
||||
self.ql.log.error(f'PC = {pc:#0{self.ql.arch.pointersize * 2 + 2}x}{pc_info}\n')
|
||||
|
||||
self.ql.log.info(f'Memory map:')
|
||||
self.ql.mem.show_mapinfo()
|
||||
self.ql.log.error(f'Memory map:')
|
||||
for info_line in self.ql.mem.get_formatted_mapinfo():
|
||||
self.ql.log.error(info_line)
|
||||
|
||||
+1
-7
@@ -45,12 +45,6 @@ class QlOsStats:
|
||||
for key, values in self.strings.items():
|
||||
ret.append(f'{key}: {", ".join(str(word) for word in values)}')
|
||||
|
||||
ret.extend(QlOsStats._banner('registry keys accessed'))
|
||||
|
||||
for key, values in self.syscalls.items():
|
||||
ret.append(f'{key}:')
|
||||
ret.extend(f' {json.dumps(value):s}' for value in values)
|
||||
|
||||
return ret
|
||||
|
||||
def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None:
|
||||
@@ -107,7 +101,7 @@ class QlWinStats(QlOsStats):
|
||||
|
||||
ret.extend(QlOsStats._banner('registry keys accessed'))
|
||||
|
||||
for key, values in self.syscalls.items():
|
||||
for key, values in self.registry.items():
|
||||
ret.append(f'{key}:')
|
||||
ret.extend(f' {json.dumps(value):s}' for value in values)
|
||||
|
||||
|
||||
@@ -197,7 +197,9 @@ class QlOsUefi(QlOs):
|
||||
self.emit_stack()
|
||||
|
||||
self.ql.log.error(f'Memory map:')
|
||||
self.ql.mem.show_mapinfo()
|
||||
for info_line in self.ql.mem.get_formatted_mapinfo():
|
||||
self.ql.log.error(info_line)
|
||||
|
||||
|
||||
def set_api(self, target: str, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL):
|
||||
super().set_api(f'hook_{target}', handler, intercept)
|
||||
|
||||
@@ -26,18 +26,13 @@ from qiling.os.windows.structs import Win32FindData
|
||||
def hook_GetFileType(ql: Qiling, address: int, params):
|
||||
hFile = params["hFile"]
|
||||
|
||||
if hFile in (STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE):
|
||||
ret = FILE_TYPE_CHAR
|
||||
else:
|
||||
obj = ql.os.handle_manager.get(hFile)
|
||||
handle = ql.os.handle_manager.get(hFile)
|
||||
|
||||
if obj is None:
|
||||
raise QlErrorNotImplemented("API not implemented")
|
||||
else:
|
||||
# technically is not always a type_char but.. almost
|
||||
ret = FILE_TYPE_CHAR
|
||||
if handle is None:
|
||||
raise QlErrorNotImplemented("API not implemented")
|
||||
|
||||
return ret
|
||||
# technically is not always a type_char but.. almost
|
||||
return FILE_TYPE_CHAR
|
||||
|
||||
# HANDLE FindFirstFileA(
|
||||
# LPCSTR lpFileName,
|
||||
@@ -156,28 +151,16 @@ def hook_ReadFile(ql: Qiling, address: int, params):
|
||||
nNumberOfBytesToRead = params["nNumberOfBytesToRead"]
|
||||
lpNumberOfBytesRead = params["lpNumberOfBytesRead"]
|
||||
|
||||
if hFile == STD_INPUT_HANDLE:
|
||||
if ql.os.automatize_input:
|
||||
# TODO maybe insert a good random generation input
|
||||
s = (b"A" * (nNumberOfBytesToRead - 1)) + b"\x00"
|
||||
else:
|
||||
ql.log.debug("Insert input")
|
||||
s = ql.os.stdin.read(nNumberOfBytesToRead)
|
||||
handle = ql.os.handle_manager.get(hFile)
|
||||
|
||||
slen = len(s)
|
||||
read_len = slen
|
||||
if handle is None:
|
||||
ql.os.last_error = ERROR_INVALID_HANDLE
|
||||
return 0
|
||||
|
||||
if slen > nNumberOfBytesToRead:
|
||||
s = s[:nNumberOfBytesToRead]
|
||||
read_len = nNumberOfBytesToRead
|
||||
data = handle.obj.read(nNumberOfBytesToRead)
|
||||
|
||||
ql.mem.write(lpBuffer, s)
|
||||
ql.mem.write_ptr(lpNumberOfBytesRead, read_len, 4)
|
||||
else:
|
||||
f = ql.os.handle_manager.get(hFile).obj
|
||||
data = f.read(nNumberOfBytesToRead)
|
||||
ql.mem.write(lpBuffer, data)
|
||||
ql.mem.write_ptr(lpNumberOfBytesRead, len(data), 4)
|
||||
ql.mem.write(lpBuffer, data)
|
||||
ql.mem.write_ptr(lpNumberOfBytesRead, len(data), 4)
|
||||
|
||||
return 1
|
||||
|
||||
@@ -201,24 +184,20 @@ def hook_WriteFile(ql: Qiling, address: int, params):
|
||||
nNumberOfBytesToWrite = params["nNumberOfBytesToWrite"]
|
||||
lpNumberOfBytesWritten = params["lpNumberOfBytesWritten"]
|
||||
|
||||
handle = ql.os.handle_manager.get(hFile)
|
||||
|
||||
if handle is None:
|
||||
ql.os.last_error = ERROR_INVALID_HANDLE
|
||||
return 0
|
||||
|
||||
fobj = handle.obj
|
||||
data = ql.mem.read(lpBuffer, nNumberOfBytesToWrite)
|
||||
|
||||
if hFile == STD_OUTPUT_HANDLE:
|
||||
s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite)
|
||||
ql.os.stdout.write(s)
|
||||
ql.os.stats.log_string(s.decode())
|
||||
ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4)
|
||||
else:
|
||||
f = ql.os.handle_manager.get(hFile)
|
||||
ql.os.stats.log_string(data.decode())
|
||||
|
||||
if f is None:
|
||||
# Invalid handle
|
||||
ql.os.last_error = ERROR_INVALID_HANDLE
|
||||
return 0
|
||||
else:
|
||||
f = f.obj
|
||||
|
||||
buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite)
|
||||
nNumberOfBytesWritten = f.write(bytes(buffer))
|
||||
ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesWritten, 4)
|
||||
written = fobj.write(bytes(data))
|
||||
ql.mem.write_ptr(lpNumberOfBytesWritten, written, 4)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
from typing import Any, MutableMapping, Optional
|
||||
|
||||
from qiling.os.windows.const import STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE
|
||||
|
||||
class Handle:
|
||||
ID = 0xa0000000
|
||||
|
||||
@@ -26,9 +28,9 @@ class Handle:
|
||||
|
||||
class HandleManager:
|
||||
# IO
|
||||
STD_INPUT_HANDLE = Handle(id=0xfffffff6)
|
||||
STD_OUTPUT_HANDLE = Handle(id=0xfffffff5)
|
||||
STD_ERROR_HANDLE = Handle(id=0xfffffff4)
|
||||
STDIN = Handle(id=STD_INPUT_HANDLE)
|
||||
STDOUT = Handle(id=STD_OUTPUT_HANDLE)
|
||||
STDERR = Handle(id=STD_ERROR_HANDLE)
|
||||
|
||||
# Register
|
||||
HKEY_CLASSES_ROOT = Handle(id=0x80000000)
|
||||
@@ -44,9 +46,9 @@ class HandleManager:
|
||||
def __init__(self):
|
||||
self.handles: MutableMapping[int, Handle] = {}
|
||||
|
||||
self.append(HandleManager.STD_INPUT_HANDLE)
|
||||
self.append(HandleManager.STD_OUTPUT_HANDLE)
|
||||
self.append(HandleManager.STD_ERROR_HANDLE)
|
||||
self.append(HandleManager.STDIN)
|
||||
self.append(HandleManager.STDOUT)
|
||||
self.append(HandleManager.STDERR)
|
||||
|
||||
def append(self, handle: Handle) -> None:
|
||||
self.handles[handle.id] = handle
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
import ntpath
|
||||
from typing import Callable
|
||||
from typing import Callable, TextIO
|
||||
|
||||
from unicorn import UcError
|
||||
|
||||
@@ -13,7 +13,7 @@ from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, FS_SEGMENT_A
|
||||
from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64
|
||||
from qiling.cc import intel
|
||||
from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT
|
||||
from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound
|
||||
from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound, QlMemoryMappedError
|
||||
from qiling.os.fcall import QlFunctionCall
|
||||
from qiling.os.memory import QlMemoryHeap
|
||||
from qiling.os.os import QlOs
|
||||
@@ -88,11 +88,43 @@ class QlOsWindows(QlOs):
|
||||
self.argv = self.ql.argv
|
||||
self.env = self.ql.env
|
||||
self.pid = self.profile.getint('KERNEL', 'pid')
|
||||
self.automatize_input = self.profile.getboolean("MISC","automatize_input")
|
||||
|
||||
self.services = {}
|
||||
self.load()
|
||||
|
||||
# only after handle manager has been set up we can assign the standard streams
|
||||
self.stdin = self._stdin
|
||||
self.stdout = self._stdout
|
||||
self.stderr = self._stderr
|
||||
|
||||
|
||||
@QlOs.stdin.setter
|
||||
def stdin(self, stream: TextIO) -> None:
|
||||
self._stdin = stream
|
||||
|
||||
handle = self.handle_manager.get(const.STD_INPUT_HANDLE)
|
||||
assert handle is not None
|
||||
|
||||
handle.obj = stream
|
||||
|
||||
@QlOs.stdout.setter
|
||||
def stdout(self, stream: TextIO) -> None:
|
||||
self._stdout = stream
|
||||
|
||||
handle = self.handle_manager.get(const.STD_OUTPUT_HANDLE)
|
||||
assert handle is not None
|
||||
|
||||
handle.obj = stream
|
||||
|
||||
@QlOs.stderr.setter
|
||||
def stderr(self, stream: TextIO) -> None:
|
||||
self._stderr = stream
|
||||
|
||||
handle = self.handle_manager.get(const.STD_ERROR_HANDLE)
|
||||
assert handle is not None
|
||||
|
||||
handle.obj = stream
|
||||
|
||||
|
||||
def load(self):
|
||||
self.setupGDT()
|
||||
@@ -116,11 +148,15 @@ class QlOsWindows(QlOs):
|
||||
segm.setup_fs(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE)
|
||||
segm.setup_gs(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE)
|
||||
|
||||
if not self.ql.mem.is_mapped(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE):
|
||||
self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]')
|
||||
if not self.ql.mem.is_available(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE):
|
||||
raise QlMemoryMappedError('cannot map FS segment, memory location is taken')
|
||||
|
||||
if not self.ql.mem.is_mapped(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE):
|
||||
self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]')
|
||||
self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]')
|
||||
|
||||
if not self.ql.mem.is_available(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE):
|
||||
raise QlMemoryMappedError('cannot map GS segment, memory location is taken')
|
||||
|
||||
self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]')
|
||||
|
||||
|
||||
def __setup_components(self):
|
||||
|
||||
@@ -40,7 +40,6 @@ split = False
|
||||
# maily for multiple times Ql run with one file
|
||||
# usage: append = test1
|
||||
append =
|
||||
automatize_input = False
|
||||
current_path = C:\
|
||||
|
||||
[SYSTEM]
|
||||
|
||||
+10
-8
@@ -93,6 +93,8 @@ class ELFTest(unittest.TestCase):
|
||||
ql.run(begin=hook_address)
|
||||
del ql
|
||||
|
||||
os.remove(snapshot)
|
||||
|
||||
def test_elf_linux_x86_snapshot_restore_reg(self):
|
||||
self._test_elf_linux_x86_snapshot_restore_common(reg=True, ctx=False)
|
||||
|
||||
@@ -1119,28 +1121,28 @@ class ELFTest(unittest.TestCase):
|
||||
ql.mem.write(0x1FFB, b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b")
|
||||
|
||||
# Needle not in haystack
|
||||
self.assertEqual([], ql.mem.search(re.escape(b"\x3a\x01\x0b\x03\x53\x29\x1b\x1c\x04\x0d\x11")))
|
||||
self.assertEqual([], ql.mem.search(b"\x3a\x01\x0b\x03\x53\x29\x1b\x1c\x04\x0d\x11"))
|
||||
|
||||
# Needle appears several times in haystack
|
||||
self.assertEqual([0x1000 + 24, 0x2000 + 38, 0x3000 + 24], ql.mem.search(re.escape(b"\x4f\x53\x06\x0d\x1e\x0d\x1a")))
|
||||
self.assertEqual([0x1000 + 24, 0x2000 + 38, 0x3000 + 24], ql.mem.search(b"\x4f\x53\x06\x0d\x1e\x0d\x1a"))
|
||||
|
||||
# Needle inside haystack
|
||||
self.assertEqual([0x1000 + 13], ql.mem.search(re.escape(b"\x0f\x01\x1e\x0d\x53\x11\x07\x1d\x53\x1d\x18"), begin=0x1000 + 10, end=0x1000 + 30))
|
||||
self.assertEqual([0x1000 + 13], ql.mem.search(b"\x0f\x01\x1e\x0d\x53\x11\x07\x1d\x53\x1d\x18", begin=0x1000 + 10, end=0x1000 + 30))
|
||||
|
||||
# Needle before haystack
|
||||
self.assertEqual([], ql.mem.search(re.escape(b"\x04\x0d\x1c\x53\x11\x07\x1d\x53\x0c\x07\x1f\x06"), begin=0x1337))
|
||||
self.assertEqual([], ql.mem.search(b"\x04\x0d\x1c\x53\x11\x07\x1d\x53\x0c\x07\x1f\x06", begin=0x1337))
|
||||
|
||||
# Needle after haystack
|
||||
self.assertEqual([], ql.mem.search(re.escape(b"\x1b\x09\x11\x53\x0f\x07\x07\x0c\x0a\x11\x0d"), end=0x3000 + 13))
|
||||
self.assertEqual([], ql.mem.search(b"\x1b\x09\x11\x53\x0f\x07\x07\x0c\x0a\x11\x0d", end=0x3000 + 13))
|
||||
|
||||
# Needle exactly inside haystack
|
||||
self.assertEqual([0x2000 + 13], ql.mem.search(re.escape(b"\x1a\x1d\x06\x53\x09\x1a\x07\x1d\x06\x0c"), begin=0x2000 + 13, end=0x2000 + 23))
|
||||
self.assertEqual([0x2000 + 13], ql.mem.search(b"\x1a\x1d\x06\x53\x09\x1a\x07\x1d\x06\x0c", begin=0x2000 + 13, end=0x2000 + 23))
|
||||
|
||||
# Needle 'tears' two mapped regions
|
||||
self.assertEqual([], ql.mem.search(re.escape(b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b"), begin=0x1F00, end=0x200F))
|
||||
self.assertEqual([], ql.mem.search(b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b", begin=0x1F00, end=0x200F))
|
||||
|
||||
# Needle is a regex
|
||||
self.assertEqual([0x1000 + 11, 0x2000 + 11, 0x3000 + 43], ql.mem.search(b"\x09\x53(\x0f|\x1a|\x04)[^\x0d]"))
|
||||
self.assertEqual([0x1000 + 11, 0x2000 + 11, 0x3000 + 43], ql.mem.search(re.compile(b"\x09\x53(\x0f|\x1a|\x04)[^\x0d]")))
|
||||
|
||||
del ql
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@ class ELFTest(unittest.TestCase):
|
||||
|
||||
|
||||
def test_elf_linux_cloexec_x8664(self):
|
||||
with open('../examples/rootfs/x8664_linux/testfile', 'wb') as f:
|
||||
f.write(b'0123456789')
|
||||
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_cloexec_test"],
|
||||
"../examples/rootfs/x8664_linux",
|
||||
verbose=QL_VERBOSE.DEBUG,
|
||||
|
||||
@@ -28,11 +28,8 @@ def posix_to_native(rootfs: str, cwd: str, path: str) -> str:
|
||||
|
||||
|
||||
class TestPathUtils(unittest.TestCase):
|
||||
@unittest.skipUnless(is_posix_host, 'POSIX host only')
|
||||
def test_convert_nt_to_posix(self):
|
||||
# test only on a POSIX host
|
||||
if not is_posix_host:
|
||||
self.skipTest('POSIX host only')
|
||||
|
||||
rootfs = PurePosixPath(r'../examples/rootfs/x86_windows')
|
||||
|
||||
expected = str(realpath(rootfs) / 'test')
|
||||
@@ -74,11 +71,8 @@ class TestPathUtils(unittest.TestCase):
|
||||
self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32\\drivers', '..\\..\\test'))
|
||||
self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\xxxx\\..\\test'))
|
||||
|
||||
@unittest.skipUnless(is_nt_host, 'NT host only')
|
||||
def test_convert_posix_to_nt(self):
|
||||
# test only on a Windows host
|
||||
if not is_nt_host:
|
||||
self.skipTest('NT host only')
|
||||
|
||||
rootfs = PureWindowsPath(r'../examples/rootfs/x86_linux')
|
||||
|
||||
expected = str(realpath(rootfs) / 'test')
|
||||
|
||||
Reference in New Issue
Block a user