diff --git a/examples/scripts/dllscollector.bat b/examples/scripts/dllscollector.bat
index d45a55fa9..048d5f9c3 100644
--- a/examples/scripts/dllscollector.bat
+++ b/examples/scripts/dllscollector.bat
@@ -57,6 +57,7 @@ CALL :collect_dll32 kdcom.dll
CALL :collect_dll32 kernel32.dll
CALL :collect_dll32 KernelBase.dll
CALL :collect_dll32 mpr.dll
+CALL :collect_dll32 mscoree.dll
CALL :collect_dll32 msvcp_win.dll
CALL :collect_dll32 msvcp60.dll
CALL :collect_dll32 msvcr120_clr0400.dll, msvcr110.dll
@@ -102,6 +103,7 @@ CALL :collect_dll64 advapi32.dll
CALL :collect_dll64 gdi32.dll
CALL :collect_dll64 kernel32.dll
CALL :collect_dll64 KernelBase.dll
+CALL :collect_dll64 mscoree.dll
CALL :collect_dll64 msvcrt.dll
CALL :collect_dll64 ntdll.dll
CALL :collect_dll64 ntoskrnl.exe
diff --git a/qiling/arch/x86_utils.py b/qiling/arch/x86_utils.py
index de6e24f24..f0f58706f 100644
--- a/qiling/arch/x86_utils.py
+++ b/qiling/arch/x86_utils.py
@@ -1,5 +1,6 @@
from abc import abstractmethod
+from typing import Optional
from qiling import Qiling
from qiling.arch.x86 import QlArchIntel
@@ -32,7 +33,7 @@ def __setitem__(self, index: int, data: bytes) -> None:
self.mem.write(self.base + (index * self.entsize), data)
- def get_next_free(self, start: int = None, end: int = None) -> int:
+ def get_next_free(self, start: Optional[int] = None, end: Optional[int] = None) -> int:
# The Linux kernel determines whether the segment is empty by judging whether the content in the current GDT segment is 0.
null_entry = b'\x00' * self.entsize
@@ -112,7 +113,7 @@ def get_entry(self, index: int) -> bytes:
def set_entry(self, index: int, data: bytes) -> None:
self.array[index] = data
- def get_free_idx(self, start: int = None, end: int = None) -> int:
+ def get_free_idx(self, start: Optional[int] = None, end: Optional[int] = None) -> int:
return self.array.get_next_free(start, end)
diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py
index f559a2a83..4e48b6c57 100644
--- a/qiling/debugger/gdb/gdb.py
+++ b/qiling/debugger/gdb/gdb.py
@@ -378,7 +378,7 @@ def handle_q(subcmd: str) -> Reply:
if query == 'Supported':
# list of supported features excluding the multithreading-related ones
- common = (
+ features = [
'BreakpointCommands+',
'ConditionalBreakpoints+',
'ConditionalTracepoints+',
@@ -408,23 +408,21 @@ def handle_q(subcmd: str) -> Reply:
'multiprocess+',
'no-resumed+',
'qXfer:auxv:read+',
- 'qXfer:exec-file:read+',
'qXfer:features:read+',
# 'qXfer:libraries-svr4:read+',
# 'qXfer:osdata:read+',
'qXfer:siginfo:read+',
'qXfer:siginfo:write+',
'qXfer:statictrace:read+',
- 'qXfer:threads:read+',
'qXfer:traceframe-info:read+',
'swbreak+',
'tracenz+',
'vfork-events+'
- )
+ ]
# might or might not need for multi thread
if self.ql.multithread:
- features = (
+ features += [
'PacketSize=47ff',
'FastTracepoints+',
'QThreadEvents+',
@@ -436,16 +434,26 @@ def handle_q(subcmd: str) -> Reply:
'qXfer:btrace-conf:read+',
'qXfer:btrace:read+',
'vContSupported+'
- )
+ ]
else:
- features = (
+ features += [
'PacketSize=3fff',
'qXfer:spu:read+',
'qXfer:spu:write+'
- )
+ ]
+
+ # os dependent features
+ if not self.ql.interpreter:
+ # filesystem dependent features
+ if hasattr(self.ql.os, 'path'):
+ features.append('qXfer:exec-file:read+')
- return ';'.join(common + features)
+ # process dependent features
+ if hasattr(self.ql.os, 'pid'):
+ features.append('qXfer:threads:read+')
+
+ return ';'.join(features)
elif query == 'Xfer':
feature, op, annex, params = data
@@ -462,15 +470,11 @@ def handle_q(subcmd: str) -> Reply:
return f'{"l" if len(content) < length else "m"}{content}'
elif feature == 'threads' and op == 'read':
- if not self.ql.baremetal and hasattr(self.ql.os, 'pid'):
- content = '\r\n'.join((
- '',
- f'',
- ''
- ))
-
- else:
- content = ''
+ content = '\r\n'.join((
+ '',
+ f'',
+ ''
+ ))
return f'l{content}'
@@ -494,7 +498,7 @@ def handle_q(subcmd: str) -> Reply:
return b'l' + auxv_data[:length]
elif feature == 'exec-file' and op == 'read':
- return f'l{os.path.abspath(self.ql.path)}'
+ return f'l{self.ql.os.path.host_to_virtual_path(self.ql.path)}'
elif feature == 'libraries-svr4' and op == 'read':
# TODO: this one requires information of loaded libraries which currently not provided
@@ -584,16 +588,20 @@ def handle_v(subcmd: str) -> Reply:
flags = int(flags, 16)
mode = int(mode, 16)
- # try to guess whether this is an emulated path or real one
- if path.startswith(os.path.abspath(self.ql.rootfs)):
- host_path = path
+ virtpath = self.ql.os.path.virtual_abspath(path)
+
+ if virtpath.startswith(r'/proc'):
+ # TODO: we really need a centralized virtual filesystem to open
+ # both emulated (like procfs) and real files, and manage their
+ # file descriptors seamlessly
+ fd = -1
else:
host_path = self.ql.os.path.virtual_to_host_path(path)
- self.ql.log.debug(f'{PROMPT} target file: {host_path}')
+ self.ql.log.debug(f'{PROMPT} target host path: {host_path}')
- if os.path.exists(host_path) and not path.startswith(r'/proc'):
- fd = os.open(host_path, flags, mode)
+ if os.path.exists(host_path):
+ fd = os.open(host_path, flags, mode)
return f'F{fd:x}'
diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py
index 878c6a4b7..ca99ecdb7 100644
--- a/qiling/extensions/trace.py
+++ b/qiling/extensions/trace.py
@@ -97,7 +97,7 @@ def __parse_op(op: X86Op) -> str:
"""
if op.type == CS_OP_REG:
- return insn.reg_name(op.value.reg)
+ return insn.reg_name(op.value.reg) or '?'
elif op.type == CS_OP_IMM:
imm = op.value.imm
@@ -113,6 +113,7 @@ def __parse_op(op: X86Op) -> str:
disp = mem.disp
ea = base + index * scale + disp
+ seg = f'{insn.reg_name(mem.segment)}:' if mem.segment else ''
# we construct the string representation for each operand; denote memory
# dereferenes with the appropriate 'ptr' prefix. the 'lea' instruction is
@@ -131,7 +132,7 @@ def __parse_op(op: X86Op) -> str:
qualifier = f'{ptr} ptr '
- return f'{qualifier}[{__resolve(ea) or f"{ea:#x}"}]'
+ return f'{qualifier}{seg}[{__resolve(ea) or f"{ea:#x}"}]'
# unexpected op type
raise RuntimeError
diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py
index 1b05e6d13..ea740da1c 100644
--- a/qiling/loader/pe.py
+++ b/qiling/loader/pe.py
@@ -248,7 +248,12 @@ def load_dll(self, name: str, is_driver: bool = False) -> int:
# in case of a dll loaded from a hooked API call, failures would not be
# recoverable and we have to give up its DllMain.
if not self.ql.os.PE_RUN:
+
+ # temporarily set PE_RUN to allow proper fcall unwinding during
+ # execution of DllMain
+ self.ql.os.PE_RUN = True
self.call_dll_entrypoint(dll, dll_base, dll_len, dll_name)
+ self.ql.os.PE_RUN = False
self.ql.log.info(f'Done loading {dll_name}')
@@ -622,7 +627,9 @@ def init_ki_user_shared_data(self):
user_shared_data_obj = KUSER_SHARED_DATA()
user_shared_data_size = ctypes.sizeof(KUSER_SHARED_DATA)
- self.ql.mem.map(addr, self.ql.mem.align_up(user_shared_data_size))
+ # TODO: initialize key fields in this structure
+
+ self.ql.mem.map(addr, self.ql.mem.align_up(user_shared_data_size), info='[kuser shared]')
self.ql.mem.write(addr, bytes(user_shared_data_obj))
def init_security_cookie(self, pe: pefile.PE, image_base: int):
@@ -657,6 +664,7 @@ def run(self):
self.sys_dlls = (
'ntdll.dll',
'kernel32.dll',
+ 'mscoree.dll',
'ucrtbase.dll'
)
@@ -695,7 +703,6 @@ def run(self):
cmdline = ntpath.join(self.ql.os.userprofile, 'Desktop', self.ql.targetname)
cmdargs = ' '.join(f'"{arg}"' if ' ' in arg else arg for arg in self.argv[1:])
- self.filepath = bytes(f'{cmdline}\x00', "utf-8")
self.cmdline = bytes(f'{cmdline} {cmdargs}\x00', "utf-8")
self.load(pe)
@@ -722,14 +729,13 @@ def load(self, pe: Optional[pefile.PE]):
self.ql.log.info(f'Loading {self.path} to {image_base:#x}')
self.ql.log.info(f'PE entry point at {self.entry_point:#x}')
- self.ql.mem.map(image_base, image_size, info=f'[{image_name}]')
+ self.ql.mem.map(image_base, image_size, info=f'{image_name}')
self.images.append(Image(image_base, image_base + pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, os.path.abspath(self.path)))
if self.is_driver:
self.init_driver_object()
self.init_registry_path()
self.init_eprocess()
- self.init_ki_user_shared_data()
# set IRQ Level in CR8 to PASSIVE_LEVEL
self.ql.arch.regs.write(UC_X86_REG_CR8, 0)
@@ -748,6 +754,8 @@ def load(self, pe: Optional[pefile.PE]):
# add image to ldr table
self.add_ldr_data_table_entry(image_name)
+ self.init_ki_user_shared_data()
+
pe.parse_data_directories()
# done manipulating pe file; write its contents into memory
@@ -817,8 +825,6 @@ def load(self, pe: Optional[pefile.PE]):
self.ql.os.fcall.call_native(self.entry_point, args, None)
elif pe is None:
- self.filepath = b""
-
self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode]")
self.init_teb()
diff --git a/qiling/os/fcall.py b/qiling/os/fcall.py
index c9bd95d2c..30fff7594 100644
--- a/qiling/os/fcall.py
+++ b/qiling/os/fcall.py
@@ -2,7 +2,7 @@
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
-from typing import Any, Callable, Iterable, MutableMapping, Optional, Mapping, Tuple, Sequence
+from typing import Any, Callable, Iterable, Iterator, MutableMapping, Optional, Mapping, Tuple, Sequence
from qiling import Qiling
from qiling.cc import QlCC
@@ -50,6 +50,21 @@ def __make_accessor(nbits: int) -> Accessor:
# let the user override default accessors or add custom ones
self.accessors.update(accessors)
+ def readEllipsis(self, ptypes: Sequence[Any]) -> Iterator[int]:
+ """
+ """
+
+ default = self.accessors[PARAM_INTN]
+
+ # count skipped slots
+ si = sum(self.accessors.get(typ, default)[2] for typ in ptypes)
+
+ while True:
+ read, _, nslots = default
+
+ yield read(si)
+ si += nslots
+
def readParams(self, ptypes: Sequence[Any]) -> Sequence[int]:
"""Walk the function parameters list and get their values.
diff --git a/qiling/os/linux/kernel_api/kernel_api.py b/qiling/os/linux/kernel_api/kernel_api.py
index 5fb9d7e12..c16ed3256 100644
--- a/qiling/os/linux/kernel_api/kernel_api.py
+++ b/qiling/os/linux/kernel_api/kernel_api.py
@@ -31,12 +31,10 @@ def hook_printk(ql: Qiling, address: int, params):
return 0
level = int(format[1]) if format[1].isdigit() else 4
- nargs = format.count("%")
- ptypes = (POINTER, ) + (PARAM_INTN, ) * nargs
- args = ql.os.fcall.readParams(ptypes)[1:]
+ args = ql.os.fcall.readEllipsis(params.values())
- count = ql.os.utils.printf(f'{PRINTK_LEVEL[level]} {format[2:]}', args, wstring=False)
- ql.os.utils.update_ellipsis(params, args)
+ count, upd_args = ql.os.utils.printf(f'{PRINTK_LEVEL[level]} {format[2:]}', args, wstring=False)
+ upd_args(params)
return count
diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py
index 1e2c5c1ba..512dee6f4 100644
--- a/qiling/os/linux/thread.py
+++ b/qiling/os/linux/thread.py
@@ -11,6 +11,7 @@
from unicorn.unicorn import UcError
from qiling import Qiling
+from qiling.const import QL_ARCH
from qiling.os.thread import *
from qiling.arch.x86_const import *
from qiling.exception import QlErrorExecutionStop
diff --git a/qiling/os/mapper.py b/qiling/os/mapper.py
index 6b79524fb..c5b2914ae 100644
--- a/qiling/os/mapper.py
+++ b/qiling/os/mapper.py
@@ -101,15 +101,17 @@ 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)
- real_path = self.path.transform_to_real_path(path)
- return ql_file.open(real_path, openflags, openmode)
+ host_path = self.path.virtual_to_host_path(path)
+
+ return ql_file.open(host_path, openflags, openmode)
def open(self, path: str, openmode: str):
if self.has_mapping(path):
return self._open_mapping(path, openmode)
- real_path = self.path.transform_to_real_path(path)
- return open(real_path, openmode)
+ host_path = self.path.virtual_to_host_path(path)
+
+ return open(host_path, openmode)
def _parse_path(self, p: Union[os.PathLike, str]) -> str:
fspath = getattr(p, '__fspath__', None)
diff --git a/qiling/os/path.py b/qiling/os/path.py
index 2cab19913..61c5b8db1 100644
--- a/qiling/os/path.py
+++ b/qiling/os/path.py
@@ -42,9 +42,9 @@ def __init__(self, rootfs: str, cwd: str, emulos: QL_OS) -> None:
self.cwd = cwd
#
+ # temporary aliases for backward compatibility
self.transform_to_relative_path = self.virtual_abspath
self.transform_to_real_path = self.virtual_to_host_path
- self.transform_to_link_path = self.virtual_to_host_path
#
@staticmethod
@@ -258,6 +258,23 @@ def __is_safe_host_path(self, hostpath: Path, strict: bool = False) -> bool:
else:
return True
+ def host_to_virtual_path(self, hostpath: str) -> str:
+ """Convert a host path to its corresponding virtual path relative to rootfs.
+
+ Args:
+ hostpath : host path
+
+ Returns: the corresponding virtual path
+
+ Raises: `ValueError` in case the host path does not map to a rootfs location
+ """
+
+ resolved = Path(hostpath).resolve(strict=False)
+ virtpath = self._cwd_anchor / resolved.relative_to(self._rootfs_path)
+
+ return str(virtpath)
+
+
def virtual_abspath(self, virtpath: str) -> str:
"""Convert a relative virtual path to an absolute virtual path based
on the current working directory.
diff --git a/qiling/os/posix/filestruct.py b/qiling/os/posix/filestruct.py
index daa978ce8..f20fd38fd 100644
--- a/qiling/os/posix/filestruct.py
+++ b/qiling/os/posix/filestruct.py
@@ -3,133 +3,136 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import os
-
-from qiling.exception import *
+from socket import socket, AddressFamily, SocketKind
+from typing import Union
try:
import fcntl
except ImportError:
pass
-import socket
class ql_socket:
- def __init__(self, socket):
+ def __init__(self, socket: socket):
self.__fd = socket.fileno()
self.__socket = socket
def __getstate__(self, *args, **kwargs):
-
_state = self.__dict__.copy()
- _state["_ql_socket__socket"] = {
- "family": self.__dict__["_ql_socket__socket"].family,
- "type": self.__dict__["_ql_socket__socket"].type,
- "proto": self.__dict__["_ql_socket__socket"].proto,
- "laddr": self.__dict__["_ql_socket__socket"].getsockname(),
- }
+ fname = f'_{self.__class__.__name__}__socket'
+ sock = self.__dict__[fname]
+
+ _state[fname] = {
+ "family" : sock.family,
+ "type" : sock.type,
+ "proto" : sock.proto,
+ "laddr" : sock.getsockname(),
+ }
return _state
def __setstate__(self, state):
self.__dict__ = state
-
+
@classmethod
- def open(self, socket_domain, socket_type, socket_protocol, opts=None):
- s = socket.socket(socket_domain, socket_type, socket_protocol)
- if opts:
- s.setsockopt(*opts)
- return self(s)
-
- def read(self, read_len):
- return os.read(self.__fd, read_len)
-
- def write(self, write_buf):
- return os.write(self.__fd, write_buf)
-
- def fileno(self):
+ def open(cls, domain: Union[AddressFamily, int], socktype: Union[SocketKind, int], protocol: int):
+ s = socket(domain, socktype, protocol)
+
+ return cls(s)
+
+ def read(self, length: int) -> bytes:
+ return os.read(self.__fd, length)
+
+ def write(self, data: bytes) -> int:
+ return os.write(self.__fd, data)
+
+ def fileno(self) -> int:
return self.__fd
-
- def close(self):
- return os.close(self.__fd)
-
- def fcntl(self, fcntl_cmd, fcntl_arg):
+
+ def close(self) -> None:
+ os.close(self.__fd)
+
+ def fcntl(self, cmd, arg):
try:
- return fcntl.fcntl(self.__fd, fcntl_cmd, fcntl_arg)
+ return fcntl.fcntl(self.__fd, cmd, arg)
except Exception:
pass
- def ioctl(self, ioctl_cmd, ioctl_arg):
+ def ioctl(self, cmd, arg):
try:
- return fcntl.ioctl(self.__fd, ioctl_cmd, ioctl_arg)
+ return fcntl.ioctl(self.__fd, cmd, arg)
except Exception:
- pass
-
- def dup(self):
+ pass
+
+ def dup(self) -> 'ql_socket':
new_s = self.__socket.dup()
- new_ql_socket = ql_socket(new_s)
- return new_ql_socket
-
- def connect(self, connect_addr):
- return self.__socket.connect(connect_addr)
-
- def shutdown(self, shutdown_how):
- return self.__socket.shutdown(shutdown_how)
-
- def bind(self, bind_addr):
- return self.__socket.bind(bind_addr)
-
- def listen(self, listen_num):
- return self.__socket.listen(listen_num)
+
+ return ql_socket(new_s)
+
+ def connect(self, address) -> None:
+ self.__socket.connect(address)
+
+ def shutdown(self, how: int) -> None:
+ return self.__socket.shutdown(how)
+
+ def bind(self, address) -> None:
+ return self.__socket.bind(address)
+
+ def listen(self, backlog: int) -> None:
+ return self.__socket.listen(backlog)
def getsockname(self):
return self.__socket.getsockname()
-
+
def getpeername(self):
return self.__socket.getpeername()
- def getsockopt(self, level, optname, optlen):
- return self.__socket.getsockopt(level, optname, optlen)
+ def getsockopt(self, level: int, optname: int, buflen: int):
+ return self.__socket.getsockopt(level, optname, buflen)
- def setsockopt(self, level, optname, optval, optlen):
- if optval is not None:
- return self.__socket.setsockopt(level, optname, optval)
+ def setsockopt(self, level: int, optname: int, value: Union[int, bytes, None], optlen: int = 0) -> None:
+ if value is None:
+ self.__socket.setsockopt(level, optname, None, optlen)
else:
- return self.__socket.setsockopt(level, optname, None, optval)
-
+ self.__socket.setsockopt(level, optname, value)
+
def accept(self):
try:
con, addr = self.__socket.accept()
- new_ql_socket = ql_socket(con)
except BlockingIOError:
# For support non-blocking sockets
- return None, None
+ addr = None
+ new_ql_socket = None
+ else:
+ new_ql_socket = ql_socket(con)
+
return new_ql_socket, addr
-
- def recv(self, recv_len, recv_flags):
- return self.__socket.recv(recv_len, recv_flags)
-
- def send(self, send_buf, send_flags):
- return self.__socket.send(send_buf, send_flags)
-
- def recvmsg(self, bufsize, ancbufsize, flags):
+
+ def recv(self, bufsize: int, flags: int) -> bytes:
+ return self.__socket.recv(bufsize, flags)
+
+ def send(self, data, flags: int) -> int:
+ return self.__socket.send(data, flags)
+
+ def recvmsg(self, bufsize: int, ancbufsize: int, flags: int):
return self.__socket.recvmsg(bufsize, ancbufsize, flags)
- def recvfrom(self, recvfrom_len, recvfrom_flags):
- return self.__socket.recvfrom(recvfrom_len, recvfrom_flags)
+ def recvfrom(self, bufsize: int, flags: int):
+ return self.__socket.recvfrom(bufsize, flags)
def sendto(self, sendto_buf, sendto_flags, sendto_addr):
return self.__socket.sendto(sendto_buf, sendto_flags, sendto_addr)
@property
- def family(self):
+ def family(self) -> AddressFamily:
return self.__socket.family
@property
- def socktype(self):
+ def socktype(self) -> SocketKind:
return self.__socket.type
@property
- def socket(self):
+ def socket(self) -> socket:
return self.__socket
# def __getattr__(self,name):
@@ -139,41 +142,40 @@ def socket(self):
# raise AttributeError("A instance has no attribute '%s'" % name)
class ql_pipe:
- def __init__(self, fd):
+ def __init__(self, fd: int):
self.__fd = fd
@classmethod
- def open(self):
+ def open(cls):
r, w = os.pipe()
- return (self(r), self(w))
-
- def read(self, read_len):
- return os.read(self.__fd, read_len)
-
- def write(self, write_buf):
- return os.write(self.__fd, write_buf)
-
- def fileno(self):
+
+ return (cls(r), cls(w))
+
+ def read(self, length: int) -> bytes:
+ return os.read(self.__fd, length)
+
+ def write(self, data: bytes) -> int:
+ return os.write(self.__fd, data)
+
+ def fileno(self) -> int:
return self.__fd
- def close(self):
- return os.close(self.__fd)
-
- def fcntl(self, fcntl_cmd, fcntl_arg):
+ def close(self) -> None:
+ os.close(self.__fd)
+
+ def fcntl(self, cmd, arg):
try:
- return fcntl.fcntl(self.__fd, fcntl_cmd, fcntl_arg)
+ return fcntl.fcntl(self.__fd, cmd, arg)
except Exception:
pass
- def ioctl(self, ioctl_cmd, ioctl_arg):
+ def ioctl(self, cmd, arg):
try:
- return fcntl.ioctl(self.__fd, ioctl_cmd, ioctl_arg)
+ return fcntl.ioctl(self.__fd, cmd, arg)
except Exception:
- pass
-
- def dup(self):
- new_fd = os.dup(self.__fd)
- new_ql_pipe = ql_pipe(new_fd)
- return new_ql_pipe
+ pass
+ def dup(self) -> 'ql_pipe':
+ new_fd = os.dup(self.__fd)
+ return ql_pipe(new_fd)
diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py
index a80c7b998..3eb4deefe 100644
--- a/qiling/os/posix/syscall/socket.py
+++ b/qiling/os/posix/syscall/socket.py
@@ -69,38 +69,44 @@ def ql_bin_to_ip(ip):
def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol):
idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1)
- regreturn = idx
if idx != -1:
+ emu_socket_value = socket_type
+
# ql_socket.open should use host platform based socket_type.
try:
- emu_socket_value = socket_type
emu_socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.os.type)
- socket_type = getattr(socket, emu_socket_type)
- ql.log.debug(f'Convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type {emu_socket_type}:{socket_type}')
+ except KeyError:
+ ql.log.error(f'Cannot convert emu_socket_type {emu_socket_value} to host platform based socket_type')
+ raise
+ try:
+ socket_type = getattr(socket, emu_socket_type)
except AttributeError:
ql.log.error(f'Cannot convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type')
raise
- except Exception:
- ql.log.error(f'Cannot convert emu_socket_type {emu_socket_value} to host platform based socket_type')
- raise
+ ql.log.debug(f'Convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type {emu_socket_type}:{socket_type}')
try:
- if ql.verbose >= QL_VERBOSE.DEBUG: # set REUSEADDR options under debug mode
- ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol, (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1))
- else:
- ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol)
- except OSError as e: # May raise error: Protocol not supported
+ sock = ql_socket.open(socket_domain, socket_type, socket_protocol)
+
+ # set REUSEADDR options under debug mode
+ if ql.verbose >= QL_VERBOSE.DEBUG:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ ql.os.fd[idx] = sock
+
+ # May raise error: Protocol not supported
+ except OSError as e:
ql.log.debug(f'{e}: {socket_domain=}, {socket_type=}, {socket_protocol=}')
- regreturn = -1
+ idx = -1
socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.os.type)
socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.os.type)
- ql.log.debug("socket(%s, %s, %s) = %d" % (socket_domain, socket_type, socket_protocol, regreturn))
+ ql.log.debug("socket(%s, %s, %s) = %d" % (socket_domain, socket_type, socket_protocol, idx))
- return regreturn
+ return idx
def ql_syscall_connect(ql: Qiling, connect_sockfd, connect_addr, connect_addrlen):
@@ -146,29 +152,31 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle
try:
optlen = min(ql.unpack32s(ql.mem.read(optlen_addr, 4)), 1024)
+
if optlen < 0:
return -EINVAL
+ emu_level = level
+
try:
- emu_level = level
emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type)
- level = getattr(socket, emu_level_name)
- ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format(
- emu_level_name, emu_level, emu_level_name, level))
+ except KeyError:
+ ql.log.error(f"Can't convert emu_level {emu_level} to host platform based level")
+ raise
+ try:
+ level = getattr(socket, emu_level_name)
except AttributeError:
- ql.log.error("Can't convert emu_level {}:{} to host platform based emu_level".format(
- emu_level_name, emu_level))
+ ql.log.error(f"Can't convert emu_level {emu_level_name}:{emu_level} to host platform based emu_level")
raise
- except Exception:
- ql.log.error("Can't convert emu_level {} to host platform based level".format(emu_level))
- raise
+ ql.log.debug(f"Convert emu_level {emu_level_name}:{emu_level} to host platform based level {emu_level_name}:{level}")
- try:
- emu_opt = optname
+ emu_opt = optname
+ try:
emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type)
+
# emu_opt_name is based on level
if emu_level_name == "IPPROTO_IP":
emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.os.type)
@@ -180,18 +188,17 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle
if emu_opt_name.endswith("_NEW") or emu_opt_name.endswith("_OLD"):
emu_opt_name = emu_opt_name[:-4]
- optname = getattr(socket, emu_opt_name)
- ql.log.debug("Convert emu_optname {}:{} to host platform based optname {}:{}".format(
- emu_opt_name, emu_opt, emu_opt_name, optname))
+ except KeyError:
+ ql.log.error(f"Can't convert emu_optname {emu_opt} to host platform based optname")
+ raise
+ try:
+ optname = getattr(socket, emu_opt_name)
except AttributeError:
- ql.log.error("Can't convert emu_optname {}:{} to host platform based emu_optname".format(
- emu_opt_name, emu_opt))
+ ql.log.error(f"Can't convert emu_optname {emu_opt_name}:{emu_opt} to host platform based emu_optname")
raise
- except Exception:
- ql.log.error("Can't convert emu_optname {} to host platform based optname".format(emu_opt))
- raise
+ ql.log.debug(f"Convert emu_optname {emu_opt_name}:{emu_opt} to host platform based optname {emu_opt_name}:{optname}")
optval = ql.os.fd[sockfd].getsockopt(level, optname, optlen)
ql.mem.write(optval_addr, optval)
@@ -210,26 +217,27 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle
ql.os.fd[sockfd].setsockopt(level, optname, None, optlen)
else:
try:
+ emu_level = level
+
try:
- emu_level = level
emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type)
- level = getattr(socket, emu_level_name)
- ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format(
- emu_level_name, emu_level, emu_level_name, level))
+ except KeyError:
+ ql.log.error(f"Can't convert emu_level {emu_level} to host platform based level")
+ raise
+ try:
+ level = getattr(socket, emu_level_name)
except AttributeError:
- ql.log.error("Can't convert emu_level {}:{} to host platform based emu_level".format(
- emu_level_name, emu_level))
+ ql.log.error(f"Can't convert emu_level {emu_level_name}:{emu_level} to host platform based emu_level")
raise
- except Exception:
- ql.log.error("Can't convert emu_level {} to host platform based level".format(emu_level))
- raise
+ ql.log.debug(f"Convert emu_level {emu_level_name}:{emu_level} to host platform based level {emu_level_name}:{level}")
- try:
- emu_opt = optname
+ emu_opt = optname
+ try:
emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type)
+
# emu_opt_name is based on level
if emu_level_name == "IPPROTO_IP":
emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.os.type)
@@ -241,18 +249,17 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle
if emu_opt_name.endswith("_NEW") or emu_opt_name.endswith("_OLD"):
emu_opt_name = emu_opt_name[:-4]
- optname = getattr(socket, emu_opt_name)
- ql.log.debug("Convert emu_optname {}:{} to host platform based optname {}:{}".format(
- emu_opt_name, emu_opt, emu_opt_name, optname))
+ except KeyError:
+ ql.log.error(f"Can't convert emu_optname {emu_opt} to host platform based optname")
+ raise
+ try:
+ optname = getattr(socket, emu_opt_name)
except AttributeError:
- ql.log.error("Can't convert emu_optname {}:{} to host platform based emu_optname".format(
- emu_opt_name, emu_opt))
+ ql.log.error(f"Can't convert emu_optname {emu_opt_name}:{emu_opt} to host platform based emu_optname")
raise
- except Exception:
- ql.log.error("Can't convert emu_optname {} to host platform based optname".format(emu_opt))
- raise
+ ql.log.debug(f"Convert emu_optname {emu_opt_name}:{emu_opt} to host platform based optname {emu_opt_name}:{optname}")
optval = ql.mem.read(optval_addr, optlen)
ql.os.fd[sockfd].setsockopt(level, optname, optval, None)
@@ -304,13 +311,13 @@ def ql_syscall_bind(ql: Qiling, bind_fd, bind_addr, bind_addrlen):
# need a proper fix, for now ipv4 comes first
elif sin_family == 2 and ql.os.bindtolocalhost == True:
- ql.os.fd[bind_fd].bind(('127.0.0.1', port))
host = "127.0.0.1"
+ ql.os.fd[bind_fd].bind((host, port))
# IPv4 should comes first
elif ql.os.ipv6 == True and sin_family == 10 and ql.os.bindtolocalhost == True:
- ql.os.fd[bind_fd].bind(('::1', port))
host = "::1"
+ ql.os.fd[bind_fd].bind((host, port))
elif ql.os.bindtolocalhost == False:
ql.os.fd[bind_fd].bind((host, port))
diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py
index 046446f63..9c1f6fcdc 100644
--- a/qiling/os/posix/syscall/unistd.py
+++ b/qiling/os/posix/syscall/unistd.py
@@ -338,23 +338,25 @@ def ql_syscall_write(ql: Qiling, fd: int, buf: int, count: int):
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")
- real_path = ql.os.path.transform_to_link_path(pathname)
- relative_path = ql.os.path.transform_to_relative_path(pathname)
+ host_path = ql.os.path.virtual_to_host_path(pathname)
+ virt_path = ql.os.path.virtual_abspath(pathname)
- if not os.path.exists(real_path):
- regreturn = -1
+ # 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')
- elif relative_path == r'/proc/self/exe':
- localpath = os.path.abspath(ql.path)
- localpath = bytes(localpath, 'utf-8') + b'\x00'
+ ql.mem.write(path_buff, p + b'\x00')
+ regreturn = len(p)
- ql.mem.write(path_buff, localpath)
- regreturn = len(localpath) - 1
+ elif os.path.exists(host_path):
+ regreturn = 0
else:
- regreturn = 0
+ regreturn = -1
- ql.log.debug("readlink(%s, 0x%x, 0x%x) = %d" % (relative_path, path_buff, path_buffsize, regreturn))
+ ql.log.debug('readlink("%s", 0x%x, 0x%x) = %d' % (virt_path, path_buff, path_buffsize, regreturn))
return regreturn
@@ -376,16 +378,17 @@ def ql_syscall_getcwd(ql: Qiling, path_buff: int, path_buffsize: int):
def ql_syscall_chdir(ql: Qiling, path_name: int):
pathname = ql.os.utils.read_cstring(path_name)
- real_path = ql.os.path.transform_to_real_path(pathname)
- relative_path = ql.os.path.transform_to_relative_path(pathname)
+ host_path = ql.os.path.virtual_to_host_path(pathname)
+ virt_path = ql.os.path.virtual_abspath(pathname)
+
+ if os.path.exists(host_path) and os.path.isdir(host_path):
+ ql.os.path.cwd = virt_path
- if os.path.exists(real_path) and os.path.isdir(real_path):
- ql.os.path.cwd = relative_path
regreturn = 0
- ql.log.debug("chdir(%s) = %d"% (relative_path, regreturn))
+ ql.log.debug("chdir(%s) = %d"% (virt_path, regreturn))
else:
regreturn = -1
- ql.log.warning("chdir(%s) = %d : not found" % (relative_path, regreturn))
+ ql.log.warning("chdir(%s) = %d : not found" % (virt_path, regreturn))
return regreturn
@@ -393,21 +396,26 @@ def ql_syscall_chdir(ql: Qiling, path_name: int):
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")
- real_path = ql.os.path.transform_to_link_path(pathname)
- relative_path = ql.os.path.transform_to_relative_path(pathname)
+ host_path = ql.os.path.virtual_to_host_path(pathname)
+ virt_path = ql.os.path.virtual_abspath(pathname)
- if not os.path.exists(real_path):
- regreturn = -1
+ # 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')
- elif relative_path == r'/proc/self/exe':
- localpath = os.path.abspath(ql.path)
- localpath = bytes(localpath, 'utf-8') + b'\x00'
+ ql.mem.write(buf, p + b'\x00')
+ regreturn = len(p)
- ql.mem.write(buf, localpath)
- regreturn = len(localpath) -1
- else:
+ 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))
+
return regreturn
diff --git a/qiling/os/thread.py b/qiling/os/thread.py
index 28adec686..dc1ccfa20 100644
--- a/qiling/os/thread.py
+++ b/qiling/os/thread.py
@@ -3,20 +3,20 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
-from abc import ABCMeta, abstractmethod
+from abc import abstractmethod
from gevent import Greenlet
-from qiling.const import *
-from .const import *
+
+from qiling import Qiling
class QlThread(Greenlet):
- __metaclass__=ABCMeta
- def __init__(self, ql):
- super(QlThread, self).__init__()
+ def __init__(self, ql: Qiling):
+ super().__init__()
+
self.ql = ql
self.log_file_fd = None
- def __str__(self):
+ def __str__(self) -> str:
return f"QlThread {self.get_id()}"
# the common functions which are used in qiling core.
diff --git a/qiling/os/utils.py b/qiling/os/utils.py
index 1228df029..fa4bacca0 100644
--- a/qiling/os/utils.py
+++ b/qiling/os/utils.py
@@ -7,12 +7,16 @@
This module is intended for general purpose functions that are only used in qiling.os
"""
-from typing import MutableMapping, Union, Sequence, MutableSequence, Tuple
+import ctypes
+from typing import Callable, Iterable, Iterator, List, MutableMapping, Sequence, Tuple, TypeVar, Union
from uuid import UUID
from qiling import Qiling
from qiling.const import QL_VERBOSE
+# TODO: separate windows-specific implementation
+from qiling.os.windows.structs import make_unicode_string
+
class QlOsUtils:
ELLIPSIS_PREF = r'__qlva_'
@@ -101,39 +105,119 @@ def __assign_arg(name: str, value: str) -> str:
else:
self.ql.log.info(log)
- def __common_printf(self, format: str, args: MutableSequence, wstring: bool):
- fmtstr = format.split("%")[1:]
+ def __common_printf(self, format: str, va_args: Iterator[int], wstring: bool) -> Tuple[str, Sequence[int]]:
+ import re
+
+ # https://docs.microsoft.com/en-us/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions
+ # %[flags][width][.precision][size]type
+ fmtstr = re.compile(r'''%
+ (?P%|
+ (?P[-+0 #]+)?
+ (?P[*]|[0-9]+)?
+ (?:.(?P[*]|[0-9]+))?
+ (?Phh|ll|I32|I64|[hjltwzIL])?
+ (?P[diopuaAcCeEfFgGsSxXZ])
+ )
+ ''', re.VERBOSE)
+
+ T = TypeVar('T')
+
+ def __dup(iterator: Iterator[T], out: List[T]) -> Iterator[T]:
+ """A wrapper iterator to record iterator elements as they are being yielded.
+ """
+
+ for elem in iterator:
+ out.append(elem)
+ yield elem
+
+ repl_args = [] # processed arguments
+ orig_args = [] # original arguments
+
+ va_list = __dup(va_args, orig_args)
+
read_string = self.read_wstring if wstring else self.read_cstring
- for i, f in enumerate(fmtstr):
- if f.startswith("s"):
- args[i] = read_string(args[i])
+ def __repl(m: re.Match) -> str:
+ """Convert printf format string tokens into Python's.
+ """
+
+ if m['follows'] == '%':
+ return '%%'
+
+ else:
+ flags = m['flags'] or ''
+
+ fill = ' ' if ' ' in flags else ''
+ align = '<' if '-' in flags else ''
+ sign = '+' if '+' in flags else ''
+ pound = '#' if '#' in flags else ''
+ zeros = '0' if '0' in flags else ''
+
+ width = m['width'] or ''
- out = format.replace(r'%llx', r'%x')
- out = out.replace(r'%p', r'%#x')
+ if width == '*':
+ width = f'{next(va_list)}'
- return out % tuple(args)
+ prec = m['precision'] or ''
- def va_list(self, format: str, ptr: int) -> MutableSequence[int]:
- count = format.count("%")
+ if prec == '*':
+ prec = f'{next(va_list)}'
- return [self.ql.mem.read_ptr(ptr + i * self.ql.arch.pointersize) for i in range(count)]
+ if prec:
+ prec = f'.{prec}'
- def sprintf(self, buff: int, format: str, args: MutableSequence, wstring: bool = False) -> int:
- out = self.__common_printf(format, args, wstring)
+ typ = m['type']
+ arg = next(va_list)
+
+ if typ in 'sS':
+ typ = 's'
+ arg = read_string(arg)
+
+ elif typ == 'Z':
+ # note: ANSI_STRING and UNICODE_STRING have identical layout
+ strcls = make_unicode_string(self.ql.arch.bits).__class__
+ data = self.ql.mem.read(arg, ctypes.sizeof(strcls))
+ strobj = strcls.from_buffer_copy(data)
+
+ typ = 's'
+ arg = read_string(strobj.Buffer)
+
+ elif typ == 'p':
+ pound = '#'
+ typ = 'x'
+
+ repl_args.append(arg)
+
+ return f'%{fill}{align}{sign}{pound}{zeros}{width}{prec}{typ}'
+
+ out = fmtstr.sub(__repl, format)
+
+ return out % tuple(repl_args), orig_args
+
+ def va_list(self, ptr: int) -> Iterator[int]:
+ while True:
+ yield self.ql.mem.read_ptr(ptr)
+
+ ptr += self.ql.arch.pointersize
+
+ def sprintf(self, buff: int, format: str, va_args: Iterator[int], wstring: bool = False) -> Tuple[int, Callable]:
+ out, args = self.__common_printf(format, va_args, wstring)
enc = 'utf-16le' if wstring else 'utf-8'
self.ql.mem.write(buff, (out + '\x00').encode(enc))
- return len(out)
+ return len(out), self.__update_ellipsis(args)
- def printf(self, format: str, args: MutableSequence, wstring: bool = False) -> int:
- out = self.__common_printf(format, args, wstring)
+ def printf(self, format: str, va_args: Iterator[int], wstring: bool = False) -> Tuple[int, Callable]:
+ out, args = self.__common_printf(format, va_args, wstring)
enc = 'utf-8'
self.ql.os.stdout.write(out.encode(enc))
- return len(out)
+ return len(out), self.__update_ellipsis(args)
+
+ def __update_ellipsis(self, args: Iterable[int]) -> Callable[[MutableMapping], None]:
+ def __do_update(params: MutableMapping) -> None:
+ params.update((f'{QlOsUtils.ELLIPSIS_PREF}{i}', a) for i, a in enumerate(args))
- def update_ellipsis(self, params: MutableMapping, args: Sequence) -> None:
- params.update((f'{QlOsUtils.ELLIPSIS_PREF}{i}', a) for i, a in enumerate(args))
+ return __do_update
\ No newline at end of file
diff --git a/qiling/os/windows/api.py b/qiling/os/windows/api.py
index bd001c381..12a8cca3d 100644
--- a/qiling/os/windows/api.py
+++ b/qiling/os/windows/api.py
@@ -111,6 +111,7 @@
LPSYSTEMTIME = POINTER
LPSYSTEM_INFO = POINTER
LPTHREAD_START_ROUTINE = POINTER
+LPTIME_ZONE_INFORMATION = POINTER
LPTOP_LEVEL_EXCEPTION_FILTER= POINTER
LPUNKNOWN = POINTER
LPVOID = POINTER
diff --git a/qiling/os/windows/clipboard.py b/qiling/os/windows/clipboard.py
index f7fecad9d..0d57e51d7 100644
--- a/qiling/os/windows/clipboard.py
+++ b/qiling/os/windows/clipboard.py
@@ -1,16 +1,17 @@
#!/usr/bin/env python3
-#
+#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
# A Simple Windows Clipboard Simulation
from typing import TYPE_CHECKING, Optional
+from qiling.os.windows.const import *
+
if TYPE_CHECKING:
from qiling.os.windows.windows import QlOsWindows
NOT_LOCKED = -1
-ERROR_CLIPBOARD_NOT_OPEN = 0x58a
class Clipboard:
@@ -19,9 +20,12 @@ def __init__(self, os: 'QlOsWindows'):
self.data = b"Default Clipboard Data"
self.os = os
- # Valid formats taken from https://doxygen.reactos.org/d8/dd6/base_
- # 2applications_2mstsc_2constants_8h_source.html
- self.formats = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 129, 130, 131, 142, 512, 767, 768, 1023]
+ self.formats = [
+ CF_TEXT, CF_BITMAP, CF_METAFILEPICT, CF_SYLK, CF_DIF, CF_TIFF, CF_OEMTEXT, CF_DIB,
+ CF_PALETTE, CF_PENDATA, CF_RIFF, CF_WAVE, CF_UNICODETEXT, CF_ENHMETAFILE, CF_HDROP,
+ CF_LOCALE, CF_MAX, CF_OWNERDISPLAY, CF_DSPTEXT, CF_DSPBITMAP, CF_DSPMETAFILEPICT,
+ CF_DSPENHMETAFILE, CF_PRIVATEFIRST, CF_PRIVATELAST, CF_GDIOBJFIRST, CF_GDIOBJLAST
+ ]
def open(self, h_wnd: int) -> bool:
"""Lock clipboard to hWnd if not already locked.
@@ -50,23 +54,35 @@ def close(self) -> bool:
def format_available(self, fmt: int) -> bool:
return fmt in self.formats
- def set_data(self, fmt: int, data: bytes) -> int:
- if fmt not in self.formats:
- return 0
-
+ def set_data(self, format: int, address: int) -> int:
hWnd = self.os.thread_manager.cur_thread.id
if self.locked_by != hWnd:
self.os.last_error = ERROR_CLIPBOARD_NOT_OPEN
return 0
- self.data = data
+ def __handle_text(a: int) -> bytes:
+ return self.os.utils.read_cstring(a).encode()
+
+ def __handle_uctext(a: int) -> bytes:
+ return self.os.utils.read_wstring(a).encode()
+
+ # TODO: support more clipboard formats
+ format_handlers = {
+ CF_TEXT : __handle_text,
+ CF_UNICODETEXT : __handle_uctext
+ }
+
+ if format not in format_handlers:
+ return 0
+
+ self.data = format_handlers[format](address)
- # BUG: this should be the handle of the clipboard object
- return 1
+ # TODO: should create a handle for the clipboard object?
+ return address
- def get_data(self, fmt: int) -> Optional[bytes]:
- if fmt not in self.formats:
+ def get_data(self, format: int) -> Optional[bytes]:
+ if format not in self.formats:
return None
hWnd = self.os.thread_manager.cur_thread.id
diff --git a/qiling/os/windows/const.py b/qiling/os/windows/const.py
index 13884a299..e7c6d8fe4 100644
--- a/qiling/os/windows/const.py
+++ b/qiling/os/windows/const.py
@@ -19,7 +19,9 @@
ERROR_MORE_DATA = 0xEA
ERROR_NO_MORE_ITEMS = 0x103
ERROR_NOT_OWNER = 0x120
-ERROR_OLD_WIN_VERSION = 0X47E
+ERROR_NO_UNICODE_TRANSLATION = 0x459
+ERROR_OLD_WIN_VERSION = 0x47E
+ERROR_CLIPBOARD_NOT_OPEN = 0x58a
# ...
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
@@ -35,25 +37,12 @@
INVALID_HANDLE_VALUE = -1
-STD_INPUT_HANDLE = 0xfffffff6
-STD_OUTPUT_HANDLE = 0xfffffff5
-STD_ERROR_HANDLE = 0xfffffff4
+STD_INPUT_HANDLE = 0xfffffff6 # -10
+STD_OUTPUT_HANDLE = 0xfffffff5 # -11
+STD_ERROR_HANDLE = 0xfffffff4 # -12
# Registry Type
-# Predefined Keys
-REG_KEYS = {
- 0x80000000: "HKEY_CLASSES_ROOT",
- 0x80000005: "HKEY_CURRENT_CONFIG",
- 0x80000001: "HKEY_CURRENT_USER",
- 0x80000007: "HKEY_CURRENT_USER_LOCAL_SETTINGS",
- 0x80000002: "HKEY_LOCAL_MACHINE",
- 0x80000004: "HKEY_PERFORMANCE_DATA",
- 0x80000060: "HKEY_PERFORMANCE_NLSTEXT",
- 0x80000050: "HKEY_PERFORMANCE_TEXT",
- 0x80000003: "HKEY_USERS"
-}
-
REG_TYPES = {
"REG_NONE": Registry.RegNone,
"REG_SZ": Registry.RegSZ,
@@ -611,17 +600,23 @@
# ...
+# documented PROCESSINFOCLASS values
# https://docs.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess
-# https://www.pinvoke.net/default.aspx/ntdll/PROCESSINFOCLASS.html
ProcessBasicInformation = 0
ProcessDebugPort = 7
-ProcessExecuteFlags = 0x22
ProcessWow64Information = 26
ProcessImageFileName = 27
ProcessBreakOnTermination = 29
ProcessProtectionInformation = 61
-ProcessDebugObjectHandle = 0x1E
-ProcessDebugFlags = 0x1F
+
+# more PROCESSINFOCLASS values
+# https://www.pinvoke.net/default.aspx/ntdll/PROCESSINFOCLASS.html
+ProcessDebugObjectHandle = 30
+ProcessDebugFlags = 31
+ProcessExecuteFlags = 34
+ProcessImageInformation = 37
+ProcessMitigationPolicy = 52
+ProcessFaultInformation = 63
# https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ps/psquery/class.htm
ThreadBasicInformation = 0x0
@@ -632,26 +627,79 @@
# ...
+# message box types:
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw
-MB_ABORTRETRYIGNORE = 0x000000020
-MB_CANCELTRYCONTINUE = 0x000000060
-MB_HELP = 0x000040000
-MB_OK = 0x000000000
-MB_OKCANCEL = 0x000000010
-MB_RETRYCANCEL = 0x000000050
-MB_YESNO = 0x000000040
-MB_YESNOCANCEL = 0x000000030
-
-IDABORT = 3
-IDCANCEL = 2
-IDCONTINUE = 11
-IDIGNORE = 5
-IDNO = 7
-IDOK = 1
-IDRETRY = 4
+MB_OK = 0x00000000
+MB_OKCANCEL = 0x00000001
+MB_ABORTRETRYIGNORE = 0x00000002
+MB_YESNOCANCEL = 0x00000003
+MB_YESNO = 0x00000004
+MB_RETRYCANCEL = 0x00000005
+MB_CANCELTRYCONTINUE = 0x00000006
+
+MB_ICONERROR = 0x00000010
+MB_ICONQUESTION = 0x00000020
+MB_ICONWARNING = 0x00000030
+MB_ICONINFORMATION = 0x00000040
+
+MB_DEFBUTTON1 = 0x00000000
+MB_DEFBUTTON2 = 0x00000100
+MB_DEFBUTTON3 = 0x00000200
+MB_DEFBUTTON4 = 0x00000300
+
+MB_APPLMODAL = 0x00000000
+MB_SYSTEMMODAL = 0x00001000
+MB_TASKMODAL = 0x00002000
+MB_HELP = 0x00004000
+
+MB_SETFOREGROUND = 0x00010000
+MB_DEFAULT_DESKTOP_ONLY = 0x00020000
+MB_TOPMOST = 0x00040000
+MB_RIGHT = 0x00080000
+MB_RTLREADING = 0x00100000
+MB_SERVICE_NOTIFICATION = 0x00200000
+
+# message box responses:
+IDOK = 1
+IDCANCEL = 2
+IDABORT = 3
+IDRETRY = 4
+IDIGNORE = 5
+IDYES = 6
+IDNO = 7
IDTRYAGAIN = 10
-IDYES = 6
+IDCONTINUE = 11
+
+# clipboard data types
+# https://doxygen.reactos.org/d8/dd6/base_2applications_2mstsc_2constants_8h_source.html
+
+CF_TEXT = 1
+CF_BITMAP = 2
+CF_METAFILEPICT = 3
+CF_SYLK = 4
+CF_DIF = 5
+CF_TIFF = 6
+CF_OEMTEXT = 7
+CF_DIB = 8
+CF_PALETTE = 9
+CF_PENDATA = 10
+CF_RIFF = 11
+CF_WAVE = 12
+CF_UNICODETEXT = 13
+CF_ENHMETAFILE = 14
+CF_HDROP = 15
+CF_LOCALE = 16
+CF_MAX = 17
+CF_OWNERDISPLAY = 128
+CF_DSPTEXT = 129
+CF_DSPBITMAP = 130
+CF_DSPMETAFILEPICT = 131
+CF_DSPENHMETAFILE = 142
+CF_PRIVATEFIRST = 512
+CF_PRIVATELAST = 767
+CF_GDIOBJFIRST = 768
+CF_GDIOBJLAST = 1023
# https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-setdefaultdlldirectories
@@ -709,3 +757,26 @@
OF_SHARE_DENY_WRITE = 0x00000020
OF_SHARE_DENY_READ = 0x00000030
OF_SHARE_DENY_NONE = 0x00000040
+
+# code pages identifiers
+CP_ACP = 0
+CP_OEMCP = 1
+CP_THREAD_ACP = 3
+CP_UTF16 = 1200
+CP_UTF16BE = 1201
+CP_ASCII = 20127
+CP_UTF7 = 65000
+CP_UTF8 = 65001
+
+# conversion types
+WC_DISCARDNS = 0x0010
+WC_SEPCHARS = 0x0020
+WC_DEFAULTCHAR = 0x0040
+WC_ERR_INVALID_CHARS = 0x0080
+WC_COMPOSITECHECK = 0x0200
+WC_NO_BEST_FIT_CHARS = 0x0400
+
+TIME_ZONE_ID_INVALID = -1
+TIME_ZONE_ID_UNKNOWN = 0
+TIME_ZONE_ID_STANDARD = 1
+TIME_ZONE_ID_DAYLIGHT = 2
diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py
index e98779897..153e08a4b 100644
--- a/qiling/os/windows/dlls/advapi32.py
+++ b/qiling/os/windows/dlls/advapi32.py
@@ -14,27 +14,35 @@ def __RegOpenKey(ql: Qiling, address: int, params):
lpSubKey = params["lpSubKey"]
phkResult = params["phkResult"]
- if hKey not in REG_KEYS:
+ handle = ql.os.handle_manager.get(hKey)
+
+ if handle is None or not handle.name.startswith('HKEY'):
return ERROR_FILE_NOT_FOUND
- s_hKey = REG_KEYS[hKey]
- key = s_hKey + "\\" + lpSubKey
+ params["hKey"] = handle.name
- # Keys in the profile are saved as KEY\PARAM = VALUE, so i just want to check that the key is the same
- keys_profile = [key.rsplit("\\", 1)[0] for key in ql.os.profile["REGISTRY"].keys()]
- if key.lower() in keys_profile:
- ql.log.debug("Using profile for key of %s" % key)
- ql.os.registry_manager.access(key)
- else:
- if not ql.os.registry_manager.exists(key):
- ql.log.debug("Value key %s not present" % key)
- return ERROR_FILE_NOT_FOUND
+ if lpSubKey:
+ key = f'{handle.name}\\{lpSubKey}'
+
+ # Keys in the profile are saved as KEY\PARAM = VALUE, so i just want to check that the key is the same
+ keys_profile = [entry.casefold() for entry in ql.os.profile["REGISTRY"].keys()]
+
+ if key.casefold() in keys_profile:
+ ql.log.debug("Using profile for key of %s" % key)
+ ql.os.registry_manager.access(key)
+
+ else:
+ if not ql.os.registry_manager.exists(key):
+ ql.log.debug("Value key %s not present" % key)
+ return ERROR_FILE_NOT_FOUND
+
+ # new handle
+ handle = Handle(obj=key)
+ ql.os.handle_manager.append(handle)
+
+ if phkResult:
+ ql.mem.write_ptr(phkResult, handle.id)
- # new handle
- new_handle = Handle(obj=key)
- ql.os.handle_manager.append(new_handle)
- if phkResult != 0:
- ql.mem.write_ptr(phkResult, new_handle.id)
return ERROR_SUCCESS
def __RegQueryValue(ql: Qiling, address: int, params, wstring: bool):
@@ -84,35 +92,34 @@ def __RegQueryValue(ql: Qiling, address: int, params, wstring: bool):
return ret
def __RegCreateKey(ql: Qiling, address: int, params):
- ret = ERROR_SUCCESS
-
hKey = params["hKey"]
lpSubKey = params["lpSubKey"]
phkResult = params["phkResult"]
- if hKey not in REG_KEYS:
+ handle = ql.os.handle_manager.get(hKey)
+
+ if handle is None or not handle.name.startswith('HKEY'):
return ERROR_FILE_NOT_FOUND
- s_hKey = REG_KEYS[hKey]
- params["hKey"] = s_hKey
+ params["hKey"] = handle.name
- keyname = f'{s_hKey}\\{lpSubKey}'
+ if lpSubKey:
+ keyname = f'{handle.name}\\{lpSubKey}'
- if not ql.os.registry_manager.exists(keyname):
- ql.os.registry_manager.create(keyname)
- ret = ERROR_SUCCESS
+ if not ql.os.registry_manager.exists(keyname):
+ ql.os.registry_manager.create(keyname)
- # new handle
- if ret == ERROR_SUCCESS:
- new_handle = Handle(obj=keyname)
- ql.os.handle_manager.append(new_handle)
- if phkResult != 0:
- ql.mem.write_ptr(phkResult, new_handle.id)
- else:
- # elicn: is this even reachable?
- new_handle = 0
+ handle = ql.os.handle_manager.search_by_obj(keyname)
- return ret
+ # make sure we have a handle for this keyname
+ if handle is None:
+ handle = Handle(obj=keyname)
+ ql.os.handle_manager.append(handle)
+
+ if phkResult:
+ ql.mem.write_ptr(phkResult, handle.id)
+
+ return ERROR_SUCCESS
def __RegSetValue(ql: Qiling, address: int, params, wstring: bool):
hKey = params["hKey"]
@@ -458,11 +465,17 @@ def hook_GetTokenInformation(ql: Qiling, address: int, params):
TokenInformationLength = params["TokenInformationLength"]
ReturnLength = params["ReturnLength"]
- token = ql.os.handle_manager.get(TokenHandle).obj
+ handle = ql.os.handle_manager.get(TokenHandle)
+
+ if handle is None:
+ ql.os.last_error = ERROR_INVALID_HANDLE
+ return 0
+
+ token = handle.obj
information_value = token.get(TokenInformationClass)
- ql.mem.write_ptr(ReturnLength, len(information_value), 4)
- return_size = ql.mem.read_ptr(ReturnLength, 4)
+ return_size = len(information_value)
+ ql.mem.write_ptr(ReturnLength, return_size, 4)
ql.log.debug("The target is checking for its permissions")
diff --git a/qiling/os/windows/dlls/kernel32/__init__.py b/qiling/os/windows/dlls/kernel32/__init__.py
index ca9762783..e07d5685a 100644
--- a/qiling/os/windows/dlls/kernel32/__init__.py
+++ b/qiling/os/windows/dlls/kernel32/__init__.py
@@ -23,3 +23,4 @@
from .consoleapi3 import *
from .debugapi import *
from .psapi import *
+from .timezoneapi import *
diff --git a/qiling/os/windows/dlls/kernel32/errhandlingapi.py b/qiling/os/windows/dlls/kernel32/errhandlingapi.py
index 5dbc32d7a..4dba7efb6 100644
--- a/qiling/os/windows/dlls/kernel32/errhandlingapi.py
+++ b/qiling/os/windows/dlls/kernel32/errhandlingapi.py
@@ -22,10 +22,13 @@ def hook_SetUnhandledExceptionFilter(ql: Qiling, address: int, params):
if handle is None:
handle = Handle(name="TopLevelExceptionHandler", obj=addr)
ql.os.handle_manager.append(handle)
+ prev_filter = 0
+
else:
+ prev_filter = handle.obj
handle.obj = addr
- return 0
+ return prev_filter
# _Post_equals_last_error_ DWORD GetLastError();
@winsdkapi(cc=STDCALL, params={})
@@ -73,11 +76,19 @@ def hook_SetErrorMode(ql: Qiling, address: int, params):
'lpArguments' : POINTER
})
def hook_RaiseException(ql: Qiling, address: int, params):
- func_addr = ql.os.handle_manager.search("TopLevelExceptionHandler").obj
+ nNumberOfArguments = params['nNumberOfArguments']
+ lpArguments = params['lpArguments']
- ql.os.fcall.call_native(func_addr, [], None)
+ handle = ql.os.handle_manager.search("TopLevelExceptionHandler")
- return 0
+ if handle is None:
+ ql.log.warning(f'RaiseException: top level exception handler not found')
+ return
+
+ exception_handler = handle.obj
+ args = [(PARAM_INTN, ql.mem.read_ptr(lpArguments + i * ql.arch.pointersize)) for i in range(nNumberOfArguments)] if lpArguments else []
+
+ ql.os.fcall.call_native(exception_handler, args, None)
# PVOID AddVectoredExceptionHandler(
# ULONG First,
diff --git a/qiling/os/windows/dlls/kernel32/handleapi.py b/qiling/os/windows/dlls/kernel32/handleapi.py
index 11e7291b7..bbc4debec 100644
--- a/qiling/os/windows/dlls/kernel32/handleapi.py
+++ b/qiling/os/windows/dlls/kernel32/handleapi.py
@@ -48,12 +48,12 @@ def hook_CloseHandle(ql: Qiling, address: int, params):
if handle is None:
ql.os.last_error = ERROR_INVALID_HANDLE
return 0
- else:
- if handle.permissions is not None and handle.permissions & HANDLE_FLAG_PROTECT_FROM_CLOSE >= 1:
- # FIXME: add error
- return 0
- else:
- ql.os.handle_manager.delete(value)
+
+ if handle.permissions is not None and handle.permissions & HANDLE_FLAG_PROTECT_FROM_CLOSE:
+ # FIXME: add error
+ return 0
+
+ ql.os.handle_manager.delete(value)
return 1
diff --git a/qiling/os/windows/dlls/kernel32/libloaderapi.py b/qiling/os/windows/dlls/kernel32/libloaderapi.py
index 658107135..2f0370c22 100644
--- a/qiling/os/windows/dlls/kernel32/libloaderapi.py
+++ b/qiling/os/windows/dlls/kernel32/libloaderapi.py
@@ -18,12 +18,10 @@ def _GetModuleHandle(ql: Qiling, address: int, params):
if lpModuleName == 0:
ret = ql.loader.pe_image_address
else:
- lpModuleName = lpModuleName.lower()
-
if not has_lib_ext(lpModuleName):
lpModuleName = f'{lpModuleName}.dll'
- image = ql.loader.get_image_by_name(lpModuleName)
+ image = ql.loader.get_image_by_name(lpModuleName, casefold=True)
if image:
ret = image.base
@@ -69,6 +67,35 @@ def hook_GetModuleHandleExW(ql: Qiling, address: int, params):
return res
+def __GetModuleFileName(ql: Qiling, address: int, params, *, wide: bool):
+ hModule = params["hModule"]
+ lpFilename = params["lpFilename"]
+ nSize = params["nSize"]
+
+ if not hModule:
+ if ql.code:
+ raise QlErrorNotImplemented('cannot retrieve module file name in shellcode mode')
+
+ hModule = ql.loader.pe_image_address
+
+ hpath = next((image.path for image in ql.loader.images if image.base == hModule), None)
+
+ if hpath is None:
+ ql.os.last_error = ERROR_INVALID_HANDLE
+ return 0
+
+ encname = 'utf-16le' if wide else 'latin'
+ vpath = ql.os.path.host_to_virtual_path(hpath)
+ truncated = vpath[:nSize - 1] + '\x00'
+ encoded = truncated.encode(encname)
+
+ if len(vpath) + 1 > nSize:
+ ql.os.last_error = ERROR_INSUFFICIENT_BUFFER
+
+ ql.mem.write(lpFilename, encoded)
+
+ return min(len(vpath), nSize)
+
# DWORD GetModuleFileNameA(
# HMODULE hModule,
# LPSTR lpFilename,
@@ -80,30 +107,7 @@ def hook_GetModuleHandleExW(ql: Qiling, address: int, params):
'nSize' : DWORD
})
def hook_GetModuleFileNameA(ql: Qiling, address: int, params):
- hModule = params["hModule"]
- lpFilename = params["lpFilename"]
- nSize = params["nSize"]
- ret = 0
-
- # GetModuleHandle can return pe_image_address as handle, and GetModuleFileName will try to retrieve it.
- # Pretty much 0 and pe_image_address value should do the same operations
- if not ql.code and (hModule == 0 or hModule == ql.loader.pe_image_address):
- filename = ql.loader.filepath
- filename_len = len(filename)
-
- if filename_len > nSize - 1:
- filename = ql.loader.filepath[:nSize - 1]
- ret = nSize
- else:
- ret = filename_len
-
- ql.mem.write(lpFilename, filename + b"\x00")
-
- else:
- ql.log.debug("hModule %x" % hModule)
- raise QlErrorNotImplemented("API not implemented")
-
- return ret
+ return __GetModuleFileName(ql, address, params, wide=False)
# DWORD GetModuleFileNameW(
# HMODULE hModule,
@@ -116,30 +120,7 @@ def hook_GetModuleFileNameA(ql: Qiling, address: int, params):
'nSize' : DWORD
})
def hook_GetModuleFileNameW(ql: Qiling, address: int, params):
- hModule = params["hModule"]
- lpFilename = params["lpFilename"]
- nSize = params["nSize"]
- ret = 0
-
- # GetModuleHandle can return pe_image_address as handle, and GetModuleFileName will try to retrieve it.
- # Pretty much 0 and pe_image_address value should do the same operations
- if not ql.code and (hModule == 0 or hModule == ql.loader.pe_image_address):
- filename = ql.loader.filepath.decode('ascii').encode('utf-16le')
- filename_len = len(filename)
-
- if filename_len > nSize - 1:
- filename = ql.loader.filepath[:nSize - 1]
- ret = nSize
- else:
- ret = filename_len
-
- ql.mem.write(lpFilename, filename + b"\x00")
-
- else:
- ql.log.debug("hModule %x" % hModule)
- raise QlErrorNotImplemented("API not implemented")
-
- return ret
+ return __GetModuleFileName(ql, address, params, wide=True)
# FARPROC GetProcAddress(
# HMODULE hModule,
@@ -188,9 +169,11 @@ def hook_GetProcAddress(ql: Qiling, address: int, params):
def _LoadLibrary(ql: Qiling, address: int, params):
lpLibFileName = params["lpLibFileName"]
- if not ql.code and lpLibFileName == ql.loader.filepath.decode():
- # Loading self
- return ql.loader.pe_image_address
+ # TODO: this searches only by basename; do we need to search by full path as well?
+ dll = ql.loader.get_image_by_name(lpLibFileName, casefold=True)
+
+ if dll is not None:
+ return dll.base
return ql.loader.load_dll(lpLibFileName)
diff --git a/qiling/os/windows/dlls/kernel32/processthreadsapi.py b/qiling/os/windows/dlls/kernel32/processthreadsapi.py
index b01aa1822..27937f0fc 100644
--- a/qiling/os/windows/dlls/kernel32/processthreadsapi.py
+++ b/qiling/os/windows/dlls/kernel32/processthreadsapi.py
@@ -8,7 +8,7 @@
from qiling.os.windows.const import *
from qiling.os.windows.fncc import *
-from qiling.os.windows.thread import QlWindowsThread
+from qiling.os.windows.thread import QlWindowsThread, THREAD_STATUS
from qiling.os.windows.handle import Handle
from qiling.os.windows.structs import Token, StartupInfo
@@ -164,20 +164,13 @@ def hook_CreateThread(ql: Qiling, address: int, params):
dwCreationFlags = params["dwCreationFlags"]
lpThreadId = params["lpThreadId"]
- # new thread obj
- new_thread = QlWindowsThread(ql)
-
- if dwCreationFlags & CREATE_SUSPENDED == CREATE_SUSPENDED:
- thread_status = QlWindowsThread.READY
+ if dwCreationFlags & CREATE_SUSPENDED:
+ thread_status = THREAD_STATUS.READY
else:
- thread_status = QlWindowsThread.RUNNING
+ thread_status = THREAD_STATUS.RUNNING
# create new thread
- thread_id = new_thread.create(
- lpStartAddress,
- lpParameter,
- thread_status
- )
+ new_thread = QlWindowsThread.create(ql, dwStackSize, lpStartAddress, lpParameter, thread_status)
# append the new thread to ThreadManager
ql.os.thread_manager.append(new_thread)
diff --git a/qiling/os/windows/dlls/kernel32/stringapiset.py b/qiling/os/windows/dlls/kernel32/stringapiset.py
index 2b9ce4e62..157a1e815 100644
--- a/qiling/os/windows/dlls/kernel32/stringapiset.py
+++ b/qiling/os/windows/dlls/kernel32/stringapiset.py
@@ -7,6 +7,7 @@
from qiling.os.windows.api import *
from qiling.os.windows.fncc import *
from qiling.os.windows.utils import cmp
+from qiling.os.windows.const import *
# BOOL GetStringTypeW(
# DWORD dwInfoType,
@@ -43,6 +44,42 @@ def hook_GetStringTypeExA(ql: Qiling, address: int, params):
# TODO: implement
return 1
+def __encoding_name(codepage: int) -> str:
+ '''Get python encoding name from codepage value.
+
+ @see: https://docs.python.org/3.8/library/codecs.html#standard-encodings
+ '''
+
+ # mapping of special codepage values to encodings
+ encodings = {
+ # available only on windows hosts
+ CP_ACP : 'mbcs',
+ CP_OEMCP : 'oem',
+ CP_THREAD_ACP : 'mbcs',
+
+ # avaiable everywhere
+ CP_UTF16 : 'utf-16',
+ CP_UTF16BE : 'utf-16be',
+ CP_ASCII : 'ascii',
+ CP_UTF7 : 'utf-7',
+ CP_UTF8 : 'utf-8'
+ }
+
+ if codepage in encodings:
+ encname = encodings[codepage]
+
+ else:
+ encname = f'cp{codepage}'
+
+ # encoding might break on the hosting system; test it and
+ # fallback to windows-1252 ('western') if it fails
+ try:
+ _ = '\x00'.encode(encname)
+ except LookupError:
+ encname = 'cp1252'
+
+ return encname
+
# int WideCharToMultiByte(
# UINT CodePage,
# DWORD dwFlags,
@@ -56,7 +93,7 @@ def hook_GetStringTypeExA(ql: Qiling, address: int, params):
@winsdkapi(cc=STDCALL, params={
'CodePage' : UINT,
'dwFlags' : DWORD,
- 'lpWideCharStr' : WSTRING, # LPCWCH
+ 'lpWideCharStr' : POINTER, # WSTRING
'cchWideChar' : INT,
'lpMultiByteStr' : LPSTR,
'cbMultiByte' : INT,
@@ -64,16 +101,54 @@ def hook_GetStringTypeExA(ql: Qiling, address: int, params):
'lpUsedDefaultChar' : LPBOOL
})
def hook_WideCharToMultiByte(ql: Qiling, address: int, params):
+ CodePage = params['CodePage']
+ dwFlags = params['dwFlags']
+ lpWideCharStr = params['lpWideCharStr']
+ cchWideChar = params['cchWideChar']
+ lpMultiByteStr = params['lpMultiByteStr']
cbMultiByte = params["cbMultiByte"]
- s_lpWideCharStr = params["lpWideCharStr"]
- lpMultiByteStr = params["lpMultiByteStr"]
- s = (s_lpWideCharStr + "\x00").encode("utf-16le")
+ if not cchWideChar:
+ # TODO: set last error
+ return 0
+
+ # -1 indicates the string is null-terminated. the string is
+ # read along with its null-terminator
+ elif cchWideChar == 0xffffffff:
+ wcstr = bytearray()
+ ch = 1
+
+ while ch != b'\x00\x00':
+ ch = ql.mem.read(lpWideCharStr, 2)
+ wcstr.extend(ch)
+
+ lpWideCharStr += 2
+
+ # read exactly cbMultiByte bytes. that may or may not include
+ # a null-terminator
+ else:
+ wcstr = ql.mem.read(lpWideCharStr, cchWideChar * 2)
+
+ decoded = wcstr.decode('utf-16le')
- if cbMultiByte != 0 and lpMultiByteStr != 0:
- ql.mem.write(lpMultiByteStr, s)
+ encname = __encoding_name(CodePage)
+ errors = 'strict' if dwFlags & WC_ERR_INVALID_CHARS else 'replace'
- return len(s)
+ try:
+ encoded = decoded.encode(encname, errors)
+ except UnicodeEncodeError:
+ ql.os.last_error = ERROR_NO_UNICODE_TRANSLATION
+ return 0
+
+ if not cbMultiByte:
+ return len(encoded)
+
+ if not lpMultiByteStr:
+ return 0
+
+ ql.mem.write(lpMultiByteStr, encoded[:cbMultiByte])
+
+ return min(len(encoded), cbMultiByte)
# int MultiByteToWideChar(
# UINT CodePage,
@@ -86,18 +161,53 @@ def hook_WideCharToMultiByte(ql: Qiling, address: int, params):
@winsdkapi(cc=STDCALL, params={
'CodePage' : UINT,
'dwFlags' : DWORD,
- 'lpMultiByteStr' : WSTRING, # LPCCH
+ 'lpMultiByteStr' : POINTER, # STRING,
'cbMultiByte' : INT,
'lpWideCharStr' : LPWSTR,
'cchWideChar' : INT
})
def hook_MultiByteToWideChar(ql: Qiling, address: int, params):
- wide_str = (params['lpMultiByteStr'] + "\x00").encode('utf-16le')
+ CodePage = params['CodePage']
+ lpMultiByteStr = params['lpMultiByteStr']
+ cbMultiByte = params['cbMultiByte']
+ lpWideCharStr = params['lpWideCharStr']
+ cchWideChar = params['cchWideChar']
+
+ if not cbMultiByte:
+ # TODO: set last error
+ return 0
+
+ # -1 indicates the string is null-terminated. the string is
+ # read along with its null-terminator
+ elif cbMultiByte == 0xffffffff:
+ mbstr = bytearray()
+ ch = 1
+
+ while ch != b'\x00':
+ ch = ql.mem.read(lpMultiByteStr, 1)
+ mbstr.extend(ch)
+
+ lpMultiByteStr += 1
+
+ # read exactly cbMultiByte bytes. that may or may not include
+ # a null-terminator
+ else:
+ mbstr = ql.mem.read(lpMultiByteStr, cbMultiByte)
+
+ # use specified code page to translate bytes into string
+ encname = __encoding_name(CodePage)
+ decoded = mbstr.decode(encname)
+
+ # this is a dry-run; just return the amount of chars
+ if not cchWideChar:
+ return len(decoded)
+
+ if not lpWideCharStr:
+ return 0
- if params['cchWideChar'] != 0:
- ql.mem.write(params['lpWideCharStr'], wide_str)
+ ql.mem.write(lpWideCharStr, decoded[:cchWideChar].encode('utf-16le'))
- return len(wide_str)
+ return min(len(decoded), cchWideChar)
def __CompareString(ql: Qiling, address: int, params) -> int:
lpString1 = params["lpString1"]
diff --git a/qiling/os/windows/dlls/kernel32/synchapi.py b/qiling/os/windows/dlls/kernel32/synchapi.py
index 50ccd5e01..0ce5c65ff 100644
--- a/qiling/os/windows/dlls/kernel32/synchapi.py
+++ b/qiling/os/windows/dlls/kernel32/synchapi.py
@@ -145,15 +145,17 @@ def hook_WaitForMultipleObjects(ql: Qiling, address: int, params):
return 0
def __OpenMutex(ql: Qiling, address: int, params):
+ lpName = params["lpName"]
+
+ if not lpName:
+ return 0
+
# The name can have a "Global" or "Local" prefix to explicitly open an object in the global or session namespace.
# It can also have no prefix
- try:
- _type, name = params["lpName"].split("\\")
- except ValueError:
- name = params["lpName"]
- _type = ""
+ _type, name = lpName.split("\\") if '\\' in lpName else ('', lpName)
handle = ql.os.handle_manager.search(name)
+
if _type == "Global":
# if is global is a Windows lock. We always return a valid handle because we have no way to emulate them
# example sample: Gandcrab e42431d37561cc695de03b85e8e99c9e31321742
@@ -175,11 +177,13 @@ def __OpenMutex(ql: Qiling, address: int, params):
raise QlErrorNotImplemented("API not implemented")
def __CreateMutex(ql: Qiling, address: int, params):
- try:
- _type, name = params["lpName"].split("\\")
- except ValueError:
- name = params["lpName"]
- _type = ""
+ lpName = params["lpName"]
+ owning = params["bInitialOwner"]
+
+ if not lpName:
+ return 0
+
+ _type, name = lpName.split("\\") if '\\' in lpName else ('', lpName)
handle = ql.os.handle_manager.search(name)
@@ -187,7 +191,6 @@ def __CreateMutex(ql: Qiling, address: int, params):
# ql.os.last_error = ERROR_ALREADY_EXISTS
return 0
- owning = params["bInitialOwner"]
mutex = Mutex(name, _type)
if owning:
@@ -279,30 +282,30 @@ def hook_ReleaseMutex(ql: Qiling, address: int, params):
def __CreateEvent(ql: Qiling, address: int, params):
# Implementation seems similar enough to Mutex to just use it
+ lpName = params["lpName"]
- try:
- namespace, name = params["lpName"].split("\\")
- except ValueError:
- name = params["lpName"]
- namespace = ""
+ if lpName:
+ namespace, name = lpName.split('\\') if '\\' in lpName else ('', lpName)
+ handle = ql.os.handle_manager.search(name)
- handle = ql.os.handle_manager.search(name)
+ if handle is not None:
+ ql.os.last_error = ERROR_ALREADY_EXISTS
+ return handle.id
- if handle is not None:
- ql.os.last_error = ERROR_ALREADY_EXISTS
- # FIXME: fail with a nullptr?
- # return 0
else:
- mutex = Mutex(name, namespace)
+ # TODO: should be None instead of empty strings?
+ namespace = ''
+ name = ''
- if params['bInitialState']:
- mutex.lock()
+ mutex = Mutex(name, namespace)
+
+ if params['bInitialState']:
+ mutex.lock()
- handle = Handle(obj=mutex, name=name)
- ql.os.handle_manager.append(handle)
+ handle = Handle(obj=mutex, name=name)
+ ql.os.handle_manager.append(handle)
- # FIXME: shouldn't it be 'id' instead of 'ID'?
- return handle.ID
+ return handle.id
# HANDLE CreateEventA(
# LPSECURITY_ATTRIBUTES lpEventAttributes,
diff --git a/qiling/os/windows/dlls/kernel32/sysinfoapi.py b/qiling/os/windows/dlls/kernel32/sysinfoapi.py
index fc2073eb4..8a5f6aa87 100644
--- a/qiling/os/windows/dlls/kernel32/sysinfoapi.py
+++ b/qiling/os/windows/dlls/kernel32/sysinfoapi.py
@@ -10,7 +10,7 @@
from qiling.os.windows.api import *
from qiling.os.windows.const import *
from qiling.os.windows.fncc import *
-from qiling.os.windows.structs import SystemInfo, SystemTime
+from qiling.os.windows.structs import FILETIME, SystemInfo, SystemTime
# NOT_BUILD_WINDOWS_DEPRECATE DWORD GetVersion(
# );
@@ -81,8 +81,20 @@ def hook_GetLocalTime(ql: Qiling, address: int, params):
'lpSystemTimeAsFileTime' : LPFILETIME
})
def hook_GetSystemTimeAsFileTime(ql: Qiling, address: int, params):
- # TODO
- pass
+ ptr = params['lpSystemTimeAsFileTime']
+
+ epoch = datetime(1601, 1, 1)
+ elapsed = datetime.now() - epoch
+
+ # number of 100-nanosecond intervals since Jan 1, 1601 utc
+ # where: (10 ** 9) / 100 -> (10 ** 7)
+ hnano = int(elapsed.total_seconds() * (10 ** 7))
+
+ mask = (1 << 32) - 1
+ hi = (hnano >> 32) & mask
+ lo = (hnano >> 0) & mask
+
+ ql.mem.write(ptr, bytes(FILETIME(lo, hi)))
# DWORD GetTickCount(
# );
diff --git a/qiling/os/windows/dlls/kernel32/timezoneapi.py b/qiling/os/windows/dlls/kernel32/timezoneapi.py
new file mode 100644
index 000000000..b5af67ded
--- /dev/null
+++ b/qiling/os/windows/dlls/kernel32/timezoneapi.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+#
+# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
+#
+
+from qiling import Qiling
+from qiling.os.windows.api import *
+from qiling.os.windows.const import *
+from qiling.os.windows.fncc import *
+
+# DWORD GetTimeZoneInformation(
+# [out] LPTIME_ZONE_INFORMATION lpTimeZoneInformation
+# );
+@winsdkapi(cc=STDCALL, params={
+ 'lpTimeZoneInformation' : LPTIME_ZONE_INFORMATION
+})
+def hook_GetTimeZoneInformation(ql: Qiling, address: int, params):
+ # TODO: implement this later. fail for now
+ return TIME_ZONE_ID_INVALID
diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py
index 73b84b3c4..c75e06d6a 100644
--- a/qiling/os/windows/dlls/msvcrt.py
+++ b/qiling/os/windows/dlls/msvcrt.py
@@ -4,6 +4,7 @@
#
import time
+from typing import Sequence
from qiling import Qiling
from qiling.exception import QlErrorNotImplemented
@@ -21,6 +22,49 @@
def hook___set_app_type(ql: Qiling, address: int, params):
pass
+def __alloc_strings_array(ql: Qiling, items: Sequence[str], *, wide: bool) -> int:
+ '''Allocate and populate an array of strings and return its address.
+ '''
+
+ enc = 'utf-16le' if wide else 'utf-8'
+
+ nitems = len(items)
+
+ # allocate room for pointers to items and a trailing null pointer
+ p_array = ql.os.heap.alloc((nitems + 1) * ql.arch.pointersize)
+
+ # encode all arguments into bytes
+ items_bytes = [f'{item}\x00'.encode(enc) for item in items]
+
+ # allocate room for the items
+ p_items_bytes = ql.os.heap.alloc(sum(len(a) for a in items_bytes))
+
+ for i, item in enumerate(items_bytes):
+ # write argument data
+ ql.mem.write(p_items_bytes, item)
+
+ # write pointer to argument data into the argv array
+ ql.mem.write_ptr(p_array + (i * ql.arch.pointersize), p_items_bytes)
+
+ p_items_bytes += len(item)
+
+ # write trailing null pointer
+ ql.mem.write_ptr(p_array + (nitems * ql.arch.pointersize), 0)
+
+ return p_array
+
+def __getmainargs(ql: Qiling, params, wide: bool) -> int:
+ argc = len(ql.argv)
+ argv = __alloc_strings_array(ql, ql.argv, wide=wide)
+ env = __alloc_strings_array(ql, [f'{k}={v}' for k, v in ql.env], wide=wide)
+
+ # write out paramters
+ ql.mem.write_ptr(params['_Argc'], argc, 4)
+ ql.mem.write_ptr(params['_Argv'], argv)
+ ql.mem.write_ptr(params['_Env'], env)
+
+ return 0
+
# int __getmainargs(
# int * _Argc,
# char *** _Argv,
@@ -36,7 +80,17 @@ def hook___set_app_type(ql: Qiling, address: int, params):
'_StartInfo' : POINTER
})
def hook___getmainargs(ql: Qiling, address: int, params):
- return 0
+ return __getmainargs(ql, params, wide=False)
+
+@winsdkapi(cc=CDECL, params={
+ '_Argc' : POINTER,
+ '_Argv' : POINTER,
+ '_Env' : POINTER,
+ '_DoWildCard' : INT,
+ '_StartInfo' : POINTER
+})
+def hook___wgetmainargs(ql: Qiling, address: int, params):
+ return __getmainargs(ql, params, wide=True)
# int* __p__fmode();
@winsdkapi(cc=CDECL, params={})
@@ -53,12 +107,14 @@ def hook___p__commode(ql: Qiling, address: int, params):
# char** __p__acmdln();
@winsdkapi(cc=CDECL, params={})
def hook___p__acmdln(ql: Qiling, address: int, params):
+ # TODO: use values calculated at __getmainargs
addr = ql.loader.import_address_table['msvcrt.dll'][b'_acmdln']
return addr
# wchar_t ** __p__wcmdln();
@winsdkapi(cc=CDECL, params={})
def hook___p__wcmdln(ql: Qiling, address: int, params):
+ # TODO: use values calculated at __getmainargs
addr = ql.loader.import_address_table['msvcrt.dll'][b'_wcmdln']
return addr
@@ -127,7 +183,7 @@ def hook__cexit(ql: Qiling, address: int, params):
'pfend' : POINTER
})
def hook__initterm(ql: Qiling, address: int, params):
- pass
+ return 0
# void exit(
# int const status
@@ -207,9 +263,10 @@ def hook_sprintf(ql: Qiling, address: int, params):
if format == 0:
format = "(null)"
- args = ql.os.utils.va_list(format, arglist)
- count = ql.os.utils.sprintf(buff, format, args, wstring=False)
- ql.os.utils.update_ellipsis(params, args)
+ args = ql.os.utils.va_list(arglist)
+
+ count, upd_args = ql.os.utils.sprintf(buff, format, args, wstring=False)
+ upd_args(params)
return count
@@ -223,12 +280,10 @@ def hook_printf(ql: Qiling, address: int, params):
if format == 0:
format = "(null)"
- nargs = format.count("%")
- ptypes = (POINTER, ) + (PARAM_INTN, ) * nargs
- args = ql.os.fcall.readParams(ptypes)[1:]
+ args = ql.os.fcall.readEllipsis(params.values())
- count = ql.os.utils.printf(format, args, wstring=False)
- ql.os.utils.update_ellipsis(params, args)
+ count, upd_args = ql.os.utils.printf(format, args, wstring=False)
+ upd_args(params)
return count
@@ -242,12 +297,10 @@ def hook_wprintf(ql: Qiling, address: int, params):
if format == 0:
format = "(null)"
- nargs = format.count("%")
- ptypes = (POINTER, ) + (PARAM_INTN, ) * nargs
- args = ql.os.fcall.readParams(ptypes)[1:]
+ args = ql.os.fcall.readEllipsis(params.values())
- count = ql.os.utils.printf(format, args, wstring=True)
- ql.os.utils.update_ellipsis(params, args)
+ count, upd_args = ql.os.utils.printf(format, args, wstring=True)
+ upd_args(params)
return count
@@ -264,9 +317,10 @@ def __stdio_common_vfprintf(ql: Qiling, address: int, params, wstring: bool):
# TODO: take _Stream into account
- args = ql.os.utils.va_list(format, arglist)
- count = ql.os.utils.printf(format, args, wstring)
- ql.os.utils.update_ellipsis(params, args)
+ args = ql.os.utils.va_list(arglist)
+
+ count, upd_args = ql.os.utils.printf(format, args, wstring)
+ upd_args(params)
return count
@@ -297,9 +351,10 @@ def __stdio_common_vsprintf(ql: Qiling, address: int, params, wstring: bool):
# TODO: take _BufferCount into account
- args = ql.os.utils.va_list(format, arglist)
- count = ql.os.utils.sprintf(buff, format, args, wstring)
- ql.os.utils.update_ellipsis(params, args)
+ args = ql.os.utils.va_list(arglist)
+
+ count, upd_args = ql.os.utils.sprintf(buff, format, args, wstring)
+ upd_args(params)
return count
@@ -373,6 +428,19 @@ def hook___stdio_common_vswprintf_s(ql: Qiling, address: int, params):
def hook___lconv_init(ql: Qiling, address: int, params):
return 0
+@winsdkapi(cc=CDECL, params={
+ 'str' : POINTER,
+ 'maxsize' : SIZE_T
+})
+def hook___strncnt(ql: Qiling, address: int, params):
+ s = params["str"]
+ maxsize = params["maxsize"]
+
+ data = ql.mem.read(s, maxsize)
+
+ # a simple hack to make sure a null terminator is found at most at 'maxsize'
+ return (data + b'\x00').find(b'\00')
+
# size_t strlen(
# const char *str
# );
diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py
index 5f424f777..ab9b4ae52 100644
--- a/qiling/os/windows/dlls/ntdll.py
+++ b/qiling/os/windows/dlls/ntdll.py
@@ -66,9 +66,12 @@ def _QueryInformationProcess(ql: Qiling, address: int, params):
addr = ql.os.heap.alloc(pbi.size)
pbi.write(addr)
value = ql.pack(addr)
+
else:
- ql.log.debug(str(flag))
- raise QlErrorNotImplemented("API not implemented")
+ # TODO: support more info class ("flag") values
+ ql.log.info(f'SetInformationProcess: no implementation for info class {flag:#04x}')
+
+ return STATUS_UNSUCCESSFUL
ql.log.debug("The target is checking the debugger via QueryInformationProcess ")
ql.mem.write(dst, value)
@@ -289,7 +292,7 @@ def _SetInformationProcess(ql: Qiling, address: int, params):
process = params["ProcessHandle"]
flag = params["ProcessInformationClass"]
dst = params["ProcessInformation"]
- pt_res = params["ReturnLength"]
+ dst_size = params["ProcessInformationLength"]
if flag == ProcessDebugFlags:
value = b"\x01" * 0x4
@@ -324,9 +327,12 @@ def _SetInformationProcess(ql: Qiling, address: int, params):
addr = ql.os.heap.alloc(pbi.size)
pbi.write(addr)
value = ql.pack(addr)
+
else:
- ql.log.debug(str(flag))
- raise QlErrorNotImplemented("API not implemented")
+ # TODO: support more info class ("flag") values
+ ql.log.info(f'SetInformationProcess: no implementation for info class {flag:#04x}')
+
+ return STATUS_UNSUCCESSFUL
# TODO: value is never used after assignment
diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py
index c54d274e8..dcdaf2eb7 100644
--- a/qiling/os/windows/dlls/ntoskrnl.py
+++ b/qiling/os/windows/dlls/ntoskrnl.py
@@ -124,12 +124,10 @@ def hook_DbgPrintEx(ql: Qiling, address: int, params):
if Format == 0:
Format = "(null)"
- nargs = Format.count("%")
- ptypes = (ULONG, ULONG, POINTER) + (PARAM_INTN, ) * nargs
- args = ql.os.fcall.readParams(ptypes)[3:]
+ args = ql.os.fcall.readEllipsis(params.values())
- count = ql.os.utils.printf(Format, args, wstring=False)
- ql.os.utils.update_ellipsis(params, args)
+ count, upd_args = ql.os.utils.printf(Format, args, wstring=False)
+ upd_args(params)
return count
@@ -147,12 +145,10 @@ def hook_DbgPrint(ql: Qiling, address: int, params):
if Format == 0:
Format = "(null)"
- nargs = Format.count("%")
- ptypes = (POINTER, ) + (PARAM_INTN, ) * nargs
- args = ql.os.fcall.readParams(ptypes)[1:]
+ args = ql.os.fcall.readEllipsis(params.values())
- count = ql.os.utils.printf(Format, args, wstring=False)
- ql.os.utils.update_ellipsis(params, args)
+ count, upd_args = ql.os.utils.printf(Format, args, wstring=False)
+ upd_args(params)
return count
diff --git a/qiling/os/windows/dlls/shell32.py b/qiling/os/windows/dlls/shell32.py
index 18bcc56c1..7c6e4dd96 100644
--- a/qiling/os/windows/dlls/shell32.py
+++ b/qiling/os/windows/dlls/shell32.py
@@ -13,7 +13,7 @@
from qiling.os.windows.fncc import *
from qiling.os.windows.handle import Handle
-from qiling.os.windows.thread import QlWindowsThread
+from qiling.os.windows.thread import QlWindowsThread, THREAD_STATUS
from qiling.exception import QlErrorNotImplemented
from qiling.os.windows.structs import ShellExecuteInfoA
@@ -74,7 +74,7 @@ def __wstr(shellex: Sequence):
if obj.show[0] == SW_HIDE:
ql.log.debug(" | With an hidden window")
- process = QlWindowsThread(ql, status=0, isFake=True)
+ process = QlWindowsThread(ql, status=THREAD_STATUS.READY)
handle = Handle(obj=process)
ql.os.handle_manager.append(handle)
diff --git a/qiling/os/windows/dlls/user32.py b/qiling/os/windows/dlls/user32.py
index a761ded42..ef8af547a 100644
--- a/qiling/os/windows/dlls/user32.py
+++ b/qiling/os/windows/dlls/user32.py
@@ -156,13 +156,15 @@ def hook_CloseClipboard(ql: Qiling, address: int, params):
'hMem' : HANDLE
})
def hook_SetClipboardData(ql: Qiling, address: int, params):
- try:
- data = bytes(params['hMem'], 'ascii', 'ignore')
- except (UnicodeEncodeError, TypeError):
- data = b""
- ql.log.debug('Failed to set clipboard data')
+ uFormat = params['uFormat']
+ hMem = params['hMem']
- return ql.os.clipboard.set_data(params['uFormat'], data)
+ handle = ql.os.clipboard.set_data(uFormat, hMem)
+
+ if not handle:
+ ql.log.debug(f'Failed to set clipboard data (format = {uFormat})')
+
+ return handle
# HANDLE GetClipboardData(
@@ -172,14 +174,17 @@ def hook_SetClipboardData(ql: Qiling, address: int, params):
'uFormat' : UINT
})
def hook_GetClipboardData(ql: Qiling, address: int, params):
- data = ql.os.clipboard.get_data(params['uFormat'])
+ uFormat = params['uFormat']
+
+ data = ql.os.clipboard.get_data(uFormat)
if data:
addr = ql.os.heap.alloc(len(data))
ql.mem.write(addr, data)
+
else:
+ ql.log.debug(f'Failed to get clipboard data (format = {uFormat})')
addr = 0
- ql.log.debug('Failed to get clipboard data')
return addr
@@ -690,12 +695,10 @@ def hook_wsprintfW(ql: Qiling, address: int, params):
if Format == 0:
Format = "(null)"
- nargs = Format.count("%")
- ptypes = (POINTER, POINTER) + (PARAM_INTN, ) * nargs
- args = ql.os.fcall.readParams(ptypes)[2:]
+ args = ql.os.fcall.readEllipsis(params.values())
- count = ql.os.utils.sprintf(Buffer, Format, args, wstring=True)
- ql.os.utils.update_ellipsis(params, args)
+ count, upd_args = ql.os.utils.sprintf(Buffer, Format, args, wstring=True)
+ upd_args(params)
return count
@@ -771,12 +774,10 @@ def hook_wsprintfA(ql: Qiling, address: int, params):
if Format == 0:
Format = "(null)"
- nargs = Format.count("%")
- ptypes = (POINTER, POINTER) + (PARAM_INTN, ) * nargs
- args = ql.os.fcall.readParams(ptypes)[2:]
+ args = ql.os.fcall.readEllipsis(params.values())
- count = ql.os.utils.sprintf(Buffer, Format, args, wstring=False)
- ql.os.utils.update_ellipsis(params, args)
+ count, upd_args = ql.os.utils.sprintf(Buffer, Format, args, wstring=False)
+ upd_args(params)
return count
@@ -793,17 +794,27 @@ def hook_wsprintfA(ql: Qiling, address: int, params):
'uType' : UINT
})
def hook_MessageBoxW(ql: Qiling, address: int, params):
- # We always return a positive result
- type_box = params["uType"]
-
- if type_box in (MB_OK, MB_OKCANCEL):
- return IDOK
-
- if type_box in (MB_YESNO, MB_YESNOCANCEL):
- return IDYES
-
- ql.log.debug(type_box)
- raise QlErrorNotImplemented("API not implemented")
+ uType = params["uType"]
+
+ buttons = uType & 0x0000000f
+ # icon = uType & 0x000000f0
+ # default = uType & 0x00000f00
+ # modal = uType & 0x0000f000
+ # order = uType & 0x00ff0000
+
+ # we strive to return a positive result when possible.
+ # if there is an "ok", "yes" or "continue" button, press it
+ press = {
+ MB_OK : IDOK,
+ MB_OKCANCEL : IDOK,
+ MB_ABORTRETRYIGNORE : IDABORT,
+ MB_YESNOCANCEL : IDYES,
+ MB_YESNO : IDYES,
+ MB_RETRYCANCEL : IDCANCEL,
+ MB_CANCELTRYCONTINUE : IDCONTINUE
+ }
+
+ return press[buttons]
# int MessageBoxA(
# HWND hWnd,
diff --git a/qiling/os/windows/handle.py b/qiling/os/windows/handle.py
index b5776c961..8e52d976c 100644
--- a/qiling/os/windows/handle.py
+++ b/qiling/os/windows/handle.py
@@ -32,24 +32,25 @@ class HandleManager:
STDOUT = Handle(id=STD_OUTPUT_HANDLE)
STDERR = Handle(id=STD_ERROR_HANDLE)
- # Register
- HKEY_CLASSES_ROOT = Handle(id=0x80000000)
- HKEY_CURRENT_CONFIG = Handle(id=0x80000005)
- HKEY_CURRENT_USER = Handle(id=0x80000001)
- HKEY_CURRENT_USER_LOCAL_SETTINGS = Handle(id=0x80000007)
- HKEY_LOCAL_MACHINE = Handle(id=0x80000002)
- HKEY_PERFORMANCE_DATA = Handle(id=0x80000004)
- HKEY_PERFORMANCE_NLSTEXT = Handle(id=0x80000060)
- HKEY_PERFORMANCE_TEXT = Handle(id=0x80000050)
- HKEY_USERS = Handle(id=0x80000003)
-
def __init__(self):
self.handles: MutableMapping[int, Handle] = {}
+ # standard io streams
self.append(HandleManager.STDIN)
self.append(HandleManager.STDOUT)
self.append(HandleManager.STDERR)
+ # registry hives
+ self.append(Handle(id=0x80000000, name='HKEY_CLASSES_ROOT'))
+ self.append(Handle(id=0x80000001, name='HKEY_CURRENT_USER'))
+ self.append(Handle(id=0x80000002, name='HKEY_LOCAL_MACHINE'))
+ self.append(Handle(id=0x80000003, name='HKEY_USERS'))
+ self.append(Handle(id=0x80000004, name='HKEY_PERFORMANCE_DATA'))
+ self.append(Handle(id=0x80000005, name='HKEY_CURRENT_CONFIG'))
+ self.append(Handle(id=0x80000007, name='HKEY_CURRENT_USER_LOCAL_SETTINGS'))
+ self.append(Handle(id=0x80000060, name='HKEY_PERFORMANCE_NLSTEXT'))
+ self.append(Handle(id=0x80000050, name='HKEY_PERFORMANCE_TEXT'))
+
def append(self, handle: Handle) -> None:
self.handles[handle.id] = handle
diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py
index 70dbff3cc..5cd0a6a68 100644
--- a/qiling/os/windows/registry.py
+++ b/qiling/os/windows/registry.py
@@ -74,7 +74,7 @@ def write(self, key: str, subkey: str, reg_type: int, data: Union[str, bytes, in
def save(self, fname: str):
if self.conf:
with open(fname, 'wb') as ofile:
- data = json.dumps(self.conf)
+ data = json.dumps(self.conf, indent=4)
ofile.write(data.encode('utf-8'))
diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py
index 84412a571..fd30b4b46 100644
--- a/qiling/os/windows/structs.py
+++ b/qiling/os/windows/structs.py
@@ -1072,6 +1072,13 @@ class LdrDataTableEntry(Struct):
return LdrDataTableEntry()
+class FILETIME(ctypes.LittleEndianStructure):
+ _fields_ = (
+ ('dwLowDateTime', ctypes.c_uint32),
+ ('dwHighDateTime', ctypes.c_int32)
+ )
+
+
class WindowsStruct:
def __init__(self, ql):
@@ -1102,14 +1109,14 @@ def generic_write(self, addr: int, attributes: list):
(val, size, endianness, typ) = elem
if typ == int:
value = val.to_bytes(size, endianness)
- self.ql.log.debug("Writing to %d with value %s" % (addr + already_written, value))
+ self.ql.log.debug("Writing to %#x with value %s" % (addr + already_written, value))
self.ql.mem.write(addr + already_written, value)
elif typ == bytes:
if isinstance(val, bytearray):
value = bytes(val)
else:
value = val
- self.ql.log.debug("Writing at addr %d value %s" % (addr + already_written, value))
+ self.ql.log.debug("Writing at addr %#x value %s" % (addr + already_written, value))
self.ql.mem.write(addr + already_written, value)
elif issubclass(typ, WindowsStruct):
@@ -1126,7 +1133,7 @@ def generic_read(self, addr: int, attributes: list):
for elem in attributes:
(val, size, endianness, type) = elem
value = self.ql.mem.read(addr + already_read, size)
- self.ql.log.debug("Reading from %d value %s" % (addr + already_read, value))
+ self.ql.log.debug("Reading from %#x value %s" % (addr + already_read, value))
if type == int:
elem[0] = int.from_bytes(value, endianness)
elif type == bytes:
@@ -1731,9 +1738,9 @@ def __init__(self, ql, length=None, maxLength=None, buffer=None):
else:
self.size = self.USHORT_SIZE * 2 + 4 + self.POINTER_SIZE
- self.length = [length, self.USHORT_SIZE, "little", int, self.USHORT_SIZE]
- self.maxLength = [maxLength, self.USHORT_SIZE, "little", int, self.USHORT_SIZE]
- self.buffer = [buffer, self.POINTER_SIZE, "little", int, self.POINTER_SIZE]
+ self.length = [length, self.USHORT_SIZE, "little", int]
+ self.maxLength = [maxLength, self.USHORT_SIZE, "little", int]
+ self.buffer = [buffer, self.POINTER_SIZE, "little", int]
# typedef struct _OBJECT_TYPE_INFORMATION {
diff --git a/qiling/os/windows/thread.py b/qiling/os/windows/thread.py
index ca36a4772..3c0d3bb5b 100644
--- a/qiling/os/windows/thread.py
+++ b/qiling/os/windows/thread.py
@@ -3,23 +3,26 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
+from enum import Enum
from typing import TYPE_CHECKING, cast
from qiling import Qiling
-from qiling.const import QL_ARCH
+from qiling.const import QL_ARCH, QL_HOOK_BLOCK
from qiling.os.thread import QlThread
if TYPE_CHECKING:
from qiling.os.windows.windows import QlOsWindows
-class QlWindowsThread(QlThread):
- # static var
- ID = 0
+class THREAD_STATUS(Enum):
READY = 0
RUNNING = 1
TERMINATED = 2
- def __init__(self, ql: Qiling, status: int = 1, isFake: bool = False):
+class QlWindowsThread(QlThread):
+ # static var
+ ID = 0
+
+ def __init__(self, ql: Qiling, status: THREAD_STATUS = THREAD_STATUS.RUNNING):
super().__init__(ql)
self.ql = ql
@@ -29,56 +32,56 @@ def __init__(self, ql: Qiling, status: int = 1, isFake: bool = False):
self.waitforthreads = []
self.tls = {}
self.tls_index = 0
- self.fake = isFake
- # create new thread
- def create(self, func_addr: int, func_params: int, status: int) -> int:
- os = cast('QlOsWindows', self.ql.os)
+ # create a new thread with context
+ @classmethod
+ def create(cls, ql: Qiling, stack_size: int, func_addr: int, func_params: int, status: THREAD_STATUS) -> 'QlWindowsThread':
+ os = cast('QlOsWindows', ql.os)
+
+ thread = cls(ql, status)
# create new stack
- stack_size = 1024
new_stack = os.heap.alloc(stack_size) + stack_size
- asize = self.ql.arch.pointersize
- context = self.ql.arch.regs.save()
+ asize = ql.arch.pointersize
+ context = ql.arch.regs.save()
# set return address
- self.ql.mem.write_ptr(new_stack - asize, os.thread_manager.thread_ret_addr)
+ ql.mem.write_ptr(new_stack - asize, os.thread_manager.thread_ret_addr)
# set parameters
- if self.ql.arch.type == QL_ARCH.X86:
- self.ql.mem.write_ptr(new_stack, func_params)
- elif self.ql.arch.type == QL_ARCH.X8664:
+ if ql.arch.type == QL_ARCH.X86:
+ ql.mem.write_ptr(new_stack, func_params)
+ elif ql.arch.type == QL_ARCH.X8664:
context["rcx"] = func_params
# set eip/rip, ebp/rbp, esp/rsp
- if self.ql.arch.type == QL_ARCH.X86:
+ if ql.arch.type == QL_ARCH.X86:
context["eip"] = func_addr
context["ebp"] = new_stack - asize
context["esp"] = new_stack - asize
- elif self.ql.arch.type == QL_ARCH.X8664:
+ elif ql.arch.type == QL_ARCH.X8664:
context["rip"] = func_addr
context["rbp"] = new_stack - asize
context["rsp"] = new_stack - asize
- self.saved_context = context
- self.status = status
+ thread.saved_context = context
- return self.id
+ return thread
def suspend(self) -> None:
self.saved_context = self.ql.arch.regs.save()
def resume(self) -> None:
self.ql.arch.regs.restore(self.saved_context)
- self.status = QlWindowsThread.RUNNING
+ self.status = THREAD_STATUS.RUNNING
def stop(self) -> None:
- self.status = QlWindowsThread.TERMINATED
+ self.status = THREAD_STATUS.TERMINATED
def is_stop(self) -> bool:
- return self.status == QlWindowsThread.TERMINATED
+ return self.status == THREAD_STATUS.TERMINATED
def waitfor(self, thread: 'QlWindowsThread') -> None:
self.waitforthreads.append(thread)
@@ -88,13 +91,12 @@ def has_waitfor(self) -> bool:
# Simple Thread Manager
-class QlWindowsThreadManagement(QlThread):
+class QlWindowsThreadManagement:
TIME_SLICE = 10
def __init__(self, ql: Qiling, os: 'QlOsWindows', cur_thread: QlWindowsThread):
- super().__init__(ql)
-
self.ql = ql
+
# main thread
self.cur_thread = cur_thread
self.threads = [self.cur_thread]
@@ -110,29 +112,37 @@ def __thread_scheduler(ql: Qiling, address: int, size: int):
else:
self.icount += 1
- self.do_schedule()
+ switched = self.do_schedule()
+
+ # in case another thread was resumed, all remaining hooks should be skipped to prevent them
+ # from running with the new thread's context.
+
+ return QL_HOOK_BLOCK if switched else 0
ql.hook_code(__thread_scheduler)
def append(self, thread: QlWindowsThread):
self.threads.append(thread)
- def need_schedule(self):
- return self.cur_thread.is_stop() or (self.icount % QlWindowsThreadManagement.TIME_SLICE) == 0
+ def do_schedule(self) -> bool:
+ need_schedule = self.cur_thread.is_stop() or (self.icount % QlWindowsThreadManagement.TIME_SLICE) == 0
+ switched = False
- def do_schedule(self) -> None:
- if self.need_schedule():
+ if need_schedule:
# if there is less than one thread, this loop won't run
for i in range(1, len(self.threads)):
next_id = (self.cur_thread.id + i) % len(self.threads)
next_thread = self.threads[next_id]
# find next thread
- if next_thread.status == QlWindowsThread.RUNNING and not next_thread.has_waitfor():
+ if next_thread.status == THREAD_STATUS.RUNNING and not next_thread.has_waitfor():
if not self.cur_thread.is_stop():
self.cur_thread.suspend()
next_thread.resume()
self.cur_thread = next_thread
+ switched = True
break
+
+ return switched
diff --git a/qiling/profiles/windows.ql b/qiling/profiles/windows.ql
index 17488330a..8dbf184ba 100644
--- a/qiling/profiles/windows.ql
+++ b/qiling/profiles/windows.ql
@@ -6,7 +6,8 @@ stack_size = 0x40000
image_address = 0x400000
dll_address = 0x7ffff0000000
entry_point = 0x140000000
-KI_USER_SHARED_DATA = 0xfffff78000000000
+# KI_USER_SHARED_DATA = 0xfffff78000000000
+KI_USER_SHARED_DATA = 0x7ffe0000
[OS32]
heap_address = 0x5000000
@@ -16,7 +17,8 @@ stack_size = 0x21000
image_address = 0x400000
dll_address = 0x10000000
entry_point = 0x40000
-KI_USER_SHARED_DATA = 0xffdf0000
+# KI_USER_SHARED_DATA = 0xffdf0000
+KI_USER_SHARED_DATA = 0x7ffe0000
[CODE]
# ram_size 0xa00000 is 10MB
diff --git a/tests/test_pe.py b/tests/test_pe.py
index 176bb894b..f1cb73fb2 100644
--- a/tests/test_pe.py
+++ b/tests/test_pe.py
@@ -218,22 +218,21 @@ def __rand_name(minlen: int, maxlen: int) -> str:
def test_pe_win_x86_multithread(self):
def _t():
- thread_id = None
- def ThreadId_onEnter(ql, address, params):
+ thread_id = -1
+
+ def ThreadId_onEnter(ql: Qiling, address: int, params):
nonlocal thread_id
+
thread_id = ql.os.thread_manager.cur_thread.id
- return address, params
ql = Qiling(["../examples/rootfs/x86_windows/bin/MultiThread.exe"], "../examples/rootfs/x86_windows")
ql.os.set_api("GetCurrentThreadId", ThreadId_onEnter, QL_INTERCEPT.ENTER)
ql.run()
-
- if not ( 1<= thread_id < 255):
- return False
-
+
del ql
- return True
-
+
+ return (1 <= thread_id < 255)
+
self.assertTrue(QLWinSingleTest(_t).run())