Merge remote-tracking branch 'origin/dev' into next

This commit is contained in:
dataisland
2022-08-25 03:11:53 +08:00
99 changed files with 4125 additions and 2638 deletions
+1 -1
View File
@@ -80,7 +80,7 @@ jobs:
cd ../qiling
cd ../examples/rootfs/x86_linux/kernel && unzip -P infected m0hamed_rootkit.ko.zip
cd ../../../../
pip3 install -e .[evm]
pip3 install -e .[evm,RE]
if [ ${{ matrix.os }} == 'ubuntu-18.04' ] and [ ${{ matrix.python-version }} == '3.9' ]; then
docker run -it --rm -v ${GITHUB_WORKSPACE}:/qiling qilingframework/qiling:dev bash -c "cd tests && ./test_onlinux.sh"
+1 -1
View File
@@ -23,7 +23,7 @@
#### CI, Website,Documentations, Logo & Swags
- FOO Kevin (chfl4gs) <chfl4gs_at_qiling_io>
- SU muchen (miraisuu) <suu_at_iling_io>
- SU muchen (miraisuu) <suu_at_qiling_io>
#### Key Contributors (in no particular order)
+1 -1
View File
@@ -182,7 +182,7 @@ With qltool, easy execution can be performed:
With shellcode:
```
$ ./qltool shellcode --os linux --arch arm --hex -f examples/shellcodes/linarm32_tcp_reverse_shell.hex
$ ./qltool code --os linux --arch arm --format hex -f examples/shellcodes/linarm32_tcp_reverse_shell.hex
```
With binary file:
+1 -1
View File
@@ -15,7 +15,7 @@ ROOTFS = r"rootfs/x86_windows"
class Solver:
def __init__(self, invalid: bytes):
# create a silent qiling instance
self.ql = Qiling([rf"{ROOTFS}/bin/crackme.exe"], ROOTFS, verbose=QL_VERBOSE.OFF)
self.ql = Qiling([rf"{ROOTFS}/bin/crackme.exe"], ROOTFS, verbose=QL_VERBOSE.DISABLED)
self.ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) # take over the input to the program using a fake stdin
self.ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) # disregard program output
+11 -1
View File
@@ -9,26 +9,36 @@ sys.path.append("..")
from qiling import Qiling
def force_call_dialog_func(ql: Qiling):
# this hook is invoked after returning from DialogBoxParamA, so its
# stack frame content is still available to us.
# get DialogFunc address
lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4))
lpDialogFunc = ql.stack_read(-8)
# setup stack for DialogFunc
ql.stack_push(0)
ql.stack_push(1001)
ql.stack_push(273)
ql.stack_push(0)
ql.stack_push(0x0401018)
# force EIP to DialogFunc
ql.arch.regs.eip = lpDialogFunc
def my_sandbox(path, rootfs):
ql = Qiling(path, rootfs)
# patch the input validation code: overwrite all its breaking points
# denoted with "jne 0x401135", so it would keep going even if there
# is an error
ql.patch(0x004010B5, b'\x90\x90')
ql.patch(0x004010CD, b'\x90\x90')
ql.patch(0x0040110B, b'\x90\x90')
ql.patch(0x00401112, b'\x90\x90')
# hook the instruction after returning from DialogBoxParamA
ql.hook_address(force_call_dialog_func, 0x00401016)
ql.run()
if __name__ == "__main__":
+8 -1
View File
@@ -9,21 +9,28 @@ sys.path.append("..")
from qiling import Qiling
def force_call_dialog_func(ql: Qiling):
# this hook is invoked after returning from DialogBoxParamA, so its
# stack frame content is still available to us.
# get DialogFunc address
lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4))
lpDialogFunc = ql.stack_read(-8)
# setup stack for DialogFunc
ql.stack_push(0)
ql.stack_push(1001)
ql.stack_push(273)
ql.stack_push(0)
ql.stack_push(0x0401018)
# force EIP to DialogFunc
ql.arch.regs.eip = lpDialogFunc
def our_sandbox(path, rootfs):
ql = Qiling(path, rootfs)
# hook the instruction after returning from DialogBoxParamA
ql.hook_address(force_call_dialog_func, 0x00401016)
ql.run()
if __name__ == "__main__":
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import sys
sys.path.append('..')
from qiling import Qiling
from qiling.const import QL_VERBOSE
from qiling.extensions.r2 import R2
def func(ql: Qiling, *args, **kwargs):
ql.os.stdout.write(b"=====hooked main=====!\n")
return
def my_sandbox(path, rootfs):
ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DISASM)
# QL_VERBOSE.DISASM will be monkey-patched when r2 is available
r2 = R2(ql)
# search bytes sequence using ql.mem.search
addrs = ql.mem.search(b'llo worl') # return all matching results
print(r2.at(addrs[0])) # find corresponding flag at the address and the offset to the flag
# search string using r2
addr = r2.strings['Hello world!'].vaddr # key must be exactly same
print(addrs[0], addr)
# print xref to string "Hello world!"
print(r2.refto(addr))
# write to string using ql.mem.write
ql.mem.write(addr, b"No hello, Bye!\x00")
# get function address and hook it
ql.hook_address(func, r2.functions['main'].offset)
# enable trace powered by r2 symsmap
# r2.enable_trace()
ql.run()
if __name__ == "__main__":
my_sandbox(["rootfs/x86_windows/bin/x86_hello.exe"], "rootfs/x86_windows")
# test shellcode mode
ARM64_LIN = bytes.fromhex('420002ca210080d2400080d2c81880d2010000d4e60300aa01020010020280d2681980d2010000d4410080d2420002cae00306aa080380d2010000d4210400f165ffff54e0000010420002ca210001caa81b80d2010000d4020004d27f0000012f62696e2f736800')
print("\nLinux ARM 64bit Shellcode")
ql = Qiling(code=ARM64_LIN, archtype="arm64", ostype="linux", verbose=QL_VERBOSE.DEBUG)
r2 = R2(ql)
# disassemble 32 instructions
print(r2._cmd('pd 32'))
ql.run()
+4 -1
View File
@@ -7,23 +7,26 @@ import sys
sys.path.append("..")
from qiling import Qiling
from qiling.const import QL_INTERCEPT, QL_CALL_BLOCK, QL_VERBOSE
from qiling.const import QL_INTERCEPT, QL_CALL_BLOCK
from qiling.os.const import STRING
def my_puts_onenter(ql: Qiling):
params = ql.os.resolve_fcall_params({'s': STRING})
print(f'puts("{params["s"]}")')
return QL_CALL_BLOCK
def my_printf_onenter(ql: Qiling):
params = ql.os.resolve_fcall_params({'s': STRING})
print(f'printf("{params["s"]}")')
return QL_CALL_BLOCK
def my_puts_onexit(ql: Qiling):
print(f'after puts')
return QL_CALL_BLOCK
if __name__ == "__main__":
+8 -4
View File
@@ -11,8 +11,12 @@ from qiling.const import QL_VERBOSE
from qiling.os.const import STRING
def get_kaimendaji_password():
def my_getenv(ql, *args, **kwargs):
env = {"ID": b"000000000000000", "ethaddr": b"11:22:33:44:55:66"}
def my_getenv(ql: Qiling):
env = {
"ID" : b"000000000000000",
"ethaddr" : b"11:22:33:44:55:66"
}
params = ql.os.resolve_fcall_params({'key': STRING})
value = env.get(params["key"], b"")
@@ -22,7 +26,7 @@ def get_kaimendaji_password():
ql.arch.regs.r0 = value_addr
ql.arch.regs.arch_pc = ql.arch.regs.lr
def get_password(ql, *args, **kwargs):
def get_password(ql: Qiling):
password_raw = ql.mem.read(ql.arch.regs.r0, ql.arch.regs.r2)
password = ''
@@ -34,7 +38,7 @@ def get_kaimendaji_password():
print("The password is: %s" % password)
def partial_run_init(ql):
def partial_run_init(ql: Qiling):
# argv prepare
ql.arch.regs.arch_sp -= 0x30
arg0_ptr = ql.arch.regs.arch_sp
+10 -7
View File
@@ -7,21 +7,24 @@ import sys
sys.path.append("..")
from qiling import Qiling
from qiling.const import QL_INTERCEPT, QL_VERBOSE
from qiling.const import QL_INTERCEPT
from qiling.os.linux.syscall_nums import SYSCALL_NR
def write_onenter(ql: Qiling, arg1, arg2, arg3, *args):
def write_onenter(ql: Qiling, fd: int, buf: int, count: int):
print("enter write syscall!")
ql.arch.regs.rsi = arg2 + 1
ql.arch.regs.rdx = arg3 - 1
def write_onexit(ql: Qiling, arg1, arg2, arg3, *args):
ql.arch.regs.rsi = buf + 1
ql.arch.regs.rdx = count - 1
def write_onexit(ql: Qiling, fd: int, buf: int, count: int, retval: int):
print("exit write syscall!")
ql.arch.regs.rax = arg3 + 1
ql.arch.regs.rax = count + 1
if __name__ == "__main__":
ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG)
ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux")
ql.os.set_syscall(SYSCALL_NR.write, write_onenter, QL_INTERCEPT.ENTER)
ql.os.set_syscall(SYSCALL_NR.write, write_onexit, QL_INTERCEPT.EXIT)
ql.run()
+11 -4
View File
@@ -10,13 +10,20 @@ from qiling import Qiling
from qiling.const import QL_VERBOSE
if __name__ == "__main__":
ql = Qiling(["rootfs/x8664_linux/bin/sleep_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEFAULT)
def dump(ql, *args, **kw):
ql.save(reg=False, cpu_context=True, snapshot="/tmp/snapshot.bin")
ql.emu_stop()
ql = Qiling(["rootfs/x8664_linux/bin/sleep_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG)
X64BASE = int(ql.profile.get("OS64", "load_address"), 16)
ql.hook_address(dump, X64BASE + 0x1094)
ql.run()
ql = Qiling(["rootfs/x8664_linux/bin/sleep_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DISASM)
# load base address from profile file
X64BASE = int(ql.profile.get("OS64", "load_address"), 16)
ql.restore(snapshot="/tmp/snapshot.bin")
# set execution starting and ending points
begin_point = X64BASE + 0x109e
end_point = X64BASE + 0x10bc
ql.run(begin=begin_point, end=end_point)
ql.run(begin = begin_point, end = end_point)
+7 -7
View File
@@ -119,16 +119,16 @@ def hook_WriteFile(ql: Qiling, address: int, params):
if hFile == 0x13371337:
buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite)
try:
r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer)
nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer)
ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4)
except Exception as e:
except Exception:
ql.log.exception("")
print("Exception = %s" % str(e))
r = 1
if r:
return 1
r = False
else:
return 0
r = True
return int(r)
else:
return _WriteFile(ql, address, params)
+2
View File
@@ -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
-12
View File
@@ -4,17 +4,5 @@ entry_point = 0x80800000
heap_size = 0x300000
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
current_path = /
+12 -6
View File
@@ -16,12 +16,18 @@ def my_abort(msg):
os.abort()
def enable_sanitized_heap(ql, fault_rate=0):
ql.os.heap = QlSanitizedMemoryHeap(ql, ql.os.heap, fault_rate=fault_rate)
heap = QlSanitizedMemoryHeap(ql, ql.os.heap, fault_rate=fault_rate)
ql.os.heap.oob_handler = lambda *args: my_abort("Out-of-bounds read detected")
ql.os.heap.bo_handler = lambda *args: my_abort("Buffer overflow/underflow detected")
ql.os.heap.bad_free_handler = lambda *args: my_abort("Double free or bad free detected")
ql.os.heap.uaf_handler = lambda *args: my_abort("Use-after-free detected")
heap.oob_handler = lambda *args: my_abort(f'Out-of-bounds read detected')
heap.bo_handler = lambda *args: my_abort(f'Buffer overflow/underflow detected')
heap.bad_free_handler = lambda *args: my_abort(f'Double free or bad free detected')
heap.uaf_handler = lambda *args: my_abort(f'Use-after-free detected')
# make sure future allocated buffers are not too close to UEFI data
heap.alloc(0x1000)
ql.os.heap = heap
ql.loader.dxe_context.heap = heap
def sanitized_emulate(path, rootfs, fault_type, verbose=QL_VERBOSE.DEBUG):
env = {'FaultType': fault_type}
@@ -39,7 +45,7 @@ Usage: ./uefi_santizied_heap.py <fault-type>
Valid fault types:
0 - POOL_OVERFLOW_MEMCPY
1 - POOL_UNDERFLOW_MEMCPY
2 - POOL_OVERFLOW_USER,
2 - POOL_OVERFLOW_USER
3 - POOL_UNDERFLOW_USER
4 - POOL_OOB_READ_AHEAD
5 - POOL_OOB_READ_BEHIND
+4
View File
@@ -1,5 +1,9 @@
import sys
# python 3.10 has not been supported yet in the latest blake2b-py release
if sys.version_info >= (3,10):
sys.exit('Sorry, Python > 3.10 is not supported for now')
# Ensure we can reach 1024 frames of recursion
#
EVM_RECURSION_LIMIT = 1024 * 12
+6 -1
View File
@@ -73,7 +73,12 @@ class QlArchUtils:
self._block_hook = None
if verbosity >= QL_VERBOSE.DISASM:
self._disasm_hook = self.ql.hook_code(self.disassembler)
try: # monkey patch disassembler
from qiling.extensions.r2 import R2
r2 = R2(self.ql)
self._disasm_hook = self.ql.hook_code(r2.disassembler)
except (ImportError, ModuleNotFoundError):
self._disasm_hook = self.ql.hook_code(self.disassembler)
if verbosity >= QL_VERBOSE.DUMP:
self._block_hook = self.ql.hook_block(ql_hook_block_disasm)
+3 -2
View File
@@ -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 @@ class GDTArray:
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 @@ class GDTManager:
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)
+9 -2
View File
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any, AnyStr, List, Mapping, MutableMapping, Op
# See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports
if TYPE_CHECKING:
from os import PathLike
from unicorn.unicorn import Uc
from configparser import ConfigParser
from logging import Logger
@@ -651,10 +652,13 @@ class Qiling(QlCoreHooks, QlCoreStructs):
# Map "ql_path" to any objects which implements QlFsMappedObject.
def add_fs_mapper(self, ql_path, real_dest):
def add_fs_mapper(self, ql_path: Union["PathLike", str], real_dest):
self.os.fs_mapper.add_fs_mapping(ql_path, real_dest)
# Remove "ql_path" mapping.
def remove_fs_mapper(self, ql_path: Union["PathLike", str]):
self.os.fs_mapper.remove_fs_mapping(ql_path)
# push to stack bottom, and update stack register
def stack_push(self, data):
return self.arch.stack_push(data)
@@ -703,6 +707,9 @@ class Qiling(QlCoreHooks, QlCoreStructs):
count : max emulation steps (instructions count); unlimited by default
"""
if self._arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and self._arch._init_thumb:
begin |= 1
self.uc.emu_start(begin, end, timeout, count)
if self._internal_exception is not None:
+60 -44
View File
@@ -13,9 +13,10 @@
# gdb remote protocol:
# https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html
import os, socket, re
import os, socket, re, tempfile
from logging import Logger
from typing import Iterator, Optional, Union
from typing import Iterator, Mapping, Optional, Union
import typing
from unicorn import UcError
from unicorn.unicorn_const import (
@@ -30,6 +31,8 @@ from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS
from qiling.debugger import QlDebugger
from qiling.debugger.gdb.xmlregs import QlGdbFeatures
from qiling.debugger.gdb.utils import QlGdbUtils
from qiling.os.linux.procfs import QlProcFS
from qiling.os.mapper import QlFsMappedCallable, QlFsMappedObject
# gdb logging prompt
PROMPT = r'gdb>'
@@ -63,7 +66,7 @@ class QlGdb(QlDebugger):
"""A simple gdbserver implementation.
"""
def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999):
def __init__(self, ql: Qiling, ip: str = '127.0.0.1', port: int = 9999):
super().__init__(ql)
if type(port) is str:
@@ -101,6 +104,11 @@ class QlGdb(QlDebugger):
self.features = QlGdbFeatures(self.ql.arch.type, self.ql.os.type)
self.regsmap = self.features.regsmap
# https://sourceware.org/bugzilla/show_bug.cgi?id=17760
# 42000 is the magic pid to indicate the remote process.
self.ql.add_fs_mapper(r'/proc/42000/maps', QlFsMappedCallable(QlProcFS.self_map, self.ql.mem))
self.fake_procfs: Mapping[int, typing.IO] = {}
def run(self):
server = GdbSerialConn(self.ip, self.port, self.ql.log)
killed = False
@@ -378,7 +386,7 @@ class QlGdb(QlDebugger):
if query == 'Supported':
# list of supported features excluding the multithreading-related ones
common = (
features = [
'BreakpointCommands+',
'ConditionalBreakpoints+',
'ConditionalTracepoints+',
@@ -408,23 +416,21 @@ class QlGdb(QlDebugger):
'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 +442,26 @@ class QlGdb(QlDebugger):
'qXfer:btrace-conf:read+',
'qXfer:btrace:read+',
'vContSupported+'
)
]
else:
features = (
features += [
'PacketSize=3fff',
'qXfer:spu:read+',
'qXfer:spu:write+'
)
]
return ';'.join(common + features)
# os dependent features
if not self.ql.interpreter:
# filesystem dependent features
if hasattr(self.ql.os, 'path'):
features.append('qXfer:exec-file:read+')
# 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,39 +478,27 @@ class QlGdb(QlDebugger):
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((
'<threads>',
f'<thread id="{self.ql.os.pid}" core="1" name="{self.ql.targetname}"/>',
'</threads>'
))
else:
content = ''
content = '\r\n'.join((
'<threads>',
f'<thread id="{self.ql.os.pid}" core="1" name="{self.ql.targetname}"/>',
'</threads>'
))
return f'l{content}'
elif feature == 'auxv' and op == 'read':
auxv_data = bytearray()
try:
with self.ql.os.fs_mapper.open('/proc/self/auxv', 'rb') as infile:
infile.seek(offset, 0) # SEEK_SET
auxv_data = infile.read(length)
if hasattr(self.ql.loader, 'auxv'):
nbytes = self.ql.arch.bits // 8
except FileNotFoundError:
auxv_data = b''
auxv_addr = self.ql.loader.auxv + offset
null_entry = bytes(nbytes * 2)
# keep reading until AUXV.AT_NULL is reached
while not auxv_data.endswith(null_entry):
auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes))
auxv_addr += nbytes
auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes))
auxv_addr += nbytes
return b'l' + auxv_data[:length]
return b'l' + auxv_data
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,24 @@ class QlGdb(QlDebugger):
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') and self.ql.os.fs_mapper.has_mapping(virtpath):
# Mapped object by itself is not backed with a host fd and thus a tempfile can
# 1. Make pread easy to implement and avoid duplicate code like seek, fd etc.
# 2. Avoid fd clash if we assign a generated fd.
tfile = tempfile.TemporaryFile("rb+")
tfile.write(self.ql.os.fs_mapper.open(virtpath, "rb+").read())
tfile.seek(0, os.SEEK_SET)
fd = tfile.fileno()
self.fake_procfs[fd] = tfile
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}'
@@ -609,6 +621,10 @@ class QlGdb(QlDebugger):
fd = int(fd, 16)
os.close(fd)
if fd in self.fake_procfs:
del self.fake_procfs[fd]
return 'F0'
return REPLY_EMPTY
@@ -628,7 +644,7 @@ class QlGdb(QlDebugger):
groups = subcmd.split(';')[1:]
for grp in groups:
cmd, tid = grp.split(':', maxsplit=1)
cmd, *tid = grp.split(':', maxsplit=1)
if cmd in ('c', f'C{SIGTRAP:02x}'):
return handle_c('')
+24 -105
View File
@@ -2,52 +2,20 @@
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import io
import os
from typing import TextIO
from qiling.os.posix import stat
class SimpleStringBuffer(TextIO):
class SimpleStringBuffer(io.BytesIO):
"""Simple FIFO pipe.
"""
def __init__(self):
self.buff = bytearray()
def read(self, n: int = -1) -> bytes:
if n == -1:
ret = self.buff
rem = bytearray()
else:
ret = self.buff[:n]
rem = self.buff[n:]
self.buff = rem
return bytes(ret)
def readline(self, limit: int = -1) -> bytes:
ret = bytearray()
while not (ret.endswith(b'\n') or len(ret) == limit):
ret.extend(self.read(1))
return bytes(ret)
def write(self, s: bytes) -> int:
self.buff.extend(s)
return len(s)
def flush(self) -> None:
pass
def writable(self) -> bool:
return True
def readable(self) -> bool:
return True
super().__init__()
# Compatible with old implementation
def seek(self, offset: int, origin: int = 0) -> int:
# Imitate os.lseek
raise OSError("Illega Seek")
@@ -55,9 +23,22 @@ class SimpleStringBuffer(TextIO):
def seekable(self) -> bool:
return False
def write(self, buf: bytes) -> int:
# For the FIFO stream, the write doesn't change pos.
pos = super().tell()
super().seek(0, os.SEEK_END)
ret = super().write(buf)
super().seek(pos)
return ret
# Compatible with previous TextIO
@property
def name(self):
return None
class SimpleStreamBase:
def __init__(self, fd: int, *args):
super().__init__(*args)
def __init__(self, fd: int):
super().__init__()
self.__fd = fd
self.__closed = False
@@ -65,6 +46,7 @@ class SimpleStreamBase:
def close(self) -> None:
self.__closed = True
@property
def closed(self) -> bool:
return self.__closed
@@ -99,72 +81,9 @@ class NullOutStream(SimpleStreamBase):
def writable(self) -> bool:
return True
class SimpleBufferedStream(TextIO):
class SimpleBufferedStream(io.BytesIO):
"""Simple buffered IO.
"""
def __init__(self):
self.buff = bytearray()
self.cur = 0
def seek(self, offset: int, origin: int = 0) -> int:
if origin == 0: # SEEK_SET
base = 0
elif origin == 1: # SEEK_CUR
base = self.cur
else: # SEEK_END
base = len(self.buff)
if base + offset >= len(self.buff):
self.cur = len(self.buff)
elif base + offset <= 0:
self.cur = 0
else:
self.cur = base + offset
return self.cur
def tell(self) -> int:
return self.cur
def read(self, n: int = -1) -> bytes:
if n == -1:
ret = self.buff[self.cur:]
self.cur = len(self.buff)
else:
ret = self.buff[self.cur:self.cur + n]
if self.cur + n >= len(self.buff):
self.cur = len(self.buff)
else:
self.cur = self.cur + n
return bytes(ret)
def readline(self, limit: int = -1) -> bytes:
ret = bytearray()
while not (ret.endswith(b'\n') or len(ret) == limit):
ret.extend(self.read(1))
return bytes(ret)
def write(self, s: bytes) -> int:
self.buff = self.buff[:self.cur]
self.buff.extend(s)
self.cur += len(s)
return len(s)
def flush(self) -> None:
pass
def writable(self) -> bool:
return True
def readable(self) -> bool:
return True
def seekable(self) -> bool:
return True
super.__init__()
+1
View File
@@ -0,0 +1 @@
from .r2 import R2
+305
View File
@@ -0,0 +1,305 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import ctypes
import json
import re
import libr
from dataclasses import dataclass, field, fields
from functools import cached_property, wraps
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Pattern, Tuple, Union
from qiling.const import QL_ARCH
from qiling.extensions import trace
from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL
if TYPE_CHECKING:
from qiling.core import Qiling
def perm2uc(permstr: str) -> int:
'''convert "-rwx" to unicorn const'''
perm = UC_PROT_NONE
dic = {
"r": UC_PROT_READ,
"w": UC_PROT_WRITE,
"x": UC_PROT_EXEC,
}
for ch in permstr:
perm += dic.get(ch, 0)
return perm
class R2Data:
def __init__(self, **kwargs):
names = set([f.name for f in fields(self)])
for k, v in kwargs.items():
if k in names:
setattr(self, k, v)
@dataclass(unsafe_hash=True, init=False)
class Section(R2Data):
name: str
size: int
vsize: int
paddr: int
vaddr: int
perm: int
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.perm = perm2uc(self.perm)
@dataclass(unsafe_hash=True, init=False)
class String(R2Data):
string: str
vaddr: int
paddr: int
size: int
length: int
section: str = None
@dataclass(unsafe_hash=True, init=False)
class Symbol(R2Data):
# see https://github.com/rizinorg/rizin/blob/dev/librz/include/rz_bin.h
SymbolType = Literal["NOTYPE", "OBJ", "FUNC", "FIELD", "IFACE", "METH", "STATIC", "SECT",
"FILE", "COMMON", "TLS", "NUM", "LOOS", "HIOS", "LOPROC", "HIPROC", "SPCL", "UNK"]
SymbolBind = Literal["LOCAL", "GLOBAL", "WEAK", "NUM", "LOOS", "HIOS", "LOPROC", "HIPROC", "IMPORT", "UNKNOWN"]
name: str
realname: str
bind: SymbolBind
size: int
type: SymbolType
vaddr: int
paddr: int
is_imported: bool
@dataclass(unsafe_hash=True, init=False)
class Instruction(R2Data):
offset: int
size: int
opcode: str # raw opcode
disasm: str = '' # flag resolved opcode
bytes: bytes
type: str
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bytes = bytes.fromhex(kwargs["bytes"])
@dataclass(unsafe_hash=True, init=False)
class Function(R2Data):
name: str
offset: int
size: int
signature: str
@dataclass(unsafe_hash=True, init=False)
class Flag(R2Data):
offset: int # should be addr but r2 calls it offset
name: str = ''
size: int = 0
def __lt__(self, other):
return self.offset < other.offset
@dataclass(unsafe_hash=True, init=False)
class Xref(R2Data):
XrefType = Literal["NULL", "CODE", "CALL", "DATA", "STRN", "UNKN"]
name: str
fromaddr: int # from is reserved word in Python
type: XrefType
perm: int
addr: int
refname: str
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.fromaddr = kwargs["from"]
self.perm = perm2uc(self.perm)
def __lt__(self, other):
return self.fromaddr < other.fromaddr
class R2:
def __init__(self, ql: "Qiling", baseaddr=(1 << 64) - 1, loadaddr=0):
super().__init__()
self.ql = ql
# r2 -B [baddr] set base address for PIE binaries
self.baseaddr = baseaddr
self.loadaddr = loadaddr # r2 -m [addr] map file at given address
self.analyzed = False
self._r2c = libr.r_core.r_core_new()
if ql.code:
self._setup_code(ql.code)
else:
self._setup_file(ql.path)
def _qlarch2r(self, archtype: QL_ARCH) -> str:
return {
QL_ARCH.X86: "x86",
QL_ARCH.X8664: "x86",
QL_ARCH.ARM: "arm",
QL_ARCH.ARM64: "arm",
QL_ARCH.A8086: "x86",
QL_ARCH.EVM: "evm.cs",
QL_ARCH.CORTEX_M: "arm",
QL_ARCH.MIPS: "mips",
QL_ARCH.RISCV: "riscv",
QL_ARCH.RISCV64: "riscv",
QL_ARCH.PPC: "ppc",
}[archtype]
def _setup_code(self, code: bytes):
path = f'malloc://{len(code)}'.encode()
fh = libr.r_core.r_core_file_open(self._r2c, path, UC_PROT_ALL, self.loadaddr)
libr.r_core.r_core_bin_load(self._r2c, path, self.baseaddr)
self._cmd(f'wx {code.hex()}')
# set architecture and bits for r2 asm
arch = self._qlarch2r(self.ql.arch.type)
self._cmd(f"e,asm.arch={arch},asm.bits={self.ql.arch.bits}")
def _setup_file(self, path: str):
path = path.encode()
fh = libr.r_core.r_core_file_open(self._r2c, path, UC_PROT_READ | UC_PROT_EXEC, self.loadaddr)
libr.r_core.r_core_bin_load(self._r2c, path, self.baseaddr)
def _cmd(self, cmd: str) -> str:
r = libr.r_core.r_core_cmd_str(
self._r2c, ctypes.create_string_buffer(cmd.encode("utf-8")))
return ctypes.string_at(r).decode('utf-8')
def _cmdj(self, cmd: str) -> Union[Dict, List[Dict]]:
return json.loads(self._cmd(cmd))
def aaa(fun):
@wraps(fun)
def wrapper(self):
if self.analyzed is False:
self._cmd("aaa")
self.analyzed = True
return fun(self)
return wrapper
@cached_property
def binfo(self) -> Dict[str, str]:
return self._cmdj("iIj")
@cached_property
def baddr(self) -> int:
return self.binfo["baddr"]
@cached_property
def bintype(self) -> str:
return self.binfo["bintype"]
@cached_property
def sections(self) -> Dict[str, Section]:
sec_lst = self._cmdj("iSj")
return {dic['name']: Section(**dic) for dic in sec_lst}
@cached_property
def strings(self) -> Dict[str, String]:
str_lst = self._cmdj("izzj")
return {dic['string']: String(**dic) for dic in str_lst}
@cached_property
def symbols(self) -> Dict[str, Symbol]:
sym_lst = self._cmdj("isj")
return {dic['name']: Symbol(**dic).vaddr for dic in sym_lst}
@cached_property
@aaa
def functions(self) -> Dict[str, Function]:
fcn_lst = self._cmdj("aflj")
return {dic['name']: Function(**dic) for dic in fcn_lst}
@cached_property
@aaa
def flags(self) -> List[Flag]:
return [Flag(**dic) for dic in self._cmdj("fj")]
@cached_property
@aaa
def xrefs(self) -> List[Xref]:
return [Xref(**dic) for dic in self._cmdj("axj")]
def at(self, addr: int, parse=False) -> Union[str, Tuple[str, int]]:
'''Given an address, return [name, offset] or "name + offset"'''
name = self._cmd(f'fd {addr}').strip()
if parse:
try:
name, offset = name.split(' + ')
offset = int(offset)
except ValueError: # split fail when offset=0
offset = 0
return name, offset
return name
def where(self, name: str, offset: int=0) -> int:
'''Given a name (+ offset), return its address (0 when not found)'''
if offset != 0: # name can already have offset, multiple + is allowd
name += f' + {offset}'
addr = self._cmd(f'?v {name}').strip() # 0x0 when name is not found
return int(addr, 16)
def refrom(self, addr: int) -> List[Xref]:
return [x for x in self.xrefs if x.fromaddr == addr]
def refto(self, addr: int) -> List[Xref]:
return [x for x in self.xrefs if x.addr == addr]
def read(self, addr: int, size: int) -> bytes:
hexstr = self._cmd(f"p8 {size} @ {addr}")
return bytes.fromhex(hexstr)
def dis_nbytes(self, addr: int, size: int) -> List[Instruction]:
insts = [Instruction(**dic) for dic in self._cmdj(f"pDj {size} @ {addr}")]
return insts
def disassembler(self, ql: 'Qiling', addr: int, size: int, filt: Pattern[str]=None) -> int:
'''A human-friendly monkey patch of QlArchUtils.disassembler powered by r2, can be used for hook_code
:param ql: Qiling instance
:param addr: start address for disassembly
:param size: size in bytes
:param filt: regex pattern to filter instructions
:return: progress of dissembler, should be equal to size if success
'''
anibbles = ql.arch.bits // 4
progress = 0
for inst in self.dis_nbytes(addr, size):
if inst.type.lower() == 'invalid':
break # stop disasm
name, offset = self.at(inst.offset, parse=True)
if filt is None or filt.search(name):
ql.log.info(f'{inst.offset:0{anibbles}x} [{name:20s} + {offset:#08x}] {inst.bytes.hex(" "):20s} {inst.disasm}')
progress = inst.offset + inst.size - addr
if progress < size:
ql.arch.utils.disassembler(ql, addr + progress, size - progress)
return progress
def enable_disasm(self, filt_str: str=''):
filt = re.compile(filt_str)
self.ql.hook_code(self.disassembler, filt)
def enable_trace(self, mode='full'):
# simple map from addr to flag name, cannot resolve addresses in the middle
self.ql.loader.symsmap = {flag.offset: flag.name for flag in self.flags}
if mode == 'full':
trace.enable_full_trace(self.ql)
elif mode == 'history':
trace.enable_history_trace(self.ql)
def __del__(self):
libr.r_core.r_core_free(self._r2c)
+33 -22
View File
@@ -6,6 +6,8 @@
import random
from enum import Enum
from qiling import Qiling
class CaneryType(Enum):
underflow = 0
overflow = 1
@@ -22,7 +24,9 @@ class QlSanitizedMemoryHeap():
ql.os.heap.uaf_handler = my_uaf_handler
"""
def __init__(self, ql, heap, fault_rate=0, canary_byte=b'\xCD'):
CANARY_SIZE = 4
def __init__(self, ql: Qiling, heap, fault_rate=0, canary_byte=b'\xCD'):
self.ql = ql
self.heap = heap
self.fault_rate = fault_rate
@@ -77,34 +81,39 @@ class QlSanitizedMemoryHeap():
"""
pass
def alloc(self, size):
def alloc(self, size: int):
chance = random.randint(1, 100)
if chance <= self.fault_rate:
# Fail the allocation.
return 0
# Add 8 bytes to the requested size so as to accomodate the canaries.
addr = self.heap.alloc(size + 8)
self.ql.mem.write(addr, self.canary_byte * (size + 8))
addr = self.heap.alloc(size + self.CANARY_SIZE * 2)
# Install canary hooks for overflow/underflow detection.
underflow_canary = (addr, addr + 3, CaneryType.underflow)
self.ql.hook_mem_write(self.bo_handler, begin=underflow_canary[0], end=underflow_canary[1])
self.ql.hook_mem_read(self.oob_handler, begin=underflow_canary[0], end=underflow_canary[1])
self.canaries.append(underflow_canary)
canary_begins = addr
canary_ends = canary_begins + self.CANARY_SIZE - 1
overflow_canary = (addr + 4 + size, addr + 4 + size + 3, CaneryType.overflow)
self.ql.hook_mem_write(self.bo_handler, begin=overflow_canary[0], end=overflow_canary[1])
self.ql.hook_mem_read(self.oob_handler, begin=overflow_canary[0], end=overflow_canary[1])
self.canaries.append(overflow_canary)
# install underflow canary and detection hooks
self.ql.mem.write(canary_begins, self.canary_byte * self.CANARY_SIZE)
self.ql.hook_mem_write(self.bo_handler, begin=canary_begins, end=canary_ends)
self.ql.hook_mem_read(self.oob_handler, begin=canary_begins, end=canary_ends)
self.canaries.append((canary_begins, canary_ends, CaneryType.underflow))
return (addr + 4)
canary_begins = addr + self.CANARY_SIZE + size
canary_ends = canary_begins + self.CANARY_SIZE - 1
def size(self, addr):
return self.heap.size(addr - 4)
# install overflow canary and detection hooks
self.ql.mem.write(canary_begins, self.canary_byte * self.CANARY_SIZE)
self.ql.hook_mem_write(self.bo_handler, begin=canary_begins, end=canary_ends)
self.ql.hook_mem_read(self.oob_handler, begin=canary_begins, end=canary_ends)
self.canaries.append((canary_begins, canary_ends, CaneryType.overflow))
def free(self, addr):
chunk = self.heap._find(addr - 4)
return (addr + self.CANARY_SIZE)
def size(self, addr: int):
return self.heap.size(addr - self.CANARY_SIZE)
def free(self, addr: int) -> bool:
chunk = self.heap._find(addr - self.CANARY_SIZE)
if not chunk:
self.bad_free_handler(self.ql, addr)
@@ -118,13 +127,15 @@ class QlSanitizedMemoryHeap():
# Make sure the chunk won't be re-used by the underlying heap.
self.heap.chunks.remove(chunk)
return True
def validate(self):
for (canary_begin, canary_end, canery_type) in self.canaries:
def validate(self) -> bool:
for canary_begin, canary_end, _ in self.canaries:
size = canary_end - canary_begin + 1
canary = self.ql.mem.read(canary_begin, size)
if canary.count(self.canary_byte) != len(canary):
return False
return True
+3 -2
View File
@@ -97,7 +97,7 @@ def __to_trace_line(record: TraceRecord, symsmap: Mapping[int, str] = {}) -> 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 __to_trace_line(record: TraceRecord, symsmap: Mapping[int, str] = {}) -> 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 __to_trace_line(record: TraceRecord, symsmap: Mapping[int, str] = {}) -> 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
+1
View File
@@ -0,0 +1 @@
For latest documentation, please visit https://docs.qiling.io
@@ -0,0 +1,2 @@
# This code structure is copied and modified from the coverage extension
__all__ = ["base", "tenet"]
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# This code structure is copied and modified from the coverage extension
from abc import ABC, abstractmethod
class QlBaseTrace(ABC):
"""
An abstract base class for trace collectors.
To add support for a new coverage format, just derive from this class and implement
all the methods marked with the @abstractmethod decorator.
"""
def __init__(self):
super().__init__()
@property
@staticmethod
@abstractmethod
def FORMAT_NAME():
raise NotImplementedError
@abstractmethod
def activate(self):
pass
@abstractmethod
def deactivate(self):
pass
@abstractmethod
def dump_trace(self, trace_file):
pass
@@ -0,0 +1,111 @@
import unicorn
from qiling.arch import arm64, arm, x86
class ArchRegs():
"""
Stores architecture's registers relevant to tracing in a format
universally usable by different tracers
"""
arm_registers = {
'$r0': unicorn.arm_const.UC_ARM_REG_R0,
'$r1': unicorn.arm_const.UC_ARM_REG_R1,
'$r2': unicorn.arm_const.UC_ARM_REG_R2,
'$r3': unicorn.arm_const.UC_ARM_REG_R3,
'$r4': unicorn.arm_const.UC_ARM_REG_R4,
'$r5': unicorn.arm_const.UC_ARM_REG_R5,
'$r6': unicorn.arm_const.UC_ARM_REG_R6,
'$r7': unicorn.arm_const.UC_ARM_REG_R7,
'$r8': unicorn.arm_const.UC_ARM_REG_R8,
'$r9': unicorn.arm_const.UC_ARM_REG_R9,
'$r10': unicorn.arm_const.UC_ARM_REG_R10,
'$r11': unicorn.arm_const.UC_ARM_REG_R11,
'$r12': unicorn.arm_const.UC_ARM_REG_R12,
'$sp': unicorn.arm_const.UC_ARM_REG_SP,
'$lr': unicorn.arm_const.UC_ARM_REG_LR,
'$pc': unicorn.arm_const.UC_ARM_REG_PC,
'$cpsr': unicorn.arm_const.UC_ARM_REG_CPSR
}
arm64_registers = {
"$pc": unicorn.arm64_const.UC_ARM64_REG_PC,
"$sp": unicorn.arm64_const.UC_ARM64_REG_SP,
"$x0": unicorn.arm64_const.UC_ARM64_REG_X0,
"$x1": unicorn.arm64_const.UC_ARM64_REG_X1,
"$x2": unicorn.arm64_const.UC_ARM64_REG_X2,
"$x3": unicorn.arm64_const.UC_ARM64_REG_X3,
"$x4": unicorn.arm64_const.UC_ARM64_REG_X4,
"$x5": unicorn.arm64_const.UC_ARM64_REG_X5,
"$x6": unicorn.arm64_const.UC_ARM64_REG_X6,
"$x7": unicorn.arm64_const.UC_ARM64_REG_X7,
"$x8": unicorn.arm64_const.UC_ARM64_REG_X8,
"$x9": unicorn.arm64_const.UC_ARM64_REG_X9,
"$x10": unicorn.arm64_const.UC_ARM64_REG_X10,
"$x11": unicorn.arm64_const.UC_ARM64_REG_X11,
"$x12": unicorn.arm64_const.UC_ARM64_REG_X12,
"$x13": unicorn.arm64_const.UC_ARM64_REG_X13,
"$x14": unicorn.arm64_const.UC_ARM64_REG_X14,
"$x15": unicorn.arm64_const.UC_ARM64_REG_X15,
"$x16": unicorn.arm64_const.UC_ARM64_REG_X16,
"$x17": unicorn.arm64_const.UC_ARM64_REG_X17,
"$x18": unicorn.arm64_const.UC_ARM64_REG_X18,
"$x19": unicorn.arm64_const.UC_ARM64_REG_X19,
"$x20": unicorn.arm64_const.UC_ARM64_REG_X20,
"$x21": unicorn.arm64_const.UC_ARM64_REG_X21,
"$x22": unicorn.arm64_const.UC_ARM64_REG_X22,
"$x23": unicorn.arm64_const.UC_ARM64_REG_X23,
"$x24": unicorn.arm64_const.UC_ARM64_REG_X24,
"$x25": unicorn.arm64_const.UC_ARM64_REG_X25,
"$x26": unicorn.arm64_const.UC_ARM64_REG_X26,
"$x27": unicorn.arm64_const.UC_ARM64_REG_X27,
"$x28": unicorn.arm64_const.UC_ARM64_REG_X28,
"$x29": unicorn.arm64_const.UC_ARM64_REG_X29,
}
x86_registers = {
'$eax': unicorn.x86_const.UC_X86_REG_EAX,
'$ebx': unicorn.x86_const.UC_X86_REG_EBX,
'$ecx': unicorn.x86_const.UC_X86_REG_ECX,
'$edx': unicorn.x86_const.UC_X86_REG_ECX,
'$ebp': unicorn.x86_const.UC_X86_REG_EBP,
'$esp': unicorn.x86_const.UC_X86_REG_ESP,
'$esi': unicorn.x86_const.UC_X86_REG_ESI,
'$edi': unicorn.x86_const.UC_X86_REG_EDI,
'$eip': unicorn.x86_const.UC_X86_REG_EIP,
}
x86_64_registers = {
'$rax': unicorn.x86_const.UC_X86_REG_RAX ,
'$rbx': unicorn.x86_const.UC_X86_REG_RBX,
'$rcx': unicorn.x86_const.UC_X86_REG_RCX,
'$rdx': unicorn.x86_const.UC_X86_REG_RCX,
'$rbp': unicorn.x86_const.UC_X86_REG_RBP,
'$rsp': unicorn.x86_const.UC_X86_REG_RSP,
'$rsi': unicorn.x86_const.UC_X86_REG_RSI,
'$rdi': unicorn.x86_const.UC_X86_REG_RDI,
'$rip': unicorn.x86_const.UC_X86_REG_RIP,
'$r8': unicorn.x86_const.UC_X86_REG_R8,
'$r9': unicorn.x86_const.UC_X86_REG_R9,
'$r10': unicorn.x86_const.UC_X86_REG_R10,
'$r11': unicorn.x86_const.UC_X86_REG_R11,
'$r12': unicorn.x86_const.UC_X86_REG_R12,
'$r13': unicorn.x86_const.UC_X86_REG_R13,
'$r14': unicorn.x86_const.UC_X86_REG_R14,
'$r15': unicorn.x86_const.UC_X86_REG_R15,
}
def __init__(self, arch):
if isinstance(arch, arm.QlArchARM):
self.registers = self.arm_registers
self.pc_key = "$pc"
elif isinstance(arch,arm64.QlArchARM64):
self.registers = self.arm64_registers
self.pc_key = "$pc"
elif isinstance(arch,x86.QlArchX86):
self.registers = self.x86_registers
self.pc_key = "$eip"
elif isinstance(arch,x86.QlArchX8664):
self.registers = self.x86_64_registers
self.pc_key = "$rip"
else:
raise("Unsupported arch")
+103
View File
@@ -0,0 +1,103 @@
# This code structure is copied and modified from the coverage extension
import qiling
from qiling.const import QL_ENDIAN
from .base import QlBaseTrace
from .registers import ArchRegs
from unicorn.unicorn_const import UC_MEM_READ, UC_MEM_WRITE
class QlDrTrace(QlBaseTrace):
"""
Traces emulation and puts it into a format viewable in Tenet
IDAPro plugin Tenet: https://github.com/gaasedelen/tenet
"""
FORMAT_NAME = "tenet"
def __init__(self, ql: qiling.Qiling):
super().__init__()
self.ql = ql
self.deltas = []
self.current_delta = []
self.current_pc = 0x0
self.arch_regs = ArchRegs(ql.arch)
self.register_values= dict()
# Initialize with ridiculous value so first delta isn't missed
for register in self.arch_regs.registers:
self.register_values[register] = 0xFEEDBABE
def _add_delta(self):
# Cover glitch cases where nothing changed
if self.current_delta != []:
# Join all delta fragments into delta line and append
self.deltas.append(",".join(self.current_delta))
self.current_delta = []
return
@staticmethod
def mem_access_callback(ql, access, address, size, value, self):
access_type = None
# Set delta based on access type
if access == UC_MEM_READ:
# Since we are reading memory, we just read it ourselves
access_type = "mr"
value = ql.mem.read(address, size)
delta = f"{access_type}={hex(address)}:{value.hex()}"
elif access == UC_MEM_WRITE:
# Hook is before it's written, so we have to use the "value"
access_type = "mw"
if ql.arch.endian == QL_ENDIAN.EL:
endian = 'little'
else:
endian = 'big'
if value < 0:
sign = True
else:
sign = False
value = int.to_bytes(value, size, endian, signed=sign)
delta = f"{access_type}={hex(address)}:{value.hex()}"
else:
print("Invalid access type")
return
# <ACCESS_TYPE>=<ACCESS_ADDRESS>:<HEX_BYTE_0><HEX_BYTE_1><HEX_BYTE_2>...
self.current_delta.append(delta)
return
@staticmethod
def code_callback(ql, address, size, self):
# Check if PC changed for next delta
pc = ql.arch.regs.read(self.arch_regs.registers[self.arch_regs.pc_key])
if pc != self.current_pc:
self._add_delta()
self.current_pc = pc
# Go through each register and see if it changed
for register in self.arch_regs.registers:
value = ql.arch.regs.read(self.arch_regs.registers[register])
if value != self.register_values[register]:
# <REG_NAME>=<REG_VALUE_AS_BASE_16>
delta = f"{register[1::]}={hex(value)}"
self.current_delta.append(delta)
# Update value
self.register_values[register] = value
return
def activate(self):
self.code_callback = self.ql.hook_code(self.code_callback, user_data=self)
self.mem_write_callback = self.ql.hook_mem_read(self.mem_access_callback, user_data=self)
self.mem_read_callback = self.ql.hook_mem_write(self.mem_access_callback, user_data=self)
def deactivate(self):
self.ql.hook_del(self.code_callback)
self.ql.hook_del(self.mem_write_callback)
self.ql.hook_del(self.mem_read_callback)
def dump_trace(self, trace_file: str):
with open(trace_file, "w") as trace:
# Write out each delta on a separate line
for delta in self.deltas:
trace.write(delta + "\n")
+46
View File
@@ -0,0 +1,46 @@
# This code structure is copied and modified from the coverage extension
from contextlib import contextmanager
from .formats import *
# Returns subclasses recursively.
def get_all_subclasses(cls):
all_subclasses = []
for subclass in cls.__subclasses__():
all_subclasses.append(subclass)
all_subclasses.extend(get_all_subclasses(subclass))
return all_subclasses
class TraceFactory():
def __init__(self):
self.trace_collectors = {subcls.FORMAT_NAME:subcls for subcls in get_all_subclasses(base.QlBaseTrace)}
@property
def formats(self):
return self.trace_collectors.keys()
def get_trace_collector(self, ql, name):
return self.trace_collectors[name](ql)
factory = TraceFactory()
@contextmanager
def collect_trace(ql, name: str, trace_file: str):
"""
Context manager for emulating a given piece of code with tracing.
Example:
with collect_trace(ql, 'tenet', 'trace.0.log'):
ql.run(...)
"""
trace = factory.get_trace_collector(ql, name)
trace.activate()
try:
yield
finally:
trace.deactivate()
if trace_file:
trace.dump_trace(trace_file)
+61 -40
View File
@@ -15,7 +15,6 @@ from elftools.elf.elffile import ELFFile
from elftools.elf.relocation import RelocationHandler
from elftools.elf.sections import Symbol, SymbolTableSection
from elftools.elf.descriptions import describe_reloc_type
from elftools.elf.segments import InterpSegment
from unicorn.unicorn_const import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC
from qiling import Qiling
@@ -100,7 +99,7 @@ class QlLoaderELF(QlLoader):
# is it a driver?
if elftype == 'ET_REL':
self.load_driver(elffile, stack_address + stack_size)
self.load_driver(elffile, stack_address + stack_size, loadbase=0x8000000)
self.ql.hook_code(hook_kernel_api)
# is it an executable?
@@ -271,7 +270,7 @@ class QlLoaderELF(QlLoader):
Top of stack remains aligned to pointer size
"""
data = (s if isinstance(s, bytes) else s.encode("utf-8")) + b'\x00'
data = s.encode('utf-8') + b'\x00'
top = self.ql.mem.align(top - len(data), self.ql.arch.pointersize)
self.ql.mem.write(top, data)
@@ -365,28 +364,31 @@ class QlLoaderELF(QlLoader):
# map vsyscall section for some specific needs
if self.ql.arch.type == QL_ARCH.X8664 and self.ql.os.type == QL_OS.LINUX:
_vsyscall_addr = int(self.profile.get('vsyscall_address'), 0)
_vsyscall_size = int(self.profile.get('vsyscall_size'), 0)
vsyscall_addr = self.profile.getint('vsyscall_address')
if self.ql.mem.is_available(_vsyscall_addr, _vsyscall_size):
vsyscall_ids = (
SYSCALL_NR.gettimeofday,
SYSCALL_NR.time,
SYSCALL_NR.getcpu
)
# each syscall should be 1KiB away
entry_size = 1024
vsyscall_size = self.ql.mem.align_up(len(vsyscall_ids) * entry_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]")
self.ql.mem.write(_vsyscall_addr, _vsyscall_size * b'\xcc')
self.ql.mem.map(vsyscall_addr, vsyscall_size, info="[vsyscall]")
assembler = self.ql.arch.assembler
def __assemble(asm: str) -> bytes:
bs, _ = assembler.asm(asm)
return bytes(bs)
_vsyscall_ids = (
SYSCALL_NR.gettimeofday,
SYSCALL_NR.time,
SYSCALL_NR.getcpu
)
for i, scid in enumerate(vsyscall_ids):
entry = __assemble(f'mov rax, {scid:#x}; syscall; ret')
for i, scid in enumerate(_vsyscall_ids):
self.ql.mem.write(_vsyscall_addr + i * 1024, __assemble(f'mov rax, {scid:#x}; syscall; ret'))
self.ql.mem.write(vsyscall_addr + i * entry_size, entry.ljust(entry_size, b'\xcc'))
def lkm_get_init(self, elffile: ELFFile) -> int:
"""Get file offset of the init_module function.
@@ -400,7 +402,7 @@ class QlLoaderELF(QlLoader):
if syms:
sym = syms[0]
addr = sym['st_value'] + elffile.get_section(sym['st_shndx'])['sh_offset']
self.ql.log.info(f'init_module = {addr:#x}')
return addr
raise QlErrorELFFormat('invalid module: symbol init_module not found')
@@ -479,6 +481,10 @@ class QlLoaderELF(QlLoader):
rev_reloc_symbols[symbol_name] = self.ql.os.hook_addr
sym_offset = self.ql.os.hook_addr - mem_start
self.ql.os.hook_addr += self.ql.arch.pointersize
elif _symbol['st_shndx'] == 'SHN_ABS':
rev_reloc_symbols[symbol_name] = _symbol['st_value']
else:
# local symbol
_section = elffile.get_section(_symbol['st_shndx'])
@@ -521,12 +527,12 @@ class QlLoaderELF(QlLoader):
elif desc in ('R_386_PC32', 'R_386_PLT32'):
val = ql.mem.read_ptr(loc, 4)
val = rev_reloc_symbols[symbol_name] + val - loc
val += rev_reloc_symbols[symbol_name] - loc
ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4)
elif desc in ('R_386_32', 'R_MIPS_32'):
val = ql.mem.read_ptr(loc, 4)
val = rev_reloc_symbols[symbol_name] + val
val += rev_reloc_symbols[symbol_name]
ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4)
elif desc == 'R_MIPS_HI16':
@@ -551,23 +557,9 @@ class QlLoaderELF(QlLoader):
def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> None:
elfdata_mapping = self.get_elfdata_mapping(elffile)
# Determine the range of memory space opened up
# mem_start = -1
# mem_end = -1
#
# for i in super().parse_program_header(ql):
# if i['p_type'] == PT_LOAD:
# if mem_start > i['p_vaddr'] or mem_start == -1:
# mem_start = i['p_vaddr']
# if mem_end < i['p_vaddr'] + i['p_memsz'] or mem_end == -1:
# mem_end = i['p_vaddr'] + i['p_memsz']
#
# mem_start = int(mem_start // 0x1000) * 0x1000
# mem_end = int(mem_end // 0x1000 + 1) * 0x1000
# FIXME
mem_start = 0x1000
mem_end = mem_start + (len(elfdata_mapping) // 0x1000 + 1) * 0x1000
# FIXME: determine true memory boundaries, taking relocation into account (if requested)
mem_start = 0
mem_end = mem_start + self.ql.mem.align_up(len(elfdata_mapping), 0x1000)
# map some memory to intercept external functions of Linux kernel
self.ql.mem.map(API_HOOK_MEM, 0x1000, info="[api_mem]")
@@ -579,15 +571,17 @@ class QlLoaderELF(QlLoader):
self.ql.mem.map(loadbase + mem_start, mem_end - mem_start, info=self.ql.path)
self.ql.mem.write(loadbase + mem_start, elfdata_mapping)
entry_point = self.lkm_get_init(elffile) + loadbase + mem_start
init_module = self.lkm_get_init(elffile) + loadbase + mem_start
self.ql.log.debug(f'init_module : {init_module:#x}')
self.brk_address = mem_end + loadbase
# Set MMAP addr
mmap_address = int(self.profile.get('mmap_address'), 0)
mmap_address = self.profile.getint('mmap_address')
self.ql.log.debug(f'mmap_address is : {mmap_address:#x}')
# self.ql.os.elf_entry = self.elf_entry = loadbase + elfhead['e_entry']
self.ql.os.entry_point = self.entry_point = entry_point
self.ql.os.entry_point = self.entry_point = init_module
self.elf_entry = self.ql.os.elf_entry = self.ql.os.entry_point
self.stack_address = self.ql.mem.align(stack_addr, self.ql.arch.pointersize)
@@ -625,6 +619,25 @@ class QlLoaderELF(QlLoader):
self.import_symbols[self.ql.os.hook_addr + 2 * self.ql.arch.pointersize] = hook_sys_open
def get_elfdata_mapping(self, elffile: ELFFile) -> bytes:
# from io import BytesIO
#
# rh = RelocationHandler(elffile)
#
# for sec in elffile.iter_sections():
# rs = rh.find_relocations_for_section(sec)
#
# if rs is not None:
# ss = BytesIO(sec.data())
# rh.apply_section_relocations(ss, rs)
#
# # apply changes to stream
# elffile.stream.seek(sec['sh_offset'])
# elffile.stream.write(ss.getbuffer())
#
# TODO: need to patch hooked symbols with their hook targets
# (e.g. replace calls to 'printk' with the hooked address that
# was allocate for it)
elfdata_mapping = bytearray()
# pick up elf header
@@ -634,7 +647,15 @@ class QlLoaderELF(QlLoader):
elfdata_mapping.extend(elf_header)
# pick up loadable sections and relocate them if needed
# FIXME: normally the address of a section would be determined by its 'sh_addr' value.
# in case of a relocatable object all its sections' sh_addr will be set to zero, so
# the value in 'sh_offset' should be used to determine the final address.
# see: https://refspecs.linuxbase.org/elf/gabi4+/ch4.sheader.html
#
# here we presume this a relocatable object and don't do any relocation (that is, it
# is relocated to 0)
# pick up loadable sections
for sec in elffile.iter_sections():
if sec['sh_flags'] & SH_FLAGS.SHF_ALLOC:
# pad aggregated elf data to the offset of the current section
+91 -77
View File
@@ -248,7 +248,12 @@ class Process:
# 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}')
@@ -330,68 +335,66 @@ class Process:
return {"name": name, "address": address}
def init_teb(self):
teb_obj = make_teb(self.ql.arch.bits)
teb_struct = make_teb(self.ql.arch.bits)
teb_addr = self.structure_last_addr
peb_addr = self.ql.mem.align_up(teb_addr + ctypes.sizeof(teb_obj), 0x10)
peb_addr = self.ql.mem.align_up(teb_addr + teb_struct.sizeof(), 0x10)
teb_obj = teb_struct.volatile_ref(self.ql.mem, teb_addr)
teb_obj.StackBase = self.stack_address + self.stack_size
teb_obj.StackLimit = self.stack_address
teb_obj.TebAddress = teb_addr
teb_obj.PebAddress = peb_addr
self.ql.log.info(f'TEB is at {teb_addr:#x}')
self.ql.mem.write(teb_addr, bytes(teb_obj))
self.structure_last_addr = peb_addr
self.TEB = teb_obj
def init_peb(self):
peb_obj = make_peb(self.ql.arch.bits)
peb_struct = make_peb(self.ql.arch.bits)
peb_addr = self.structure_last_addr
ldr_addr = self.ql.mem.align_up(peb_addr + ctypes.sizeof(peb_obj), 0x10)
ldr_addr = self.ql.mem.align_up(peb_addr + peb_struct.sizeof(), 0x10)
# we must set an heap, will try to retrieve this value. Is ok to be all \x00
# we must set a heap, will try to retrieve this value. Is ok to be all \x00
peb_obj = peb_struct.volatile_ref(self.ql.mem, peb_addr)
peb_obj.ImageBaseAddress = self.pe_image_address
peb_obj.LdrAddress = ldr_addr
peb_obj.ProcessParameters = self.ql.os.heap.alloc(0x100)
peb_obj.ProcessHeap = self.ql.os.heap.alloc(0x100)
peb_obj.NumberOfProcessors = self.ql.os.profile.getint('HARDWARE', 'number_processors')
self.ql.log.info(f'PEB is at {peb_addr:#x}')
self.ql.mem.write(peb_addr, bytes(peb_obj))
self.structure_last_addr = ldr_addr
self.PEB = peb_obj
def init_ldr_data(self):
ldr_obj = make_ldr_data(self.ql.arch.bits)
ldr_cls = ldr_obj.__class__
ldr_struct = make_ldr_data(self.ql.arch.bits)
ldr_addr = self.structure_last_addr
nobj_addr = self.ql.mem.align_up(ldr_addr + ctypes.sizeof(ldr_obj), 0x10)
nobj_addr = self.ql.mem.align_up(ldr_addr + ldr_struct.sizeof(), 0x10)
ldr_obj.InLoadOrderModuleList.Flink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset
ldr_obj.InLoadOrderModuleList.Blink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset
ldr_obj = ldr_struct.volatile_ref(self.ql.mem, ldr_addr)
ldr_obj.InLoadOrderModuleList.Flink = ldr_addr + ldr_struct.InLoadOrderModuleList.offset
ldr_obj.InLoadOrderModuleList.Blink = ldr_addr + ldr_struct.InLoadOrderModuleList.offset
ldr_obj.InMemoryOrderModuleList.Flink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset
ldr_obj.InMemoryOrderModuleList.Blink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset
ldr_obj.InMemoryOrderModuleList.Flink = ldr_addr + ldr_struct.InMemoryOrderModuleList.offset
ldr_obj.InMemoryOrderModuleList.Blink = ldr_addr + ldr_struct.InMemoryOrderModuleList.offset
ldr_obj.InInitializationOrderModuleList.Flink = ldr_addr + ldr_cls.InInitializationOrderModuleList.offset
ldr_obj.InInitializationOrderModuleList.Blink = ldr_addr + ldr_cls.InInitializationOrderModuleList.offset
ldr_obj.InInitializationOrderModuleList.Flink = ldr_addr + ldr_struct.InInitializationOrderModuleList.offset
ldr_obj.InInitializationOrderModuleList.Blink = ldr_addr + ldr_struct.InInitializationOrderModuleList.offset
self.ql.log.info(f'LDR is at {ldr_addr:#x}')
self.ql.mem.write(ldr_addr, bytes(ldr_obj))
self.structure_last_addr = nobj_addr
self.LDR = ldr_obj
def add_ldr_data_table_entry(self, dll_name: str):
entry_obj = make_ldr_data_table_entry(self.ql.arch.bits)
entry_cls = entry_obj.__class__
entry_struct = make_ldr_data_table_entry(self.ql.arch.bits)
entry_size = ctypes.sizeof(entry_obj)
entry_addr = self.ql.os.heap.alloc(entry_size)
entry_addr = self.ql.os.heap.alloc(entry_struct.sizeof())
def populate_unistr(obj, s: str) -> None:
encoded = s.encode('utf-16le')
@@ -407,50 +410,47 @@ class Process:
image = self.get_image_by_name(dll_name, casefold=True)
assert image, 'image should have been added to loader.images first'
entry_obj.DllBase = image.base
populate_unistr(entry_obj.FullDllName, ntpath.join(self.ql.os.winsys, dll_name))
populate_unistr(entry_obj.BaseDllName, dll_name)
with entry_struct.ref(self.ql.mem, entry_addr) as entry_obj:
entry_obj.DllBase = image.base
populate_unistr(entry_obj.FullDllName, ntpath.join(self.ql.os.winsys, dll_name))
populate_unistr(entry_obj.BaseDllName, dll_name)
# Flink
if self.ldr_list:
flink_base, flink = self.ldr_list[-1]
# Flink
if self.ldr_list:
with entry_struct.ref(self.ql.mem, self.ldr_list[-1]) as flink:
entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderLinks.Flink
entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderLinks.Flink
entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderLinks.Flink
entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderLinks.Flink
entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderLinks.Flink
entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderLinks.Flink
flink.InLoadOrderLinks.Flink = entry_addr + entry_struct.InLoadOrderLinks.offset
flink.InMemoryOrderLinks.Flink = entry_addr + entry_struct.InMemoryOrderLinks.offset
flink.InInitializationOrderLinks.Flink = entry_addr + entry_struct.InInitializationOrderLinks.offset
flink.InLoadOrderLinks.Flink = entry_addr + entry_cls.InLoadOrderLinks.offset
flink.InMemoryOrderLinks.Flink = entry_addr + entry_cls.InMemoryOrderLinks.offset
flink.InInitializationOrderLinks.Flink = entry_addr + entry_cls.InInitializationOrderLinks.offset
else:
# a volatile ref to self.PEB.LdrAddress
flink = self.LDR
else:
flink_base, flink = (self.PEB.LdrAddress, self.LDR)
entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderModuleList.Flink
entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderModuleList.Flink
entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderModuleList.Flink
entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderModuleList.Flink
entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderModuleList.Flink
entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderModuleList.Flink
flink.InLoadOrderModuleList.Flink = entry_addr + entry_struct.InLoadOrderLinks.offset
flink.InMemoryOrderModuleList.Flink = entry_addr + entry_struct.InMemoryOrderLinks.offset
flink.InInitializationOrderModuleList.Flink = entry_addr + entry_struct.InInitializationOrderLinks.offset
flink.InLoadOrderModuleList.Flink = entry_addr + entry_cls.InLoadOrderLinks.offset
flink.InMemoryOrderModuleList.Flink = entry_addr + entry_cls.InMemoryOrderLinks.offset
flink.InInitializationOrderModuleList.Flink = entry_addr + entry_cls.InInitializationOrderLinks.offset
# Blink
blink = self.LDR
# Blink
blink_base = self.PEB.LdrAddress
blink = self.LDR
entry_obj.InLoadOrderLinks.Blink = blink.InLoadOrderModuleList.Blink
entry_obj.InMemoryOrderLinks.Blink = blink.InMemoryOrderModuleList.Blink
entry_obj.InInitializationOrderLinks.Blink = blink.InInitializationOrderModuleList.Blink
entry_obj.InLoadOrderLinks.Blink = blink.InLoadOrderModuleList.Blink
entry_obj.InMemoryOrderLinks.Blink = blink.InMemoryOrderModuleList.Blink
entry_obj.InInitializationOrderLinks.Blink = blink.InInitializationOrderModuleList.Blink
blink.InLoadOrderModuleList.Blink = entry_addr + entry_struct.InLoadOrderLinks.offset
blink.InMemoryOrderModuleList.Blink = entry_addr + entry_struct.InMemoryOrderLinks.offset
blink.InInitializationOrderModuleList.Blink = entry_addr + entry_struct.InInitializationOrderLinks.offset
blink.InLoadOrderModuleList.Blink = entry_addr + entry_cls.InLoadOrderLinks.offset
blink.InMemoryOrderModuleList.Blink = entry_addr + entry_cls.InMemoryOrderLinks.offset
blink.InInitializationOrderModuleList.Blink = entry_addr + entry_cls.InInitializationOrderLinks.offset
self.ql.mem.write(flink_base, bytes(flink))
self.ql.mem.write(blink_base, bytes(blink))
self.ql.mem.write(entry_addr, bytes(entry_obj))
self.ldr_list.append((entry_addr, entry_obj))
self.ldr_list.append(entry_addr)
@staticmethod
def directory_exists(pe: pefile.PE, entry: str) -> bool:
@@ -577,53 +577,68 @@ class Process:
drv_addr = self.structure_last_addr
# PDRIVER_OBJECT DriverObject
drv_obj = make_driver_object(self.ql, drv_addr, self.ql.arch.bits)
nobj_addr = self.ql.mem.align_up(drv_addr + ctypes.sizeof(drv_obj), 0x10)
drvobj_cls = make_driver_object(self.ql.arch.bits)
nobj_addr = self.ql.mem.align_up(drv_addr + drvobj_cls.sizeof(), 0x10)
self.ql.log.info(f'DriverObject is at {drv_addr:#x}')
self.ql.mem.write(drv_addr, bytes(drv_obj))
# note: driver object is volatile; no need to flush its contents to mem
self.structure_last_addr = nobj_addr
self.driver_object_address = drv_addr
self.driver_object = drv_obj
self.driver_object = drvobj_cls.volatile_ref(self.ql.mem, drv_addr)
def init_registry_path(self):
regpath_addr = self.structure_last_addr
# PUNICODE_STRING RegistryPath
regpath_obj = make_unicode_string(self.ql.arch.bits,
ucstrtype = make_unicode_string(self.ql.arch.bits)
regpath_obj = ucstrtype(
Length=0,
MaximumLength=0,
Buffer=regpath_addr
Buffer=regpath_addr # FIXME: pointing to self? this does not seem right
)
nobj_addr = self.ql.mem.align_up(regpath_addr + ctypes.sizeof(regpath_obj), 0x10)
nobj_addr = self.ql.mem.align_up(regpath_addr + ucstrtype.sizeof(), 0x10)
self.ql.log.info(f'RegistryPath is at {regpath_addr:#x}')
self.ql.mem.write(regpath_addr, bytes(regpath_obj))
regpath_obj.save_to(self.ql.mem, regpath_addr)
self.structure_last_addr = nobj_addr
self.regitry_path_address = regpath_addr
def init_eprocess(self):
eproc_obj = make_eprocess(self.ql.arch.bits)
eproc_addr = self.structure_last_addr
nobj_addr = self.ql.mem.align_up(eproc_addr + ctypes.sizeof(eproc_obj), 0x10)
self.ql.mem.write(eproc_addr, bytes(eproc_obj))
eproc_struct = make_eprocess(self.ql.arch.bits)
nobj_addr = self.ql.mem.align_up(eproc_addr + eproc_struct.sizeof(), 0x10)
with eproc_struct.ref(self.ql.mem, eproc_addr) as eproc_obj:
eproc_obj.dummy = b''
self.structure_last_addr = nobj_addr
self.eprocess_address = eproc_addr
def init_ki_user_shared_data(self):
addr = self.ql.os.profile.getint(f'OS{self.ql.arch.bits}', 'KI_USER_SHARED_DATA')
sysconf = self.ql.os.profile['SYSTEM']
osconf = self.ql.os.profile[f'OS{self.ql.arch.bits}']
user_shared_data_obj = KUSER_SHARED_DATA()
user_shared_data_size = ctypes.sizeof(KUSER_SHARED_DATA)
kusd_addr = osconf.getint('KI_USER_SHARED_DATA')
kust_struct = KUSER_SHARED_DATA
self.ql.mem.map(kusd_addr, self.ql.mem.align_up(kust_struct.sizeof()), info='[kuser shared data]')
self.ql.mem.map(addr, self.ql.mem.align_up(user_shared_data_size))
self.ql.mem.write(addr, bytes(user_shared_data_obj))
# initialize an instance with a few key fields
kusd_obj = kust_struct.volatile_ref(self.ql.mem, kusd_addr)
kusd_obj.ImageNumberLow = 0x014c # IMAGE_FILE_MACHINE_I386
kusd_obj.ImageNumberHigh = 0x8664 # IMAGE_FILE_MACHINE_AMD64
kusd_obj.NtSystemRoot = self.ql.os.windir
kusd_obj.NtProductType = sysconf.getint('productType')
kusd_obj.NtMajorVersion = sysconf.getint('majorVersion')
kusd_obj.NtMinorVersion = sysconf.getint('minorVersion')
kusd_obj.KdDebuggerEnabled = 0
kusd_obj.NXSupportPolicy = 0 # NX_SUPPORT_POLICY_ALWAYSOFF
self.ql.os.KUSER_SHARED_DATA = kusd_obj
def init_security_cookie(self, pe: pefile.PE, image_base: int):
if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG'):
@@ -657,6 +672,7 @@ class QlLoaderPE(QlLoader, Process):
self.sys_dlls = (
'ntdll.dll',
'kernel32.dll',
'mscoree.dll',
'ucrtbase.dll'
)
@@ -695,7 +711,6 @@ class QlLoaderPE(QlLoader, Process):
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 +737,13 @@ class QlLoaderPE(QlLoader, Process):
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 +762,8 @@ class QlLoaderPE(QlLoader, Process):
# 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 +833,6 @@ class QlLoaderPE(QlLoader, Process):
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()
+16 -1
View File
@@ -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 @@ class QlFunctionCall:
# 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.
+3 -5
View File
@@ -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
+14
View File
@@ -14,6 +14,8 @@ from qiling.cc import QlCC, intel, arm, mips, riscv, ppc
from qiling.const import QL_ARCH, QL_OS
from qiling.os.fcall import QlFunctionCall
from qiling.os.const import *
from qiling.os.linux.procfs import QlProcFS
from qiling.os.mapper import QlFsMappedCallable
from qiling.os.posix.posix import QlOsPosix
from . import futex
@@ -118,6 +120,14 @@ class QlOsLinux(QlOsPosix):
if getattr(self.fd[i], 'close_on_exec', 0):
self.fd[i] = None
def setup_procfs(self):
self.fs_mapper.add_fs_mapping(r'/proc/self/auxv', QlFsMappedCallable(QlProcFS.self_auxv, self))
self.fs_mapper.add_fs_mapping(r'/proc/self/cmdline', QlFsMappedCallable(QlProcFS.self_cmdline, self))
self.fs_mapper.add_fs_mapping(r'/proc/self/environ', QlFsMappedCallable(QlProcFS.self_environ, self))
self.fs_mapper.add_fs_mapping(r'/proc/self/exe', QlFsMappedCallable(QlProcFS.self_exe, self))
self.fs_mapper.add_fs_mapping(r'/proc/self/maps', QlFsMappedCallable(QlProcFS.self_map, self.ql.mem))
def hook_syscall(self, ql, intno = None):
return self.load_syscall()
@@ -133,6 +143,10 @@ class QlOsLinux(QlOsPosix):
def run(self):
# do not set-up procfs for drivers and shellcode
if not self.ql.code and not self.ql.loader.is_driver:
self.setup_procfs()
if self.ql.exit_point is not None:
self.exit_point = self.ql.exit_point
+71
View File
@@ -0,0 +1,71 @@
import io
from typing import TYPE_CHECKING, AnyStr, Optional, Sized
from qiling.os.mapper import QlFsMappedObject
if TYPE_CHECKING:
from qiling.os.linux.linux import QlOsLinux
from qiling.os.memory import QlMemoryManager
class QlProcFS:
@staticmethod
def self_auxv(os: 'QlOsLinux') -> QlFsMappedObject:
nbytes = os.ql.arch.bits // 8
auxv_addr = os.ql.loader.auxv
null_entry = bytes(nbytes * 2)
auxv_data = bytearray()
# keep reading until AUXV.AT_NULL is reached
while not auxv_data.endswith(null_entry):
auxv_data.extend(os.ql.mem.read(auxv_addr, nbytes))
auxv_addr += nbytes
auxv_data.extend(os.ql.mem.read(auxv_addr, nbytes))
auxv_addr += nbytes
return io.BytesIO(bytes(auxv_data))
@staticmethod
def self_cmdline(os: 'QlOsLinux') -> QlFsMappedObject:
entries = (arg.encode('utf-8') for arg in os.ql.argv)
cmdline = b'\x00'.join(entries) + b'\x00'
return io.BytesIO(cmdline)
@staticmethod
def self_environ(os: 'QlOsLinux') -> QlFsMappedObject:
def __to_bytes(s: AnyStr) -> bytes:
if isinstance(s, str):
return s.encode('utf-8')
return s
entries = (b'='.join((__to_bytes(k), __to_bytes(v))) for k, v in os.ql.env.items())
environ = b'\x00'.join(entries) + b'\x00'
return io.BytesIO(environ)
@staticmethod
def self_exe(os: 'QlOsLinux') -> QlFsMappedObject:
with open(os.ql.path, 'rb') as exefile:
content = exefile.read()
return io.BytesIO(content)
@staticmethod
def self_map(mem: 'QlMemoryManager') -> QlFsMappedObject:
content = b""
mapinfo = mem.get_mapinfo()
for lbound, ubound, perms, label, container in mapinfo:
content += f"{lbound:x}-{ubound:x}\t{perms}p\t0\t00:00\t0\t{container if container else label}\n".encode("utf-8")
return io.BytesIO(content)
+3
View File
@@ -11,6 +11,7 @@ from abc import abstractmethod
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
@@ -593,6 +594,8 @@ class QlLinuxThreadManagement:
def stop(self):
self.ql.log.debug("[Thread Manager] Stop the world.")
self.ql.emu_stop()
global LINUX_THREAD_ID
LINUX_THREAD_ID = 2000
while len(self.threads) != 0:
t = self.threads.pop()
self.ql.log.debug(f"[Thread Manager] Thread IDs: { {t.id for t in self.threads} }")
+38 -9
View File
@@ -57,6 +57,19 @@ class QlFsMappedObject:
def name(self):
raise NotImplementedError("QlFsMappedObject property not implemented: name")
# This is a helper class to allow users to pass any class to add_fs_mapper
#
# Everytime open is called on the mapped path, cls(*args, **kwargs) will be called.
class QlFsMappedCallable:
def __init__(self, cls, *args, **kwargs) -> None:
self._args = args
self._kwargs = kwargs
self._cls = cls
def __call__(self) -> QlFsMappedObject:
return self._cls(*self._args, **self._kwargs)
class QlFsMapper:
def __init__(self, path: QlOsPath):
@@ -69,7 +82,7 @@ class QlFsMapper:
if isinstance(real_dest, str):
obj = ql_file.open(real_dest, openflags, openmode)
elif inspect.isclass(real_dest):
elif callable(real_dest):
obj = real_dest()
else:
@@ -83,7 +96,7 @@ class QlFsMapper:
if isinstance(real_dest, str):
obj = open(real_dest, openmode)
elif inspect.isclass(real_dest):
elif callable(real_dest):
obj = real_dest()
else:
@@ -101,15 +114,23 @@ class QlFsMapper:
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)
if not self.path.is_safe_host_path(host_path):
raise PermissionError(f'unsafe path: {host_path}')
return ql_file.open(host_path, openflags, openmode)
def 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)
if not self.path.is_safe_host_path(host_path):
raise PermissionError(f'unsafe path: {host_path}')
return open(host_path, openmode)
def _parse_path(self, p: Union[os.PathLike, str]) -> str:
fspath = getattr(p, '__fspath__', None)
@@ -123,18 +144,26 @@ class QlFsMapper:
return p
def add_fs_mapping(self, ql_path: Union[os.PathLike, str], real_dest: Any) -> None:
def add_fs_mapping(self, ql_path: Union[os.PathLike, str], real_dest: Union[str, QlFsMappedObject, QlFsMappedCallable]) -> None:
"""Map an object to Qiling emulated file system.
Args:
ql_path: Emulated path which should be convertable to a string or a hashable object. e.g. pathlib.Path
real_dest: Mapped object, can be a string, an object or a class.
real_dest: Mapped object, can be a string, an object or a callable(class).
string: mapped path in the host machine, e.g. '/dev/urandom' -> '/dev/urandom'
object: mapped object, will be returned each time the emulated path has been opened
class: mapped class, will be used to create a new instance each time the emulated path has been opened
class: mapped callable, will be used to create a new instance each time the emulated path has been opened
"""
ql_path = self._parse_path(ql_path)
real_dest = self._parse_path(real_dest)
self._mapping[ql_path] = real_dest
def remove_fs_mapping(self, ql_path: Union[os.PathLike, str]):
"""Remove a mapping from the fs mapper.
Args:
ql_path (Union[os.PathLike, str]): The mapped path.
"""
del self._mapping[self._parse_path(ql_path)]
+1
View File
@@ -0,0 +1 @@
+18 -1
View File
@@ -42,9 +42,9 @@ class QlOsPath:
self.cwd = cwd
# <TEMPORARY>
# 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
# </TEMPORARY>
@staticmethod
@@ -258,6 +258,23 @@ class QlOsPath:
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.
+98 -96
View File
@@ -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):
@classmethod
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):
new_s = self.__socket.dup()
new_ql_socket = ql_socket(new_s)
return new_ql_socket
pass
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)
def dup(self) -> 'ql_socket':
new_s = self.__socket.dup()
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
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)
addr = None
new_ql_socket = None
else:
new_ql_socket = ql_socket(con)
def recvmsg(self, bufsize, ancbufsize, flags):
return new_ql_socket, addr
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 @@ class ql_socket:
# 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):
pass
def dup(self) -> 'ql_pipe':
new_fd = os.dup(self.__fd)
new_ql_pipe = ql_pipe(new_fd)
return new_ql_pipe
return ql_pipe(new_fd)
+89 -71
View File
@@ -67,40 +67,57 @@ def ql_bin_to_ip(ip):
return ipaddress.ip_address(ip).compressed
def ql_unix_socket_path(ql: Qiling, sun_path: bytearray) -> str:
if sun_path[0] == 0:
# Abstract Unix namespace
# TODO: isolate from host namespace
# TODO: Windows
ql.log.warning(f'Beware! Usage of hosts abstract socket namespace {bytes(sun_path)}')
return sun_path.decode()
sun_path = sun_path.split(b'\0')[0].decode()
return ql.os.path.transform_to_real_path(sun_path)
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 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:
except KeyError:
ql.log.error(f'Cannot convert emu_socket_type {emu_socket_value} to host platform based socket_type')
raise
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
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
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:
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):
@@ -115,8 +132,7 @@ def ql_syscall_connect(ql: Qiling, connect_sockfd, connect_addr, connect_addrlen
try:
if s.family == family:
if s.family == AF_UNIX:
sun_path = sock_addr[2 : ].split(b"\x00")[0]
sun_path = ql.os.path.transform_to_real_path(sun_path.decode())
sun_path = ql_unix_socket_path(ql, sock_addr[2:])
s.connect(sun_path)
regreturn = 0
elif s.family == AF_INET:
@@ -146,29 +162,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)
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)
ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format(
emu_level_name, emu_level, emu_level_name, level))
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}")
emu_opt = optname
try:
emu_opt = optname
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 +198,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]
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)
ql.log.debug("Convert emu_optname {}:{} to host platform based optname {}:{}".format(
emu_opt_name, emu_opt, emu_opt_name, optname))
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 +227,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)
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)
ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format(
emu_level_name, emu_level, emu_level_name, level))
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}")
emu_opt = optname
try:
emu_opt = optname
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 +259,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]
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)
ql.log.debug("Convert emu_optname {}:{} to host platform based optname {}:{}".format(
emu_opt_name, emu_opt, emu_opt_name, optname))
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)
@@ -297,20 +314,19 @@ def ql_syscall_bind(ql: Qiling, bind_fd, bind_addr, bind_addrlen):
port = port + 8000
if sin_family == 1:
path = data[2 : ].split(b'\x00')[0]
path = ql.os.path.transform_to_real_path(path.decode())
path = ql_unix_socket_path(ql, data[2:])
ql.log.info(path)
ql.os.fd[bind_fd].bind(path)
# 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))
@@ -545,19 +561,22 @@ def ql_syscall_recvfrom(ql: Qiling, sockfd: int, buf: int, length: int, flags: i
ql.log.debug("%s" % tmp_buf)
sin_family = int(sock.family)
data = struct.pack("<h", sin_family)
sockaddr_out = struct.pack("<h", sin_family)
if sin_family == 1:
ql.log.debug("recvfrom() path is " + tmp_addr)
data += tmp_addr.encode()
# Abstract Unix socket path is not filled in recvfrom
ql.log.debug(f"recvfrom() path is '{tmp_addr or 'UNIX ABSTRACT NAMESPACE'}'")
if tmp_addr:
sockaddr_out += tmp_addr.encode()
else:
ql.log.debug("recvfrom() addr is %s:%d" % (tmp_addr[0], tmp_addr[1]))
data += struct.pack(">H", tmp_addr[1])
data += ipaddress.ip_address(tmp_addr[0]).packed
sockaddr_out += struct.pack(">H", tmp_addr[1])
sockaddr_out += ipaddress.ip_address(tmp_addr[0]).packed
addrlen = ql.mem.read_ptr(addrlen)
data = data[:addrlen]
sockaddr_out = sockaddr_out[:addrlen]
ql.mem.write(addr, data)
if addr:
ql.mem.write(addr, sockaddr_out)
ql.mem.write(buf, tmp_buf)
return len(tmp_buf)
@@ -600,8 +619,7 @@ def ql_syscall_sendto(ql: Qiling, sockfd: int, sendto_buf, sendto_len, sendto_fl
ql.log.debug("sendto() len is " + str(sendto_len))
if sin_family == 1:
path = data[2 : ].split(b'\x00')[0]
path = ql.os.path.transform_to_real_path(path.decode())
path = ql_unix_socket_path(ql, data[2:])
ql.log.debug("sendto() path is " + str(path))
regreturn = sock.sendto(bytes(tmp_buf), sendto_flags, path)
+1 -1
View File
@@ -17,7 +17,7 @@ def ql_syscall_writev(ql: Qiling, fd: int, vec: int, vlen: int):
regreturn += l
buf = ql.mem.read(addr, l)
ql.log.debug(f'{buf.decode()!r}')
ql.log.debug(f'{bytes(buf)}')
if hasattr(ql.os.fd[fd], 'write'):
ql.os.fd[fd].write(buf)
+38 -34
View File
@@ -143,12 +143,8 @@ def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int):
if not os.path.exists(real_path):
regreturn = -1
elif stat.S_ISFIFO(Stat(real_path).st_mode):
regreturn = 0
else:
regreturn = -1
regreturn = 0
if regreturn == -1:
ql.log.debug(f'File not found or skipped: {access_path}')
@@ -338,23 +334,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
else:
elif os.path.exists(host_path):
regreturn = 0
ql.log.debug("readlink(%s, 0x%x, 0x%x) = %d" % (relative_path, path_buff, path_buffsize, regreturn))
else:
regreturn = -1
ql.log.debug('readlink("%s", 0x%x, 0x%x) = %d' % (virt_path, path_buff, path_buffsize, regreturn))
return regreturn
@@ -376,16 +374,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,20 +392,25 @@ 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):
# cover procfs psaudo files first
# TODO: /proc/self/root, /proc/self/cwd
if virt_path == r'/proc/self/exe':
p = ql.os.path.host_to_virtual_path(ql.path)
p = p.encode('utf-8')
ql.mem.write(buf, p + b'\x00')
regreturn = len(p)
elif os.path.exists(host_path):
regreturn = 0
else:
regreturn = -1
elif relative_path == r'/proc/self/exe':
localpath = os.path.abspath(ql.path)
localpath = bytes(localpath, 'utf-8') + b'\x00'
ql.mem.write(buf, localpath)
regreturn = len(localpath) -1
else:
regreturn = 0
ql.log.debug('readlinkat(%d, "%s", 0x%x, 0x%x) = %d' % (dfd, virt_path, buf, bufsize, regreturn))
return regreturn
+244
View File
@@ -0,0 +1,244 @@
from __future__ import annotations
import ctypes
from contextlib import contextmanager
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Iterator, Type, TypeVar, Optional
if TYPE_CHECKING:
from qiling.os.memory import QlMemoryManager
class BaseStruct(ctypes.LittleEndianStructure):
"""An abstract class for C structures.
"""
T = TypeVar('T', bound='BaseStruct')
def save_to(self, mem: QlMemoryManager, address: int) -> None:
"""Store structure contents to a specified memory address.
Args:
mem: memory manager instance
address: destination address
"""
data = bytes(self)
mem.write(address, data)
@classmethod
def load_from(cls: Type[T], mem: QlMemoryManager, address: int) -> T:
"""Construct and populate a structure from saved contents.
Args:
mem: memory manager instance
address: source address
Returns: populated structure instance
"""
data = mem.read(address, cls.sizeof())
return cls.from_buffer(data)
@classmethod
def volatile_ref(cls: Type[T], mem: QlMemoryManager, address: int) -> T:
"""Refer to a memory location as a volatile structure variable.
Args:
mem : memory manager instance
address : bind address
Example:
>>> class Point(BaseStruct):
... _fields_ = [
... ('x', ctypes.c_uint32),
... ('y', ctypes.c_uint32)
... ]
>>> # bind a volatile Point structure to address `ptr`
>>> p = Point.volatile_ref(ql.mem, ptr)
... if p.x > 10: # x value is read directly from memory
... p.x = 10 # x value is written directly to memory
... # y value in memory remains unchanged
>>>
"""
# map all structure field names to their types
_fields = dict((fname, ftype) for fname, ftype, *_ in cls._fields_)
class VolatileStructRef(cls):
"""Turn a BaseStruct subclass into a volatile structure.
Field values are never cached: when retrieving a field's value, its value
is read from memory and when setting a field's value, its value is flushed
to memory.
This is useful to make sure a structure's fields are alway synced with memory.
"""
def __getattribute__(self, name: str) -> Any:
# accessing a structure field?
if name in _fields:
field = cls.__dict__[name]
ftype = _fields[name]
if issubclass(ftype, BaseStruct):
fvalue = ftype.volatile_ref(mem, address + field.offset)
else:
# load field's bytes from memory and tranform them into a value
data = mem.read(address + field.offset, field.size)
fvalue = ftype.from_buffer(data)
if hasattr(fvalue, 'value'):
fvalue = fvalue.value
# set the value to the structure in order to maintain consistency with ctypes.Structure
super().__setattr__(name, fvalue)
return fvalue
# return attribute value
return super().__getattribute__(name)
def __setattr__(self, name: str, value: Any) -> None:
# accessing a structure field?
if name in _fields:
field = cls.__dict__[name]
ftype = _fields[name]
# transform value into field bytes and write them to memory
fvalue = ftype(*value) if hasattr(ftype, '_length_') else ftype(value)
data = bytes(fvalue)
mem.write(address + field.offset, data)
# proceed to set the value to the structure in order to maintain consistency with ctypes.Structure
# set attribute value
super().__setattr__(name, value)
return VolatileStructRef()
@classmethod
@contextmanager
def ref(cls: Type[T], mem: QlMemoryManager, address: int) -> Iterator[T]:
"""A structure context manager to facilitate updating structure contents.
On context enter, a structure is created and populated from the specified memory
address. All changes to structure content are written back to memory on context
exit. If the structure content has not changed, no memory writes occur.
Args:
mem : memory manager instance
address : bind address
Example:
>>> class Point(BaseStruct):
... _fields_ = [
... ('x', ctypes.c_uint32),
... ('y', ctypes.c_uint32)
... ]
>>> # bind a Point structure to address `ptr`
>>> with Point.ref(ql.mem, ptr) as p:
... p.x = 10
... p.y = 20
>>> # p data has changed and will be written back to `ptr`
>>> # bind a Point structure to address `ptr`
>>> with Point.ref(ql.mem, ptr) as p:
... print(f'saved coordinates: {p.x}, {p.y}')
>>> # p data has not changed and nothing will be written back
"""
instance = cls.load_from(mem, address)
orig_data = hash(bytes(instance))
try:
yield instance
finally:
curr_data = hash(bytes(instance))
if curr_data != orig_data:
instance.save_to(mem, address)
@classmethod
def sizeof(cls) -> int:
"""Get structure size in bytes.
"""
return ctypes.sizeof(cls)
@classmethod
def offsetof(cls, fname: str) -> int:
"""Get field offset within the structure.
Args:
fname: field name
Returns: field offset in bytes
Raises: `AttributeError` if the specified field does not exist
"""
return getattr(cls, fname).offset
@classmethod
def memberat(cls, offset: int) -> Optional[str]:
"""Get the member name at a given offset.
Args:
offset: field offset within the structure
Returns: field name, or None if no field starts at the specified offset
"""
return next((fname for fname, *_ in cls._fields_ if cls.offsetof(fname) == offset), None)
# TODO: replace the lru_cache decorator with functools.cache when moving to Python 3.9
@lru_cache(maxsize=2)
def get_aligned_struct(archbits: int) -> Type[BaseStruct]:
"""Provide an aligned version of BaseStruct based on the underlying
architecture properties.
Args:
archbits: required alignment in bits
"""
class AlignedStruct(BaseStruct):
_pack_ = archbits // 8
return AlignedStruct
def get_aligned_union(archbits: int):
"""Provide an aligned union class based on the underlying architecture
properties. This class does not inherit the special BaseStruct methods.
FIXME: ctypes.Union endianess cannot be set arbitrarily, rather it depends
on the hosting system. ctypes.LittleEndianUnion and ctypes.BigEndianUnion
are available only starting from Python 3.11
Args:
archbits: required alignment in bits
"""
class AlignedUnion(ctypes.Union):
_pack_ = archbits // 8
return AlignedUnion
def get_native_type(archbits: int) -> Type[ctypes._SimpleCData]:
"""Select a ctypes integer type whose size matches the emulated
architecture native size.
"""
__type = {
32 : ctypes.c_uint32,
64 : ctypes.c_uint64
}
return __type[archbits]
+7 -7
View File
@@ -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.
+102 -20
View File
@@ -7,12 +7,15 @@
This module is intended for general purpose functions that are only used in qiling.os
"""
from typing import MutableMapping, Union, Sequence, MutableSequence, Tuple
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 +104,118 @@ class QlOsUtils:
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<follows>%|
(?P<flags>[-+0 #]+)?
(?P<width>[*]|[0-9]+)?
(?:.(?P<precision>[*]|[0-9]+))?
(?P<size>hh|ll|I32|I64|[hjltwzIL])?
(?P<type>[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.
"""
out = format.replace(r'%llx', r'%x')
out = out.replace(r'%p', r'%#x')
if m['follows'] == '%':
return '%%'
return out % tuple(args)
else:
flags = m['flags'] or ''
def va_list(self, format: str, ptr: int) -> MutableSequence[int]:
count = format.count("%")
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 ''
return [self.ql.mem.read_ptr(ptr + i * self.ql.arch.pointersize) for i in range(count)]
width = m['width'] or ''
def sprintf(self, buff: int, format: str, args: MutableSequence, wstring: bool = False) -> int:
out = self.__common_printf(format, args, wstring)
if width == '*':
width = f'{next(va_list)}'
prec = m['precision'] or ''
if prec == '*':
prec = f'{next(va_list)}'
if prec:
prec = f'.{prec}'
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
ucstr_struct = make_unicode_string(self.ql.arch.bits)
with ucstr_struct.ref(self.ql.mem, arg) as ucstr_obj:
typ = 's'
arg = read_string(ucstr_obj.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, params: MutableMapping, args: Sequence) -> None:
params.update((f'{QlOsUtils.ELLIPSIS_PREF}{i}', a) for i, a in enumerate(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))
return __do_update
+2
View File
@@ -100,6 +100,7 @@ LPINTERNET_BUFFERSA = POINTER
LPMESSAGEFILTER = POINTER
LPMODULEINFO = POINTER
LPNLSVERSIONINFO = POINTER
LPOSVERSIONINFOEXA = POINTER
LPOSVERSIONINFOEXW = POINTER
LPOVERLAPPED = POINTER
LPPOINT = POINTER
@@ -111,6 +112,7 @@ LPSTR = POINTER
LPSYSTEMTIME = POINTER
LPSYSTEM_INFO = POINTER
LPTHREAD_START_ROUTINE = POINTER
LPTIME_ZONE_INFORMATION = POINTER
LPTOP_LEVEL_EXCEPTION_FILTER= POINTER
LPUNKNOWN = POINTER
LPVOID = POINTER
+30 -14
View File
@@ -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 @@ class Clipboard:
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 @@ class Clipboard:
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()
# BUG: this should be the handle of the clipboard object
return 1
def __handle_uctext(a: int) -> bytes:
return self.os.utils.read_wstring(a).encode()
def get_data(self, fmt: int) -> Optional[bytes]:
if fmt not in self.formats:
# 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)
# TODO: should create a handle for the clipboard object?
return address
def get_data(self, format: int) -> Optional[bytes]:
if format not in self.formats:
return None
hWnd = self.os.thread_manager.cur_thread.id
+112 -37
View File
@@ -12,14 +12,20 @@ ERROR_FILE_NOT_FOUND = 0x2
ERROR_PATH_NOT_FOUND = 0x3
ERROR_ACCESS_DENIED = 0x5
ERROR_INVALID_HANDLE = 0x6
ERROR_GEN_FAILURE = 0x1f
ERROR_FILE_EXISTS = 0x50
ERROR_INVALID_PARAMETER = 0x57
ERROR_BUFFER_OVERFLOW = 0x6F
ERROR_INSUFFICIENT_BUFFER = 0x7A
ERROR_BAD_PATHNAME = 0xa1
ERROR_ALREADY_EXISTS = 0xB7
ERROR_MORE_DATA = 0xEA
ERROR_NO_MORE_ITEMS = 0x103
ERROR_DIRECTORY = 0x10b
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 +41,12 @@ STATUS_NO_YIELD_PERFORMED = 0x40000024
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 +604,23 @@ PF_XSAVE_ENABLED = 0x17
# ...
# 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 +631,79 @@ ThreadHideFromDebugger = 0x11
# ...
# 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
MB_OK = 0x00000000
MB_OKCANCEL = 0x00000001
MB_ABORTRETRYIGNORE = 0x00000002
MB_YESNOCANCEL = 0x00000003
MB_YESNO = 0x00000004
MB_RETRYCANCEL = 0x00000005
MB_CANCELTRYCONTINUE = 0x00000006
IDABORT = 3
IDCANCEL = 2
IDCONTINUE = 11
IDIGNORE = 5
IDNO = 7
IDOK = 1
IDRETRY = 4
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 +761,26 @@ OF_SHARE_EXCLUSIVE = 0x00000010
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
+151 -101
View File
@@ -3,6 +3,8 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Callable, Tuple
from qiling import Qiling
from qiling.os.windows.api import *
from qiling.os.windows.const import *
@@ -14,27 +16,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 +94,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 +467,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")
@@ -483,10 +498,12 @@ def hook_GetTokenInformation(ql: Qiling, address: int, params):
'pSid' : PSID
})
def hook_GetSidSubAuthorityCount(ql: Qiling, address: int, params):
sid = ql.os.handle_manager.get(params["pSid"]).obj
addr_authority_count = sid.addr + 1 # +1 because the first byte is revision
pSid = params['pSid']
return addr_authority_count
# SID address is used as its handle id
sid = ql.os.handle_manager.get(pSid).obj
return pSid + sid.offsetof('SubAuthorityCount')
# PDWORD GetSidSubAuthority(
# PSID pSid,
@@ -497,11 +514,13 @@ def hook_GetSidSubAuthorityCount(ql: Qiling, address: int, params):
'nSubAuthority' : DWORD
})
def hook_GetSidSubAuthority(ql: Qiling, address: int, params):
num = params["nSubAuthority"]
sid = ql.os.handle_manager.get(params["pSid"]).obj
addr_authority = sid.addr + 8 + (ql.arch.pointersize * num)
pSid = params['pSid']
nSubAuthority = params['nSubAuthority']
return addr_authority
# SID address is used as its handle id
sid = ql.os.handle_manager.get(pSid).obj
return pSid + sid.offsetof('SubAuthority') + (4 * nSubAuthority)
# LSTATUS RegEnumValueA(
# HKEY hKey,
@@ -664,69 +683,91 @@ def hook_StartServiceA(ql: Qiling, address: int, params):
})
def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params):
count = params["nSubAuthorityCount"]
subs = b''.join(ql.pack32(params[f'nSubAuthority{i}']) for i in range(count))
subauths = tuple(params[f'nSubAuthority{i}'] for i in range(count))
sid = Sid(ql, revision=1, subs_count=count, identifier=5, subs=subs)
sid_addr = ql.os.heap.alloc(sid.size)
sid.write(sid_addr)
sid_struct = make_sid(auth_count=len(subauths))
sid_addr = ql.os.heap.alloc(sid_struct.sizeof())
handle = Handle(obj=sid, id=sid_addr)
sid_obj = sid_struct(
Revision = 1,
SubAuthorityCount = len(subauths),
IdentifierAuthority = (5,),
SubAuthority = subauths
)
sid_obj.save_to(ql.mem, sid_addr)
handle = Handle(obj=sid_obj, id=sid_addr)
ql.os.handle_manager.append(handle)
dest = params["pSid"]
ql.mem.write_ptr(dest, sid_addr)
return 1
# Some default Sids:
__adminsid = None # Administrators (S-1-5-32-544)
__userssid = None # All Users (S-1-5-32-545)
__guestssid = None # All Users (S-1-5-32-546)
__poweruserssid = None # Power Users (S-1-5-32-547)
def __create_default_sid(ql: Qiling, subauths: Tuple[int, ...]):
sid_struct = make_sid(auth_count=len(subauths))
sid_obj = sid_struct(
Revision = 1,
SubAuthorityCount = len(subauths),
IdentifierAuthority = (5,),
SubAuthority = tuple(subauths)
)
def get_adminsid(ql):
global __adminsid
return sid_obj
if __adminsid is None:
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_ADMINS[0x220]
subs = b"\x20\x00\x00\x00\x20\x02\x00\x00"
__adminsid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2)
def singleton(func: Callable):
"""A decorator for functions that produce singleton objects.
return __adminsid
When a decorated function is called for the first time, its
singleton object will be created. The same object will be returned
on all consequent calls regardless of the passed arguments (if any).
"""
def get_userssid(ql):
global __userssid
__singleton = None
if __userssid is None:
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_USERS[0x221]
subs = b"\x20\x00\x00\x00\x21\x02\x00\x00"
__userssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2)
def wrapper(*args, **kwargs):
nonlocal __singleton
return __userssid
if __singleton is None:
__singleton = func(*args, **kwargs)
def get_guestssid(ql):
global __guestssid
return __singleton
if __guestssid is None:
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_GUESTS[0x222]
subs = b"\x20\x00\x00\x00\x22\x02\x00\x00"
__guestssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2)
return wrapper
return __guestssid
# Administrators (S-1-5-32-544)
@singleton
def __admin_sid(ql: Qiling):
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_ADMINS[0x220]
def get_poweruserssid(ql):
global __poweruserssid
return __create_default_sid(ql, (0x20, 0x220))
if __poweruserssid is None:
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_POWER_USERS[0x223]
subs = b"\x20\x00\x00\x00\x23\x02\x00\x00"
__poweruserssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2)
# All Users (S-1-5-32-545)
@singleton
def __users_sid(ql: Qiling):
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_USERS[0x221]
return __poweruserssid
return __create_default_sid(ql, (0x20, 0x221))
# All Users (S-1-5-32-546)
@singleton
def __guests_sid(ql: Qiling):
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_GUESTS[0x222]
return __create_default_sid(ql, (0x20, 0x222))
# Power Users (S-1-5-32-547)
@singleton
def __powerusers_sid(ql: Qiling):
# nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20]
# nSubAuthority1 = DOMAIN_ALIAS_RID_POWER_USERS[0x223]
return __create_default_sid(ql, (0x20, 0x223))
# BOOL WINAPI CheckTokenMembership(
@@ -740,24 +781,32 @@ def get_poweruserssid(ql):
'IsMember' : PBOOL
})
def hook_CheckTokenMembership(ql: Qiling, address: int, params):
token_handle = params["TokenHandle"]
sid = ql.os.handle_manager.get(params["SidToCheck"]).obj
TokenHandle = params['TokenHandle']
SidToCheck = params['SidToCheck']
sid = ql.os.handle_manager.get(SidToCheck).obj
IsMember = False
# If TokenHandle is NULL, CheckTokenMembership uses the impersonation token of the calling thread.
IsMember = 0
if token_handle == 0:
if not TokenHandle:
# For now, treat power users as admins
if get_adminsid(ql) == sid or get_poweruserssid(ql) == sid:
IsMember = 1 if ql.os.profile["SYSTEM"]["permission"] == "root" else 0
elif get_userssid(ql) == sid:
if __admin_sid(ql) == sid or __powerusers_sid(ql) == sid:
IsMember = ql.os.profile["SYSTEM"]["permission"] == "root"
elif __users_sid(ql) == sid:
# FIXME: is this true for all tokens? probably not...
IsMember = 1
elif get_guestssid(ql) == sid:
IsMember = 0
IsMember = True
elif __guests_sid(ql) == sid:
IsMember = False
else:
assert False, 'unimplemented'
raise NotImplementedError
else:
assert False, 'unimplemented'
ql.mem.write_ptr(params['IsMember'], IsMember)
raise NotImplementedError
ql.mem.write_ptr(params['IsMember'], int(IsMember))
return 1
@@ -768,6 +817,7 @@ def hook_CheckTokenMembership(ql: Qiling, address: int, params):
'pSid' : PSID
})
def hook_FreeSid(ql: Qiling, address: int, params):
# TODO: should also remove from ql.os.handle_manager ?
ql.os.heap.free(params["pSid"])
return 0
@@ -23,3 +23,4 @@ from .consoleapi2 import *
from .consoleapi3 import *
from .debugapi import *
from .psapi import *
from .timezoneapi import *
+11 -3
View File
@@ -8,10 +8,17 @@ from qiling.os.windows.api import *
from qiling.os.windows.const import *
from qiling.os.windows.fncc import *
def __is_debugger_present(ql: Qiling) -> int:
"""Read PEB.BeingDebugger fied to determine whether debugger
is present or not.
"""
return ql.loader.PEB.BeingDebugged
# BOOL IsDebuggerPresent();
@winsdkapi(cc=STDCALL, params={})
def hook_IsDebuggerPresent(ql: Qiling, address: int, params):
return 0
return __is_debugger_present(ql)
# BOOL CheckRemoteDebuggerPresent(
# HANDLE hProcess,
@@ -22,9 +29,10 @@ def hook_IsDebuggerPresent(ql: Qiling, address: int, params):
'pbDebuggerPresent' : PBOOL
})
def hook_CheckRemoteDebuggerPresent(ql: Qiling, address: int, params):
pointer = params["pbDebuggerPresent"]
pbDebuggerPresent = params['pbDebuggerPresent']
ql.mem.write(pointer, b'\x00')
res = __is_debugger_present(ql)
ql.mem.write(pbDebuggerPresent, ql.pack8(res))
return 1
@@ -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,
+116 -78
View File
@@ -15,7 +15,7 @@ from qiling.os.windows.api import *
from qiling.os.windows.const import *
from qiling.os.windows.fncc import *
from qiling.os.windows.handle import Handle
from qiling.os.windows.structs import Win32FindData
from qiling.os.windows.structs import FILETIME, make_win32_find_data
# DWORD GetFileType(
# HANDLE hFile
@@ -46,50 +46,67 @@ def hook_FindFirstFileA(ql: Qiling, address: int, params):
filename = params['lpFileName']
pointer = params['lpFindFileData']
if filename == 0:
if not filename:
return INVALID_HANDLE_VALUE
elif len(filename) >= MAX_PATH:
if len(filename) >= MAX_PATH:
return ERROR_INVALID_PARAMETER
target_dir = os.path.join(ql.rootfs, filename.replace("\\", os.sep))
ql.log.info('TARGET_DIR = %s' % target_dir)
real_path = ql.os.path.transform_to_real_path(filename)
host_path = ql.os.path.virtual_to_host_path(filename)
# Verify the directory is in ql.rootfs to ensure no path traversal has taken place
if not os.path.exists(real_path):
if not ql.os.path.is_safe_host_path(host_path):
ql.os.last_error = ERROR_FILE_NOT_FOUND
return INVALID_HANDLE_VALUE
# Check if path exists
filesize = 0
try:
f = ql.os.fs_mapper.open(real_path, "r")
filesize = os.path.getsize(real_path)
f = ql.os.fs_mapper.open(host_path, "r")
filesize = os.path.getsize(host_path)
except FileNotFoundError:
ql.os.last_error = ERROR_FILE_NOT_FOUND
return INVALID_HANDLE_VALUE
# Get size of the file
file_size_low = filesize & 0xffffff
file_size_high = filesize >> 32
return INVALID_HANDLE_VALUE
# Create a handle for the path
new_handle = Handle(obj=f)
ql.os.handle_manager.append(new_handle)
# Spoof filetime values
filetime = ql.pack64(datetime.now().microsecond)
# calculate file time
epoch = datetime(1601, 1, 1)
elapsed = datetime.now() - epoch
find_data = Win32FindData(
ql,
FILE_ATTRIBUTE_NORMAL,
filetime, filetime, filetime,
file_size_high, file_size_low,
0, 0,
filename,
0, 0, 0, 0,)
# number of 100-nanosecond intervals since Jan 1, 1601 utc
# where: (10 ** 9) / 100 -> (10 ** 7)
hnano = int(elapsed.total_seconds() * (10 ** 7))
find_data.write(pointer)
mask = (1 << 32) - 1
ftime = FILETIME(
(hnano >> 0) & mask,
(hnano >> 32) & mask
)
fdata_struct = make_win32_find_data(ql.arch.bits, wide=False)
with fdata_struct.ref(ql.mem, pointer) as fdata_obj:
fdata_obj.dwFileAttributes = FILE_ATTRIBUTE_NORMAL
fdata_obj.ftCreationTime = ftime
fdata_obj.ftLastAccessTime = ftime
fdata_obj.ftLastWriteTime = ftime
fdata_obj.nFileSizeHigh = (filesize >> 32) & mask
fdata_obj.nFileSizeLow = (filesize >> 0) & mask
fdata_obj.dwReserved0 = 0
fdata_obj.dwReserved1 = 0
fdata_obj.cFileName = filename
fdata_obj.cAlternateFileName = 0
fdata_obj.dwFileType = 0
fdata_obj.dwCreatorType = 0
fdata_obj.wFinderFlags = 0
return new_handle.id
@@ -190,13 +207,12 @@ def hook_WriteFile(ql: Qiling, address: int, params):
ql.os.last_error = ERROR_INVALID_HANDLE
return 0
fobj = handle.obj
data = ql.mem.read(lpBuffer, nNumberOfBytesToWrite)
if hFile == STD_OUTPUT_HANDLE:
if hFile in (STD_OUTPUT_HANDLE, STD_ERROR_HANDLE):
ql.os.stats.log_string(data.decode())
written = fobj.write(bytes(data))
written = handle.obj.write(bytes(data))
ql.mem.write_ptr(lpNumberOfBytesWritten, written, 4)
return 1
@@ -211,11 +227,10 @@ def _CreateFile(ql: Qiling, address: int, params):
# hTemplateFile = params["hTemplateFile"]
# access mask DesiredAccess
mode = ""
if dwDesiredAccess & GENERIC_WRITE:
mode += "wb"
mode = "wb"
else:
mode += "rb"
mode = "rb"
try:
f = ql.os.fs_mapper.open(s_lpFileName, mode)
@@ -270,11 +285,13 @@ def hook_CreateFileA(ql: Qiling, address: int, params):
def hook_CreateFileW(ql: Qiling, address: int, params):
return _CreateFile(ql, address, params)
def _GetTempPath(ql: Qiling, address: int, params, wide: bool):
temp_path = ntpath.join(ql.rootfs, 'Windows', 'Temp')
def _GetTempPath(ql: Qiling, address: int, params, *, wide: bool):
vtmpdir = ntpath.join(ql.os.windir, 'Temp')
htmpdir = ql.os.path.virtual_to_host_path(vtmpdir)
if not os.path.exists(temp_path):
os.makedirs(temp_path, 0o755)
if ql.os.path.is_safe_host_path(htmpdir):
if not os.path.exists(htmpdir):
os.makedirs(htmpdir, 0o755)
nBufferLength = params['nBufferLength']
lpBuffer = params['lpBuffer']
@@ -282,7 +299,7 @@ def _GetTempPath(ql: Qiling, address: int, params, wide: bool):
enc = 'utf-16le' if wide else 'utf-8'
# temp dir path has to end with a path separator
tmpdir = f'{ntpath.join(ql.os.windir, "Temp")}{ntpath.sep}'.encode(enc)
tmpdir = f'{vtmpdir}{ntpath.sep}'.encode(enc)
cstr = tmpdir + '\x00'.encode(enc)
if nBufferLength >= len(cstr):
@@ -324,29 +341,26 @@ def hook_GetTempPathA(ql: Qiling, address: int, params):
'cchBuffer' : DWORD
})
def hook_GetShortPathNameW(ql: Qiling, address: int, params):
paths = params["lpszLongPath"].split("\\")
dst = params["lpszShortPath"]
max_size = params["cchBuffer"]
res = paths[0]
lpszLongPath = params['lpszLongPath']
lpszShortPath = params['lpszShortPath']
cchBuffer = params['cchBuffer']
for path in paths[1:]:
nameAndExt = path.split(".")
name = nameAndExt[0]
ext = "" if len(nameAndExt) == 1 else "." + nameAndExt[1]
def __shorten(p: str) -> str:
name, ext = ntpath.splitext(p)
if len(name) > 8:
name = name[:6] + "~1"
return f'{(name[:6] + "~1") if len(name) > 8 else name}{ext}'
res += "\\" + name + ext
shortpath = ntpath.join(*(__shorten(elem) for elem in lpszLongPath.split(ntpath.sep)))
encoded = f'{shortpath}\x00'.encode('utf-16le')
res += "\x00"
res = res.encode("utf-16le")
if len(shortpath) > cchBuffer:
return len(shortpath) + 1
if max_size < len(res):
return len(res)
if lpszShortPath:
ql.mem.write(lpszShortPath, encoded)
ql.mem.write(dst, res)
return len(res) - 1
# on succes, return chars count excluding null-term
return len(shortpath)
# BOOL GetVolumeInformationW(
@@ -471,18 +485,20 @@ def hook_GetDiskFreeSpaceW(ql: Qiling, address: int, params):
'lpSecurityAttributes' : LPSECURITY_ATTRIBUTES
})
def hook_CreateDirectoryA(ql: Qiling, address: int, params):
path_name = params["lpPathName"]
target_dir = os.path.join(ql.rootfs, path_name.replace("\\", os.sep))
ql.log.info('TARGET_DIR = %s' % target_dir)
lpPathName = params['lpPathName']
# Verify the directory is in ql.rootfs to ensure no path traversal has taken place
real_path = ql.os.path.transform_to_real_path(path_name)
dst = ql.os.path.virtual_to_host_path(lpPathName)
if os.path.exists(real_path):
if not ql.os.path.is_safe_host_path(dst):
ql.os.last_error = ERROR_GEN_FAILURE
return 0
if os.path.exists(dst):
ql.os.last_error = ERROR_ALREADY_EXISTS
return 0
os.mkdir(real_path)
os.mkdir(dst, 0o755)
return 1
# DWORD GetFileSize(
@@ -494,13 +510,20 @@ def hook_CreateDirectoryA(ql: Qiling, address: int, params):
'lpFileSizeHigh' : LPDWORD
})
def hook_GetFileSize(ql: Qiling, address: int, params):
hFile = params["hFile"]
handle = ql.os.handle_manager.get(hFile)
if handle is None:
ql.os.last_error = ERROR_INVALID_HANDLE
return -1 # INVALID_FILE_SIZE
try:
handle = ql.os.handle_manager.get(params['hFile'])
return os.path.getsize(handle.obj.name)
except:
ql.os.last_error = ERROR_INVALID_HANDLE
return 0xFFFFFFFF #INVALID_FILE_SIZE
return -1 # INVALID_FILE_SIZE
def _CreateFileMapping(ql: Qiling, address: int, params):
hFile = params['hFile']
@@ -619,14 +642,23 @@ def hook_UnmapViewOfFile(ql: Qiling, address: int, params):
'bFailIfExists' : BOOL
})
def hook_CopyFileA(ql: Qiling, address: int, params):
lpExistingFileName = ql.os.path.transform_to_real_path(params["lpExistingFileName"])
lpNewFileName = ql.os.path.transform_to_real_path(params["lpNewFileName"])
bFailIfExists = params["bFailIfExists"]
lpExistingFileName = params['lpExistingFileName']
lpNewFileName = params['lpNewFileName']
bFailIfExists = params['bFailIfExists']
if bFailIfExists and os.path.exists(lpNewFileName):
src = ql.os.path.virtual_to_host_path(lpExistingFileName)
dst = ql.os.path.virtual_to_host_path(lpNewFileName)
if not ql.os.path.is_safe_host_path(src) or not ql.os.path.is_safe_host_path(dst):
ql.os.last_error = ERROR_GEN_FAILURE
return 0
copyfile(lpExistingFileName, lpNewFileName)
if bFailIfExists and os.path.exists(dst):
ql.os.last_error = ERROR_FILE_EXISTS
return 0
copyfile(src, dst)
return 1
# BOOL SetFileAttributesA(
@@ -663,6 +695,22 @@ def hook_SetFileApisToANSI(ql: Qiling, address: int, params):
def hook_SetFileApisToOEM(ql: Qiling, address: int, params):
pass
def _DeleteFile(ql: Qiling, address: int, params):
lpFileName = params["lpFileName"]
dst = ql.os.path.virtual_to_host_path(lpFileName)
if not ql.os.path.is_safe_host_path(dst):
ql.os.last_error = ERROR_GEN_FAILURE
return 0
try:
os.remove(dst)
except OSError:
return 0
return 1
# BOOL DeleteFileA(
# LPCSTR lpFileName
# );
@@ -670,12 +718,7 @@ def hook_SetFileApisToOEM(ql: Qiling, address: int, params):
'lpFileName' : LPCSTR
})
def hook_DeleteFileA(ql: Qiling, address: int, params):
lpFileName = ql.os.path.transform_to_real_path(params["lpFileName"])
try:
os.remove(lpFileName)
return 1
except:
return 0
return _DeleteFile(ql, address, params)
# BOOL DeleteFileW(
# LPCWSTR lpFileName
@@ -684,9 +727,4 @@ def hook_DeleteFileA(ql: Qiling, address: int, params):
'lpFileName' : LPCWSTR
})
def hook_DeleteFileW(ql: Qiling, address: int, params):
lpFileName = ql.os.path.transform_to_real_path(params["lpFileName"])
try:
os.remove(lpFileName)
return 1
except:
return 0
return _DeleteFile(ql, address, params)
+6 -6
View File
@@ -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
+37 -54
View File
@@ -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)
@@ -8,9 +8,9 @@ from qiling.os.windows.api import *
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
from qiling.os.windows.structs import Token, make_startup_info
# void ExitProcess(
# UINT uExitCode
@@ -22,14 +22,34 @@ def hook_ExitProcess(ql: Qiling, address: int, params):
ql.emu_stop()
ql.os.PE_RUN = False
def _GetStartupInfo(ql: Qiling, address: int, params):
startup_info = StartupInfo(ql, 0xc3c930, 0, 0, 0, 0x64, 0x64, 0x84, 0x80, 0xff, 0x40, 0x1, STD_INPUT_HANDLE,
STD_OUTPUT_HANDLE, STD_ERROR_HANDLE)
def _GetStartupInfo(ql: Qiling, address: int, params, *, wide: bool):
lpStartupInfo = params['lpStartupInfo']
sui_struct = make_startup_info(ql.arch.bits)
pointer = params["lpStartupInfo"]
startup_info.write(pointer)
enc = 'utf-16le' if wide else 'latin1'
desktop_title = f'QilingDesktop\x00'.encode(enc)
return 0
# TODO: fill out with real / configurable values rather than bogus / fixed ones
with sui_struct.ref(ql.mem, lpStartupInfo) as sui_obj:
sui_obj.cb = sui_struct.sizeof()
sui_obj.lpDesktop = ql.os.heap.alloc(len(desktop_title))
sui_obj.lpTitle = 0
sui_obj.dwX = 0
sui_obj.dwY = 0
sui_obj.dwXSize = 100
sui_obj.dwYSize = 100
sui_obj.dwXCountChars = 132
sui_obj.dwYCountChars = 128
sui_obj.dwFillAttribute = 0xff
sui_obj.dwFlags = 0x40
sui_obj.wShowWindow = 1
sui_obj.cbReserved2 = 0
sui_obj.lpReserved2 = 0
sui_obj.hStdInput = STD_INPUT_HANDLE
sui_obj.hStdOutput = STD_OUTPUT_HANDLE
sui_obj.hStdError = STD_ERROR_HANDLE
return STATUS_SUCCESS
# VOID WINAPI GetStartupInfoA(
# _Out_ LPSTARTUPINFO lpStartupInfo
@@ -38,7 +58,7 @@ def _GetStartupInfo(ql: Qiling, address: int, params):
'lpStartupInfo' : LPSTARTUPINFOA
})
def hook_GetStartupInfoA(ql: Qiling, address: int, params):
return _GetStartupInfo(ql, address, params)
return _GetStartupInfo(ql, address, params, wide=False)
# VOID WINAPI GetStartupInfoW(
# _Out_ LPSTARTUPINFO lpStartupInfo
@@ -47,7 +67,7 @@ def hook_GetStartupInfoA(ql: Qiling, address: int, params):
'lpStartupInfo' : LPSTARTUPINFOW
})
def hook_GetStartupInfoW(ql: Qiling, address: int, params):
return _GetStartupInfo(ql, address, params)
return _GetStartupInfo(ql, address, params, wide=True)
# DWORD TlsAlloc();
@winsdkapi(cc=STDCALL, params={})
@@ -164,20 +184,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)
+122 -12
View File
@@ -7,6 +7,7 @@ from qiling import Qiling
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
if cbMultiByte != 0 and lpMultiByteStr != 0:
ql.mem.write(lpMultiByteStr, s)
# -1 indicates the string is null-terminated. the string is
# read along with its null-terminator
elif cchWideChar == 0xffffffff:
wcstr = bytearray()
ch = 1
return len(s)
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')
encname = __encoding_name(CodePage)
errors = 'strict' if dwFlags & WC_ERR_INVALID_CHARS else 'replace'
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 params['cchWideChar'] != 0:
ql.mem.write(params['lpWideCharStr'], wide_str)
if not cbMultiByte:
# TODO: set last error
return 0
return len(wide_str)
# -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
ql.mem.write(lpWideCharStr, decoded[:cchWideChar].encode('utf-16le'))
return min(len(decoded), cchWideChar)
def __CompareString(ql: Qiling, address: int, params) -> int:
lpString1 = params["lpString1"]
+31 -28
View File
@@ -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)
handle = Handle(obj=mutex, name=name)
ql.os.handle_manager.append(handle)
if params['bInitialState']:
mutex.lock()
# FIXME: shouldn't it be 'id' instead of 'ID'?
return handle.ID
handle = Handle(obj=mutex, name=name)
ql.os.handle_manager.append(handle)
return handle.id
# HANDLE CreateEventA(
# LPSECURITY_ATTRIBUTES lpEventAttributes,
+46 -13
View File
@@ -10,7 +10,7 @@ from qiling import Qiling
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, SYSTEMTIME, make_system_info
# NOT_BUILD_WINDOWS_DEPRECATE DWORD GetVersion(
# );
@@ -40,13 +40,24 @@ def hook_GetVersionExW(ql: Qiling, address: int, params):
return __GetVersionEx(ql, address, params)
def __GetSystemInfo(ql: Qiling, address: int, params):
pointer = params["lpSystemInfo"]
lpSystemInfo = params['lpSystemInfo']
# FIXME: dll_size no longer reflects the upper bound of used memory; should find a better way to specify max_address
system_info = SystemInfo(ql, 0, ql.mem.pagesize, ql.loader.pe_image_address,
ql.loader.dll_address + ql.loader.dll_size, 0x3, 0x4, 0x24a, ql.mem.pagesize * 10,
0x6, 0x4601)
system_info.write(pointer)
sysinfo_struct = make_system_info(ql.arch.bits)
# FIXME:
# - load configurable values rather than fixed / bogus ones
# - loader.dll_size no longer reflects the upper bound of used memory; should find a better way to specify max_address
with sysinfo_struct.ref(ql.mem, lpSystemInfo) as si:
si.dwOemId = 0
si.dwPageSize = ql.mem.pagesize
si.lpMinimumApplicationAddress = ql.loader.pe_image_address
si.lpMaximumApplicationAddress = ql.loader.dll_address + ql.loader.dll_size
si.dwActiveProcessorMask = 0x3
si.dwNumberOfProcessors = 0x4
si.dwProcessorType = 0x24a
si.dwAllocationGranularity = ql.mem.pagesize * 10
si.wProcessorLevel = 0x6
si.wProcessorRevision = 0x4601
return 0
@@ -66,11 +77,18 @@ def hook_GetSystemInfo(ql: Qiling, address: int, params):
'lpSystemTime' : LPSYSTEMTIME
})
def hook_GetLocalTime(ql: Qiling, address: int, params):
ptr = params['lpSystemTime']
d = datetime.now()
lpSystemTime = params['lpSystemTime']
now = datetime.now()
system_time = SystemTime(ql, d.year, d.month, d.isoweekday(), d.day, d.hour, d.minute, d.second, d.microsecond // 1000)
system_time.write(ptr)
with SYSTEMTIME.ref(ql.mem, lpSystemTime) as st:
st.wYear = now.year
st.wMonth = now.month
st.wDayOfWeek = now.isoweekday()
st.wDay = now.day
st.wHour = now.hour
st.wMinute = now.minute
st.wSecond = now.second
st.wMilliseconds = now.microsecond // 1000
return 0
@@ -81,8 +99,23 @@ 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
ftime = FILETIME(
(hnano >> 0) & mask,
(hnano >> 32) & mask
)
ftime.save_to(ql.mem, ptr)
# DWORD GetTickCount(
# );
@@ -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
+9 -2
View File
@@ -48,5 +48,12 @@ def hook_Process32FirstW(ql: Qiling, address: int, params):
'lppe' : LPPROCESSENTRY32W
})
def hook_Process32NextW(ql: Qiling, address: int, params):
# Return True if more process, 0 else
return int(ql.os.syscall_count["Process32NextW"] < 3) # I don' know how many process the sample want's to cycle
global __call_count
__call_count += 1
# FIXME: this is an undocumented workaround, probably to satisfy one of
# the samples. better implement that as an ad-hoc hook there
return int(__call_count < 3)
__call_count = 0
+80 -60
View File
@@ -11,7 +11,7 @@ from qiling.exception import QlErrorNotImplemented
from qiling.os.windows.api import *
from qiling.os.windows.const import *
from qiling.os.windows.fncc import *
from qiling.os.windows.structs import OsVersionInfoExA
from qiling.os.windows.structs import make_os_version_info_ex
from qiling.os.windows.utils import cmp
# HFILE _lclose(
@@ -544,20 +544,6 @@ def hook_IsBadWritePtr(ql: Qiling, address: int, params):
# Check write permission for size of memory
return 0 # ACCESS_TRUE
def compare(p1: int, operator: int, p2: int) -> bool:
op = {
VER_EQUAL : lambda a, b: a == b,
VER_GREATER : lambda a, b: a > b,
VER_GREATER_EQUAL : lambda a, b: a >= b,
VER_LESS : lambda a, b: a < b,
VER_LESS_EQUAL : lambda a, b: a <= b
}.get(operator)
if not op:
raise QlErrorNotImplemented('')
return op(p1, p2)
# BOOL VerifyVersionInfoW(
# LPOSVERSIONINFOEXW lpVersionInformation,
# DWORD dwTypeMask,
@@ -569,64 +555,98 @@ def compare(p1: int, operator: int, p2: int) -> bool:
'dwlConditionMask' : DWORDLONG
})
def hook_VerifyVersionInfoW(ql: Qiling, address: int, params):
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-verifyversioninfow2
pointer = params["lpVersionInformation"]
return __VerifyVersionInfo(ql, address, params, wide=True)
os_asked = OsVersionInfoExA(ql)
os_asked.read(pointer)
# BOOL VerifyVersionInfoA(
# LPOSVERSIONINFOEXA lpVersionInformation,
# DWORD dwTypeMask,
# DWORDLONG dwlConditionMask
# );
@winsdkapi(cc=STDCALL, params={
'lpVersionInformation' : LPOSVERSIONINFOEXA,
'dwTypeMask' : DWORD,
'dwlConditionMask' : DWORDLONG
})
def hook_VerifyVersionInfoA(ql: Qiling, address: int, params):
return __VerifyVersionInfo(ql, address, params, wide=False)
def __VerifyVersionInfo(ql: Qiling, address: int, params, *, wide: bool):
# see: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-verifyversioninfow
lpVersionInformation = params['lpVersionInformation']
dwTypeMask = params['dwTypeMask']
dwlConditionMask = params['dwlConditionMask']
oviex_struct = make_os_version_info_ex(ql.arch.bits, wide=wide)
askedOsVersionInfo = oviex_struct.load_from(ql.mem, lpVersionInformation)
# reading emulated os version info from profile
# FIXME: read the necessary information from KUSER_SHARED_DATA instead
osconfig = ql.os.profile['SYSTEM']
emulOsVersionInfo = oviex_struct(
dwMajorVersion = osconfig.getint('majorVersion'),
dwMinorVersion = osconfig.getint('minorVersion'),
dwBuildNumber = 0,
dwPlatformId = 0,
wServicePackMajor = osconfig.getint('VER_SERVICEPACKMAJOR'),
wServicePackMinor = 0,
wSuiteMask = 0,
wProductType = osconfig.getint('productType')
)
# check criteria by the order they should be evaluated. the online microsoft
# documentation only specify the first five, so not sure about the other three.
#
# each criteria is associated with the OSVERSIONINFOEX[A|W] it corresponds to.
checks = (
(1, 'dwMajorVersion'), # VER_MAJORVERSION
(0, 'dwMinorVersion'), # VER_MINORVERSION
(2, 'dwBuildNumber'), # VER_BUILDNUMBER
(5, 'wServicePackMajor'), # VER_SERVICEPACKMAJOR
(4, 'wServicePackMinor'), # VER_SERVICEPACKMINOR
(3, 'dwPlatformId'), # VER_PLATFORMID
(6, 'wSuiteMask'), # VER_SUITENAME
(7, 'wProductType') # VER_PRODUCT_TYPE
)
ConditionMask: dict = ql.os.hooks_variables["ConditionMask"]
res = True
opstr = {
VER_EQUAL : '==',
VER_GREATER : '>',
VER_GREATER_EQUAL : '>=',
VER_LESS : '<',
VER_LESS_EQUAL : '<='
}
for bit, field in checks:
if dwTypeMask & (1 << bit):
asked = getattr(askedOsVersionInfo, field)
emuld = getattr(emulOsVersionInfo, field)
for key, value in ConditionMask.items():
if value not in opstr:
raise QlErrorNotImplemented(f'API not implemented with operator {value}')
# extract the condition code for the required field
cond = (dwlConditionMask >> (bit * VER_NUM_BITS_PER_CONDITION_MASK)) & VER_CONDITION_MASK
# Versions should be compared together
if key in (VER_MAJORVERSION, VER_MINORVERSION, VER_PRODUCT_TYPE):
concat = f'{os_asked.major[0]}{os_asked.minor[0]}{os_asked.product[0]}'
# special case for VER_SUITENAME
if bit == 6:
cond_op = {
VER_AND : lambda a, b: (a & b) == b, # all members of b must be present
VER_OR : lambda a, b: (a & b) != 0 # at least one member of b must be present
}[cond]
# Just a print for analysts, will remove it from here in the future
if key == VER_MAJORVERSION:
ql.log.debug("The Target is checking the windows Version!")
version_asked = SYSTEMS_VERSION.get(concat, None)
res &= cond_op(emuld, asked)
if version_asked is None:
raise QlErrorNotImplemented(f'API not implemented for version {concat}')
else:
# cond operates as a bitmask, so multiple 'if' statements are appropriately
# used here. do not turn this into an 'elif' construct.
ql.log.debug(f'The target asks for version {opstr[value]} {version_asked}')
if (cond & VER_GREATER) and (emuld > asked):
return 1
qiling_os = \
f'{ql.os.profile.get("SYSTEM", "majorVersion")}' + \
f'{ql.os.profile.get("SYSTEM", "minorVersion")}' + \
f'{ql.os.profile.get("SYSTEM", "productType")}'
if (cond & VER_LESS) and (emuld < asked):
return 1
# We can finally compare
res = compare(int(qiling_os), value, int(concat))
if (cond & VER_EQUAL):
res &= (emuld == asked)
elif key == VER_SERVICEPACKMAJOR:
res = compare(ql.os.profile.getint("SYSTEM", "VER_SERVICEPACKMAJOR"), value, os_asked.service_major[0])
if not res:
ql.os.last_error = ERROR_OLD_WIN_VERSION
else:
raise QlErrorNotImplemented("API not implemented for key %s" % key)
# The result is a AND between every value, so if we find a False we just exit from the loop
if not res:
ql.os.last_error = ERROR_OLD_WIN_VERSION
return 0
# reset mask
ql.os.hooks_variables["ConditionMask"] = {}
return res
return int(res)
def __GetUserName(ql: Qiling, address: int, params, wstring: bool):
lpBuffer = params["lpBuffer"]
+10 -45
View File
@@ -66,52 +66,17 @@ def hook_InterlockedDecrement(ql: Qiling, address: int, params):
'Condition' : BYTE
})
def hook_VerSetConditionMask(ql: Qiling, address: int, params):
# ConditionMask = params["ConditionMask"]
TypeMask = params["TypeMask"]
Condition = params["Condition"]
# see: https://docs.microsoft.com/en-us/windows/win32/sysinfo/verifying-the-system-version
ConditionMask = ql.os.hooks_variables.get("ConditionMask", {})
ConditionMask = params['ConditionMask']
TypeMask = params['TypeMask']
Condition = params['Condition']
if TypeMask == 0:
ret = ConditionMask
else:
Condition &= VER_CONDITION_MASK
Condition &= VER_CONDITION_MASK
if Condition == 0:
ret = ConditionMask
else:
if TypeMask & VER_PRODUCT_TYPE:
# ConditionMask |= ullCondMask << (7 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_PRODUCT_TYPE] = Condition
elif TypeMask & VER_SUITENAME:
# ConditionMask |= ullCondMask << (6 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_SUITENAME] = Condition
elif TypeMask & VER_SERVICEPACKMAJOR:
# ConditionMask |= ullCondMask << (5 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_SERVICEPACKMAJOR] = Condition
elif TypeMask & VER_SERVICEPACKMINOR:
# ConditionMask |= ullCondMask << (4 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_SERVICEPACKMINOR] = Condition
elif TypeMask & VER_PLATFORMID:
# ConditionMask |= ullCondMask << (3 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_PLATFORMID] = Condition
elif TypeMask & VER_BUILDNUMBER:
# ConditionMask |= ullCondMask << (2 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_BUILDNUMBER] = Condition
elif TypeMask & VER_MAJORVERSION:
# ConditionMask |= ullCondMask << (1 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_MAJORVERSION] = Condition
elif TypeMask & VER_MINORVERSION:
# ConditionMask |= ullCondMask << (0 * VER_NUM_BITS_PER_CONDITION_MASK)
ConditionMask[VER_MINORVERSION] = Condition
if Condition:
for i in range(8):
if TypeMask & (1 << i):
ConditionMask |= Condition << (i * VER_NUM_BITS_PER_CONDITION_MASK)
ret = 1
# ConditionMask should be updated locally
# https://docs.microsoft.com/it-it/windows/win32/sysinfo/verifying-the-system-version
# But since we don't have the pointer to the variable, an hack is to use the environment.
# Feel free to push a better solution
# Since I can't work with bits, and since we had to work with the environment anyway, let's use a dict
ql.os.hooks_variables["ConditionMask"] = ConditionMask
return ret
return ConditionMask
+89 -21
View File
@@ -4,6 +4,7 @@
#
import time
from typing import Sequence
from qiling import Qiling
from qiling.exception import QlErrorNotImplemented
@@ -21,6 +22,49 @@ from qiling.os.windows.handle import Handle
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
# );
+124 -91
View File
@@ -31,50 +31,54 @@ def hook_memcpy(ql: Qiling, address: int, params):
src = params['src']
count = params['count']
try:
data = bytes(ql.mem.read(src, count))
ql.mem.write(dest, data)
except Exception:
ql.log.exception("")
data = bytes(ql.mem.read(src, count))
ql.mem.write(dest, data)
return dest
def _QueryInformationProcess(ql: Qiling, address: int, params):
flag = params["ProcessInformationClass"]
dst = params["ProcessInformation"]
pt_res = params["ReturnLength"]
obuf_ptr = params["ProcessInformation"]
obuf_len = params['ProcessInformationLength']
res_size_ptr = params["ReturnLength"]
if flag == ProcessDebugFlags:
value = b"\x01" * 0x4
res_data = ql.pack32(1)
elif flag == ProcessDebugPort:
value = b"\x00" * 0x4
res_data = ql.pack32(0)
elif flag == ProcessDebugObjectHandle:
return STATUS_PORT_NOT_SET
elif flag == ProcessBasicInformation:
pbi = structs.ProcessBasicInformation(ql,
exitStatus=0,
pebBaseAddress=ql.loader.TEB.PebAddress,
affinityMask=0,
basePriority=0,
uniqueId=ql.os.profile.getint("KERNEL", "pid"),
parentPid=ql.os.profile.getint("KERNEL", "parent_pid")
kconf = ql.os.profile['KERNEL']
pbi_struct = structs.make_process_basic_info(ql.arch.bits)
pci_obj = pbi_struct(
ExitStatus=0,
PebBaseAddress=ql.loader.TEB.PebAddress,
AffinityMask=0,
BasePriority=0,
UniqueProcessId=kconf.getint('pid'),
InheritedFromUniqueProcessId=kconf.getint('parent_pid')
)
addr = ql.os.heap.alloc(pbi.size)
pbi.write(addr)
value = ql.pack(addr)
res_data = bytes(pci_obj)
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}')
ql.log.debug("The target is checking the debugger via QueryInformationProcess ")
ql.mem.write(dst, value)
return STATUS_UNSUCCESSFUL
if pt_res:
ql.mem.write_ptr(pt_res, 8, 1)
res_size = len(res_data)
if obuf_len >= res_size:
ql.mem.write(obuf_ptr, res_data)
if res_size_ptr:
ql.mem.write_ptr(res_size_ptr, res_size)
return STATUS_SUCCESS
@@ -85,7 +89,7 @@ def _QueryInformationProcess(ql: Qiling, address: int, params):
# _In_ ULONG ProcessInformationLength,
# _Out_opt_ PULONG ReturnLength
# );
@winsdkapi(cc=CDECL, params={
@winsdkapi(cc=STDCALL, params={
'ProcessHandle' : HANDLE,
'ProcessInformationClass' : PROCESSINFOCLASS,
'ProcessInformation' : PVOID,
@@ -117,46 +121,43 @@ def hook_NtQueryInformationProcess(ql: Qiling, address: int, params):
return _QueryInformationProcess(ql, address, params)
def _QuerySystemInformation(ql: Qiling, address: int, params):
siClass = params["SystemInformationClass"]
pt_res = params["ReturnLength"]
dst = params["SystemInformation"]
if (siClass == SystemBasicInformation):
bufferLength = params["SystemInformationLength"]
SystemInformationClass = params['SystemInformationClass']
SystemInformation = params['SystemInformation']
SystemInformationLength = params['SystemInformationLength']
ReturnLength = params['ReturnLength']
if SystemInformationClass == SystemBasicInformation:
max_uaddr = {
QL_ARCH.X86 : 0x7FFEFFFF,
QL_ARCH.X8664: 0x7FFFFFFEFFFF
}[ql.arch.type]
sbi = structs.SystemBasicInforation(
ql,
Reserved=0,
TimerResolution=156250,
PageSize=ql.mem.pagesize,
NumberOfPhysicalPages=0x003FC38A,
LowestPhysicalPageNumber=1,
HighestPhysicalPageNumber=0x0046DFFF,
AllocationGranularity=1,
MinimumUserModeAddress=0x10000,
MaximumUserModeAddress=max_uaddr,
ActiveProcessorsAffinityMask=0x3F,
NumberOfProcessors=0x6
sbi_struct = structs.make_system_basic_info(ql.arch.bits)
# FIXME: retrieve the necessary info from KUSER_SHARED_DATA
sbi_obj = sbi_struct(
TimerResolution = 156250,
PageSize = ql.mem.pagesize,
NumberOfPhysicalPages = 0x003FC38A,
LowestPhysicalPageNumber = 1,
HighestPhysicalPageNumber = 0x0046DFFF,
AllocationGranularity = 1,
MinimumUserModeAddress = 0x10000,
MaximumUserModeAddress = max_uaddr,
ActiveProcessorsAffinityMask = 0x3F,
NumberOfProcessors = 6
)
if (bufferLength==sbi.size):
sbi.write(dst)
if pt_res:
ql.mem.write_ptr(pt_res, sbi.size, 1)
else:
if pt_res:
ql.mem.write_ptr(pt_res, sbi.size, 1)
if ReturnLength:
ql.mem.write_ptr(ReturnLength, sbi_struct.sizeof(), 4)
if SystemInformationLength < sbi_struct.sizeof():
return STATUS_INFO_LENGTH_MISMATCH
sbi_obj.save_to(ql.mem, SystemInformation)
else:
ql.log.debug(str(siClass))
raise QlErrorNotImplemented("API not implemented")
raise QlErrorNotImplemented(f'not implemented for {SystemInformationClass=}')
return STATUS_SUCCESS
@@ -229,35 +230,64 @@ def hook_ZwCreateDebugObject(ql: Qiling, address: int, params):
'ReturnLength' : PULONG
})
def hook_ZwQueryObject(ql: Qiling, address: int, params):
infoClass = params["ObjectInformationClass"]
dest = params["ObjectInformation"]
size_dest = params["ReturnLength"]
string = "DebugObject".encode("utf-16le")
handle = params['Handle']
ObjectInformationClass = params['ObjectInformationClass']
ObjectInformation = params['ObjectInformation']
ObjectInformationLength = params['ObjectInformationLength']
ReturnLength = params['ReturnLength']
string_addr = ql.os.heap.alloc(len(string))
ql.log.debug(str(string_addr))
ql.log.debug(str(string))
ql.mem.write(string_addr, string)
us = structs.UnicodeString(ql, len(string), len(string), string_addr)
s = 'DebugObject'.encode('utf-16le')
addr = ql.os.heap.alloc(len(s))
ql.mem.write(addr, s)
if infoClass == ObjectTypeInformation:
res = structs.ObjectTypeInformation(ql, us, 1, 1)
unistr_struct = structs.make_unicode_string(ql.arch.bits)
elif infoClass == ObjectAllTypesInformation:
# FIXME: there is an error in how these structs are read by al-khaser. Have no idea on where, so we are
# bypassing it
# oti = structs.ObjectTypeInformation(ql, us, 1, 1)
# res = structs.ObjectAllTypesInformation(ql, 2, oti)
return 1
unistr_obj = unistr_struct(
Length = len(s),
MaximumLength = len(s),
Buffer = addr
)
oti_struct = structs.make_object_type_info(ql.arch.bits)
oti_obj = oti_struct(
TypeName = unistr_obj,
TotalNumberOfObjects = 1,
TotalNumberOfHandles = 1
)
oati_struct = structs.make_object_all_types_info(ql.arch.bits, 1)
if ObjectInformationClass == ObjectTypeInformation:
out = oti_obj
elif ObjectInformationClass == ObjectAllTypesInformation:
# FIXME: al-khaser refers the object named 'DebugObject' twice: the first time it creates a handle
# for it (so number of handles is expected to be higher than 0) and then closes it. the next time
# it accesses it (here), it expects the number of handles to be 0.
#
# ideally we would track the handles for each object, but since we do not - this is a hack to let
# it pass.
oti_obj.TotalNumberOfHandles = 0
oati_obj = oati_struct(
NumberOfObjectTypes = 1,
ObjectTypeInformation = (oti_obj,)
)
out = oati_obj
else:
raise QlErrorNotImplemented("API not implemented")
raise QlErrorNotImplemented(f'API not implemented ({ObjectInformationClass=})')
if dest and params["Handle"]:
res.write(dest)
if ReturnLength:
ql.mem.write_ptr(ReturnLength, out.sizeof(), 4)
if size_dest:
ql.mem.write_ptr(size_dest, res.size, 4)
if ObjectInformationLength < out.sizeof():
return STATUS_INFO_LENGTH_MISMATCH
if ObjectInformation and handle:
out.save_to(ql.mem, ObjectInformation)
return STATUS_SUCCESS
@@ -289,7 +319,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
@@ -310,23 +340,26 @@ def _SetInformationProcess(ql: Qiling, address: int, params):
ql.mem.write_ptr(dst, 0, 1)
elif flag == ProcessBasicInformation:
pbi = structs.ProcessBasicInformation(
ql,
exitStatus=0,
pebBaseAddress=ql.loader.TEB.PebAddress,
affinityMask=0,
basePriority=0,
uniqueId=ql.os.profile.getint("KERNEL", "pid"),
parentPid=ql.os.profile.getint("KERNEL", "parent_pid")
kconf = ql.os.profile['KERNEL']
pbi_struct = structs.make_process_basic_info(ql.arch.bits)
pci_obj = pbi_struct(
ExitStatus=0,
PebBaseAddress=ql.loader.TEB.PebAddress,
AffinityMask=0,
BasePriority=0,
UniqueProcessId=kconf.getint('pid'),
InheritedFromUniqueProcessId=kconf.getint('parent_pid')
)
ql.log.debug("The target may be attempting to modify the PEB debug flag")
addr = ql.os.heap.alloc(pbi.size)
pbi.write(addr)
value = ql.pack(addr)
value = bytes(pbi_obj)
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
+81 -92
View File
@@ -14,15 +14,6 @@ from qiling.os.windows.structs import *
from qiling.os.windows.wdk_const import DO_DEVICE_INITIALIZING, DO_EXCLUSIVE
from qiling.utils import verify_ret
# typedef struct _OSVERSIONINFOW {
# ULONG dwOSVersionInfoSize;
# ULONG dwMajorVersion;
# ULONG dwMinorVersion;
# ULONG dwBuildNumber;
# ULONG dwPlatformId;
# WCHAR szCSDVersion[128];
# }
# NTSYSAPI NTSTATUS RtlGetVersion(
# PRTL_OSVERSIONINFOW lpVersionInformation
# );
@@ -30,13 +21,16 @@ from qiling.utils import verify_ret
'lpVersionInformation' : PRTL_OSVERSIONINFOW
})
def hook_RtlGetVersion(ql: Qiling, address: int, params):
pointer = params["lpVersionInformation"]
pointer = params['lpVersionInformation']
os = OsVersionInfoW(ql)
os.read(pointer)
os.major[0] = ql.os.profile.getint("SYSTEM", "majorVersion")
os.minor[0] = ql.os.profile.getint("SYSTEM", "minorVersion")
os.write(pointer)
osverinfo_struct = make_os_version_info(ql.arch.bits, wide=True)
with osverinfo_struct.ref(ql.mem, pointer) as osverinfo_obj:
# read the necessary information from KUSER_SHARED_DATA
kusd_obj = ql.os.KUSER_SHARED_DATA
osverinfo_obj.dwOSVersionInfoSize = osverinfo_struct.sizeof()
osverinfo_obj.dwMajorVersion = kusd_obj.NtMajorVersion
osverinfo_obj.dwMinorVersion = kusd_obj.NtMinorVersion
ql.log.debug("The target is checking the windows Version!")
@@ -77,6 +71,16 @@ def hook_ZwSetInformationThread(ql: Qiling, address: int, params):
return STATUS_SUCCESS
def __Close(ql: Qiling, address: int, params):
value = params["Handle"]
handle = ql.os.handle_manager.get(value)
if handle is None:
return STATUS_INVALID_HANDLE
return STATUS_SUCCESS
# NTSYSAPI NTSTATUS ZwClose(
# HANDLE Handle
# );
@@ -84,27 +88,13 @@ def hook_ZwSetInformationThread(ql: Qiling, address: int, params):
'Handle' : HANDLE
})
def hook_ZwClose(ql: Qiling, address: int, params):
value = params["Handle"]
handle = ql.os.handle_manager.get(value)
if handle is None:
return STATUS_INVALID_HANDLE
return STATUS_SUCCESS
return __Close(ql, address, params)
@winsdkapi(cc=STDCALL, params={
'Handle' : HANDLE
})
def hook_NtClose(ql: Qiling, address: int, params):
value = params["Handle"]
handle = ql.os.handle_manager.get(value)
if handle is None:
return STATUS_INVALID_HANDLE
return STATUS_SUCCESS
return __Close(ql, address, params)
# NTSYSAPI ULONG DbgPrintEx(
# ULONG ComponentId,
@@ -124,12 +114,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 +135,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
@@ -162,30 +148,27 @@ def __IoCreateDevice(ql: Qiling, address: int, params):
DeviceCharacteristics = params['DeviceCharacteristics']
DeviceObject = params['DeviceObject']
device_object = make_device_object(ql.arch.bits)
device_object.Type = 3 # FILE_DEVICE_CD_ROM_FILE_SYSTEM ?
device_object.DeviceExtension = ql.os.heap.alloc(DeviceExtensionSize)
device_object.Size = ctypes.sizeof(device_object) + DeviceExtensionSize
device_object.ReferenceCount = 1
device_object.DriverObject.value = DriverObject
device_object.NextDevice.value = 0
device_object.AttachedDevice.value = 0
device_object.CurrentIrp.value = 0
device_object.Timer.value = 0
device_object.Flags = DO_DEVICE_INITIALIZING
devobj_struct = make_device_object(ql.arch.bits)
devobj_addr = ql.os.heap.alloc(devobj_struct.sizeof())
if params.get('Exclusive'):
device_object.Flags |= DO_EXCLUSIVE
with devobj_struct.ref(ql.mem, devobj_addr) as devobj_obj:
devobj_obj.Type = 3 # FILE_DEVICE_CD_ROM_FILE_SYSTEM ?
devobj_obj.DeviceExtension = ql.os.heap.alloc(DeviceExtensionSize)
devobj_obj.Size = devobj_struct.sizeof() + DeviceExtensionSize
devobj_obj.ReferenceCount = 1
devobj_obj.DriverObject = DriverObject
devobj_obj.NextDevice = 0
devobj_obj.AttachedDevice = 0
devobj_obj.CurrentIrp = 0
devobj_obj.Timer = 0
devobj_obj.Flags = DO_DEVICE_INITIALIZING | (DO_EXCLUSIVE if params.get('Exclusive') else 0)
devobj_obj.Characteristics = DeviceCharacteristics
device_object.Characteristics = DeviceCharacteristics
addr = ql.os.heap.alloc(ctypes.sizeof(device_object))
ql.mem.write(addr, bytes(device_object))
ql.mem.write_ptr(DeviceObject, addr)
# update out param
ql.mem.write_ptr(DeviceObject, devobj_addr)
# update DriverObject.DeviceObject
ql.loader.driver_object.DeviceObject = addr
ql.loader.driver_object.DeviceObject = devobj_addr
return STATUS_SUCCESS
@@ -638,12 +621,12 @@ def hook_KeLeaveCriticalRegion(ql: Qiling, address: int, params):
def hook_MmMapLockedPagesSpecifyCache(ql: Qiling, address: int, params):
MemoryDescriptorList = params['MemoryDescriptorList']
mdl_class = make_mdl(ql.arch.bits).__class__
mdl_struct = make_mdl(ql.arch.bits)
mdl_buffer = ql.mem.read(MemoryDescriptorList, ctypes.sizeof(mdl_class))
mdl = mdl_class.from_buffer(mdl_buffer)
with mdl_struct.ref(ql.mem, MemoryDescriptorList) as mdl_obj:
address = mdl_obj.MappedSystemVa
return mdl.MappedSystemVa.value
return address
# void ProbeForRead(
# const volatile VOID *Address,
@@ -747,43 +730,51 @@ def hook_RtlMultiByteToUnicodeN(ql: Qiling, address: int, params):
# OUT PULONG ReturnLength
# );
def _NtQuerySystemInformation(ql: Qiling, address: int, params):
if params["SystemInformationClass"] == 0xb: # SystemModuleInformation
# if SystemInformationLength = 0, we return the total size in ReturnLength
NumberOfModules = 1
# see: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm
rpmi_class = make_rtl_process_module_info(ql.arch.bits).__class__
SystemInformationClass = params['SystemInformationClass']
ReturnLength = params['ReturnLength']
SystemInformationLength = params['SystemInformationLength']
SystemInformation = params['SystemInformation']
if SystemInformationClass == 0xb: # SystemModuleInformation
# only 1 module for ntoskrnl.exe
# FIXME: let users customize this?
size = 4 + ctypes.sizeof(rpmi_class) * NumberOfModules
num_modules = 1
if params["ReturnLength"] != 0:
ql.mem.write_ptr(params["ReturnLength"], size)
rpm_struct = make_rtl_process_modules(ql.arch.bits, num_modules)
if params["SystemInformationLength"] < size:
if ReturnLength:
ql.mem.write_ptr(ReturnLength, rpm_struct.sizeof())
# if SystemInformationLength = 0, we return the total size in ReturnLength
if SystemInformationLength < rpm_struct.sizeof():
return STATUS_INFO_LENGTH_MISMATCH
else: # return all the loaded modules
module = make_rtl_process_module_info(ql.arch.bits)
module.Section = 0
module.MappedBase = 0
with rpm_struct.ref(ql.mem, SystemInformation) as rpm_obj:
rpm_obj.NumberOfModules = num_modules
if ql.loader.is_driver:
image = ql.loader.get_image_by_name("ntoskrnl.exe")
assert image, 'image is a driver, but ntoskrnl.exe was not loaded'
# cycle through all the loaded modules
for i in range(num_modules):
rpmi_obj = rpm_obj.Modules[i]
module.ImageBase = image.base
# FIXME: load real values instead of bogus ones
rpmi_obj.Section = 0
rpmi_obj.MappedBase = 0
module.ImageSize = 0xab000
module.Flags = 0x8804000
module.LoadOrderIndex = 0 # order of this module
module.InitOrderIndex = 0
module.LoadCount = 1
module.OffsetToFileName = len(b"\\SystemRoot\\system32\\")
module.FullPathName = b"\\SystemRoot\\system32\\ntoskrnl.exe"
if ql.loader.is_driver:
image = ql.loader.get_image_by_name("ntoskrnl.exe")
assert image, 'image is a driver, but ntoskrnl.exe was not loaded'
process_modules = ql.pack32(NumberOfModules) + bytes(module)
ql.mem.write(params["SystemInformation"], process_modules)
rpmi_obj.ImageBase = image.base
rpmi_obj.ImageSize = 0xab000
rpmi_obj.Flags = 0x8804000
rpmi_obj.LoadOrderIndex = 0 # order of this module
rpmi_obj.InitOrderIndex = 0
rpmi_obj.LoadCount = 1
rpmi_obj.OffsetToFileName = len(b"\\SystemRoot\\system32\\")
rpmi_obj.FullPathName = b"\\SystemRoot\\system32\\ntoskrnl.exe"
return STATUS_SUCCESS
@@ -871,8 +862,6 @@ def hook_PsGetCurrentProcess(ql: Qiling, address: int, params):
# HANDLE PsGetCurrentProcessId();
@winsdkapi(cc=STDCALL, params={})
def hook_PsGetCurrentProcessId(ql: Qiling, address: int, params):
# current process ID is 101
# TODO: let user customize this?
return ql.os.pid
# NTSTATUS
+33 -30
View File
@@ -5,7 +5,6 @@
import ntpath
import os
from typing import Sequence
from qiling import Qiling
from qiling.os.windows.api import *
@@ -13,9 +12,9 @@ from qiling.os.windows.const import *
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
from qiling.os.windows.structs import make_shellex_info
def _SHGetFileInfo(ql: Qiling, address: int, params) -> int:
uFlags = params["uFlags"]
@@ -61,20 +60,22 @@ def hook_SHGetFileInfoA(ql: Qiling, address: int, params):
def hook_SHGetFileInfoW(ql: Qiling, address: int, params):
return _SHGetFileInfo(ql, address, params)
def _ShellExecute(ql: Qiling, obj: ShellExecuteInfoA):
def __wstr(shellex: Sequence):
return ql.os.utils.read_wstring(shellex[0]) if shellex[0] else ''
def _ShellExecute(ql: Qiling, shellex_obj, *, wide: bool):
read_str = ql.os.utils.read_wstring if wide else ql.os.utils.read_cstring
def __read_str(ptr: int):
return read_str(ptr) if ptr else ''
ql.log.debug(f'Target executed a shell command!')
ql.log.debug(f' | Operation : "{__wstr(obj.verb)}"')
ql.log.debug(f' | Parameters : "{__wstr(obj.params)}"')
ql.log.debug(f' | File : "{__wstr(obj.file)}"')
ql.log.debug(f' | Directory : "{__wstr(obj.dir)}"')
ql.log.debug(f' | Operation : "{__read_str(shellex_obj.lpVerb)}"')
ql.log.debug(f' | File : "{__read_str(shellex_obj.lpFile)}"')
ql.log.debug(f' | Parameters : "{__read_str(shellex_obj.lpParameters)}"')
ql.log.debug(f' | Directory : "{__read_str(shellex_obj.lpDirectory)}"')
if obj.show[0] == SW_HIDE:
ql.log.debug(" | With an hidden window")
if shellex_obj.nShow == 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)
@@ -87,17 +88,16 @@ def _ShellExecute(ql: Qiling, obj: ShellExecuteInfoA):
'pExecInfo' : POINTER
})
def hook_ShellExecuteExW(ql: Qiling, address: int, params):
pointer = params["pExecInfo"]
pExecInfo = params['pExecInfo']
shellInfo = ShellExecuteInfoA(ql)
shellInfo.read(pointer)
shellex_struct = make_shellex_info(ql.arch.bits)
handle = _ShellExecute(ql, shellInfo)
with shellex_struct.ref(ql.mem, pExecInfo) as shellex_obj:
handle = _ShellExecute(ql, shellex_obj, wide=True)
# Write results
shellInfo.instApp[0] = 0x21
shellInfo.process[0] = handle.id
shellInfo.write(pointer)
# Write results
shellex_obj.hInstApp = 33
shellex_obj.hProcess = handle.id
return 1
@@ -118,15 +118,18 @@ def hook_ShellExecuteExW(ql: Qiling, address: int, params):
'nShowCmd' : INT
})
def hook_ShellExecuteW(ql: Qiling, address: int, params):
_ShellExecute(ql, ShellExecuteInfoA(
ql,
hwnd=params["hwnd"],
lpVerb=params["lpOperation"],
lpFile=params["lpFile"],
lpParams=params["lpParameters"],
lpDir=params["lpDirectory"],
show=params["nShowCmd"]
))
shellex_struct = make_shellex_info(ql.arch.bits)
shellex_obj = shellex_struct(
hwnd = params['hwnd'],
lpVerb = params['lpOperation'],
lpFile = params['lpFile'],
lpParameters = params['lpParameters'],
lpDirectory = params['lpDirectory'],
nShow = params['nShowCmd']
)
_ShellExecute(ql, shellex_obj, wide=True)
return 33
+40 -29
View File
@@ -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"]
uType = params["uType"]
if type_box in (MB_OK, MB_OKCANCEL):
return IDOK
buttons = uType & 0x0000000f
# icon = uType & 0x000000f0
# default = uType & 0x00000f00
# modal = uType & 0x0000f000
# order = uType & 0x00ff0000
if type_box in (MB_YESNO, MB_YESNOCANCEL):
return IDYES
# 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
}
ql.log.debug(type_box)
raise QlErrorNotImplemented("API not implemented")
return press[buttons]
# int MessageBoxA(
# HWND hWnd,
@@ -827,11 +838,11 @@ def hook_MessageBoxA(ql: Qiling, address: int, params):
'lpPoint' : LPPOINT
})
def hook_GetCursorPos(ql: Qiling, address: int, params):
dest = params["lpPoint"]
lpPoint = params['lpPoint']
# TODO: maybe we can add it to the profile too
p = Point(ql, 50, 50)
p.write(dest)
p = Point(50, 50)
p.save_to(ql.mem, lpPoint)
return 0
+58 -14
View File
@@ -51,22 +51,41 @@ def hook_WSASocketA(ql: Qiling, address: int, params):
'namelen' : INT
})
def hook_connect(ql: Qiling, address: int, params):
sin_family = ql.mem.read(params["name"], 1)[0]
sin_port = int.from_bytes(ql.mem.read(params["name"] + 2, 2), byteorder="big")
name = params["name"]
namelen = params['namelen']
# sockaddr structure type needs to be defined based on the family field value.
# both types (for ipv4 and ipv6) have it on the same offset, so we assume ipv4
# by default for convinience.
sockaddr_struct = make_sockaddr_in()
# sockaddr is not based on BaseStruct, so we have to read it manually
sockaddr_data = ql.mem.read(name, namelen)
sockaddr_obj = sockaddr_struct.from_buffer_copy(sockaddr_data[:ctypes.sizeof(sockaddr_struct)])
sin_family = sockaddr_obj.sin_family
sin_port = sockaddr_obj.sin_port
if sin_family == 0x17: # IPv6
segments = list(map("{:02x}".format, ql.mem.read(params["name"] + 8, 16)))
sin_addr = ":".join(["".join(x) for x in zip(segments[0::2], segments[1::2])])
sockaddr_struct = make_sockaddr_in6()
sockaddr_obj = sockaddr_struct.from_buffer_copy(sockaddr_data[:ctypes.sizeof(sockaddr_struct)])
# read address bytes and show them as pairs
segments = [f'{b:02x}' for b in sockaddr_obj.sin6_addr.Byte]
sin_addr = ':'.join(''.join(x) for x in zip(segments[0::2], segments[1::2]))
elif sin_family == 0x2: # IPv4
sin_addr = ".".join((str(octet) for octet in ql.mem.read(params["name"] + 4, 4)))
a = sockaddr_obj.sin_addr
sin_addr = '.'.join((a.s_b1, a.s_b2, a.s_b3, a.s_b4))
else:
ql.log.debug("sockaddr sin_family unhandled variant")
return 0
ql.log.debug(f"{params['name']:#010x}: sockaddr_in{6 if sin_family == 0x17 else ''}",
f"{{sin_family={sin_family:#04x}, sin_port={sin_port}, sin_addr={sin_addr}}}",
sep="")
ql.log.debug(f'{sockaddr_struct.__name__} @ {name:#010x} : {sin_family=:#04x}, {sin_port=}, {sin_addr=}')
# FIXME: wait.. we just printed stuff out, and did not connect anywhere..
return 0
# hostent * gethostbyname(
@@ -76,14 +95,39 @@ def hook_connect(ql: Qiling, address: int, params):
'name' : POINTER
})
def hook_gethostbyname(ql: Qiling, address: int, params):
ip_str = ql.os.profile.get("NETWORK", "dns_response_ip")
name_ptr = params['name']
# set string value back to arg to let it show on log
params['name'] = ql.os.utils.read_cstring(name_ptr)
# prepare the ip address data bytes
ip_str = ql.os.profile.get('NETWORK', 'dns_response_ip')
ip = bytes((int(octet) for octet in reversed(ip_str.split('.'))))
name_ptr = params["name"]
# params["name"] = ql.os.utils.read_cstring(name_ptr)
# allocate room for the ip address data bytes
list_item = ql.os.heap.alloc(len(ip))
ql.mem.write(list_item, ip)
hostnet = Hostent(ql, name_ptr, 0, 2, 4, ip)
hostnet_addr = ql.os.heap.alloc(hostnet.size)
hostnet.write(hostnet_addr)
# allocate room for a list with one item, followed by a nullptr sentinel
addr_list = ql.os.heap.alloc(2 * ql.arch.pointersize)
ql.mem.write_ptr(addr_list + 0 * ql.arch.pointersize, list_item)
ql.mem.write_ptr(addr_list + 1 * ql.arch.pointersize, 0)
# we need a pointer to a nullptr; reuse the sentinel address for that
d_nullptr = addr_list + 1 * ql.arch.pointersize
hostent_struct = make_hostent(ql.arch.bits)
hostnet_addr = ql.os.heap.alloc(hostent_struct.sizeof())
hostent_obj = hostent_struct(
h_name = name_ptr,
h_aliases = d_nullptr,
h_addrtype = 2, # AF_INET
h_length = len(ip),
h_addr_list = addr_list
)
hostent_obj.save_to(ql.mem, hostnet_addr)
return hostnet_addr
+12 -11
View File
@@ -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
+1 -1
View File
@@ -74,7 +74,7 @@ class RegConf:
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'))
File diff suppressed because it is too large Load Diff
+43 -33
View File
@@ -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 @@ class QlWindowsThread(QlThread):
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 @@ class QlWindowsThread(QlThread):
# 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 @@ class QlWindowsThreadManagement(QlThread):
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
+135 -135
View File
@@ -3,13 +3,12 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import ctypes
from typing import Iterable, Tuple, TypeVar
from unicorn import UcError
from qiling import Qiling
from qiling.const import QL_OS
from qiling.exception import QlErrorSyscallError
from qiling.os.const import POINTER
from qiling.os.windows.fncc import STDCALL
from qiling.os.windows.wdk_const import *
@@ -29,76 +28,76 @@ def has_lib_ext(name: str) -> bool:
return ext in ("dll", "exe", "sys", "drv")
def io_Write(ql: Qiling, in_buffer: bytes):
heap = ql.os.heap
def io_Write(ql: Qiling, in_buffer: bytes) -> int:
major_func = ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE]
if ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE] == 0:
# raise error?
return (False, None)
if not major_func:
raise QlErrorSyscallError('null MajorFunction field')
driver_object_cls = ql.loader.driver_object.__class__
buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(driver_object_cls))
device_object = driver_object_cls.from_buffer(buf)
# keep track of all heap allocation within this scope to be able
# to free them when done
allocations = []
alloc_addr = []
def build_mdl(buffer_size: int, data=None):
mdl = make_mdl(ql.arch.bits)
def __heap_alloc(size: int) -> int:
address = ql.os.heap.alloc(size)
allocations.append(address)
mapped_address = heap.alloc(buffer_size)
alloc_addr.append(mapped_address)
mdl.MappedSystemVa.value = mapped_address
mdl.StartVa.value = mapped_address
mdl.ByteOffset = 0
mdl.ByteCount = buffer_size
if data:
written = data if len(data) <= buffer_size else data[:buffer_size]
ql.mem.write(mapped_address, written)
return address
return mdl
def __free_all(allocations: Iterable[int]) -> None:
for address in allocations:
ql.os.heap.free(address)
# allocate memory regions for IRP and IO_STACK_LOCATION
irp = make_irp(ql.arch.bits)
irpstack_class = irp.irpstack._type_
# allocate memory for IRP
irp_struct = make_irp(ql.arch.bits)
irp_addr = __heap_alloc(irp_struct.sizeof())
ql.log.info(f'IRP is at {irp_addr:#x}')
irp_addr = heap.alloc(ctypes.sizeof(irp))
alloc_addr.append(irp_addr)
# populate the structure
with irp_struct.ref(ql.mem, irp_addr) as irp_obj:
irpstack_addr = heap.alloc(ctypes.sizeof(irpstack_class))
alloc_addr.append(irpstack_addr)
# allocate memory for IO_STACK_LOCATION
irpstack_struct = make_io_stack_location(ql.arch.bits)
irpstack_addr = __heap_alloc(irpstack_struct.sizeof())
ql.log.info(f'IO_STACK_LOCATION is at {irpstack_addr:#x}')
# setup irp stack parameters
irpstack = irpstack_class()
# setup IRP structure
irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class))
# populate the structure
with irpstack_struct.ref(ql.mem, irpstack_addr) as irpstack_obj:
irpstack_obj.MajorFunction = IRP_MJ_WRITE
irpstack_obj.Parameters.Write.Length = len(in_buffer)
irpstack.MajorFunction = IRP_MJ_WRITE
irpstack.Parameters.Write.Length = len(in_buffer)
ql.mem.write(irpstack_addr, bytes(irpstack))
# load DeviceObject from memory
drvobj_struct = ql.loader.driver_object.__class__
devobj_obj = drvobj_struct.load_from(ql.loader.driver_object.DeviceObject)
if device_object.Flags & DO_BUFFERED_IO:
# BUFFERED_IO
system_buffer_addr = heap.alloc(len(in_buffer))
alloc_addr.append(system_buffer_addr)
ql.mem.write(system_buffer_addr, bytes(in_buffer))
irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr
elif device_object.Flags & DO_DIRECT_IO:
if devobj_obj.Flags & DO_BUFFERED_IO:
system_buffer_addr = __heap_alloc(len(in_buffer))
ql.mem.write(system_buffer_addr, bytes(in_buffer))
irp_obj.AssociatedIrp.SystemBuffer = system_buffer_addr
# DIRECT_IO
mdl = build_mdl(len(in_buffer))
mdl_addr = heap.alloc(ctypes.sizeof(mdl))
alloc_addr.append(mdl_addr)
elif devobj_obj.Flags & DO_DIRECT_IO:
mdl_struct = make_mdl(ql.arch.bits)
mdl_addr = __heap_alloc(mdl_struct.sizeof())
with mdl_struct.ref(ql.mem, mdl_addr) as mdl_obj:
mapped_address = __heap_alloc(len(in_buffer))
mdl_obj.MappedSystemVa = mapped_address
mdl_obj.StartVa = mapped_address
mdl_obj.ByteOffset = 0
mdl_obj.ByteCount = len(in_buffer)
irp_obj.MdlAddress = mdl_addr
ql.mem.write(mdl_addr, bytes(mdl))
irp.MdlAddress.value = mdl_addr
else:
# NEITHER_IO
input_buffer_size = len(in_buffer)
input_buffer_addr = heap.alloc(input_buffer_size)
alloc_addr.append(input_buffer_addr)
ql.mem.write(input_buffer_addr, bytes(in_buffer))
irp.UserBuffer.value = input_buffer_addr
else:
input_buffer_addr = __heap_alloc(len(in_buffer))
# everything is done! Write IRP to memory
ql.mem.write(irp_addr, bytes(irp))
ql.mem.write(input_buffer_addr, bytes(in_buffer))
irp_obj.UserBuffer = input_buffer_addr
# set function args
# TODO: make sure this is indeed STDCALL
@@ -108,23 +107,22 @@ def io_Write(ql: Qiling, in_buffer: bytes):
(POINTER, irp_addr)
))
ql.log.info(f'Executing from {major_func:#x}')
try:
# now emulate
ql.run(ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE])
ql.run(major_func)
except UcError as err:
verify_ret(ql, err)
# read current IRP state
irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp))
irp = irp.from_buffer(irp_buffer)
# read updated IRP state before releasing resources
with irp_struct.ref(ql.mem, irp_addr) as irp_obj:
info = irp_obj.IoStatus.Information
io_status = irp.IoStatus
# now free all alloc memory
for addr in alloc_addr:
# print("freeing heap memory at 0x%x" %addr) # FIXME: the output is not deterministic??
heap.free(addr)
# free all allocated memory
__free_all(allocations)
return True, io_status.Information.value
return info
# Emulate DeviceIoControl() of Windows
# BOOL DeviceIoControl(
@@ -136,8 +134,14 @@ def io_Write(ql: Qiling, in_buffer: bytes):
# DWORD nOutBufferSize,
# LPDWORD lpBytesReturned,
# LPOVERLAPPED lpOverlapped);
def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple:
def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple[int, int, bytes]:
major_func = ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]
if not major_func:
raise QlErrorSyscallError('null MajorFunction field')
# keep track of all heap allocation within this scope to be able
# to free them when done
allocations = []
def __heap_alloc(size: int) -> int:
@@ -153,27 +157,9 @@ def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple:
def ioctl_code(DeviceType: int, Function: int, Method: int, Access: int) -> int:
return (DeviceType << 16) | (Access << 14) | (Function << 2) | Method
def build_mdl(buffer_size, data=None):
mdl = make_mdl(ql.arch.bits)
mapped_address = __heap_alloc(buffer_size)
mdl.MappedSystemVa.value = mapped_address
mdl.StartVa.value = mapped_address
mdl.ByteOffset = 0
mdl.ByteCount = buffer_size
if data:
written = data if len(data) <= buffer_size else data[:buffer_size]
ql.mem.write(mapped_address, written)
return mdl
if ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL] == 0:
# raise error?
return (None, None, None)
# create new memory region to store input data
_ioctl_code, output_buffer_size, in_buffer = params
# extract data transfer method
devicetype, function, ctl_method, access = _ioctl_code
@@ -184,52 +170,57 @@ def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple:
# create new memory region to store out data
output_buffer_addr = __heap_alloc(output_buffer_size)
# allocate memory regions for IRP and IO_STACK_LOCATION
irp = make_irp(ql.arch.bits)
irpstack_class = irp.irpstack._type_
irp_addr = __heap_alloc(ctypes.sizeof(irp))
irpstack_addr = __heap_alloc(ctypes.sizeof(irpstack_class))
# setup irp stack parameters
irpstack = irpstack_class()
# setup IRP structure
irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class))
ql.log.info("IRP is at 0x%x, IO_STACK_LOCATION is at 0x%x" %(irp_addr, irpstack_addr))
irpstack.Parameters.DeviceIoControl.IoControlCode = ioctl_code(devicetype, function, ctl_method, access)
irpstack.Parameters.DeviceIoControl.OutputBufferLength = output_buffer_size
irpstack.Parameters.DeviceIoControl.InputBufferLength = input_buffer_size
irpstack.Parameters.DeviceIoControl.Type3InputBuffer.value = input_buffer_addr # used by IOCTL_METHOD_NEITHER
ql.mem.write(irpstack_addr, bytes(irpstack))
if ctl_method == METHOD_NEITHER:
irp.UserBuffer.value = output_buffer_addr # used by IOCTL_METHOD_NEITHER
# allocate memory for AssociatedIrp.SystemBuffer
# used by IOCTL_METHOD_IN_DIRECT, IOCTL_METHOD_OUT_DIRECT and IOCTL_METHOD_BUFFERED
system_buffer_size = max(input_buffer_size, output_buffer_size)
system_buffer_addr = __heap_alloc(system_buffer_size)
# init data from input buffer
ql.mem.write(system_buffer_addr, bytes(in_buffer))
irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr
if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT):
# Create MDL structure for output data
# used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT
mdl = build_mdl(output_buffer_size)
mdl_addr = __heap_alloc(ctypes.sizeof(mdl))
# allocate memory for IRP
irp_struct = make_irp(ql.arch.bits)
irp_addr = __heap_alloc(irp_struct.sizeof())
ql.log.info(f'IRP is at {irp_addr:#x}')
ql.mem.write(mdl_addr, bytes(mdl))
irp.MdlAddress.value = mdl_addr
# populate the structure
with irp_struct.ref(ql.mem, irp_addr) as irp_obj:
# everything is done! Write IRP to memory
ql.mem.write(irp_addr, bytes(irp))
# allocate memory for IO_STACK_LOCATION
irpstack_struct = make_io_stack_location(ql.arch.bits)
irpstack_addr = __heap_alloc(irpstack_struct.sizeof())
ql.log.info(f'IO_STACK_LOCATION is at {irpstack_addr:#x}')
# populate the structure
with irpstack_struct.ref(ql.mem, irpstack_addr) as irpstack_obj:
irpstack_obj.Parameters.DeviceIoControl.IoControlCode = ioctl_code(devicetype, function, ctl_method, access)
irpstack_obj.Parameters.DeviceIoControl.OutputBufferLength = output_buffer_size
irpstack_obj.Parameters.DeviceIoControl.InputBufferLength = input_buffer_size
irpstack_obj.Parameters.DeviceIoControl.Type3InputBuffer = input_buffer_addr # used by IOCTL_METHOD_NEITHER
irp_obj.irpstack = irpstack_addr
if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT):
mdl_struct = make_mdl(ql.arch.bits)
mdl_addr = __heap_alloc(mdl_struct.sizeof())
# Create MDL structure for output data
with mdl_struct.ref(ql.mem, mdl_addr) as mdl_obj:
mapped_address = __heap_alloc(output_buffer_size)
mdl_obj.MappedSystemVa = mapped_address
mdl_obj.StartVa = mapped_address
mdl_obj.ByteOffset = 0
mdl_obj.ByteCount = output_buffer_size
# used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT
irp_obj.MdlAddress = mdl_addr
elif ctl_method == METHOD_NEITHER:
# used by IOCTL_METHOD_NEITHER
irp_obj.UserBuffer = output_buffer_addr
irp_obj.AssociatedIrp.SystemBuffer = system_buffer_addr
# set function args
ql.log.info("Executing IOCTL with DeviceObject = 0x%x, IRP = 0x%x" %(ql.loader.driver_object.DeviceObject, irp_addr))
# TODO: make sure this is indeed STDCALL
ql.os.fcall = ql.os.fcall_select(STDCALL)
ql.os.fcall.writeParams((
@@ -237,30 +228,39 @@ def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple:
(POINTER, irp_addr)
))
ql.log.info(f'Executing from {major_func:#x}')
try:
ql.log.info(f"Executing from: {ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]:#x}")
# now emulate IOCTL's DeviceControl
ql.run(ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL])
ql.run(major_func)
except UcError as err:
verify_ret(ql, err)
# read current IRP state
irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp))
irp = irp.__class__.from_buffer(irp_buffer)
# read updated IRP state before releasing resources
with irp_struct.ref(ql.mem, irp_addr) as irp_obj:
io_status = irp_obj.IoStatus
mdl_addr = irp_obj.MdlAddress
io_status = irp.IoStatus
info = io_status.Information
status = io_status.Status.Status
# read output data
output_data = b''
if io_status.Status.Status >= 0:
if status >= 0:
if ctl_method == METHOD_BUFFERED:
output_data = ql.mem.read(system_buffer_addr, io_status.Information.value)
if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT):
output_data = ql.mem.read(mdl.MappedSystemVa.value, io_status.Information.value)
if ctl_method == METHOD_NEITHER:
output_data = ql.mem.read(output_buffer_addr, io_status.Information.value)
output_data = ql.mem.read(system_buffer_addr, info)
elif ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT):
with mdl_struct.ref(ql.mem, mdl_addr) as mdl_obj:
mapped_va = mdl_obj.MappedSystemVa
output_data = ql.mem.read(mapped_va, info)
elif ctl_method == METHOD_NEITHER:
output_data = ql.mem.read(output_buffer_addr, info)
# now free all alloc memory
__free_all(allocations)
return io_status.Status.Status, io_status.Information.value, output_data
return status, info, output_data
+4 -9
View File
@@ -65,9 +65,9 @@ class QlOsWindows(QlOs):
self.stats = QlWinStats()
ossection = f'OS{self.ql.arch.bits}'
heap_base = self.profile.getint(ossection, 'heap_address')
heap_size = self.profile.getint(ossection, 'heap_size')
ossection = self.profile[f'OS{self.ql.arch.bits}']
heap_base = ossection.getint('heap_address')
heap_size = ossection.getint('heap_size')
self.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size)
@@ -82,9 +82,7 @@ class QlOsWindows(QlOs):
self.PE_RUN = False
self.last_error = 0
# variables used inside hooks
self.hooks_variables = {}
self.syscall_count = {}
self.argv = self.ql.argv
self.env = self.ql.env
self.pid = self.profile.getint('KERNEL', 'pid')
@@ -191,9 +189,6 @@ class QlOsWindows(QlOs):
api_func = getattr(api, f'hook_{api_name}', None)
if api_func:
self.syscall_count.setdefault(api_name, 0)
self.syscall_count[api_name] += 1
try:
api_func(ql, address, api_name)
except Exception as ex:
-11
View File
@@ -1,10 +1,3 @@
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[KERNEL]
version = 7
ticks_per_second = 18.206
@@ -17,9 +10,5 @@ stack_size = 0x4000
base_address = 0x7000
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
automatize_input = False
current_path = A:\
-12
View File
@@ -17,19 +17,7 @@ gid = 1000
pid = 1996
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
current_path = /
-13
View File
@@ -11,7 +11,6 @@ load_address = 0x555555554000
interp_address = 0x7ffff7dd5000
mmap_address = 0x7fffb7dd6000
vsyscall_address = 0xffffffffff600000
vsyscall_size = 0x1000
[OS32]
@@ -28,19 +27,7 @@ gid = 1000
pid = 1996
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
current_path = /
-12
View File
@@ -24,19 +24,7 @@ gid = 1000
pid = 1996
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
current_path = /
+2 -12
View File
@@ -15,27 +15,17 @@ cpupage_address = 0xfc4048d8
cpupage_tls_address = 0xfc405000
tls_data_address = 0xfc406000
[KERNEL]
uid = 1000
gid = 1000
pid = 1996
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
current_path = /
[NETWORK]
# To use IPv6 or not, to avoid binary double bind. ipv6 and ipv4 bind the same port at the same time
bindtolocalhost = True
+4 -13
View File
@@ -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
@@ -28,18 +30,7 @@ pid = 1996
parent_pid = 0
shell_pid = 10
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
current_path = C:\
[SYSTEM]
+7 -2
View File
@@ -433,10 +433,15 @@ def profile_setup(ostype: QL_OS, filename: Optional[str]):
return config
# verify if emulator returns properly
def verify_ret(ql, err):
def verify_ret(ql: 'Qiling', err):
# init_sp location is not consistent; this is here to work around that
if not hasattr(ql.os, 'init_sp'):
ql.os.init_sp = ql.loader.init_sp
ql.log.debug("Got exception %u: init SP = %x, current SP = %x, PC = %x" %(err.errno, ql.os.init_sp, ql.arch.regs.arch_sp, ql.arch.regs.arch_pc))
ql.os.RUN = False
if hasattr(ql.os, 'RUN'):
ql.os.RUN = False
# timeout is acceptable in this case
if err.errno in (UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED):
+2 -1
View File
@@ -90,6 +90,7 @@ def handle_code(options: argparse.Namespace):
profile=options.profile,
filter=options.filter,
endian=archendian,
thumb=options.thumb,
)
return ql
@@ -184,7 +185,7 @@ if __name__ == '__main__':
code_parser.add_argument('-f', '--filename', metavar="FILE", help="filename")
code_parser.add_argument('-i', '--input', metavar="INPUT", dest="input", help='input hex value')
code_parser.add_argument('--arch', required=True, choices=arch_map)
code_parser.add_argument('--thumb', action='store_true', help='specify thumb mode for ARM')
code_parser.add_argument('--thumb', action='store_true', default=False, help='specify thumb mode for ARM')
code_parser.add_argument('--endian', choices=('little', 'big'), default='little', help='specify endianess for bi-endian archs')
code_parser.add_argument('--os', required=True, choices=os_map)
code_parser.add_argument('--rootfs', default='.', help='emulated root filesystem, that is where all libraries reside')
+6 -3
View File
@@ -10,7 +10,7 @@ VERSION = "1.4.4" + "-dev"
requirements = [
"capstone>=4.0.1",
"unicorn>=2.0.0-rc7",
"unicorn>=2.0.0",
"pefile>=2022.5.30",
"python-registry>=1.3.1",
"keystone-engine>=0.9.2",
@@ -18,7 +18,7 @@ requirements = [
"gevent>=20.9.0",
"multiprocess>=0.70.12.2",
"windows-curses>=2.1.0;platform_system=='Windows'",
"pyyaml>=6.0"
"pyyaml>=6.0",
]
extras = {
@@ -40,8 +40,11 @@ extras = {
"cmd2"
],
"fuzz" : [
"unicornafl>=2.0.0;platform_system=='Windows'",
"unicornafl>=2.0.0;platform_system!='Windows'",
"fuzzercorn>=0.0.1;platform_system=='Linux'"
],
"RE": [
"r2libr>=5.7.4",
]
}
-5
View File
@@ -1,5 +0,0 @@
[LOG]
dir = log_test
[MISC]
append = append_test
-12
View File
@@ -4,17 +4,5 @@ entry_point = 0x80800000
heap_size = 0x300000
[LOG]
# log directory output
# usage: dir = qlog
dir =
# split log file, use with multithread
split = False
[MISC]
# append string into different logs
# maily for multiple times Ql run with one file
# usage: append = test1
append =
current_path = /
+10 -10
View File
@@ -44,17 +44,17 @@ class TestAndroid(unittest.TestCase):
del ql
#@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux')
#def test_android_arm(self):
# test_binary = "../examples/rootfs/arm64_android6.0/bin/arm_android_jniart"
# rootfs = "../examples/rootfs/arm64_android6.0"
# env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"}
@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux')
def test_android_arm(self):
test_binary = "../examples/rootfs/arm64_android6.0/bin/arm_android_jniart"
rootfs = "../examples/rootfs/arm64_android6.0"
env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"}
# ql = Qiling([test_binary], rootfs, env, multithread=True)
# ql.os.set_syscall("close", my_syscall_close)
# ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))
# ql.run()
# del ql
ql = Qiling([test_binary], rootfs, env, multithread=True)
ql.os.set_syscall("close", my_syscall_close)
ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))
ql.run()
del ql
if __name__ == "__main__":
+7 -3
View File
@@ -209,8 +209,12 @@ class ELFTest(unittest.TestCase):
def test_elf_linux_x86(self):
ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_hello"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG, log_file="test.qlog")
filename = 'test.qlog'
ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_hello"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG, log_file=filename)
ql.run()
os.remove(filename)
del ql
@@ -352,7 +356,7 @@ class ELFTest(unittest.TestCase):
all_mem = ql.mem.save()
ql.mem.restore(all_mem)
ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG, profile='profiles/append_test.ql')
ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG)
ql.os.set_api('puts', my_puts)
ql.run()
del ql
@@ -1073,7 +1077,7 @@ class ELFTest(unittest.TestCase):
del ql
def test_elf_linux_armeb(self):
ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG, profile='profiles/append_test.ql')
ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG)
ql.run()
del ql
+28 -27
View File
@@ -5,8 +5,6 @@
import os, sys, unittest
from unicorn import UcError
sys.path.append("..")
from qiling import Qiling
from qiling.const import QL_INTERCEPT, QL_VERBOSE
@@ -19,61 +17,64 @@ class ELF_KO_Test(unittest.TestCase):
@unittest.skipIf(IS_FAST_TEST, 'fast test')
def test_demigod_m0hamed_x86(self):
checklist = {}
checklist = []
@linux_kernel_api(params={
"format": STRING
})
def my_printk(ql: Qiling, address: int, params):
ql.log.info(f'oncall printk: params = {params}')
def __my_printk(ql: Qiling, address: int, params):
ql.log.info(f'my printk: {params=}')
checklist['oncall'] = params['format']
checklist.append(params['format'])
return 0
ql = Qiling(["../examples/rootfs/x86_linux/kernel/m0hamed_rootkit.ko"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG)
ql.os.set_api("printk", my_printk)
ql.os.set_api("printk", __my_printk)
ba = ql.loader.load_address
ql.run(ba + 0x01e0, ba + 0x01fa)
try:
ql.run(ba + 0x11e0, ba + 0x11fa)
except UcError as e:
self.fail(e)
else:
self.assertEqual("DONT YOU EVER TRY TO READ THIS FILE OR I AM GOING TO DESTROY YOUR MOST SECRET DREAMS", checklist['oncall'])
self.assertEqual("DONT YOU EVER TRY TO READ THIS FILE OR I AM GOING TO DESTROY YOUR MOST SECRET DREAMS", checklist.pop(0))
self.assertEqual(len(checklist), 0)
def test_demigod_hello_x8664(self):
checklist = {}
checklist = []
def my_onenter(ql: Qiling, address: int, params):
ql.log.info(f'onenter printk: params = {params}')
def __onenter_printk(ql: Qiling, address: int, params):
ql.log.info(f'about to enter printk: {params=}')
checklist['onenter'] = params['format']
checklist.append(params['format'])
ql = Qiling(["../examples/rootfs/x8664_linux/kernel/hello.ko"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG)
ql.os.set_api("printk", my_onenter, QL_INTERCEPT.ENTER)
ql.os.set_api("printk", __onenter_printk, QL_INTERCEPT.ENTER)
ba = ql.loader.load_address
ql.run(ba + 0x1064, ba + 0x107e)
ql.run(ba + 0x64, ba + 0x7e) # run lkm_example_init
ql.run(ba + 0x7f, ba + 0x90) # run lkm_example_exit
self.assertEqual("\x016Hello, World: %p!\n", checklist['onenter'])
self.assertIn('Hello', checklist.pop(0))
self.assertIn('Goodbye', checklist.pop(0))
self.assertEqual(len(checklist), 0)
def test_demigod_hello_mips32(self):
checklist = {}
checklist = []
def my_onexit(ql: Qiling, address: int, params, retval: int):
ql.log.info(f'onexit printk: params = {params}')
def __onexit_printk(ql: Qiling, address: int, params, retval: int):
ql.log.info(f'done with printk: {params=}')
checklist['onexit'] = params['format']
checklist.append(params['format'])
ql = Qiling(["../examples/rootfs/mips32_linux/kernel/hello.ko"], "../examples/rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG)
ql.os.set_api("printk", my_onexit, QL_INTERCEPT.EXIT)
ql.os.set_api("printk", __onexit_printk, QL_INTERCEPT.EXIT)
ba = ql.loader.load_address
ql.run(ba + 0x1060, ba + 0x1084)
ql.run(ba + 0x60, ba + 0x84) # run hello
ql.run(ba + 0x88, ba + 0x98) # run goodbye
self.assertIn('Hello', checklist.pop(0))
self.assertEqual(len(checklist), 0)
self.assertEqual("\x016Hello, World!\n", checklist['onexit'])
if __name__ == "__main__":
unittest.main()
+13 -5
View File
@@ -34,12 +34,20 @@ class ELFTest(unittest.TestCase):
verbose=QL_VERBOSE.DEBUG,
multithread=True)
err = ql_file.open('output.txt', os.O_RDWR | os.O_CREAT, 0o777)
filename = 'output.txt'
err = ql_file.open(filename, os.O_RDWR | os.O_CREAT, 0o777)
ql.os.stderr = err
ql.run()
os.close(err.fileno())
with open('output.txt', 'rb') as f:
self.assertTrue(b'fail' in f.read())
err.close()
with open(filename, 'rb') as f:
content = f.read()
# cleanup
os.remove(filename)
self.assertIn(b'fail', content)
del ql
@@ -92,7 +100,7 @@ class ELFTest(unittest.TestCase):
except:
pass
buf_out = None
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_multithreading"], "../examples/rootfs/x8664_linux", multithread=True, profile= "profiles/append_test.ql")
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_multithreading"], "../examples/rootfs/x8664_linux", multithread=True)
ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER)
ql.run()
+16 -39
View File
@@ -68,8 +68,7 @@ class PETest(unittest.TestCase):
def test_pe_win_x8664_hello(self):
def _t():
ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_hello.exe"], "../examples/rootfs/x8664_windows",
verbose=QL_VERBOSE.DEFAULT)
ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_hello.exe"], "../examples/rootfs/x8664_windows")
ql.run()
del ql
return True
@@ -79,8 +78,7 @@ class PETest(unittest.TestCase):
def test_pe_win_x86_hello(self):
def _t():
ql = Qiling(["../examples/rootfs/x86_windows/bin/x86_hello.exe"], "../examples/rootfs/x86_windows",
verbose=QL_VERBOSE.DEFAULT, profile="profiles/append_test.ql")
ql = Qiling(["../examples/rootfs/x86_windows/bin/x86_hello.exe"], "../examples/rootfs/x86_windows")
ql.run()
del ql
return True
@@ -90,8 +88,7 @@ class PETest(unittest.TestCase):
def test_pe_win_x8664_file_upx(self):
def _t():
ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_file_upx.exe"], "../examples/rootfs/x8664_windows",
verbose=QL_VERBOSE.DEFAULT)
ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_file_upx.exe"], "../examples/rootfs/x8664_windows")
ql.run()
del ql
return True
@@ -101,8 +98,7 @@ class PETest(unittest.TestCase):
def test_pe_win_x86_file_upx(self):
def _t():
ql = Qiling(["../examples/rootfs/x86_windows/bin/x86_file_upx.exe"], "../examples/rootfs/x86_windows",
verbose=QL_VERBOSE.DEFAULT)
ql = Qiling(["../examples/rootfs/x86_windows/bin/x86_file_upx.exe"], "../examples/rootfs/x86_windows")
ql.run()
del ql
return True
@@ -218,22 +214,21 @@ class PETest(unittest.TestCase):
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())
@@ -360,30 +355,12 @@ class PETest(unittest.TestCase):
@unittest.skipIf(IS_FAST_TEST, 'fast test')
def test_pe_win_al_khaser(self):
def _t():
ql = Qiling(["../examples/rootfs/x86_windows/bin/al-khaser.bin"], "../examples/rootfs/x86_windows")
ql = Qiling(["../examples/rootfs/x86_windows/bin/al-khaser.bin"], "../examples/rootfs/x86_windows", verbose=QL_VERBOSE.OFF)
# The hooks are to remove the prints to file. It crashes. will debug why in the future
# def results(ql):
#
# if ql.arch.regs.ebx == 1:
# print("BAD")
# else:
# print("GOOD ")
# ql.arch.regs.eip = 0x402ee4
#
#ql.hook_address(results, 0x00402e66)
# ole32 functions are not implemented yet; stop before the binary
# starts using them
ql.run(end=0x004016ae)
# the program alloc 4 bytes and then tries to write 0x2cc bytes.
# I have no idea of why this code should work without this patch
ql.patch(0x00401984, b'\xb8\x04\x00\x00\x00')
def end(ql):
print("We are finally done")
ql.emu_stop()
ql.hook_address(end, 0x004016ae)
ql.run()
del ql
return True
+11 -6
View File
@@ -11,7 +11,8 @@ from unicorn import UcError
sys.path.append("..")
from qiling import Qiling
from qiling.const import QL_STOP, QL_VERBOSE
from qiling.os.const import POINTER, DWORD, STRING, HANDLE
from qiling.os.const import POINTER, DWORD, HANDLE
from qiling.exception import QlErrorSyscallError
from qiling.os.windows import utils
from qiling.os.windows.wdk_const import *
from qiling.os.windows.api import *
@@ -98,8 +99,7 @@ class PETest(unittest.TestCase):
buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite)
if hFile == 0x13371337:
success, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer)
r = int(success)
nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer)
elif hFile == 0xfffffff5:
s = buffer.decode()
@@ -203,7 +203,12 @@ class PETest(unittest.TestCase):
# asmint32 driver should have been initialized by now. otherwise we get an exception
amsint32: Qiling = getattr(ql, 'amsint32_driver')
utils.io_Write(amsint32, ql.pack32(0xdeadbeef))
# asmint32 driver init doesn't get to run far enough to initialize necessary data
# structures. it is expected to fail.
try:
utils.io_Write(amsint32, ql.pack32(0xdeadbeef))
except QlErrorSyscallError:
pass
# TODO: not sure whether this one is really STDCALL
fcall = amsint32.os.fcall_select(STDCALL)
@@ -264,6 +269,6 @@ class PETest(unittest.TestCase):
# - Call DriverUnload
del ql
if __name__ == "__main__":
unittest.main()
unittest.main()
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env python3
import sys, unittest
sys.path.append("..")
from qiling import Qiling
from qiling.const import QL_VERBOSE
from qiling.extensions.r2.r2 import R2
EVM_CODE = bytes.fromhex("6060604052341561000f57600080fd5b60405160208061031c833981016040528080519060200190919050508060018190556000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050610299806100836000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806318160ddd1461005c57806370a0823114610085578063a9059cbb146100d2575b600080fd5b341561006757600080fd5b61006f61012c565b6040518082815260200191505060405180910390f35b341561009057600080fd5b6100bc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610132565b6040518082815260200191505060405180910390f35b34156100dd57600080fd5b610112600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061017a565b604051808215151515815260200191505060405180910390f35b60015481565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080826000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205403101515156101cb57600080fd5b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555060019050929150505600a165627a7a7230582098f1551a391a3e65b3ce45cfa2b3fa5f91eea9a3e7181a81454e025ea0d7151c0029")
class R2Test(unittest.TestCase):
def test_shellcode_disasm(self):
ql = Qiling(code=EVM_CODE, archtype="evm", verbose=QL_VERBOSE.DISABLED)
r2 = R2(ql)
pd = r2._cmd("pd 32")
self.assertTrue('callvalue' in pd)
def test_addr_flag(self):
ql = Qiling(["../examples/rootfs/x86_windows/bin/x86_hello.exe"], "../examples/rootfs/x86_windows",
verbose=QL_VERBOSE.DISABLED) # x8864_hello does not have 'main'
r2 = R2(ql)
print(r2.where('main'))
self.assertEqual(r2.at(r2.where('main')), 'main')
if __name__ == "__main__":
unittest.main()
+7
View File
@@ -18,6 +18,7 @@ MIPS32EL_LIN = unhexlify('ffff0628ffffd004ffff05280110e4270ff08424ab0f02240c0101
X86_WIN = unhexlify('fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300')
X8664_WIN = unhexlify('fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd54831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373616765426f7800')
ARM_LIN = unhexlify('01308fe213ff2fe178460e300190491a921a0827c251033701df2f62696e2f2f7368')
ARM_THUMB = unhexlify('401c01464fea011200bf')
ARM64_LIN = unhexlify('420002ca210080d2400080d2c81880d2010000d4e60300aa01020010020280d2681980d2010000d4410080d2420002cae00306aa080380d2010000d4210400f165ffff54e0000010420002ca210001caa81b80d2010000d4020004d27f0000012f62696e2f736800')
X8664_FBSD = unhexlify('6a61586a025f6a015e990f054897baff02aaaa80f2ff524889e699046680c2100f05046a0f05041e4831f6990f0548976a035852488d7424f080c2100f0548b8523243427730637257488d3e48af74084831c048ffc00f055f4889d04889fe48ffceb05a0f0575f799043b48bb2f62696e2f2f73685253545f5257545e0f05')
X8664_macos = unhexlify('4831f65648bf2f2f62696e2f7368574889e74831d24831c0b00248c1c828b03b0f05')
@@ -44,6 +45,12 @@ class TestShellcode(unittest.TestCase):
ql = Qiling(code = ARM_LIN, archtype = "arm", ostype = "linux", verbose=QL_VERBOSE.OFF)
ql.run()
def test_linux_arm_thumb(self):
print("Linux ARM Thumb Shllcode")
ql = Qiling(code = ARM_THUMB, archtype = "arm", ostype = "linux", verbose=QL_VERBOSE.OFF, thumb = True)
ql.run()
def test_linux_arm64(self):
print("Linux ARM 64bit Shellcode")
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import sys, unittest
from typing import Optional
sys.path.append("..")
import ctypes
from qiling import Qiling
from qiling.const import QL_ARCH, QL_OS
from qiling.os.struct import BaseStruct
class DummyInternalStruct(BaseStruct):
_fields_ = [
('X', ctypes.c_uint32)
]
# this is defined to let 'assertEqual' work as expected
def __eq__(self, other) -> bool:
return isinstance(other, DummyInternalStruct) and self.X == other.X
class DummyStruct(BaseStruct):
_fields_ = [
('A', ctypes.c_uint32),
('B', ctypes.c_uint64),
('C', DummyInternalStruct),
('D', ctypes.c_char * 16)
]
# we only need context and not going to run anything anyway, so just use whatever
NOPSLED = b'\x90' * 8
ROOTFS = r'../examples/rootfs/x8664_linux'
class StructTest(unittest.TestCase):
def setUp(self) -> None:
ql = Qiling(code=NOPSLED, rootfs=ROOTFS, archtype=QL_ARCH.X8664, ostype=QL_OS.LINUX)
self.ptr = 0x100000
self.mem = ql.mem
self.expected = {
'A' : 0xdeadface,
'B' : 0x1020304050607080,
'C' : DummyInternalStruct(0x11213141),
'D' : b'Hello World!',
}
# create a dummy structure with expected values
dummy = DummyStruct(**self.expected)
# emit dummy structure to memory
ql.mem.map(self.ptr, ql.mem.align_up(dummy.sizeof()))
ql.mem.write(self.ptr, bytes(dummy))
def __read_data(self, offset: int = 0, size: Optional[int] = None) -> bytearray:
return self.mem.read(self.ptr + offset, size or DummyStruct.sizeof())
def __write_data(self, offset: int, data: bytes) -> None:
self.mem.write(self.ptr + offset, data)
@staticmethod
def __to_uint(data: bytearray) -> int:
return int.from_bytes(data, 'little', signed=False)
def test_load_from(self):
dummy = DummyStruct.load_from(self.mem, self.ptr)
self.assertEqual(self.expected['A'], dummy.A)
self.assertEqual(self.expected['B'], dummy.B)
self.assertEqual(self.expected['C'], dummy.C)
self.assertEqual(self.expected['D'], dummy.D)
def test_save_to(self):
dummy = DummyStruct(
A=0x0c0a0f0e,
B=0x1828384858687888,
C=DummyInternalStruct(0x19293949),
D=b'Goodbye World!'
)
dummy.save_to(self.mem, self.ptr)
obj_data = bytes(dummy)
mem_data = self.__read_data()
self.assertEqual(obj_data, mem_data)
def test_ref_discard(self):
data_before = self.__read_data()
unused = []
with DummyStruct.ref(self.mem, self.ptr) as dummy:
print(f'B = {dummy.B:#x}')
print(f'C = {dummy.C}')
unused.append(dummy.A + 1337)
data_after = self.__read_data()
self.assertEqual(data_before, data_after)
def test_ref_save(self):
expected = 0x10303070
with DummyStruct.ref(self.mem, self.ptr) as dummy:
print(f'B = {dummy.B:#x}')
print(f'C = {dummy.C}')
dummy.A = expected
data = self.__read_data(DummyStruct.offsetof('A'), 4)
self.assertEqual(expected, StructTest.__to_uint(data))
def test_ref_save_internal(self):
expected = 0x16363676
with DummyStruct.ref(self.mem, self.ptr) as dummy:
dummy.C.X = expected
data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4)
self.assertEqual(expected, StructTest.__to_uint(data))
def test_volatile_ref(self):
dummy = DummyStruct.volatile_ref(self.mem, self.ptr)
expected = 0x01030307
dummy.A = expected
data = self.__read_data(DummyStruct.offsetof('A'), 4)
self.assertEqual(expected, StructTest.__to_uint(data))
self.assertEqual(self.expected['B'], dummy.B)
self.assertEqual(self.expected['C'], dummy.C)
expected = b'Volatility Test!'
self.__write_data(DummyStruct.offsetof('D'), expected)
self.assertEqual(expected, dummy.D)
def test_volatile_ref_internal(self):
dummy = DummyStruct.volatile_ref(self.mem, self.ptr)
expected = 0x51535357
dummy.C.X = expected
data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4)
self.assertEqual(expected, StructTest.__to_uint(data))
if __name__ == "__main__":
unittest.main()