diff --git a/examples/crackme_x86_linux.py b/examples/crackme_x86_linux.py index 37a057c6..7c1d1647 100644 --- a/examples/crackme_x86_linux.py +++ b/examples/crackme_x86_linux.py @@ -16,14 +16,11 @@ ROOTFS = r"rootfs/x86_linux" class Solver: def __init__(self, invalid: bytes): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) - mock_stdout = pipe.NullOutStream(sys.stdout.fileno()) - # create a silent qiling instance - self.ql = Qiling([rf"{ROOTFS}/bin/crackme_linux"], ROOTFS, - verbose=QL_VERBOSE.OFF, # thwart qiling logger output - stdin=mock_stdin, # take over the input to the program using a fake stdin - stdout=mock_stdout) # disregard program output + self.ql = Qiling([rf"{ROOTFS}/bin/crackme_linux"], ROOTFS, verbose=QL_VERBOSE.OFF) + + 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 # execute program until it reaches the 'main' function self.ql.run(end=0x0804851b) @@ -32,7 +29,7 @@ class Solver: # # since the emulation halted upon entering 'main', its return address is there on # the stack. we use it to limit the emulation till function returns - self.replay_starts = self.ql.reg.arch_pc + self.replay_starts = self.ql.arch.regs.arch_pc self.replay_ends = self.ql.stack_read(0) # instead of restarting the whole program every time a new flag character is guessed, diff --git a/examples/crackme_x86_windows.py b/examples/crackme_x86_windows.py index e256ef73..d60f40c8 100644 --- a/examples/crackme_x86_windows.py +++ b/examples/crackme_x86_windows.py @@ -14,13 +14,10 @@ def instruction_count(ql: Qiling, address: int, size: int, user_data): user_data[0] += 1 def get_count(flag): - mock_stdin = pipe.SimpleStringBuffer() - mock_stdout = pipe.SimpleStringBuffer() + ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", verbose=QL_VERBOSE.OFF) - ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", - verbose=QL_VERBOSE.OFF, - stdin=mock_stdin, - stdout=mock_stdout) + ql.os.stdin = pipe.SimpleStringBuffer() + ql.os.stdout = pipe.SimpleStringBuffer() ql.os.stdin.write(bytes("".join(flag) + "\n", 'utf-8')) count = [0] diff --git a/examples/crackme_x86_windows_auto.py b/examples/crackme_x86_windows_auto.py index be5f517b..8911199d 100644 --- a/examples/crackme_x86_windows_auto.py +++ b/examples/crackme_x86_windows_auto.py @@ -8,26 +8,64 @@ sys.path.append("..") from qiling import Qiling from qiling.extensions import pipe +from qiling.const import QL_INTERCEPT, QL_VERBOSE +from qiling.os.windows.api import HWND, UINT, LONG -def force_call_dialog_func(ql: Qiling): - # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) - # 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.reg.eip = lpDialogFunc +def hook_DialogBoxParamA_onexit(ql: Qiling, address: int, params, retval: int): + # extract lpDialogFunc value + # [see arguments list at 'qiling/os/windows/dlls/user32.py' -> 'hook_DialogBoxParamA'] + lpDialogFunc = params['lpDialogFunc'] -def our_sandbox(path, rootfs): - ql = Qiling(path, rootfs, stdin=pipe.SimpleInStream(sys.stdin.fileno())) + def call_DialogFunc(ql: Qiling): + # we would like to resume from the exact same address that used to invoke + # this hook. in order to prevent an endless loop of hook invocations, we + # remove the hook through its handle. + hh.remove() + + WM_COMMAND = 0x111 + IDS_APPNAME = 1001 + + # [steps #3 and #4] + # set up the arguments and call the address passed through the lpDialogFunc + # param. make sure it resumes back to where we were. + ql.os.fcall.call_native(lpDialogFunc, ( + (HWND, 0), + (UINT, WM_COMMAND), + (UINT, IDS_APPNAME), + (LONG, 0), + ), ql.arch.regs.arch_pc) + + # get DialogBoxParamA return address; should be the first item on the stack + retaddr = ql.arch.stack_read(0) + + # we would like to call DialogFunc as soon as DialogBoxParamA returns, so we + # hook its return address. once it returns, 'call_DialogFunc' will be invoked. + hh = ql.hook_address(call_DialogFunc, retaddr) + +def our_sandbox(path: str, rootfs: str): + ql = Qiling([path], rootfs, verbose=QL_VERBOSE.DEFAULT) + + # this crackme's logic lies within the function passed to DialogBoxParamA through + # the lpDialogFunc parameter. normally DialogBoxParamA would call the function + # passed through that parameter, but Qiling's implementation for it doesn't do + # that. + # + # to solve this crackme and force the "success" dialog to show, we will: + # 1. set up a mock stdin and feed it with the correct flag + # 1. hook DialogBoxParamA to see where its lpDialogFunc param points to + # 2. set up a valid set of arguments DialogFunc expects to see + # 3. call it and see it greets us with a "success" message + + # [step #1] + # set up a mock stdin and feed it with mocked keystrokes + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) + ql.os.stdin.write(b'Ea5yR3versing\n') + + # [step #2] + # intercept DialogBoxParamA on exit + ql.os.set_api('DialogBoxParamA', hook_DialogBoxParamA_onexit, QL_INTERCEPT.EXIT) - ql.os.stdin.write(b"Ea5yR3versing\n") - ql.hook_address(force_call_dialog_func, 0x00401016) ql.run() if __name__ == "__main__": - # Flag is : Ea5yR3versing - our_sandbox(["rootfs/x86_windows/bin/Easy_CrackMe.exe"], "rootfs/x86_windows") + our_sandbox(r"rootfs/x86_windows/bin/Easy_CrackMe.exe", r"rootfs/x86_windows") diff --git a/examples/crackme_x86_windows_setcallback.py b/examples/crackme_x86_windows_setcallback.py index 06856c3e..f873d7d8 100644 --- a/examples/crackme_x86_windows_setcallback.py +++ b/examples/crackme_x86_windows_setcallback.py @@ -10,7 +10,7 @@ from qiling import Qiling def force_call_dialog_func(ql: Qiling): # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) + lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) # setup stack for DialogFunc ql.stack_push(0) ql.stack_push(1001) @@ -18,7 +18,7 @@ def force_call_dialog_func(ql: Qiling): ql.stack_push(0) ql.stack_push(0x0401018) # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + ql.arch.regs.eip = lpDialogFunc def my_sandbox(path, rootfs): ql = Qiling(path, rootfs) diff --git a/examples/crackme_x86_windows_unpatch.py b/examples/crackme_x86_windows_unpatch.py index 30bbee29..1e43e50f 100644 --- a/examples/crackme_x86_windows_unpatch.py +++ b/examples/crackme_x86_windows_unpatch.py @@ -10,7 +10,7 @@ from qiling import Qiling def force_call_dialog_func(ql: Qiling): # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) + lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) # setup stack for DialogFunc ql.stack_push(0) ql.stack_push(1001) @@ -18,7 +18,7 @@ def force_call_dialog_func(ql: Qiling): ql.stack_push(0) ql.stack_push(0x0401018) # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + ql.arch.regs.eip = lpDialogFunc def our_sandbox(path, rootfs): ql = Qiling(path, rootfs) diff --git a/examples/doogie_8086_crack.py b/examples/doogie_8086_crack.py index c76bd6f2..35dc43b9 100644 --- a/examples/doogie_8086_crack.py +++ b/examples/doogie_8086_crack.py @@ -119,7 +119,7 @@ def echo_key(ql: Qiling, key): def show_once(ql: Qiling, key): klen = len(key) - ql.reg.ax = klen + ql.arch.regs.ax = klen ql.mem.write(0x87F4, key) # Partial exectution to skip input reading ql.run(begin=0x801B, end=0x803d) @@ -133,7 +133,7 @@ def third_stage(keys): "rootfs/8086", console=False) ql.add_fs_mapper(0x80, QlDisk("rootfs/8086/doogie/doogie.DOS_MBR", 0x80)) - ql.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) + ql.os.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) hk = ql.hook_code(stop, begin=0x8018, end=0x8018) ql.run() ql.hook_del(hk) @@ -172,10 +172,10 @@ def read_until_zero(ql: Qiling, addr): def set_required_datetime(ql: Qiling): ql.log.info("Setting Feburary 06, 1990") - ql.reg.ch = BIN2BCD(19) - ql.reg.cl = BIN2BCD(1990%100) - ql.reg.dh = BIN2BCD(2) - ql.reg.dl = BIN2BCD(6) + ql.arch.regs.ch = BIN2BCD(19) + ql.arch.regs.cl = BIN2BCD(1990%100) + ql.arch.regs.dh = BIN2BCD(2) + ql.arch.regs.dl = BIN2BCD(6) def stop(ql, addr, data): ql.emu_stop() @@ -187,7 +187,7 @@ def first_stage(): console=False) ql.add_fs_mapper(0x80, QlDisk("rootfs/8086/doogie/doogie.DOS_MBR", 0x80)) # Doogie suggests that the datetime should be 1990-02-06. - ql.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) + ql.os.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) # A workaround to stop the program. hk = ql.hook_code(stop, begin=0x8018, end=0x8018) ql.run() diff --git a/examples/extensions/idaplugin/custom_script.py b/examples/extensions/idaplugin/custom_script.py index c58cfa5e..78ffca21 100644 --- a/examples/extensions/idaplugin/custom_script.py +++ b/examples/extensions/idaplugin/custom_script.py @@ -5,10 +5,10 @@ class QILING_IDA(): pass def _show_context(self, ql:Qiling): - registers = [ k for k in ql.reg.register_mapping.keys() if type(k) is str ] + registers = [ k for k in ql.arch.regs.register_mapping.keys() if type(k) is str ] for idx in range(0, len(registers), 3): regs = registers[idx:idx+3] - s = "\t".join(map(lambda v: f"{v:4}: {ql.reg.__getattr__(v):016x}", regs)) + s = "\t".join(map(lambda v: f"{v:4}: {ql.arch.regs.__getattr__(v):016x}", regs)) ql.log.info(s) def custom_prepare(self, ql:Qiling): diff --git a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py index 83c8d003..2f40ee79 100755 --- a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py @@ -17,50 +17,53 @@ Steps: $ rm -fr afl_outputs/default/ """ -# No more need for importing unicornafl, try ql.afl_fuzz instead! +# No more need for importing unicornafl, try afl.ql_afl_fuzz instead! import os import sys -from typing import Any, Optional +from typing import Optional sys.path.append("../../..") from qiling import Qiling from qiling.const import QL_VERBOSE from qiling.extensions import pipe -from qiling.extensions.afl import ql_afl_fuzz +from qiling.extensions import afl def main(input_file: str): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) - ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", - verbose=QL_VERBOSE.OFF, # keep qiling logging off - console=False, # thwart program output - stdin=mock_stdin, # redirect stdin to our mock to feed it with incoming fuzzed keystrokes - stdout=None, - stderr=None) + verbose=QL_VERBOSE.OFF, # keep qiling logging off + console=False) # thwart program output + + # redirect stdin to our mock to feed it with incoming fuzzed keystrokes + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) def place_input_callback(ql: Qiling, input: bytes, persistent_round: int) -> Optional[bool]: - """Called with every newly generated input. + """Feed generated stimuli to the fuzzed target. + + This method is called with every fuzzing iteration. """ + # feed fuzzed input to our mock stdin ql.os.stdin.write(input) + # signal afl to proceed with this input return True - def start_afl(_ql: Qiling): - """Callback from inside. + def start_afl(ql: Qiling): + """Have Unicorn fork and start instrumentation. """ - ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point]) + + afl.ql_afl_fuzz(ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point]) # get image base address ba = ql.loader.images[0].base - # make process crash whenever __stack_chk_fail@plt is about to be called. + # make the process crash whenever __stack_chk_fail@plt is about to be called. # this way afl will count stack protection violations as crashes ql.hook_address(callback=lambda x: os.abort(), address=ba + 0x1225) - # set a hook on main() to let unicorn fork and start instrumentation + # set afl instrumentation [re]starting point. we set it to 'main' ql.hook_address(callback=start_afl, address=ba + 0x122c) # okay, ready to roll diff --git a/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py b/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py index ccb2cea0..e8e3e56f 100755 --- a/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py @@ -10,11 +10,7 @@ import sys, os, ctypes class SimpleFuzzer: def run(self): - ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", - console=False, # No output - stdin=None, - stdout=None, - stderr=None) + ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", console=False) ba = ql.loader.images[0].base try: # Only instrument the function `fun`, so we don't need to instrument libc and ld diff --git a/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py b/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py index 3aace0d0..2e13e229 100755 --- a/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py +++ b/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py @@ -21,16 +21,13 @@ from qiling.extensions import pipe from qiling.extensions.afl import ql_afl_fuzz def main(input_file, enable_trace=False): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) + ql = Qiling(["./arm_fuzz"], "../../rootfs/arm_qnx", console=enable_trace) - ql = Qiling(["./arm_fuzz"], "../../rootfs/arm_qnx", - stdin=mock_stdin, - stdout=1 if enable_trace else None, - stderr=1 if enable_trace else None, - console = True if enable_trace else False) + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) - # or this for output: - # ... stdout=sys.stdout, stderr=sys.stderr) + if not enable_trace: + ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) + ql.os.stderr = pipe.NullOutStream(sys.stderr.fileno()) def place_input_callback(ql: Qiling, input: bytes, _: int): ql.os.stdin.write(input) @@ -50,7 +47,7 @@ def main(input_file, enable_trace=False): if enable_trace: # The following lines are only for `-t` debug output - md = ql.create_disassembler() + md = ql.arch.disassembler count = [0] def spaced_hex(data): diff --git a/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py b/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py index ec46c647..ca018af0 100644 --- a/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py +++ b/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py @@ -59,7 +59,7 @@ def patcher(ql): def check_pc(ql): print("=" * 50) - print("Hit fuzz point, stop at PC = 0x%x" % ql.reg.arch_pc) + print("Hit fuzz point, stop at PC = 0x%x" % ql.arch.regs.arch_pc) print("=" * 50) ql.emu_stop() diff --git a/examples/hello_arm_linux_custom_syscall.py b/examples/hello_arm_linux_custom_syscall.py index 83855b06..ec21c687 100644 --- a/examples/hello_arm_linux_custom_syscall.py +++ b/examples/hello_arm_linux_custom_syscall.py @@ -30,9 +30,9 @@ if __name__ == "__main__": # Known issue: If the syscall func is not be implemented in qiling, qiling does # not know which func should be replaced. # In that case, you must specify syscall by its number. - ql.set_syscall(0x04, my_syscall_write) + ql.os.set_syscall(0x04, my_syscall_write) # set syscall by syscall name - #ql.set_syscall("write", my_syscall_write) + #ql.os.set_syscall("write", my_syscall_write) ql.run() diff --git a/examples/hello_arm_qnx_customapi.py b/examples/hello_arm_qnx_customapi.py index 9773450f..7924afd7 100644 --- a/examples/hello_arm_qnx_customapi.py +++ b/examples/hello_arm_qnx_customapi.py @@ -28,7 +28,7 @@ def my_puts_onexit(ql: Qiling): if __name__ == "__main__": ql = Qiling(["rootfs/arm_qnx/bin/hello_static"], "rootfs/arm_qnx") - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) - ql.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/examples/hello_arm_uboot.py b/examples/hello_arm_uboot.py index 691318df..9ed8fba5 100644 --- a/examples/hello_arm_uboot.py +++ b/examples/hello_arm_uboot.py @@ -19,11 +19,11 @@ def get_kaimendaji_password(): value_addr = ql.os.heap.alloc(len(value)) ql.mem.write(value_addr, value) - ql.reg.r0 = value_addr - ql.reg.arch_pc = ql.reg.lr + ql.arch.regs.r0 = value_addr + ql.arch.regs.arch_pc = ql.arch.regs.lr def get_password(ql, *args, **kwargs): - password_raw = ql.mem.read(ql.reg.r0, ql.reg.r2) + password_raw = ql.mem.read(ql.arch.regs.r0, ql.arch.regs.r2) password = '' for item in password_raw: @@ -36,21 +36,21 @@ def get_kaimendaji_password(): def partial_run_init(ql): # argv prepare - ql.reg.arch_sp -= 0x30 - arg0_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x30 + arg0_ptr = ql.arch.regs.arch_sp ql.mem.write(arg0_ptr, b"kaimendaji") - ql.reg.arch_sp -= 0x10 - arg1_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x10 + arg1_ptr = ql.arch.regs.arch_sp ql.mem.write(arg1_ptr, b"000000") # arbitrary password - ql.reg.arch_sp -= 0x20 - argv_ptr = ql.reg.arch_sp - ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) - ql.mem.write(argv_ptr + ql.pointersize, ql.pack(arg1_ptr)) + ql.arch.regs.arch_sp -= 0x20 + argv_ptr = ql.arch.regs.arch_sp + ql.mem.write_ptr(argv_ptr, arg0_ptr) + ql.mem.write_ptr(argv_ptr + ql.arch.pointersize, arg1_ptr) - ql.reg.r2 = 2 - ql.reg.r3 = argv_ptr + ql.arch.regs.r2 = 2 + ql.arch.regs.r3 = argv_ptr with open("../examples/rootfs/blob/u-boot.bin.img", "rb") as f: diff --git a/examples/hello_linuxx8664_intercept.py b/examples/hello_linuxx8664_intercept.py index 48df92d7..532970a4 100644 --- a/examples/hello_linuxx8664_intercept.py +++ b/examples/hello_linuxx8664_intercept.py @@ -12,16 +12,16 @@ from qiling.os.linux.syscall_nums import SYSCALL_NR def write_onenter(ql: Qiling, arg1, arg2, arg3, *args): print("enter write syscall!") - ql.reg.rsi = arg2 + 1 - ql.reg.rdx = arg3 - 1 + ql.arch.regs.rsi = arg2 + 1 + ql.arch.regs.rdx = arg3 - 1 def write_onexit(ql: Qiling, arg1, arg2, arg3, *args): print("exit write syscall!") - ql.reg.rax = arg3 + 1 + ql.arch.regs.rax = arg3 + 1 if __name__ == "__main__": ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(SYSCALL_NR.write, write_onenter, QL_INTERCEPT.ENTER) - ql.set_syscall(SYSCALL_NR.write, write_onexit, QL_INTERCEPT.EXIT) + 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() diff --git a/examples/hello_mips32el_linux_function_hook.py b/examples/hello_mips32el_linux_function_hook.py index 849b55f0..74d32c49 100644 --- a/examples/hello_mips32el_linux_function_hook.py +++ b/examples/hello_mips32el_linux_function_hook.py @@ -23,7 +23,7 @@ def my_puts_onexit(ql: Qiling): if __name__ == "__main__": ql = Qiling(["rootfs/mips32el_linux/bin/mips32el_double_hello"], "rootfs/mips32el_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/examples/hello_x8664_linux_customapi.py b/examples/hello_x8664_linux_customapi.py index e624ff33..57892c11 100644 --- a/examples/hello_x8664_linux_customapi.py +++ b/examples/hello_x8664_linux_customapi.py @@ -16,5 +16,5 @@ def my_puts(ql: Qiling): if __name__ == "__main__": ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts) + ql.os.set_api('puts', my_puts) ql.run() diff --git a/examples/hello_x8664_linux_disasm.py b/examples/hello_x8664_linux_disasm.py index adfb22f7..9719828d 100644 --- a/examples/hello_x8664_linux_disasm.py +++ b/examples/hello_x8664_linux_disasm.py @@ -6,9 +6,29 @@ import sys sys.path.append("..") +from typing import Mapping from capstone import Cs + from qiling import Qiling +def __map_regs() -> Mapping[int, int]: + """Map Capstone x86 regs definitions to Unicorn's. + """ + + from capstone import x86_const as cs_x86_const + from unicorn import x86_const as uc_x86_const + + def __canonicalized_mapping(module, prefix: str) -> Mapping[str, int]: + return dict((k[len(prefix):], getattr(module, k)) for k in dir(module) if k.startswith(prefix)) + + cs_x86_regs = __canonicalized_mapping(cs_x86_const, 'X86_REG') + uc_x86_regs = __canonicalized_mapping(uc_x86_const, 'UC_X86_REG') + + return dict((cs_x86_regs[k], uc_x86_regs[k]) for k in cs_x86_regs if k in uc_x86_regs) + +# capstone to unicorn regs mapping +CS_UC_REGS = __map_regs() + def trace(ql: Qiling, address: int, size: int, md: Cs): """Emit tracing info for each and every instruction that is about to be executed. @@ -19,27 +39,36 @@ def trace(ql: Qiling, address: int, size: int, md: Cs): md: initialized disassembler object """ + # read current instruction bytes and disassemble it buf = ql.mem.read(address, size) - nibbles = ql.archbit // 4 + insn = next(md.disasm(buf, address)) - esc_dgray = "\x1b[90m" - esc_reset = "\x1b[39m" + nibbles = ql.arch.bits // 4 + color_faded = '\033[2m' + color_reset = '\033[0m' - for insn in md.disasm(buf, address): - opcode = ''.join(f'{b:02x}' for b in insn.bytes) + # get values of the registers referenced by this instruction. + # + # note: since this method is called before the instruction has been emulated, the 'rip' + # register still points to the current instruction, while the instruction considers it + # as if it was pointing to the next one. that will cause 'rip' to show an incorrect value + reads = (f'{md.reg_name(reg)} = {ql.arch.regs.read(CS_UC_REGS[reg]):#x}' for reg in insn.regs_access()[0]) - # BUG: insn.regs_read doesn't work well, so we use insn.regs_access()[0] instead - reads = (f'{md.reg_name(reg)} = {ql.reg.read(reg):#x}' for reg in insn.regs_access()[0]) - trace_line = f'{insn.address:0{nibbles}x} | {opcode:20s} {insn.mnemonic:10} {insn.op_str:35s} | {", ".join(reads)}' + # construct a human-readable trace line + trace_line = f'{insn.address:0{nibbles}x} | {insn.bytes.hex():24s} {insn.mnemonic:12} {insn.op_str:35s} | {", ".join(reads)}' - # emit trace line in dark gray so it would be easier to tell trace info from other log entries - ql.log.info(f'{esc_dgray}{trace_line}{esc_reset}') + # emit the trace line in a faded color, so it would be easier to tell trace info from other log entries + ql.log.info(f'{color_faded}{trace_line}{color_reset}') if __name__ == "__main__": - ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux") + ql = Qiling([r"rootfs/x8664_linux/bin/x8664_hello"], r"rootfs/x8664_linux") - md = ql.create_disassembler() + # acquire a disassembler instance bound to arch + md = ql.arch.disassembler md.detail = True + # register the trace method to be called before each instruction ql.hook_code(trace, user_data=md) + + # go! ql.run() diff --git a/examples/hello_x8664_windows_customapi.py b/examples/hello_x8664_windows_customapi.py index 3f195aad..522a8a39 100644 --- a/examples/hello_x8664_windows_customapi.py +++ b/examples/hello_x8664_windows_customapi.py @@ -39,9 +39,9 @@ def my_onexit(ql: Qiling, address: int, params, retval: int): def my_sandbox(path, rootfs): ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG) - ql.set_api("_cexit", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("puts", my_puts, QL_INTERCEPT.CALL) - ql.set_api("atexit", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("_cexit", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("puts", my_puts, QL_INTERCEPT.CALL) + ql.os.set_api("atexit", my_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/examples/mcu/gd32vf103_blink.py b/examples/mcu/gd32vf103_blink.py index d6621c96..003e5a1c 100644 --- a/examples/mcu/gd32vf103_blink.py +++ b/examples/mcu/gd32vf103_blink.py @@ -21,7 +21,7 @@ delay_cycles_begin = 0x800015c delay_cycles_end = 0x800018c def skip_delay(ql): - ql.reg.pc = delay_cycles_end + ql.arch.regs.pc = delay_cycles_end ql.hook_address(skip_delay, delay_cycles_begin) ql.hw.gpioc.hook_set(13, lambda : print('Set PC13')) diff --git a/examples/mcu/stm32f407_hack_lock.py b/examples/mcu/stm32f407_hack_lock.py index a5d14465..de41dad4 100644 --- a/examples/mcu/stm32f407_hack_lock.py +++ b/examples/mcu/stm32f407_hack_lock.py @@ -48,7 +48,7 @@ def crack(passwd): ql.hw.systick.set_ratio(100) ql.run(count=1000000, end=0x8003225) - if ql.arch.get_pc() == 0x8003225: + if ql.arch.effective_pc == 0x8003225: print('Success, the passwd is', passwd) else: print('Fail, the passwd is not', passwd) diff --git a/examples/mcu/stm32f407_mnist_oled.py b/examples/mcu/stm32f407_mnist_oled.py index 3bab0edc..6055518d 100644 --- a/examples/mcu/stm32f407_mnist_oled.py +++ b/examples/mcu/stm32f407_mnist_oled.py @@ -28,8 +28,8 @@ ql.hw.systick.ratio = 1000 ## a temporary method def hook_smlabb(ql): - ql.reg.r3 = ql.reg.r2 + ql.reg.r1 * ql.reg.r3 - ql.reg.pc = (ql.reg.pc + 4) | 1 + ql.arch.regs.r3 = ql.arch.regs.r2 + ql.arch.regs.r1 * ql.arch.regs.r3 + ql.arch.regs.pc = (ql.arch.regs.pc + 4) | 1 ql.hook_address(hook_smlabb, 0x8007a12) ql.hook_address(hook_smlabb, 0x8007b60) diff --git a/examples/mcu/stm32f411_i2c_lcd.py b/examples/mcu/stm32f411_i2c_lcd.py index bd036d7c..48b14383 100644 --- a/examples/mcu/stm32f411_i2c_lcd.py +++ b/examples/mcu/stm32f411_i2c_lcd.py @@ -42,7 +42,7 @@ if __name__ == "__main__": delay_start = 0x8002936 delay_end = 0x8002955 def skip_delay(ql): - ql.reg.pc = delay_end + ql.arch.regs.pc = delay_end ql.hook_address(skip_delay, delay_start) ql.run(count=100000) diff --git a/examples/netgear_6220_mips32el_linux.py b/examples/netgear_6220_mips32el_linux.py index 323094bf..06299773 100644 --- a/examples/netgear_6220_mips32el_linux.py +++ b/examples/netgear_6220_mips32el_linux.py @@ -61,8 +61,8 @@ def my_netgear(path, rootfs): ql.os.root = False ql.add_fs_mapper('/proc', '/proc') - ql.set_syscall(4004, my_syscall_write) - ql.set_api('bind', my_bind, QL_INTERCEPT.ENTER) # intercepting the bind call on enter + ql.os.set_syscall(4004, my_syscall_write) + ql.os.set_api('bind', my_bind, QL_INTERCEPT.ENTER) # intercepting the bind call on enter ql.run() diff --git a/examples/petya_8086_crack.py b/examples/petya_8086_crack.py index 1735090a..75220269 100644 --- a/examples/petya_8086_crack.py +++ b/examples/petya_8086_crack.py @@ -28,7 +28,7 @@ def one_round(ql: Qiling, key: bytes, key_address): gkeys = generate_key(key) ql.mem.write(key_address, gkeys) ql.run(begin=verfication_start_ip, end=verfication_start_ip+6) - lba37 = ql.mem.read(ql.reg.sp + 0x220, 0x200) + lba37 = ql.mem.read(ql.arch.regs.sp + 0x220, 0x200) for ch in lba37: if ch != 0x37: return False @@ -62,12 +62,12 @@ def second_stage(ql: Qiling): #nonce = get_nonce(disk) verfication_data = disk.read_sectors(0x37, 1) nonce_data = disk.read_sectors(0x36, 1) - ql.reg.sp -= 0x200 - verification_data_address = ql.reg.sp - ql.reg.sp -= 0x200 - nonce_address = ql.reg.sp + 0x21 - ql.reg.sp -= 0x20 - key_address = ql.reg.sp + ql.arch.regs.sp -= 0x200 + verification_data_address = ql.arch.regs.sp + ql.arch.regs.sp -= 0x200 + nonce_address = ql.arch.regs.sp + 0x21 + ql.arch.regs.sp -= 0x20 + key_address = ql.arch.regs.sp ql.mem.write(verification_data_address, verfication_data) ql.mem.write(nonce_address - 0x21, nonce_data) ql.arch.stack_push(0x200) diff --git a/examples/sality.py b/examples/sality.py index 330decaa..6cc54812 100644 --- a/examples/sality.py +++ b/examples/sality.py @@ -89,8 +89,8 @@ def _WriteFile(ql: Qiling, address: int, params): if hFile == 0xfffffff5: s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) - ql.os.utils.string_appearance(s.decode()) - ql.mem.write(lpNumberOfBytesWritten, ql.pack(nNumberOfBytesToWrite)) + ql.os.stats.log_string(s.decode()) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite) else: f = ql.os.handle_manager.get(hFile) if f is None: @@ -101,7 +101,7 @@ def _WriteFile(ql: Qiling, address: int, params): f = f.obj buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) f.write(bytes(buffer)) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) return ret @winsdkapi(cc=STDCALL, params={ @@ -120,7 +120,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) try: r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) except Exception as e: ql.log.exception("") print("Exception = %s" % str(e)) @@ -170,7 +170,7 @@ def hook_StartServiceA(ql: Qiling, address: int, params): def hook_stop_address(ql): - print(" >>>> Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> Stop address: 0x%08x" % ql.arch.regs.arch_pc) ql.emu_stop() @@ -181,10 +181,10 @@ if __name__ == "__main__": ql.amsint32_driver = None # emulate some Windows API - ql.set_api("CreateThread", hook_CreateThread) - ql.set_api("CreateFileA", hook_CreateFileA) - ql.set_api("WriteFile", hook_WriteFile) - ql.set_api("StartServiceA", hook_StartServiceA) + ql.os.set_api("CreateThread", hook_CreateThread) + ql.os.set_api("CreateFileA", hook_CreateFileA) + ql.os.set_api("WriteFile", hook_WriteFile) + ql.os.set_api("StartServiceA", hook_StartServiceA) #init sality ql.hook_address(hook_stop_address, 0x40EFFB) ql.run() diff --git a/examples/simple_efi_x8664.py b/examples/simple_efi_x8664.py index 32c21a28..c6410b0c 100644 --- a/examples/simple_efi_x8664.py +++ b/examples/simple_efi_x8664.py @@ -40,7 +40,7 @@ if __name__ == "__main__": ql = Qiling(["rootfs/x8664_efi/bin/TcgPlatformSetupPolicy"], "rootfs/x8664_efi", env=env, verbose=QL_VERBOSE.DEBUG) - ql.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) - ql.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) + ql.os.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) ql.run() diff --git a/examples/tendaac1518_httpd.py b/examples/tendaac1518_httpd.py index 0090b944..819e8310 100644 --- a/examples/tendaac1518_httpd.py +++ b/examples/tendaac1518_httpd.py @@ -75,7 +75,7 @@ def my_sandbox(path, rootfs): ql.debugger = False if ql.debugger == True: - ql.set_syscall("vfork", myvfork) + ql.os.set_syscall("vfork", myvfork) ql.run() diff --git a/examples/windows_trace.py b/examples/windows_trace.py index c6b40ba1..e218b833 100644 --- a/examples/windows_trace.py +++ b/examples/windows_trace.py @@ -38,14 +38,14 @@ class colors: def dump_regs(ql: Qiling): regs = { - 'eax': ql.reg.eax, - 'ebx': ql.reg.ebx, - 'ecx': ql.reg.ecx, - 'edx': ql.reg.edx, - 'edi': ql.reg.edi, - 'esi': ql.reg.esi, - 'ebp': ql.reg.ebp, - 'esp': ql.reg.esp + 'eax': ql.arch.regs.eax, + 'ebx': ql.arch.regs.ebx, + 'ecx': ql.arch.regs.ecx, + 'edx': ql.arch.regs.edx, + 'edi': ql.arch.regs.edi, + 'esi': ql.arch.regs.esi, + 'ebp': ql.arch.regs.ebp, + 'esp': ql.arch.regs.esp } if not hasattr(dump_regs, 'regs'): diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index f27925d1..e1b84d18 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -3,8 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from abc import ABC -from typing import Optional +from abc import abstractmethod from unicorn import Uc from unicorn.unicorn import UcContext @@ -12,21 +11,37 @@ from capstone import Cs from keystone import Ks from qiling import Qiling +from qiling.const import QL_ARCH, QL_ENDIAN +from .register import QlRegisterManager from .utils import QlArchUtils -class QlArch(ABC): +class QlArch: + type: QL_ARCH + bits: int + def __init__(self, ql: Qiling): self.ql = ql self.utils = QlArchUtils(ql) - self._disasm: Optional[Cs] = None - self._asm: Optional[Ks] = None - - # ql.init_Uc - initialized unicorn engine @property - def init_uc(self) -> Uc: - return self.get_init_uc() + @abstractmethod + def uc(self) -> Uc: + """Get unicorn instance bound to arch. + """ + pass + + @property + @abstractmethod + def regs(self) -> QlRegisterManager: + """Architectural registers. + """ + + pass + + @property + def pointersize(self) -> int: + return self.bits // 8 def stack_push(self, value: int) -> int: """Push a value onto the architectural stack. @@ -37,10 +52,10 @@ class QlArch(ABC): Returns: the top of stack after pushing the value """ - self.ql.reg.arch_sp -= self.ql.pointersize - self.ql.mem.write(self.ql.reg.arch_sp, self.ql.pack(value)) + self.regs.arch_sp -= self.pointersize + self.ql.mem.write_ptr(self.regs.arch_sp, value) - return self.ql.reg.arch_sp + return self.regs.arch_sp def stack_pop(self) -> int: @@ -49,8 +64,8 @@ class QlArch(ABC): Returns: the value at the top of stack """ - data = self.ql.unpack(self.ql.mem.read(self.ql.reg.arch_sp, self.ql.pointersize)) - self.ql.reg.arch_sp += self.ql.pointersize + data = self.ql.mem.read_ptr(self.regs.arch_sp) + self.regs.arch_sp += self.pointersize return data @@ -64,12 +79,12 @@ class QlArch(ABC): Args: offset: offset in bytes from the top of the stack, not necessarily aligned to the native stack item size. the offset may be either positive or netagive, where - a 0 value means overwriting the value at the top of the stack + a 0 value means retrieving the value at the top of the stack Returns: the value at the specified address """ - return self.ql.unpack(self.ql.mem.read(self.ql.reg.arch_sp + offset, self.ql.pointersize)) + return self.ql.mem.read_ptr(self.regs.arch_sp + offset) def stack_write(self, offset: int, value: int) -> None: @@ -84,48 +99,40 @@ class QlArch(ABC): a 0 value means overwriting the value at the top of the stack """ - self.ql.mem.write(self.ql.reg.arch_sp + offset, self.ql.pack(value)) - - - # set PC - def set_pc(self, address: int) -> None: - self.ql.reg.arch_pc = address - - - # get PC - def get_pc(self) -> int: - return self.ql.reg.arch_pc - - - # set stack pointer - def set_sp(self, address: int) -> None: - self.ql.reg.arch_sp = address - - - # get stack pointer - def get_sp(self) -> int: - return self.ql.reg.arch_sp + self.ql.mem.write_ptr(self.regs.arch_sp + offset, value) # Unicorn's CPU state save - def context_save(self) -> UcContext: - return self.ql.uc.context_save() + def save(self) -> UcContext: + return self.uc.context_save() # Unicorn's CPU state restore method - def context_restore(self, saved_context: UcContext): - self.ql.uc.context_restore(saved_context) + def restore(self, saved_context: UcContext): + self.uc.context_restore(saved_context) - def create_disassembler(self) -> Cs: - """Get disassembler insatnce bound to arch. + @property + @abstractmethod + def disassembler(self) -> Cs: + """Get disassembler instance bound to arch. """ - raise NotImplementedError(self.__class__.__name__) + pass - def create_assembler(self) -> Ks: - """Get assembler insatnce bound to arch. + @property + @abstractmethod + def assembler(self) -> Ks: + """Get assembler instance bound to arch. """ - raise NotImplementedError(self.__class__.__name__) + pass + + @property + @abstractmethod + def endian(self) -> QL_ENDIAN: + """Get processor endianess. + """ + + pass \ No newline at end of file diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index f593f4a9..c03d0d8c 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -3,118 +3,110 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_THUMB, UC_MODE_BIG_ENDIAN -from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_THUMB -from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB +from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_THUMB, CS_MODE_BIG_ENDIAN +from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_BIG_ENDIAN from qiling import Qiling -from qiling.const import QL_ARCH, QL_ENDIAN from qiling.arch.arch import QlArch -from qiling.arch.arm_const import * -from qiling.exception import QlErrorArch +from qiling.arch import arm_const +from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN class QlArchARM(QlArch): - def __init__(self, ql: Qiling): + type = QL_ARCH.ARM + bits = 32 + + def __init__(self, ql: Qiling, endian: QL_ENDIAN, thumb: bool): super().__init__(ql) - reg_maps = ( - reg_map, - ) - - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) + self._init_endian = endian + self._init_thumb = thumb self.arm_get_tls_addr = 0xFFFF0FE0 - # get initialized unicorn engine - def get_init_uc(self) -> Uc: - if self.ql.archendian == QL_ENDIAN.EB: - mode = UC_MODE_ARM + UC_MODE_BIG_ENDIAN + @cached_property + def uc(self) -> Uc: + mode = UC_MODE_ARM - elif self.ql.archtype == QL_ARCH.ARM_THUMB: - mode = UC_MODE_THUMB + if self._init_endian == QL_ENDIAN.EB: + mode += UC_MODE_BIG_ENDIAN - elif self.ql.archtype == QL_ARCH.ARM: - mode = UC_MODE_ARM - - else: - raise QlErrorArch(f'unsupported arch type {self.ql.archtype}') + if self._init_thumb: + mode += UC_MODE_THUMB return Uc(UC_ARCH_ARM, mode) + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = arm_const.reg_map + pc_reg = 'pc' + sp_reg = 'sp' - # get PC - def get_pc(self) -> int: - return self.ql.reg.pc | self.thumb + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) @property - def thumb(self) -> int: - """ Thumb execution state bit """ - - return (self.ql.reg.cpsr >> 5) & 1 + def is_thumb(self) -> bool: + return bool(self.regs.cpsr & (1 << 5)) - def create_disassembler(self) -> Cs: + @property + def endian(self) -> QL_ENDIAN: + # FIXME: ARM is a bi-endian architecture which allows flipping core endianess + # while running. endianess is tested in runtime through CPSR[9], however unicorn + # doesn't reflect the endianess correctly through that bit. + # @see: https://github.com/unicorn-engine/unicorn/issues/1542 + # + # we work around this by using the initial endianess configuration, even though + # it might have been changed since. + # + # return QL_ENDIAN.EB if self.regs.cpsr & (1 << 9) else QL_ENDIAN.EL + + return self._init_endian + + @property + def effective_pc(self) -> int: + """Get effective PC value, taking Thumb mode into account. + """ + + # append 1 to pc if in thumb mode, or 0 otherwise + return self.regs.pc + int(self.is_thumb) + + @property + def disassembler(self) -> Cs: # note: we do not cache the disassembler instance; rather we refresh it - # each time to make sure thumb mode is taken into account + # each time to make sure current endianess and thumb mode are taken into + # account - if self.ql.archtype == QL_ARCH.ARM: - # FIXME: mode should take endianess into account - mode = CS_MODE_THUMB if self.thumb else CS_MODE_ARM + mode = CS_MODE_ARM - elif self.ql.archtype == QL_ARCH.ARM_THUMB: - mode = CS_MODE_THUMB + if self.endian == QL_ENDIAN.EB: + mode += CS_MODE_BIG_ENDIAN - else: - raise QlErrorArch(f'unexpected arch type {self.ql.archtype}') + if self.is_thumb: + mode += CS_MODE_THUMB return Cs(CS_ARCH_ARM, mode) - - def create_assembler(self) -> Ks: + @property + def assembler(self) -> Ks: # note: we do not cache the assembler instance; rather we refresh it - # each time to make sure thumb mode is taken into account + # each time to make sure current endianess and thumb mode are taken into + # account - if self.ql.archtype == QL_ARCH.ARM: - # FIXME: mode should take endianess into account - mode = KS_MODE_THUMB if self.thumb else KS_MODE_ARM + mode = KS_MODE_ARM - elif self.ql.archtype == QL_ARCH.ARM_THUMB: - mode = KS_MODE_THUMB + if self.endian == QL_ENDIAN.EB: + mode += KS_MODE_BIG_ENDIAN - else: - raise QlErrorArch(f'unexpected arch type {self.ql.archtype}') + if self.is_thumb: + mode += KS_MODE_THUMB return Ks(KS_ARCH_ARM, mode) def enable_vfp(self) -> None: - self.ql.reg.c1_c0_2 = self.ql.reg.c1_c0_2 | (0xf << 20) + # set full access to cp10 and cp11 + self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0b11 << 20) | (0b11 << 22) - if self.ql.archendian == QL_ENDIAN.EB: - self.ql.reg.fpexc = 0x40000000 - #self.ql.reg.fpexc = 0x00000040 - else: - self.ql.reg.fpexc = 0x40000000 - - """ - set_tls - """ - def init_get_tls(self): - self.ql.mem.map(0xFFFF0000, 0x1000, info="[arm_tls]") - """ - 'adr r0, data; ldr r0, [r0]; mov pc, lr; data:.ascii "\x00\x00"' - """ - sc = b'\x04\x00\x8f\xe2\x00\x00\x90\xe5\x0e\xf0\xa0\xe1\x00\x00\x00\x00' - - # if ql.archendian == QL_ENDIAN.EB: - # sc = swap_endianess(sc) - - self.ql.mem.write(self.ql.arch.arm_get_tls_addr, sc) - self.ql.log.debug("Set init_kernel_get_tls") - - def swap_endianess(self, s: bytes, blksize=4) -> bytes: - blocks = (s[i:i + blksize] for i in range(0, len(s), blksize)) - - return b''.join(bytes(reversed(b)) for b in blocks) \ No newline at end of file + self.regs.fpexc = (1 << 30) diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 6a08a50f..0367a818 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -3,44 +3,48 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_ARM64, UC_MODE_ARM from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM -from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN +from keystone import Ks, KS_ARCH_ARM64, KS_MODE_ARM -from qiling import Qiling from qiling.arch.arch import QlArch -from qiling.arch.arm64_const import * +from qiling.arch import arm64_const +from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN class QlArchARM64(QlArch): - def __init__(self, ql: Qiling): - super().__init__(ql) + type = QL_ARCH.ARM64 + bits = 64 - reg_maps = ( - reg_map, - reg_map_w - ) - - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) - - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_ARM64, UC_MODE_ARM) - def create_disassembler(self) -> Cs: - if self._disasm is None: - self._disasm = Cs(CS_ARCH_ARM64, CS_MODE_ARM) + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **arm64_const.reg_map, + **arm64_const.reg_map_w + ) - return self._disasm + pc_reg = 'pc' + sp_reg = 'sp' - def create_assembler(self) -> Ks: - if self._asm is None: - self._asm = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - return self._asm + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL + + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_ARM64, CS_MODE_ARM) + + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_ARM64, KS_MODE_ARM) def enable_vfp(self): - self.ql.reg.cpacr_el1 = self.ql.reg.cpacr_el1 | 0x300000 + self.regs.cpacr_el1 = self.regs.cpacr_el1 | 0x300000 diff --git a/qiling/arch/arm_utils.py b/qiling/arch/arm_utils.py new file mode 100644 index 00000000..a8570086 --- /dev/null +++ b/qiling/arch/arm_utils.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from qiling import Qiling + +def init_get_tls(ql: Qiling, address: int) -> None: + # adr r0, data + # ldr r0, [r0] + # mov pc, lr + # + # data: + # .ascii "\x00\x00" + + code = bytes.fromhex(''' + 04 00 8f e2 + 00 00 90 e5 + 0e f0 a0 e1 + 00 00 00 00 + ''') + + # if endian == QL_ENDIAN.EB: + # code = swap_endianess(code) + + base = ql.mem.align(address) + size = ql.mem.align_up(len(code)) + + ql.mem.map(base, size, info="[arm_tls]") + ql.mem.write(address, code) + + ql.log.debug('Set kernel get_tls') + +# def swap_endianess(s: bytes, blksize: int = 4) -> bytes: +# blocks = (s[i:i + blksize] for i in range(0, len(s), blksize)) +# +# return b''.join(bytes(reversed(b)) for b in blocks) \ No newline at end of file diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 58c72db6..698ce996 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -3,94 +3,103 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property +from contextlib import ContextDecorator + from unicorn import Uc, UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_MCLASS, UC_MODE_THUMB from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_MCLASS, CS_MODE_THUMB from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB -from contextlib import ContextDecorator - -from qiling.const import QL_VERBOSE +from qiling import Qiling +from qiling.arch.arm import QlArchARM +from qiling.arch import cortex_m_const +from qiling.arch.register import QlRegisterManager +from qiling.arch.cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP +from qiling.const import QL_ARCH, QL_ENDIAN, QL_VERBOSE from qiling.exception import QlErrorNotImplemented -from .arm import QlArchARM -from .cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP, reg_map - class QlInterruptContext(ContextDecorator): - def __init__(self, ql): + def __init__(self, ql: Qiling): self.ql = ql self.reg_context = ['xpsr', 'pc', 'lr', 'r12', 'r3', 'r2', 'r1', 'r0'] def __enter__(self): for reg in self.reg_context: - val = self.ql.reg.read(reg) + val = self.ql.arch.regs.read(reg) self.ql.arch.stack_push(val) if self.ql.verbose >= QL_VERBOSE.DISASM: self.ql.log.info(f'Enter into interrupt') def __exit__(self, *exc): - retval = self.ql.arch.get_pc() - + retval = self.ql.arch.effective_pc if retval & EXC_RETURN.MASK != EXC_RETURN.MASK: self.ql.log.warning('Interrupt Crash') self.ql.stop() else: # Exit handler mode - self.ql.reg.write('ipsr', 0) + self.ql.arch.regs.write('ipsr', 0) # switch the stack accroding exc_return - old_ctrl = self.ql.reg.read('control') + old_ctrl = self.ql.arch.regs.read('control') if retval & EXC_RETURN.RETURN_SP: - self.ql.reg.write('control', old_ctrl | CONTROL.SPSEL) + self.ql.arch.regs.write('control', old_ctrl | CONTROL.SPSEL) else: - self.ql.reg.write('control', old_ctrl & ~CONTROL.SPSEL) + self.ql.arch.regs.write('control', old_ctrl & ~CONTROL.SPSEL) # Restore stack for reg in reversed(self.reg_context): val = self.ql.arch.stack_pop() if reg == 'xpsr': - self.ql.reg.write('XPSR_NZCVQG', val) + self.ql.arch.regs.write('XPSR_NZCVQG', val) else: - self.ql.reg.write(reg, val) + self.ql.arch.regs.write(reg, val) if self.ql.verbose >= QL_VERBOSE.DISASM: self.ql.log.info('Exit from interrupt') class QlArchCORTEX_M(QlArchARM): - def __init__(self, ql): - super().__init__(ql) + type = QL_ARCH.ARM + bits = 32 - reg_maps = ( - reg_map, - ) + def __init__(self, ql: Qiling): + super().__init__(ql, endian=QL_ENDIAN.EL, thumb=True) - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - - def get_init_uc(self): + @cached_property + def uc(self): return Uc(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB) - def create_disassembler(self) -> Cs: + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = cortex_m_const.reg_map + pc_reg = 'pc' + sp_reg = 'sp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + + @cached_property + def disassembler(self) -> Cs: return Cs(CS_ARCH_ARM, CS_MODE_ARM + CS_MODE_MCLASS + CS_MODE_THUMB) - def create_assembler(self) -> Ks: + @cached_property + def assembler(self) -> Ks: return Ks(KS_ARCH_ARM, KS_MODE_ARM + KS_MODE_THUMB) @property - def thumb(self): - return 1 + def is_thumb(self): + return True def is_handler_mode(self): - return self.ql.reg.read('ipsr') > 1 + return self.regs.ipsr > 1 def using_psp(self): - return not self.is_handler_mode() and (self.ql.reg.read('control') & CONTROL.SPSEL) > 0 + return not self.is_handler_mode() and (self.regs.control & CONTROL.SPSEL) > 0 def init_context(self): - self.ql.reg.write('lr', 0xffffffff) - self.ql.reg.write('msp', self.ql.mem.read_ptr(0x0)) - self.ql.reg.write('pc' , self.ql.mem.read_ptr(0x4)) + self.regs.lr = 0xffffffff + self.regs.msp = self.ql.mem.read_ptr(0x0) + self.regs.pc = self.ql.mem.read_ptr(0x4) def unicorn_exception_handler(self, ql, intno): forward_mapper = { @@ -124,28 +133,28 @@ class QlArchCORTEX_M(QlArchARM): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def interrupt_handler(self, ql, intno): - basepri = self.ql.reg.read('basepri') & 0xf0 + basepri = self.regs.basepri & 0xf0 if basepri and basepri <= ql.hw.nvic.get_priority(intno): return - if intno > IRQ.HARD_FAULT and (ql.reg.read('primask') & 0x1): + if intno > IRQ.HARD_FAULT and (self.regs.primask & 0x1): return - - if intno != IRQ.NMI and (ql.reg.read('faultmask') & 0x1): + + if intno != IRQ.NMI and (self.regs.faultmask & 0x1): return if ql.verbose >= QL_VERBOSE.DISASM: ql.log.debug(f'Handle the intno: {intno}') - + with QlInterruptContext(ql): isr = intno + 16 offset = isr * 4 entry = ql.mem.read_ptr(offset) - exc_return = 0xFFFFFFFD if self.ql.arch.using_psp() else 0xFFFFFFF9 + exc_return = 0xFFFFFFFD if self.using_psp() else 0xFFFFFFF9 - self.ql.reg.write('ipsr', isr) - self.ql.reg.write('pc', entry) - self.ql.reg.write('lr', exc_return) + self.regs.write('ipsr', isr) + self.regs.write('pc', entry) + self.regs.write('lr', exc_return) - self.ql.emu_start(self.ql.arch.get_pc(), 0, count=0xffffff) + self.ql.emu_start(self.effective_pc, 0, count=0xffffff) diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index cfcc1abc..b7479cd5 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -9,6 +9,9 @@ from .vm.evm import QlArchEVMEmulator class QlArchEVM(QlArch): + type = QL_ARCH.EVM + bits = 1 + def __init__(self, ql) -> None: super(QlArchEVM, self).__init__(ql) self.evm = QlArchEVMEmulator(self.ql) @@ -28,5 +31,10 @@ class QlArchEVM(QlArch): def stack_write(self, offset, data): return None - def get_init_uc(self): + @property + def uc(self): return None + + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index ee26b6c4..8391becc 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -3,57 +3,66 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_MIPS, UC_MODE_MIPS32, UC_MODE_BIG_ENDIAN, UC_MODE_LITTLE_ENDIAN from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32, CS_MODE_BIG_ENDIAN, CS_MODE_LITTLE_ENDIAN from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS32, KS_MODE_BIG_ENDIAN, KS_MODE_LITTLE_ENDIAN from qiling import Qiling -from qiling.const import QL_ENDIAN from qiling.arch.arch import QlArch -from qiling.arch.mips_const import * +from qiling.arch import mips_const +from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN class QlArchMIPS(QlArch): - def __init__(self, ql: Qiling): + type = QL_ARCH.MIPS + bits = 32 + + def __init__(self, ql: Qiling, endian: QL_ENDIAN): super().__init__(ql) - reg_maps = ( - reg_map, - reg_map_afpr128 - ) + self._init_endian = endian - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) - - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: endian = { QL_ENDIAN.EB: UC_MODE_BIG_ENDIAN, QL_ENDIAN.EL: UC_MODE_LITTLE_ENDIAN - }[self.ql.archendian] + }[self.endian] return Uc(UC_ARCH_MIPS, UC_MODE_MIPS32 + endian) - def create_disassembler(self) -> Cs: - if self._disasm is None: - endian = { - QL_ENDIAN.EL : CS_MODE_LITTLE_ENDIAN, - QL_ENDIAN.EB : CS_MODE_BIG_ENDIAN - }[self.ql.archendian] + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **mips_const.reg_map, + **mips_const.reg_map_afpr128 + ) - self._disasm = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) + pc_reg = 'pc' + sp_reg = 'sp' - return self._disasm + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - def create_assembler(self) -> Ks: - if self._asm is None: - endian = { - QL_ENDIAN.EL : KS_MODE_LITTLE_ENDIAN, - QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN - }[self.ql.archendian] + @cached_property + def disassembler(self) -> Cs: + endian = { + QL_ENDIAN.EL : CS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB : CS_MODE_BIG_ENDIAN + }[self.endian] - self._asm = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + endian) + return Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) - return self._asm + @cached_property + def assembler(self) -> Ks: + endian = { + QL_ENDIAN.EL : KS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN + }[self.endian] + + return Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + endian) + + @property + def endian(self) -> QL_ENDIAN: + return self._init_endian diff --git a/qiling/arch/msr.py b/qiling/arch/msr.py new file mode 100644 index 00000000..08409cff --- /dev/null +++ b/qiling/arch/msr.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from unicorn import Uc + +class QlMsrManager: + """Enables access to Intel MSR. + """ + + def __init__(self, uc: Uc) -> None: + self.uc = uc + + def read(self, msr: int) -> int: + """Read a model-specific register value. + """ + + return self.uc.msr_read(msr) + + def write(self, msr: int, value: int): + """Write a model-specific register value. + """ + + self.uc.msr_write(msr, value) diff --git a/qiling/arch/register.py b/qiling/arch/register.py index 55ec23b4..fab8f3af 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -5,31 +5,37 @@ from typing import Any, Mapping, MutableMapping, Union -from qiling import Qiling +from unicorn import Uc class QlRegisterManager: - """This class exposes the ql.reg features that allows you to directly access + """This class exposes the ql.arch.regs features that allows you to directly access or assign values to CPU registers of a particular architecture. - - Registers exposed are listed in the *_const.py files in the respective - arch directories and are mapped to Unicorn Engine's definitions """ - def __init__(self, ql: Qiling): + def __init__(self, uc: Uc, regs_map: Mapping[str, int], pc_reg: str, sp_reg: str): + """Initialize the registers manager. + + Args: + uc: initialized unicorn instance + regs_map: registers names mapped to their corresponding unicorn definitions + pc_reg: name of the architectural program counter register + sp_reg: name of the architectural stack pointer register + """ + # this funny way of initialization is used to avoid calling self setattr and # getattr upon init. if it did, it would go into an endless recursion - self.register_mapping: MutableMapping[str, int] - super().__setattr__('register_mapping', {}) + self.register_mapping: Mapping[str, int] + super().__setattr__('register_mapping', regs_map) - self.ql = ql - self.uc_pc = 0 - self.uc_sp = 0 + self.uc = uc + self.uc_pc = self.register_mapping[pc_reg] + self.uc_sp = self.register_mapping[sp_reg] def __getattr__(self, name: str) -> Any: name = name.lower() if name in self.register_mapping: - return self.ql.uc.reg_read(self.register_mapping[name]) + return self.uc.reg_read(self.register_mapping[name]) else: return super().__getattribute__(name) @@ -39,19 +45,12 @@ class QlRegisterManager: name = name.lower() if name in self.register_mapping: - self.ql.uc.reg_write(self.register_mapping[name], value) + self.uc.reg_write(self.register_mapping[name], value) else: super().__setattr__(name, value) - def expand_mapping(self, extra: Mapping[str, int]) -> None: - """Expand registers mapping with additional ones. - """ - - self.register_mapping.update(extra) - - # read register def read(self, register: Union[str, int]): """Read a register value. @@ -60,7 +59,7 @@ class QlRegisterManager: if type(register) is str: register = self.register_mapping[register.lower()] - return self.ql.uc.reg_read(register) + return self.uc.reg_read(register) def write(self, register: Union[str, int], value: int) -> None: @@ -70,18 +69,7 @@ class QlRegisterManager: if type(register) is str: register = self.register_mapping[register.lower()] - return self.ql.uc.reg_write(register, value) - - - def msr(self, msr: int, value: int = None): - """Read or write a model-specific register (MSR) value. - Intel architecture only - """ - - if value is None: - return self.ql.uc.msr_read(msr) - - self.ql.uc.msr_write(msr, value) + return self.uc.reg_write(register, value) def save(self) -> MutableMapping[str, Any]: @@ -99,34 +87,12 @@ class QlRegisterManager: self.write(reg, val) - # TODO: This needs to be implemented for all archs - def bit(self, reg: Union[str, int]) -> int: - """Get register size in bits. - """ - - if type(reg) is str: - reg = self.register_mapping[reg] - - return self.ql.arch.get_reg_bit(reg) - - - # Generic methods to get SP and IP across Arch's # - # These functions should only be used if the # - # caller is dealing with multiple Arch's # - def register_sp(self, sp_id: int): - self.uc_sp = sp_id - - - def register_pc(self, pc_id: int): - self.uc_pc = pc_id - - @property def arch_pc(self) -> int: """Get the value of the architectural program counter register. """ - return self.ql.uc.reg_read(self.uc_pc) + return self.uc.reg_read(self.uc_pc) @arch_pc.setter @@ -134,7 +100,7 @@ class QlRegisterManager: """Set the value of the architectural program counter register. """ - return self.ql.uc.reg_write(self.uc_pc, value) + return self.uc.reg_write(self.uc_pc, value) @property def arch_pc_name(self) -> str: @@ -148,7 +114,7 @@ class QlRegisterManager: """Get the value of the architectural stack pointer register. """ - return self.ql.uc.reg_read(self.uc_sp) + return self.uc.reg_read(self.uc_sp) @arch_sp.setter @@ -156,4 +122,4 @@ class QlRegisterManager: """Set the value of the architectural stack pointer register. """ - return self.ql.uc.reg_write(self.uc_sp, value) + return self.uc.reg_write(self.uc_sp, value) diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 17d09079..e3d06af4 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -3,54 +3,66 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_RISCV, UC_MODE_RISCV32 from capstone import Cs from keystone import Ks -from qiling import Qiling from qiling.arch.arch import QlArch +from qiling.arch.register import QlRegisterManager +from qiling.arch import riscv_const from qiling.arch.riscv_const import * +from qiling.const import QL_ARCH, QL_ENDIAN from qiling.exception import QlErrorNotImplemented - class QlArchRISCV(QlArch): - def __init__(self, ql: Qiling): - super().__init__(ql) + type = QL_ARCH.RISCV + bits = 32 - reg_maps = ( - reg_map, - reg_csr_map, - reg_float_map, - ) - - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) - - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) - def create_disassembler(self) -> Cs: + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **riscv_const.reg_map, + **riscv_const.reg_csr_map, + **riscv_const.reg_float_map, + ) + + pc_reg = 'pc' + sp_reg = 'sp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL + + @cached_property + def disassembler(self) -> Cs: try: from capstone import CS_ARCH_RISCV, CS_MODE_RISCV32, CS_MODE_RISCVC - return Cs(CS_ARCH_RISCV, CS_MODE_RISCV32 + CS_MODE_RISCVC) except ImportError: raise QlErrorNotImplemented("Capstone does not yet support riscv, upgrade to capstone 5.0") + else: + return Cs(CS_ARCH_RISCV, CS_MODE_RISCV32 + CS_MODE_RISCVC) - def create_assembler(self) -> Ks: + @cached_property + def assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") def enable_float(self): - self.ql.reg.mstatus = self.ql.reg.mstatus | MSTATUS.FS_DIRTY + self.regs.mstatus = self.regs.mstatus | MSTATUS.FS_DIRTY def init_context(self): - self.ql.reg.pc = 0x08000000 + self.regs.pc = 0x08000000 def unicorn_exception_handler(self, ql, intno): if intno == 2: - ql.log.warning(f'[{hex(self.get_pc())}] Illegal instruction') + ql.log.warning(f'[{hex(self.regs.arch_pc)}] Illegal instruction') else: raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index c7a31d08..295cf117 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -3,31 +3,35 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_RISCV, UC_MODE_RISCV64 from capstone import Cs from keystone import Ks -from qiling import Qiling from qiling.arch.riscv_const import * +from qiling.const import QL_ARCH from qiling.exception import QlErrorNotImplemented from .riscv import QlArchRISCV - class QlArchRISCV64(QlArchRISCV): - def __init__(self, ql: Qiling): - super().__init__(ql) + type = QL_ARCH.RISCV64 + bits = 64 - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV64) - def create_disassembler(self) -> Cs: + @cached_property + def disassembler(self) -> Cs: try: from capstone import CS_ARCH_RISCV, CS_MODE_RISCV64, CS_MODE_RISCVC - return Cs(CS_ARCH_RISCV, CS_MODE_RISCV64 + CS_MODE_RISCVC) except ImportError: raise QlErrorNotImplemented("Capstone does not yet support riscv, upgrade to capstone 5.0") + else: + return Cs(CS_ARCH_RISCV, CS_MODE_RISCV64 + CS_MODE_RISCVC) - def create_assembler(self) -> Ks: + @cached_property + def assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 76149024..2764bd72 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -9,6 +9,7 @@ This module is intended for general purpose functions that are only used in qili from typing import Tuple from os.path import basename +from functools import lru_cache from keystone import (Ks, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_X86, KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_16, KS_MODE_32, KS_MODE_64, @@ -24,52 +25,68 @@ class QlArchUtils: self._disasm_hook = None self._block_hook = None - def get_offset_and_name(self, addr: int) -> Tuple[int, str]: + @lru_cache(maxsize=64) + def get_base_and_name(self, addr: int) -> Tuple[int, str]: for begin, end, _, name, _ in self.ql.mem.map_info: if begin <= addr < end: - return addr - begin, basename(name) + return begin, basename(name) return addr, '-' def disassembler(self, ql: Qiling, address: int, size: int): - tmp = ql.mem.read(address, size) - qd = ql.arch.create_disassembler() + data = ql.mem.read(address, size) - offset, name = self.get_offset_and_name(address) - log_data = f'{address:0{ql.archbit // 4}x} [{name:20s} + {offset:#08x}] {tmp.hex(" "):30s}' - log_insn = '\n> '.join(f'{insn.mnemonic:20s} {insn.op_str}' for insn in qd.disasm(tmp, address)) + # knowing that all binary sections are aligned to page boundary allows + # us to 'cheat' and search for the containing image using the aligned + # address instead of the actual one. + # + # also, the locality property determines that consequent instructions + # are most likely to reside at the same page in memory, so the containing + # page of the current instruction is probably the same as the previous + # one. + # + # both assumptions make it possible to cache the search results and pull + # them off by lru, which provides about 20% speed-up in this case + ba, name = self.get_base_and_name(ql.mem.align(address)) - ql.log.info(log_data + log_insn) + anibbles = ql.arch.bits // 4 + + for insn in ql.arch.disassembler.disasm(data, address): + offset = insn.address - ba + + ql.log.info(f'{insn.address:0{anibbles}x} [{name:20s} + {offset:#08x}] {insn.bytes.hex(" "):20s} {insn.mnemonic:20s} {insn.op_str}') if ql.verbose >= QL_VERBOSE.DUMP: - for reg in ql.reg.register_mapping: - if type(reg) is str: - ql.log.debug(f'{reg}\t: {ql.reg.read(reg):#x}') + for reg in ql.arch.regs.register_mapping: + ql.log.info(f'{reg:10s} : {ql.arch.regs.read(reg):#x}') - def setup_output(self): - def ql_hook_block_disasm(ql, address, size): - self.ql.log.info("\nTracing basic block at 0x%x" % (address)) + def setup_output(self, verbosity: QL_VERBOSE): + def ql_hook_block_disasm(ql: Qiling, address: int, size: int): + self.ql.log.info(f'\nTracing basic block at {address:#x}') if self._disasm_hook: self._disasm_hook.remove() self._disasm_hook = None + if self._block_hook: self._block_hook.remove() self._block_hook = None - if self.ql.verbose >= QL_VERBOSE.DISASM: - if self.ql.verbose >= QL_VERBOSE.DUMP: - self._block_hook = self.ql.hook_block(ql_hook_block_disasm) + if verbosity >= QL_VERBOSE.DISASM: 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) + # used by qltool prior to ql instantiation. to get an assembler object # after ql instantiation, use the appropriate ql.arch method -def assembler(arch: QL_ARCH, endianess: QL_ENDIAN) -> Ks: +def assembler(arch: QL_ARCH, endianess: QL_ENDIAN, is_thumb: bool) -> Ks: """Instantiate an assembler object for a specified architecture. Args: arch: architecture type endianess: architecture endianess + is_thumb: thumb mode for ARM (ignored otherwise) Returns: an assembler object """ @@ -79,14 +96,15 @@ def assembler(arch: QL_ARCH, endianess: QL_ENDIAN) -> Ks: QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN }[endianess] + thumb = KS_MODE_THUMB if is_thumb else 0 + asm_map = { - QL_ARCH.ARM : (KS_ARCH_ARM, KS_MODE_ARM), - QL_ARCH.ARM64 : (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN), - QL_ARCH.ARM_THUMB : (KS_ARCH_ARM, KS_MODE_THUMB), - QL_ARCH.MIPS : (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian), - QL_ARCH.A8086 : (KS_ARCH_X86, KS_MODE_16), - QL_ARCH.X86 : (KS_ARCH_X86, KS_MODE_32), - QL_ARCH.X8664 : (KS_ARCH_X86, KS_MODE_64) + QL_ARCH.ARM : (KS_ARCH_ARM, KS_MODE_ARM + endian + thumb), + QL_ARCH.ARM64 : (KS_ARCH_ARM64, KS_MODE_ARM), + QL_ARCH.MIPS : (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian), + QL_ARCH.A8086 : (KS_ARCH_X86, KS_MODE_16), + QL_ARCH.X86 : (KS_ARCH_X86, KS_MODE_32), + QL_ARCH.X8664 : (KS_ARCH_X86, KS_MODE_64) } if arch in asm_map: diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 34b77cc0..b634849f 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -3,271 +3,156 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from struct import pack +from functools import cached_property +from typing import Union from unicorn import Uc, UC_ARCH_X86, UC_MODE_16, UC_MODE_32, UC_MODE_64 from capstone import Cs, CS_ARCH_X86, CS_MODE_16, CS_MODE_32, CS_MODE_64 from keystone import Ks, KS_ARCH_X86, KS_MODE_16, KS_MODE_32, KS_MODE_64 -from qiling import Qiling +from unicorn.x86_const import UC_X86_REG_EFLAGS + from qiling.arch.arch import QlArch -from qiling.arch.x86_const import * -from qiling.exception import QlGDTError +from qiling.arch.msr import QlMsrManager +from qiling.arch.register import QlRegisterManager +from qiling.arch import x86_const +from qiling.const import QL_ARCH, QL_ENDIAN class QlArchIntel(QlArch): + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL + + @cached_property + def msr(self) -> QlMsrManager: + """Model-Specific Registers. + """ + + return QlMsrManager(self.uc) + +class QlArchA8086(QlArchIntel): + type = QL_ARCH.A8086 + bits = 16 + + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_X86, UC_MODE_16) + + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **x86_const.reg_map_8, + **x86_const.reg_map_16, + **x86_const.reg_map_misc + ) + + pc_reg = 'ip' + sp_reg = 'sp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_X86, CS_MODE_16) + + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_16) + +class QlArchX86(QlArchIntel): + type = QL_ARCH.X86 + bits = 32 + + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_X86, UC_MODE_32) + + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **x86_const.reg_map_8, + **x86_const.reg_map_16, + **x86_const.reg_map_32, + **x86_const.reg_map_cr, + **x86_const.reg_map_st, + **x86_const.reg_map_misc + ) + + pc_reg = 'eip' + sp_reg = 'esp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_X86, CS_MODE_32) + + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_32) + +class QlArchX8664(QlArchIntel): + type = QL_ARCH.X8664 + bits = 64 + + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_X86, UC_MODE_64) + + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **x86_const.reg_map_8, + **x86_const.reg_map_16, + **x86_const.reg_map_32, + **x86_const.reg_map_64, + **x86_const.reg_map_cr, + **x86_const.reg_map_st, + **x86_const.reg_map_misc, + **x86_const.reg_map_64_b, + **x86_const.reg_map_64_w, + **x86_const.reg_map_64_d, + **x86_const.reg_map_seg_base + ) + + pc_reg = 'rip' + sp_reg = 'rsp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_X86, CS_MODE_64) + + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_64) # TODO: generalize this - def get_reg_bit(self, register: int) -> int: + def __reg_bits(self, register: int) -> int: # all regs in reg_map_misc are 16 bits except of eflags if register == UC_X86_REG_EFLAGS: return 32 regmaps = ( - (reg_map_8, 8), - (reg_map_16, 16), - (reg_map_32, 32), - (reg_map_64, 64), - (reg_map_misc, 16), - (reg_map_cr, 64 if self.ql.archbit == 64 else 32), - (reg_map_st, 32), - (reg_map_seg_base, 64 if self.ql.archbit == 64 else 32), + (x86_const.reg_map_8, 8), + (x86_const.reg_map_16, 16), + (x86_const.reg_map_32, 32), + (x86_const.reg_map_64, 64), + (x86_const.reg_map_misc, 16), + (x86_const.reg_map_cr, 64), # 32 bits for x86 + (x86_const.reg_map_st, 32), + (x86_const.reg_map_seg_base, 64), # 32 bits for x86 ) return next((rsize for rmap, rsize in regmaps if register in rmap.values()), 0) -class QlArchA8086(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) + # note: this method was not generalized for all archs since it requires a bookkeeping + # of all registers, while it is used only by gdb and only for x86-64 + def reg_bits(self, reg: Union[str, int]) -> int: + """Get register size in bits. + """ - reg_maps = ( - reg_map_8, - reg_map_16, - reg_map_misc - ) + if type(reg) is str: + reg = self.regs.register_mapping[reg] - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - - self.ql.reg.register_pc(reg_map_16["sp"]) - self.ql.reg.register_sp(reg_map_16["ip"]) - - def get_init_uc(self) -> Uc: - return Uc(UC_ARCH_X86, UC_MODE_16) - - def create_disassembler(self) -> Cs: - if not self._disasm: - self._disasm = Cs(CS_ARCH_X86, CS_MODE_16) - - return self._disasm - - def create_assembler(self) -> Ks: - if not self._asm: - self._asm = Ks(KS_ARCH_X86, KS_MODE_16) - - return self._asm - -class QlArchX86(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) - - reg_maps = ( - reg_map_8, - reg_map_16, - reg_map_32, - reg_map_cr, - reg_map_st, - reg_map_misc - ) - - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - - self.ql.reg.register_sp(reg_map_32["esp"]) - self.ql.reg.register_pc(reg_map_32["eip"]) - - def get_init_uc(self) -> Uc: - return Uc(UC_ARCH_X86, UC_MODE_32) - - def create_disassembler(self) -> Cs: - if not self._disasm: - self._disasm = Cs(CS_ARCH_X86, CS_MODE_32) - - return self._disasm - - def create_assembler(self) -> Ks: - if not self._asm: - self._asm = Ks(KS_ARCH_X86, KS_MODE_32) - - return self._asm - -class QlArchX8664(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) - - reg_maps = ( - reg_map_8, - reg_map_16, - reg_map_32, - reg_map_64, - reg_map_cr, - reg_map_st, - reg_map_misc, - reg_map_64_b, - reg_map_64_w, - reg_map_64_d, - reg_map_seg_base - ) - - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - - self.ql.reg.register_sp(reg_map_64["rsp"]) - self.ql.reg.register_pc(reg_map_64["rip"]) - - def get_init_uc(self) -> Uc: - return Uc(UC_ARCH_X86, UC_MODE_64) - - def create_disassembler(self) -> Cs: - if not self._disasm: - self._disasm = Cs(CS_ARCH_X86, CS_MODE_64) - - return self._disasm - - def create_assembler(self) -> Ks: - if not self._asm: - self._asm = Ks(KS_ARCH_X86, KS_MODE_64) - - return self._asm - - -class GDTManager: - # Added GDT management module. - def __init__(self, ql: Qiling, GDT_ADDR = QL_X86_GDT_ADDR, GDT_LIMIT = QL_X86_GDT_LIMIT, GDT_ENTRY_ENTRIES = 16): - ql.log.debug(f"Map GDT at {hex(GDT_ADDR)} with GDT_LIMIT={GDT_LIMIT}") - - if not ql.mem.is_mapped(GDT_ADDR, GDT_LIMIT): - ql.mem.map(GDT_ADDR, GDT_LIMIT, info="[GDT]") - - # setup GDT by writing to GDTR - ql.reg.write(UC_X86_REG_GDTR, (0, GDT_ADDR, GDT_LIMIT, 0x0)) - - self.ql = ql - self.gdt_number = GDT_ENTRY_ENTRIES - # self.gdt_used = [False] * GDT_ENTRY_ENTRIES - self.gdt_addr = GDT_ADDR - self.gdt_limit = GDT_LIMIT - - - def register_gdt_segment(self, index: int, SEGMENT_ADDR: int, SEGMENT_SIZE: int, SPORT, RPORT): - # FIXME: Temp fix for FS and GS - if index in (14, 15): - if not self.ql.mem.is_mapped(SEGMENT_ADDR, SEGMENT_ADDR): - self.ql.mem.map(SEGMENT_ADDR, SEGMENT_ADDR, info="[FS/GS]") - - if index < 0 or index >= self.gdt_number: - raise QlGDTError("Ql GDT register index error!") - # create GDT entry, then write GDT entry into GDT table - gdt_entry = self._create_gdt_entry(SEGMENT_ADDR, SEGMENT_SIZE, SPORT, QL_X86_F_PROT_32) - self.ql.mem.write(self.gdt_addr + (index << 3), gdt_entry) - # self.gdt_used[index] = True - self.ql.log.debug(f"Write to {hex(self.gdt_addr + (index << 3))} for new entry {gdt_entry}") - - - def get_gdt_buf(self, start: int, end: int) -> bytearray: - return self.ql.mem.read(self.gdt_addr + (start << 3), (end << 3) - (start << 3)) - - - def set_gdt_buf(self, start: int, end: int, buf: bytes) -> None: - self.ql.mem.write(self.gdt_addr + (start << 3), buf[ : (end << 3) - (start << 3)]) - - - def get_free_idx(self, start: int = 0, end: int = -1) -> int: - # The Linux kernel determines whether the segment is empty by judging whether the content in the current GDT segment is 0. - if end == -1: - end = self.gdt_number - - for i in range(start, end): - if self.ql.unpack64(self.ql.mem.read(self.gdt_addr + (i << 3), 8)) == 0: - return i - - return -1 - - - def _create_gdt_entry(self, base, limit, access, flags): - to_ret = limit & 0xffff - to_ret |= (base & 0xffffff) << 16 - to_ret |= (access & 0xff) << 40 - to_ret |= ((limit >> 16) & 0xf) << 48 - to_ret |= (flags & 0xff) << 52 - to_ret |= ((base >> 24) & 0xff) << 56 - return pack(' bool: + return (0 < index < self.num_entries) + + def __getitem__(self, index: int) -> bytes: + if not self.__in_bounds(index): + raise QlGDTError('invalid GDT entry index') + + return bytes(self.mem.read(self.base + (index * self.entsize), self.entsize)) + + def __setitem__(self, index: int, data: bytes) -> None: + assert len(data) == self.entsize + + if not self.__in_bounds(index): + raise QlGDTError('invalid GDT entry index') + + self.mem.write(self.base + (index * self.entsize), data) + + def get_next_free(self, start: int = None, end: 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 + + # first gdt entry is always null, start from 1 + if start is None: + start = 1 + + if end is None: + end = self.num_entries + + return next((i for i in range(start, end) if self[i] == null_entry), -1) + + +class GDTManager: + def __init__(self, ql: Qiling, base = QL_X86_GDT_ADDR, limit = QL_X86_GDT_LIMIT, num_entries = 16): + ql.log.debug(f'Mapping GDT at {base:#x} with limit {limit:#x}') + + if not ql.mem.is_mapped(base, limit): + ql.mem.map(base, limit, info="[GDT]") + + # setup GDT by writing to GDTR + ql.arch.regs.write(UC_X86_REG_GDTR, (0, base, limit, 0x0)) + + self.array = GDTArray(ql.mem, base, num_entries) + + @staticmethod + def make_entry(base: int, limit: int, access: int, flags: int) -> bytes: + """Encode specified arguments into a new GDT entry. + """ + + maxbits = lambda val, nbits: val & ~((1 << nbits) - 1) == 0 + + assert maxbits(base, 32) + assert maxbits(limit, 20) + assert maxbits(access, 8) + assert maxbits(flags, 4) + + # base: 8 + 24 bits + base_hi = (base >> 24) & 0xff + base_lo = base & ((1 << 24) - 1) + + # limit: 4 + 16 bits + limit_hi = (limit >> 16) & 0xf + limit_lo = limit & ((1 << 16) - 1) + + entry = base_hi << 56 | flags << 52 | limit_hi << 48 | access << 40 | base_lo << 16 | limit_lo + + return entry.to_bytes(8, 'little', signed=False) + + @staticmethod + def make_selector(idx: int, rpl: int) -> int: + assert rpl & ~0b11 == 0 + + return (idx << 3) | QL_X86_S_GDT | rpl + + def register_gdt_segment(self, index: int, seg_base: int, seg_limit: int, access: int) -> int: + flags = QL_X86_F_PROT_32 + + # is this a huge segment? + if seg_limit > (1 << 16): + # on 4K granularity the lower 12 bits are implicitly all set + assert seg_limit & ((1 << 12) - 1) == 0xfff + + seg_limit >>= 12 + flags |= QL_X86_F_GRANULARITY + + # create GDT entry, then write GDT entry into GDT table + self.array[index] = GDTManager.make_entry(seg_base, seg_limit, access, flags) + + return GDTManager.make_selector(index, (access >> 5) & 0b11) + + def get_entry(self, index: int) -> bytes: + return self.array[index] + + 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: + return self.array.get_next_free(start, end) + + +class SegmentManager: + def __init__(self, arch: QlArchIntel, gdtm: GDTManager): + self.arch = arch + self.gdtm = gdtm + + @abstractmethod + def setup_cs_ds_ss_es(self, base: int, size: int) -> None: + pass + + @abstractmethod + def setup_fs(self, base: int, size: int) -> None: + pass + + @abstractmethod + def setup_gs(self, base: int, size: int) -> None: + pass + + +class SegmentManager86(SegmentManager): + def setup_cs_ds_ss_es(self, base: int, size: int) -> None: + # While debugging the linux kernel segment, the cs segment was found on the third segment of gdt. + access = QL_X86_A_PRESENT | QL_X86_A_CODE | QL_X86_A_CODE_READABLE | QL_X86_A_PRIV_3 | QL_X86_A_EXEC | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(3, base, size - 1, access) + + self.arch.regs.cs = selector + + # TODO : The section permission here should be QL_X86_A_PRIV_3, but I do n’t know why it can only be set to QL_X86_A_PRIV_0. + # While debugging the Linux kernel segment, I found that the three segments DS, SS, and ES all point to the same location in the GDT table. + # This position is the fifth segment table of GDT. + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_0 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(5, base, size - 1, access) + + self.arch.regs.ds = selector + self.arch.regs.ss = selector + self.arch.regs.es = selector + + def setup_fs(self, base: int, size: int) -> None: + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(14, base, size - 1, access) + + self.arch.regs.fs = selector + + def setup_gs(self, base: int, size: int) -> None: + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(15, base, size - 1, access) + + self.arch.regs.gs = selector + + +class SegmentManager64(SegmentManager): + def setup_cs_ds_ss_es(self, base: int, size: int) -> None: + # While debugging the linux kernel segment, the cs segment was found on the sixth segment of gdt. + access = QL_X86_A_PRESENT | QL_X86_A_CODE | QL_X86_A_CODE_READABLE | QL_X86_A_PRIV_3 | QL_X86_A_EXEC | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(6, base, size - 1, access) + + self.arch.regs.cs = selector + + # TODO : The section permission here should be QL_X86_A_PRIV_3, but I do n’t know why it can only be set to QL_X86_A_PRIV_0. + # When I debug the Linux kernel, I find that only the SS is set to the fifth segment table, and the rest are not set. + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_0 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(5, base, size - 1, access) + + # self.arch.regs.ds = selector + self.arch.regs.ss = selector + # self.arch.regs.es = selector + + def setup_fs(self, base: int, size: int) -> None: + self.arch.msr.write(IA32_FS_BASE_MSR, base) + + def setup_gs(self, base: int, size: int) -> None: + self.arch.msr.write(IA32_GS_BASE_MSR, base) diff --git a/qiling/cc/__init__.py b/qiling/cc/__init__.py index 7d69ed49..351072c7 100644 --- a/qiling/cc/__init__.py +++ b/qiling/cc/__init__.py @@ -4,20 +4,20 @@ from typing import Callable, Tuple -from qiling import Qiling +from qiling.arch.arch import QlArch class QlCC: """Calling convention base class. """ - def __init__(self, ql: Qiling) -> None: + def __init__(self, arch: QlArch) -> None: """Initialize a calling convention instance. Args: - ql: qiling instance + arch: underlying architecture instance """ - self.ql = ql + self.arch = arch @staticmethod def getNumSlots(argbits: int) -> int: @@ -111,11 +111,11 @@ class QlCommonBaseCC(QlCC): _shadow = 0 _retaddr_on_stack = True - def __init__(self, ql: Qiling, retreg: int): - super().__init__(ql) + def __init__(self, arch: QlArch, retreg: int): + super().__init__(arch) # native address size in bytes - self._asize = self.ql.pointersize + self._asize = self.arch.pointersize # return value register self._retreg = retreg @@ -151,24 +151,24 @@ class QlCommonBaseCC(QlCC): return reg_access, reg def getRawParam(self, index: int, argbits: int = None) -> int: - read, loc = self.__access_param(index, self.ql.stack_read, self.ql.reg.read) + read, loc = self.__access_param(index, self.arch.stack_read, self.arch.regs.read) mask = (0 if argbits is None else (1 << argbits)) - 1 return read(loc) & mask def setRawParam(self, index: int, value: int, argbits: int = None) -> None: - write, loc = self.__access_param(index, self.ql.stack_write, self.ql.reg.write) + write, loc = self.__access_param(index, self.arch.stack_write, self.arch.regs.write) mask = (0 if argbits is None else (1 << argbits)) - 1 write(loc, value & mask) def getReturnValue(self) -> int: - return self.ql.reg.read(self._retreg) + return self.arch.regs.read(self._retreg) def setReturnValue(self, value: int) -> None: - self.ql.reg.write(self._retreg, value) + self.arch.regs.write(self._retreg, value) def reserve(self, nslots: int) -> None: assert nslots < len(self._argregs), 'too many slots' @@ -176,4 +176,4 @@ class QlCommonBaseCC(QlCC): # count how many slots should be reserved on the stack si = self._argregs[:nslots].count(None) - self.ql.reg.arch_sp -= (self._shadow + si) * self._asize + self.arch.regs.arch_sp -= (self._shadow + si) * self._asize diff --git a/qiling/cc/arm.py b/qiling/cc/arm.py index 3d0aaa25..6517ee26 100644 --- a/qiling/cc/arm.py +++ b/qiling/cc/arm.py @@ -8,8 +8,8 @@ from unicorn.arm64_const import ( UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7 ) -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC class QlArmBaseCC(QlCommonBaseCC): """Calling convention base class for ARM-based systems. @@ -22,20 +22,20 @@ class QlArmBaseCC(QlCommonBaseCC): def setReturnAddress(self, addr: int) -> None: # TODO: do we need to update LR? - self.ql.arch.stack_push(addr) + self.arch.stack_push(addr) def unwind(self, nslots: int) -> int: # TODO: cleanup? - return self.ql.arch.stack_pop() + return self.arch.stack_pop() class aarch64(QlArmBaseCC): _argregs = (UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3, UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7) + (None, ) * 8 - def __init__(self, ql: Qiling) -> None: - super().__init__(ql, UC_ARM64_REG_X0) + def __init__(self, arch: QlArch) -> None: + super().__init__(arch, UC_ARM64_REG_X0) class aarch32(QlArmBaseCC): _argregs = (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3) + (None, ) * 12 - def __init__(self, ql: Qiling) -> None: - super().__init__(ql, UC_ARM_REG_R0) + def __init__(self, arch: QlArch) -> None: + super().__init__(arch, UC_ARM_REG_R0) diff --git a/qiling/cc/intel.py b/qiling/cc/intel.py index 9d90ab54..4ed9f8fe 100644 --- a/qiling/cc/intel.py +++ b/qiling/cc/intel.py @@ -8,29 +8,29 @@ from unicorn.x86_const import ( UC_X86_REG_R9, UC_X86_REG_R10 ) -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC class QlIntelBaseCC(QlCommonBaseCC): """Calling convention base class for Intel-based systems. Supports arguments passing over registers and stack. """ - def __init__(self, ql: Qiling): + def __init__(self, arch: QlArch): retreg = { 16: UC_X86_REG_AX, 32: UC_X86_REG_EAX, 64: UC_X86_REG_RAX - }[ql.archbit] + }[arch.bits] - super().__init__(ql, retreg) + super().__init__(arch, retreg) def setReturnAddress(self, addr: int) -> None: - self.ql.arch.stack_push(addr) + self.arch.stack_push(addr) def unwind(self, nslots: int) -> int: # no cleanup; just pop out the return address - return self.ql.arch.stack_pop() + return self.arch.stack_pop() class QlIntel64(QlIntelBaseCC): """Calling convention base class for Intel-based 64-bit systems. @@ -107,6 +107,6 @@ class stdcall(QlIntel32): def unwind(self, nslots: int) -> int: retaddr = super().unwind(nslots) - self.ql.reg.arch_sp += (nslots * self._asize) + self.arch.regs.arch_sp += (nslots * self._asize) return retaddr diff --git a/qiling/cc/mips.py b/qiling/cc/mips.py index ea69f90d..9eafc4d0 100644 --- a/qiling/cc/mips.py +++ b/qiling/cc/mips.py @@ -4,16 +4,16 @@ from unicorn.mips_const import UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3 -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC class mipso32(QlCommonBaseCC): _argregs = (UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3) + (None, ) * 12 _shadow = 4 _retaddr_on_stack = False - def __init__(self, ql: Qiling): - super().__init__(ql, UC_MIPS_REG_V0) + def __init__(self, arch: QlArch): + super().__init__(arch, UC_MIPS_REG_V0) @staticmethod def getNumSlots(argbits: int): @@ -21,4 +21,4 @@ class mipso32(QlCommonBaseCC): def unwind(self, nslots: int) -> int: # TODO: stack frame unwiding? - return self.ql.reg.ra + return self.arch.regs.ra diff --git a/qiling/cc/riscv.py b/qiling/cc/riscv.py index e768f6c4..7a834ecd 100644 --- a/qiling/cc/riscv.py +++ b/qiling/cc/riscv.py @@ -2,8 +2,8 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC from unicorn.riscv_const import ( UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, @@ -18,8 +18,8 @@ class riscv(QlCommonBaseCC): _argregs = (UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5) + (None, ) * 10 - def __init__(self, ql: Qiling): - super().__init__(ql, UC_RISCV_REG_A0) + def __init__(self, arch: QlArch): + super().__init__(arch, UC_RISCV_REG_A0) @staticmethod def getNumSlots(argbits: int): diff --git a/qiling/const.py b/qiling/const.py index 1b5bf4aa..7e21ae25 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from enum import Enum, IntEnum +from enum import Enum, Flag, IntEnum from typing import Any, Mapping, Type class QL_ENDIAN(IntEnum): @@ -14,7 +14,6 @@ class QL_ARCH(IntEnum): X86 = 101 X8664 = 102 ARM = 103 - ARM_THUMB = 104 ARM64 = 105 MIPS = 106 A8086 = 107 @@ -53,17 +52,13 @@ class QL_INTERCEPT(IntEnum): ENTER = 2 EXIT = 3 -QL_DEBUGGER_ALL = (QL_DEBUGGER.IDAPRO, QL_DEBUGGER.GDB, QL_DEBUGGER.QDB) - -QL_ARCH_ENDIAN = (QL_ARCH.MIPS, QL_ARCH.ARM) -QL_ARCH_1BIT = (QL_ARCH.EVM,) -QL_ARCH_16BIT = (QL_ARCH.A8086,) -QL_ARCH_32BIT = (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.MIPS, QL_ARCH.X86, QL_ARCH.CORTEX_M, QL_ARCH.RISCV) -QL_ARCH_64BIT = (QL_ARCH.ARM64, QL_ARCH.X8664, QL_ARCH.RISCV64) - -QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) -QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) +class QL_STOP(Flag): + NONE = 0 + STACK_POINTER = (1 << 0) + EXIT_TRAP = (1 << 1) +QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) +QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) QL_OS_BAREMETAL = (QL_OS.MCU,) QL_OS_INTERPRETER = (QL_OS.EVM,) @@ -74,7 +69,7 @@ def __reverse_enum(e: Type[Enum]) -> Mapping[str, Any]: '''Create a reverse mapping for an enum. ''' - return dict((v.name.lower(), v.value) for v in e.__members__.values()) + return dict((v.name.lower(), v) for v in e.__members__.values()) debugger_map: Mapping[str, QL_DEBUGGER] = __reverse_enum(QL_DEBUGGER) arch_map : Mapping[str, QL_ARCH] = __reverse_enum(QL_ARCH) @@ -95,5 +90,6 @@ loader_map = { } arch_os_map = { - QL_ARCH.EVM: QL_OS.EVM, + QL_ARCH.EVM : QL_OS.EVM, + QL_ARCH.CORTEX_M : QL_OS.MCU } diff --git a/qiling/core.py b/qiling/core.py index 4b1abc66..713dfc8e 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -4,98 +4,78 @@ # from configparser import ConfigParser -import ntpath, os, pickle, platform +import os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports -from typing import Callable, Dict, List, Union +from typing import AnyStr, List, Mapping, MutableMapping, Sequence, Union from typing import TYPE_CHECKING from unicorn.unicorn import Uc if TYPE_CHECKING: - from .arch.register import QlRegisterManager from .arch.arch import QlArch from .os.os import QlOs from .os.memory import QlMemoryManager from .hw.hw import QlHwManager from .loader.loader import QlLoader -from .const import QL_ARCH_ENDIAN, QL_ENDIAN, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL +from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_STOP, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType +from .host import QlHost from .utils import * from .core_struct import QlCoreStructs from .core_hooks import QlCoreHooks from .__version__ import __version__ # Mixin Pattern -class Qiling(QlCoreHooks, QlCoreStructs): +class Qiling(QlCoreHooks, QlCoreStructs): def __init__( self, - argv=None, - rootfs=None, - env=None, - code=None, - ostype=None, - archtype=None, - bigendian=False, - verbose=QL_VERBOSE.DEFAULT, - profile=None, + argv: Sequence[str] = None, + rootfs: str = None, + env: MutableMapping[AnyStr, AnyStr] = {}, + code: bytes = None, + ostype: Union[str, QL_OS] = None, + archtype: Union[str, QL_ARCH] = None, + verbose: QL_VERBOSE = QL_VERBOSE.DEFAULT, + profile: str = None, console=True, log_file=None, log_override=None, log_plain=False, - libcache = False, multithread = False, filter = None, - stop_on_stackpointer = False, - stop_on_exit_trap = False, - stdin=None, - stdout=None, - stderr=None, + stop: QL_STOP = QL_STOP.NONE, + *, + endian: QL_ENDIAN = None, + thumb: bool = False, + libcache: bool = False ): """ Create a Qiling instance. For each argument or property, please refer to its docstring. e.g. Qiling.multithread.__doc__ - - The only exception is "bigendian" parameter, see Qiling.archendian.__doc__ for details. """ ################################## # Definition during ql=Qiling() # ################################## - self._argv = argv - self._rootfs = rootfs - self._env = env if env else {} + self._env = env self._code = code - self._ostype = ostype - self._archtype = archtype - self._archendian = QL_ENDIAN.EL - self._archbit = None - self._pointersize = None - self._profile = profile - self._console = console - self._log_file = log_file self._multithread = multithread self._log_file_fd = None self._log_filter = None - self._log_override = log_override - self._log_plain = log_plain self._filter = filter - self._platform_os = ostype_convert(platform.system().lower()) - self._platform_arch = arch_convert(platform.machine().lower()) self._internal_exception = None self._uc = None - self._stop_options = QlStopOptions(stackpointer=stop_on_stackpointer, exit_trap=stop_on_exit_trap) + self._stop_options = stop ################################## # Definition after ql=Qiling() # ################################## - self._verbose = verbose - self._libcache = libcache self._patch_bin = [] self._patch_lib = [] self._debug_stop = False - self._debugger = None + self._debugger = False ############################### # Properties configured later # @@ -112,112 +92,106 @@ class Qiling(QlCoreHooks, QlCoreStructs): ############## # argv setup # ############## - if self._argv is None: - self._argv = ["qilingcode"] + if argv is None: + argv = ['qilingcode'] - elif not os.path.exists(str(self._argv[0])): - raise QlErrorFileNotFound("Target binary not found: %s" % (self._argv[0])) + elif not os.path.exists(argv[0]): + raise QlErrorFileNotFound(f'Target binary not found: "{argv[0]}"') + + self._argv = argv + self._path = self.argv[0] + self._targetname = os.path.basename(self.path) ################ # rootfs setup # ################ - if self._rootfs is None: - self._rootfs = "." - - elif not os.path.exists(self._rootfs): - raise QlErrorFileNotFound("Target rootfs not found: %s" % (self._rootfs)) - - self._path = self._argv[0] - self._targetname = ntpath.basename(self.path) + if rootfs is None: + rootfs = '.' + + elif not os.path.exists(rootfs): + raise QlErrorFileNotFound(f'Target rootfs not found: "{rootfs}"') + + self._rootfs = rootfs ################# # arch os setup # ################# - if type(self._archtype) is str: - self._archtype = arch_convert(self._archtype) - if type(self._ostype) is str: - self._ostype = ostype_convert(self._ostype) + self._host = QlHost() - if self._archtype is None: - guessed_archtype, guessed_ostype, guessed_archendian = ql_guess_emu_env(self._path) - self._archtype = guessed_archtype - self._archendian = guessed_archendian - - if self._ostype is None: - self._ostype = guessed_ostype + if type(archtype) is str: + archtype = arch_convert(archtype) - elif self._ostype == None: - self._ostype = arch_os_convert(self._archtype) - - if not ql_is_valid_ostype(self._ostype): - raise QlErrorOsType("Invalid OS: %s" % (self._ostype)) + if type(ostype) is str: + ostype = ostype_convert(ostype) - if not ql_is_valid_arch(self._archtype): - raise QlErrorArch("Invalid ARCH: %s" % (self._archtype)) + # if provided arch was invalid or not provided, guess arch and os + if archtype is None: + guessed_archtype, guessed_ostype, guessed_archendian = ql_guess_emu_env(self.path) + archtype = guessed_archtype - ######################## - # Archbit & Endianness # - ######################## - self._archbit = ql_get_arch_bits(self._archtype) - self._pointersize = (self.archbit // 8) - - if bigendian == True and self._archtype in QL_ARCH_ENDIAN: - self._archendian = QL_ENDIAN.EB + if ostype is None: + ostype = guessed_ostype - # Once we finish setting up archendian and arcbit, we can init QlCoreStructs. - QlCoreStructs.__init__(self, self._archendian, self._archbit) - + if endian is None: + endian = guessed_archendian - ####################################### - # Loader and General Purpose OS check # - ####################################### - self._loader = loader_setup(self._ostype, self) + # if arch was set but os was not, try to guess it by arch + elif ostype is None: + ostype = arch_os_convert(archtype) - ##################### - # Profile & Logging # - ##################### - self._profile, debugmsg = profile_setup(self) + # arch should have been determined by now; fail if not + if archtype is None or not ql_is_valid_arch(archtype): + raise QlErrorArch(f'Uknown or unsupported architecture: "{archtype}"') - # Log's configuration - self._log_file_fd, self._log_filter = ql_setup_logger(self, - self._log_file, - self._console, - self._filter, - self._log_override, - self._log_plain) + # os should have been determined by now; fail if not + if ostype is None or not ql_is_valid_ostype(ostype): + raise QlErrorOsType(f'Unknown or unsupported operating system: "{ostype}"') - self.log.setLevel(ql_resolve_logger_level(self._verbose)) + # if endianess is still undetermined, set it to little-endian. + # this setting is ignored for architectures with predfined endianess + if endian is None: + endian = QL_ENDIAN.EL - # Now that the logger is configured, we can log profile debug msg: - self.log.debug(debugmsg) + # make sure args were canonicalized successfully + assert type(archtype) is QL_ARCH + assert type(ostype) is QL_OS + assert type(endian) is QL_ENDIAN + + self._arch = arch_setup(archtype, endian, thumb, self) + self._ostype = ostype + + self.uc = self.arch.uc + + # Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks + QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) + + if not self.interpreter: + QlCoreHooks.__init__(self, self.uc) + + ########## + # Logger # + ########## + self._log_file_fd, self._log_filter = ql_setup_logger(self, log_file, console, self._filter, log_override, log_plain) + + self.verbose = verbose + + ########### + # Profile # + ########### + self._profile = profile_setup(self, self.ostype, profile) + + ########## + # Loader # + ########## + self._loader = loader_setup(self, self.ostype, libcache) ############## # Components # ############## if not self.interpreter: self._mem = component_setup("os", "memory", self) - self._reg = component_setup("arch", "register", self) - - - self._arch = arch_setup(self.archtype, self) - - # Once we finish setting up arch layer, we can init QlCoreHooks. - if not self.interpreter: - self.uc = self.arch.init_uc - QlCoreHooks.__init__(self, self.uc) - - self.arch.utils.setup_output() - self._os = os_setup(self.archtype, self.ostype, self) - - if stdin is not None: - self._os.stdin = stdin - - if stdout is not None: - self._os.stdout = stdout - - if stderr is not None: - self._os.stderr = stderr + self._os = os_setup(self.ostype, self) # Run the loader self.loader.run() @@ -235,14 +209,6 @@ class Qiling(QlCoreHooks, QlCoreStructs): """ return self._mem - @property - def reg(self) -> "QlRegisterManager": - """ Qiling register manager. - - Example: ql.reg.eax = 1 - """ - return self._reg - @property def hw(self) -> "QlHwManager": """ Qiling hardware manager. @@ -293,24 +259,6 @@ class Qiling(QlCoreHooks, QlCoreStructs): # If an option doesn't have a setter, it means that it can be only set during Qiling.__init__ - @property - def console(self) -> bool: - """ Specify whether enabling console output. - - Type: bool - Example: Qiling(console=True) - """ - return self._console - - @property - def log_file(self) -> str: - """ Log to a file. - - Type: str - Example: Qiling(log_file="./ql.log") - """ - return self._log_file - @property def multithread(self) -> bool: """ Specify whether multithread has been enabled. @@ -335,10 +283,9 @@ class Qiling(QlCoreHooks, QlCoreStructs): return self._profile @property - def argv(self) -> List[str]: + def argv(self) -> Sequence[str]: """ The program argv. - Type: List[str] Example: Qiling(argv=['/bin/ls', '-a']) """ return self._argv @@ -353,10 +300,9 @@ class Qiling(QlCoreHooks, QlCoreStructs): return self._rootfs @property - def env(self) -> Dict[str, str]: + def env(self) -> MutableMapping[AnyStr, AnyStr]: """ The program environment variables. - Type: Dict[str, str] Example: Qiling(env={"LC_ALL" : "en_US.UTF-8"}) """ return self._env @@ -376,58 +322,10 @@ class Qiling(QlCoreHooks, QlCoreStructs): - "windows" : Windows - "uefi" : UEFI - "dos" : DOS - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) + Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664") """ return self._ostype - @property - def archtype(self) -> QL_ARCH: - """ The emulated architecture type. - - Note: Please pass None or one of the strings below to Qiling.__init__. - If you use shellcode, you must specify ostype and archtype manually. - - Type: int - Values: - - "x86" : x86_32 - - "x8664" : x86_64 - - "mips" : MIPS - - "arm" : ARM - - "arm_thumb" : ARM with thumb mode. - - "arm64" : ARM64 - - "a8086" : 8086 - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) - """ - return self._archtype - - @property - def archendian(self) -> QL_ENDIAN: - """ The architecure endian. - - Note: Please pass "bigendian=True" or "bingendian=False" to set this property. - This option only takes effect for shellcode. - - Type: int - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) - """ - return self._archendian - - @property - def archbit(self) -> int: - """ The bits of the current architecutre. - - Type: int - """ - return self._archbit - - @property - def pointersize(self) -> int: - """ The pointer size of current architecture. - - Type: int - """ - return self._pointersize - @property def code(self) -> bytes: """ The shellcode to execute. @@ -435,7 +333,7 @@ class Qiling(QlCoreHooks, QlCoreStructs): Note: It can't be used with "argv" parameter. Type: bytes - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) + Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664") """ return self._code @@ -475,39 +373,14 @@ class Qiling(QlCoreHooks, QlCoreStructs): return self.ostype in QL_OS_BAREMETAL @property - def platform_os(self): - """ Specify current platform os where Qiling runs on. - - Type: int - Values: All possible values from platform.system() + def host(self) -> QlHost: + """Provide an interface to the hosting platform where Qiling runs on. """ - return self._platform_os - @platform_os.setter - def platform_os(self, value): - if type(value) is str: - self._platform_os = ostype_convert(value.lower()) - else: - self._platform_os = value + return self._host @property - def platform_arch(self): - """ Specify current platform arch where Qiling runs on. - - Type: int - Values: All possible values from platform.system() - """ - return self._platform_arch - - @platform_arch.setter - def platform_arch(self, value): - if type(value) is str: - self._platform_arch = arch_convert(value.lower()) - else: - self._platform_arch = value - - @property - def internal_exception(self) -> Exception: + def internal_exception(self) -> Optional[Exception]: """ Internal exception catched during Unicorn callback. Not intended for regular users. Type: Exception @@ -515,44 +388,28 @@ class Qiling(QlCoreHooks, QlCoreStructs): return self._internal_exception @property - def libcache(self) -> bool: - """ Whether cache dll files. Only take effect in Windows emulation. + def verbose(self) -> QL_VERBOSE: + """Set verbosity level. - Type: bool - Example: - ql = Qiling(libcache=False) - - ql.libcache = True - """ - return self._libcache - - @libcache.setter - def libcache(self, lc): - self._libcache = lc - - @property - def verbose(self): - """ Set the verbose level. - - Type: int - Values: - - -1 : logging.NOTSET, turn off all the output. - - 0 : logging.WARNING, almost no additional logs except the program output. - - >=1: logging.INFO, the default logging level. - - >=4: logging.DEBUG. - - >=10: Disasm each executed instruction. - - >=20: The most verbose output, dump registers and disasm the function blocks. - Example: - ql = Qiling(verbose=5) - - ql.verbose = 0 + Values: + `QL_VERBOSE.DISABLED`: turn off logging + `QL_VERBOSE.OFF` : mask off anything below warnings, errors and critical severity + `QL_VERBOSE.DEFAULT` : info logging level: default verbosity + `QL_VERBOSE.DEBUG` : debug logging level: higher verbosity + `QL_VERBOSE.DISASM` : debug verbosity along with disassembly trace (slow!) + `QL_VERBOSE.DUMP` : disassembly trace along with cpu context dump """ return self._verbose @verbose.setter - def verbose(self, v): + def verbose(self, v: QL_VERBOSE): self._verbose = v - self.log.setLevel(ql_resolve_logger_level(self._verbose)) - self.arch.utils.setup_output() + + self.log.setLevel(ql_resolve_logger_level(v)) + self.arch.utils.setup_output(v) @property - def patch_bin(self) -> list: + def patch_bin(self) -> List[Tuple[int, bytes]]: """ Return the patches for binary. Type: list @@ -560,7 +417,7 @@ class Qiling(QlCoreHooks, QlCoreStructs): return self._patch_bin @property - def patch_lib(self) -> list: + def patch_lib(self) -> List[Tuple[int, bytes, str]]: """ Return the patches for library. Type: list @@ -583,23 +440,23 @@ class Qiling(QlCoreHooks, QlCoreStructs): self._debug_stop = ds @property - def debugger(self) -> Union[str, bool]: + def debugger(self) -> bool: + return bool(self._debugger) + + @debugger.setter + def debugger(self, dbger: Union[str, bool]): """ Enable debugger. - Type: debugger instance Values: - "gdb": enable gdb. - True : an alias to "gdb". - "gdb:0.0.0.0:1234" : gdb which listens on 0.0.0.0:1234 - "qdb": enable qdb. - "qdb:rr": enable qdb with reverse debugging support. + Example: ql.debugger = True ql.debugger = "qdb" """ - return self._debugger - - @debugger.setter - def debugger(self, dbger): self._debugger = dbger @property @@ -633,14 +490,12 @@ class Qiling(QlCoreHooks, QlCoreStructs): self._uc = u @property - def stop_options(self) -> "QlStopOptions": - """ The stop options configured: - - stackpointer: Stop execution on a negative stackpointer - - exit_trap: Stop execution when the ip enters a guarded region - - any: Is any of the options enabled? + def stop_options(self) -> QL_STOP: + """ The stop options configured (multiple options apply): + - `QL_STOP.STACK_POINTER` : Stop execution on a negative stackpointer + - `QL_STOP.EXIT_TRAP` : Stop execution when the pc value enters a guarded region - Returns: - QlStopOptions: What stop options are configured + Returns: configured options """ return self._stop_options @@ -658,42 +513,43 @@ class Qiling(QlCoreHooks, QlCoreStructs): raise RuntimeError("Fail to patch %s at address 0x%x" % (filename, addr)) def _init_stop_guard(self): - if not self.stop_options.any: + if not self.stop_options: return # Allocate a guard page, we need this in both cases # On a negative stack pointer, we still need a return address (otherwise we end up at 0) # Make sure it is not close to the heap (PE), otherwise the heap cannot grow self._exit_trap_addr = self.mem.find_free_space(0x1000, minaddr=0x9000000, align=0x10) - self.mem.map(self._exit_trap_addr, 0x1000, info='[Stop guard]') + self.mem.map(self._exit_trap_addr, 0x1000, info='[Stop guard page]') # Stop on a negative stack pointer - if self.stop_options.stackpointer: - def _check_sp(ql, address, size): + if QL_STOP.STACK_POINTER in self.stop_options: + def _check_sp(ql: Qiling, address: int, size: int): if not ql.loader.skip_exit_check: - sp = ql._initial_sp - ql.reg.arch_sp - if sp < 0: + if ql._initial_sp < ql.arch.regs.arch_sp: self.log.info('Process returned from entrypoint (stackpointer)!') ql.emu_stop() self.hook_code(_check_sp) # Stop when running to exit trap address - if self.stop_options.exit_trap: - def _exit_trap(ql): + if QL_STOP.EXIT_TRAP in self.stop_options: + def _exit_trap(ql: Qiling): self.log.info('Process returned from entrypoint (exit_trap)!') ql.emu_stop() self.hook_address(_exit_trap, self._exit_trap_addr) def write_exit_trap(self): - self._initial_sp = self.reg.arch_sp - if self.stop_options.any: + self._initial_sp = self.arch.regs.arch_sp + + if self.stop_options: if not self.loader.skip_exit_check: - self.log.debug(f'Setting up exit trap at 0x{hex(self._exit_trap_addr)}') + self.log.debug(f'Setting up exit trap at {self._exit_trap_addr:#x}') self.stack_write(0, self._exit_trap_addr) - elif self.stop_options.exit_trap: - self.log.debug(f'Loader {self.loader} requested to skip exit_trap!') + + elif QL_STOP.EXIT_TRAP in self.stop_options: + self.log.debug(f'Loader requested to skip exit_trap!') ############### @@ -709,67 +565,75 @@ class Qiling(QlCoreHooks, QlCoreStructs): self.timeout = timeout self.count = count - # init debugger - if self._debugger != False and self._debugger != None and not self.interpreter: - self._debugger = debugger_setup(self._debugger, self) - if self.interpreter: return self.arch.run(code) - else: - self.write_exit_trap() - # patch binary - self.__enable_bin_patch() - # emulate the binary - self.os.run() + # init debugger (if set) + debugger = debugger_setup(self._debugger, self) + + # patch binary + self.__enable_bin_patch() + + self.write_exit_trap() + # emulate the binary + self.os.run() # run debugger - if self._debugger != False and self._debugger != None: - self._debugger.run() - + if debugger and self.debugger: + debugger.run() + # patch code to memory address - def patch(self, addr, code, file_name=b''): - if file_name == b'': - self.patch_bin.append((addr, code)) + def patch(self, offset: int, data: bytes, target: str = None) -> None: + """Volatilely patch binary and libraries with arbitrary content. + Patching may be done prior to emulation start. + + Args: + offset: offset in target to patch + data: patch data + target: target library name to patch (or `None` for the main executable binary) + """ + + if target is None: + self.patch_bin.append((offset, data)) else: - self.patch_lib.append((addr, code, file_name.decode())) + self.patch_lib.append((offset, data, target)) # save all qiling instance states - def save(self, reg=True, mem=True, fd=False, cpu_context=False, os_context=False, loader=False, snapshot=None): + def save(self, reg=True, mem=True, fd=False, cpu_context=False, os=False, loader=False, *, snapshot: str = None): saved_states = {} - if reg == True: - saved_states.update({"reg": self.reg.save()}) + if reg: + saved_states["reg"] = self.arch.regs.save() - if mem == True: - saved_states.update({"mem": self.mem.save()}) + if mem: + saved_states["mem"] = self.mem.save() - if fd == True: - saved_states.update({"fd": self.os.fd.save()}) + if fd: + saved_states["fd"] = self.os.fd.save() - if cpu_context == True: - saved_states.update({"cpu_context": self.arch.context_save()}) + if cpu_context: + saved_states["cpu_context"] = self.arch.save() - if os_context == True: - saved_states.update({"os_context": self.os.save()}) + if os: + saved_states["os"] = self.os.save() - if loader == True: - saved_states.update({"loader": self.loader.save()}) + if loader: + saved_states["loader"] = self.loader.save() - if snapshot != None: + if snapshot is not None: with open(snapshot, "wb") as save_state: pickle.dump(saved_states, save_state) - else: - return saved_states + + return saved_states # restore states qiling instance from saved_states - def restore(self, saved_states=None, snapshot=None): + def restore(self, saved_states: Mapping[str, Any] = {}, *, snapshot: str = None): # snapshot will be ignored if saved_states is set - if saved_states == None and snapshot != None: + if (not saved_states) and (snapshot is not None): with open(snapshot, "rb") as load_state: saved_states = pickle.load(load_state) @@ -777,10 +641,10 @@ class Qiling(QlCoreHooks, QlCoreStructs): self.mem.restore(saved_states["mem"]) if "cpu_context" in saved_states: - self.arch.context_restore(saved_states["cpu_context"]) + self.arch.restore(saved_states["cpu_context"]) if "reg" in saved_states: - self.reg.restore(saved_states["reg"]) + self.arch.regs.restore(saved_states["reg"]) if "fd" in saved_states: self.os.fd.restore(saved_states["fd"]) @@ -792,24 +656,6 @@ class Qiling(QlCoreHooks, QlCoreStructs): self.loader.restore(saved_states["loader"]) - # Either hook or replace syscall/api with custom api/syscall - # - if intercept is None, replace syscall with custom function - # - if intercept is ENTER/EXIT, hook syscall at enter/exit with custom function - # If replace function name is needed, first syscall must be available - # - ql.set_syscall(0x04, my_syscall_write) - # - ql.set_syscall("write", my_syscall_write) - # TODO: Add correspoinding API in ql.os! - def set_syscall(self, target_syscall, intercept_function, intercept = None): - self.os.set_syscall(target_syscall, intercept_function, intercept) - - - # Either replace or hook API - # - if intercept is None, replace API with custom function - # - if intercept is ENTER/EXIT, hook API at enter/exit with custom function - def set_api(self, api_name, intercept_function, intercept = None): - self.os.set_api(api_name, intercept_function, intercept) - - # Map "ql_path" to any objects which implements QlFsMappedObject. def add_fs_mapper(self, ql_path, real_dest): self.os.fs_mapper.add_fs_mapping(ql_path, real_dest) @@ -836,23 +682,6 @@ class Qiling(QlCoreHooks, QlCoreStructs): def stack_write(self, offset, data): return self.arch.stack_write(offset, data) - # Assembler/Diassembler API - @property - def assembler(self): - return self.create_assembler() - - - @property - def disassembler(self): - return self.create_disassembler() - - - def create_disassembler(self): - return self.arch.create_disassembler() - - - def create_assembler(self): - return self.arch.create_assembler() # stop emulation def emu_stop(self): diff --git a/qiling/core_hooks.py b/qiling/core_hooks.py index e00af4b0..7b5f85ac 100644 --- a/qiling/core_hooks.py +++ b/qiling/core_hooks.py @@ -67,7 +67,7 @@ class QlCoreHooks: hooks_list = self._insn_hook[hook_type] for hook in hooks_list: - if hook.bound_check(ql.reg.arch_pc): + if hook.bound_check(ql.arch.regs.arch_pc): ret = hook.call(ql, *args[:-1]) if type(ret) is tuple: @@ -87,7 +87,7 @@ class QlCoreHooks: hooks_list = self._hook[hook_type] for hook in hooks_list: - if hook.bound_check(ql.reg.arch_pc): + if hook.bound_check(ql.arch.regs.arch_pc): ret = hook.call(ql, addr, size) if type(ret) is int and ret & QL_HOOK_BLOCK: diff --git a/qiling/debugger/disassember.py b/qiling/debugger/disassember.py index d0467189..ffcbd501 100644 --- a/qiling/debugger/disassember.py +++ b/qiling/debugger/disassember.py @@ -24,12 +24,13 @@ class QlDisassember(): def disasm_elf(self, seg_name='.text'): def disasm(ql, address, size): - md = ql.create_disassembler() + md = ql.arch.disassembler md.detail = True + return md.disasm(ql.mem.read(address, size), address) disasm_result = [] - if self.ql._archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: BASE = int(self.ql.profile.get("OS32", "load_address"), 16) seg_start = 0x0 seg_end = 0x0 diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index e26ab517..ebd2c91e 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -88,7 +88,7 @@ class QlGdb(QlDebugger, object): def addr_to_str(self, addr: int, short: bool = False, endian: Literal['little', 'big'] = 'big') -> str: # a hacky way to divide archbits by 2 if short, and leave it unchanged if not - nbits = self.ql.archbit // (int(short) + 1) + nbits = self.ql.arch.bits // (int(short) + 1) if nbits == 64: s = f'{int.from_bytes(self.ql.pack64(addr), byteorder=endian):016x}' @@ -179,22 +179,22 @@ class QlGdb(QlDebugger, object): } return adapter.get(arch) - idhex, spid, pcid = gdbqmark_converter(self.ql.archtype) - sp = self.addr_to_str(self.ql.reg.arch_sp) - pc = self.addr_to_str(self.ql.reg.arch_pc) - nullfill = "0" * int(self.ql.archbit / 4) + idhex, spid, pcid = gdbqmark_converter(self.ql.arch.type) + sp = self.addr_to_str(self.ql.arch.regs.arch_sp) + pc = self.addr_to_str(self.ql.arch.regs.arch_pc) + nullfill = "0" * int(self.ql.arch.bits / 4) - if self.ql.archtype== QL_ARCH.MIPS: - if self.ql.archendian == QL_ENDIAN.EB: - sp = self.addr_to_str(self.ql.reg.arch_sp, endian ="little") - pc = self.addr_to_str(self.ql.reg.arch_pc, endian ="little") + if self.ql.arch.type == QL_ARCH.MIPS: + if self.ql.arch.endian == QL_ENDIAN.EB: + sp = self.addr_to_str(self.ql.arch.regs.arch_sp, endian ="little") + pc = self.addr_to_str(self.ql.arch.regs.arch_pc, endian ="little") self.send('T%.2x%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, sp, pcid, pc)) else: self.send('T%.2x%.2x:%s;%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, nullfill, spid, sp, pcid, pc)) def handle_c(subcmd): - self.gdb.resume_emu(self.ql.reg.arch_pc) + self.gdb.resume_emu(self.ql.arch.regs.arch_pc) if self.gdb.bp_list == [self.entry_point]: self.send("W00") @@ -208,48 +208,48 @@ class QlGdb(QlDebugger, object): def handle_g(subcmd): s = '' - if self.ql.archtype== QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: for reg in self.tables[QL_ARCH.A8086][:16]: - r = self.ql.reg.read(reg) + r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: for reg in self.tables[QL_ARCH.X86][:16]: - r = self.ql.reg.read(reg) + r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: for reg in self.tables[QL_ARCH.X8664][:24]: - r = self.ql.reg.read(reg) - if self.ql.reg.bit(reg) == 64: + r = self.ql.arch.regs.read(reg) + if self.ql.arch.reg_bits(reg) == 64: tmp = self.addr_to_str(r) - elif self.ql.reg.bit(reg) == 32: + elif self.ql.arch.reg_bits(reg) == 32: tmp = self.addr_to_str(r, short = True) s += tmp - elif self.ql.archtype == QL_ARCH.ARM: + elif self.ql.arch.type == QL_ARCH.ARM: # r0-r12,sp,lr,pc,cpsr ,see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 for reg in self.tables[QL_ARCH.ARM][:16] + [self.tables[QL_ARCH.ARM][25]]: # if reg is pc, make sure to take thumb mode into account - r = self.ql.arch.get_pc() if reg == "pc" else self.ql.reg.read(reg) + r = self.ql.arch.effective_pc if reg == "pc" else self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype == QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: for reg in self.tables[QL_ARCH.ARM64][:33]: - r = self.ql.reg.read(reg) + r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype == QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: for reg in self.tables[QL_ARCH.MIPS][:38]: - r = self.ql.reg.read(reg) - if self.ql.archendian == QL_ENDIAN.EL: + r = self.ql.arch.regs.read(reg) + if self.ql.arch.endian == QL_ENDIAN.EL: tmp = self.addr_to_str(r, endian ="little") else: tmp = self.addr_to_str(r) @@ -261,52 +261,52 @@ class QlGdb(QlDebugger, object): def handle_G(subcmd): count = 0 - if self.ql.archtype == QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.A8086][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.A8086][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.X86][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.X86][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: for i in range(0, 17*16, 16): reg_data = subcmd[i:i+15] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.X8664][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.X8664][count], reg_data) count += 1 for j in range(17*16, 17*16+15*8, 8): reg_data = subcmd[j:j+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.X8664][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.X8664][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.ARM: + elif self.ql.arch.type == QL_ARCH.ARM: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i + 7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.ARM][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.ARM][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: for i in range(0, len(subcmd), 16): reg_data = subcmd[i:i+15] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.ARM64][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.ARM64][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.MIPS][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.MIPS][count], reg_data) count += 1 self.send('OK') @@ -353,23 +353,23 @@ class QlGdb(QlDebugger, object): reg_index = int(subcmd, 16) reg_value = None try: - if self.ql.archtype== QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: if reg_index <= 9: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.A8086][reg_index-1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.A8086][reg_index-1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: if reg_index <= 24: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.X86][reg_index-1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X86][reg_index-1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: if reg_index <= 32: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.X8664][reg_index-1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X8664][reg_index-1]) else: reg_value = 0 if reg_index <= 17: @@ -377,26 +377,26 @@ class QlGdb(QlDebugger, object): elif 17 < reg_index: reg_value = self.addr_to_str(reg_value, short = True) - elif self.ql.archtype== QL_ARCH.ARM: + elif self.ql.arch.type == QL_ARCH.ARM: if reg_index < 26: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.ARM][reg_index - 1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM][reg_index - 1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: if reg_index <= 32: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.ARM64][reg_index - 1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM64][reg_index - 1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: if reg_index <= 37: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) else: reg_value = 0 - if self.ql.archendian == QL_ENDIAN.EL: + if self.ql.arch.endian == QL_ENDIAN.EL: reg_value = self.addr_to_str(reg_value, endian="little") else: reg_value = self.addr_to_str(reg_value) @@ -413,50 +413,50 @@ class QlGdb(QlDebugger, object): def handle_P(subcmd): reg_index, reg_data = subcmd.split('=') reg_index = int(reg_index, 16) - reg_name = self.tables[self.ql.archtype][reg_index] + reg_name = self.tables[self.ql.arch.type][reg_index] - if self.ql.archtype== QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: reg_data = int(reg_data, 16) reg_data = int.from_bytes(struct.pack(' Write to register %s with %x\n" % (self.tables[self.ql.archtype][reg_index], reg_data)) + self.ql.log.info("gdb> Write to register %s with %x\n" % (self.tables[self.ql.arch.type][reg_index], reg_data)) self.send('OK') @@ -492,7 +492,7 @@ class QlGdb(QlDebugger, object): elif subcmd.startswith('Xfer:features:read'): xfercmd_file = subcmd.split(':')[3] xfercmd_abspath = os.path.dirname(os.path.abspath(__file__)) - xml_folder = arch_convert_str(self.ql.archtype).lower() + xml_folder = arch_convert_str(self.ql.arch.type).lower() xfercmd_file = os.path.join(xfercmd_abspath,"xml",xml_folder, xfercmd_file) if os.path.exists(xfercmd_file) and self.ql.ostype is not QL_OS.WINDOWS: diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index 4d5466f2..4c8a5247 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -3,52 +3,59 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn import * -from qiling.const import * +from qiling import Qiling +from qiling.const import QL_ARCH +PROMPT = r'gdb>' -class QlGdbUtils(object): +class QlGdbUtils: def __init__(self): + self.ql: Qiling + self.current_address = 0x0 self.current_address_size = 0x0 self.last_bp = 0x0 - self.ql = None self.entry_point = None self.exit_point = None self.soft_bp = False self.has_soft_bp = False self.bp_list = [] - self.mapping = [] + # self.mapping = [] self.breakpoint_count = 0x0 self.skip_bp_count = 0x0 - self._tmp_hook = None - def initialize(self, ql, hook_address, exit_point=None, mappings=None): + def initialize(self, ql: Qiling, hook_address: int, exit_point: int, mappings=[]): self.ql = ql - if self.ql.baremetal: + + if ql.baremetal: self.current_address = self.entry_point else: - self.current_address = self.entry_point = self.ql.os.entry_point + self.current_address = self.entry_point = ql.os.entry_point + self.exit_point = exit_point - self.mapping = mappings - self._tmp_hook = self.ql.hook_address(self.entry_point_hook, hook_address) + # self.mapping = mappings - def entry_point_hook(self, ql, *args, **kwargs): - self.ql.hook_del(self._tmp_hook) - self.ql.hook_code(self.dbg_hook) - self.ql.stop() - self.ql.log.info("gdb> Stop at entry point: %#x" % self.ql.reg.arch_pc) + def __entry_point_hook(ql: Qiling): + ql.hook_del(ep_hret) + ql.hook_code(self.dbg_hook) + ql.stop() - def dbg_hook(self, ql, address, size): - """ - Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg + ql.log.info(f'{PROMPT} Stop at entry point: {ql.arch.regs.arch_pc:#x}') + + ep_hret = ql.hook_address(__entry_point_hook, hook_address) + + + def dbg_hook(self, ql: Qiling, address: int, size: int): + """Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg """ + try: - if self.ql.archtype == QL_ARCH.ARM: - address = address | self.arch.thumb + if ql.arch.type == QL_ARCH.ARM: + if ql.arch.is_thumb: + address += 1 - self.mapping.append([(hex(address))]) + # self.mapping.append([(hex(address))]) self.current_address = address hit_soft_bp = False @@ -62,49 +69,51 @@ class QlGdbUtils(object): self.skip_bp_count -= 1 else: self.breakpoint_count += 1 - self.ql.stop() + ql.stop() self.last_bp = address - ql.log.info("gdb> Breakpoint found, stop at address: 0x%x" % address) - + ql.log.info(f'{PROMPT} Breakpoint found, stop at address: {address:#x}') + elif address == self.last_bp: self.last_bp = 0x0 self.has_soft_bp = hit_soft_bp - + if self.current_address + size == self.exit_point: - ql.log.debug("gdb> emulation entrypoint at 0x%x" % (self.entry_point)) - ql.log.debug("gdb> emulation exitpoint at 0x%x" % (self.exit_point)) - - except KeyboardInterrupt as ex: - ql.log.info("gdb> Paused at 0x%x, instruction size = %u" % (address, size)) - self.ql.stop() + ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}') + ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}') + + except KeyboardInterrupt: + ql.log.info(f'{PROMPT} Paused at {address:#x}, instruction size = {size:d}') + ql.stop() + except: - raise + raise - def bp_insert(self, addr): + def bp_insert(self, addr: int): if addr not in self.bp_list: self.bp_list.append(addr) - self.ql.log.info('gdb> Breakpoint added at: 0x%x' % addr) + self.ql.log.info(f'{PROMPT} Breakpoint added at: {addr:#x}') - def bp_remove(self, addr, type = None, len = None): + def bp_remove(self, addr: int, type = None, len = None): self.bp_list.remove(addr) - self.ql.log.info('gdb> Breakpoint removed at: 0x%x' % addr) + self.ql.log.info(f'{PROMPT} Breakpoint removed at: {addr:#x}') - def resume_emu(self, address=None, skip_bp=0): - """ - Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg + def resume_emu(self, address: int = None, skip_bp: int = 0): + """Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg """ if address is not None: - if self.ql.archtype == QL_ARCH.ARM: - address |= self.ql.arch.thumb + if self.ql.arch.type == QL_ARCH.ARM: + if self.ql.arch.is_thumb: + address += 1 + self.current_address = address self.skip_bp_count = skip_bp + if self.exit_point is not None: - self.ql.log.info('gdb> Resume at: 0x%x' % self.current_address) + self.ql.log.info(f'{PROMPT} Resume at: {self.current_address:#x}') self.ql.emu_start(self.current_address, self.exit_point) - \ No newline at end of file diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index cf4a1d08..7f638e7f 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -54,10 +54,10 @@ def examine_mem(ql: Qiling, line: str) -> Union[bool, (str, int, int)]: else: rest = _args - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + if ql.arch.type == QL_ARCH.ARM: rest = rest.replace("fp", "r11") - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: rest = rest.replace("fp", "s8") # for supporting addition of register with constant value @@ -66,8 +66,8 @@ def examine_mem(ql: Qiling, line: str) -> Union[bool, (str, int, int)]: items = [] for elem in elems: - if elem in ql.reg.register_mapping.keys(): - items.append(getattr(ql.reg, elem, None)) + if elem in ql.arch.regs.register_mapping.keys(): + items.append(getattr(ql.arch.regs, elem, None)) else: items.append(read_int(elem)) @@ -108,8 +108,8 @@ def examine_mem(ql: Qiling, line: str) -> Union[bool, (str, int, int)]: offset = line * sz * 4 print(f"0x{addr+offset:x}:\t", end="") - idx = line * ql.pointersize - for each in mem_read[idx:idx+ql.pointersize]: + idx = line * ql.arch.pointersize + for each in mem_read[idx:idx+ql.arch.pointersize]: data = unpack(each, sz) prefix = "0x" if ft in ("x", "a") else "" pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' @@ -121,11 +121,6 @@ def examine_mem(ql: Qiling, line: str) -> Union[bool, (str, int, int)]: return True -# get terminal window height and width -def get_terminal_size() -> Iterable: - return map(int, os.popen('stty size', 'r').read().split()) - - # try to read data from ql memory def _try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: @@ -145,23 +140,23 @@ def _try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: """ - Context Manager for rendering UI - """ COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) # decorator function for printing divider -def context_printer(field_name, ruler="─"): +def context_printer(title: str, *, footer: bool = False, ruler="─"): def decorator(context_dumper): def wrapper(*args, **kwargs): - height, width = get_terminal_size() - bar = (width - len(field_name)) // 2 - 1 - print(ruler * bar, field_name, ruler * bar) + cols, _ = os.get_terminal_size() + + print(title.center(cols, ruler)) context_dumper(*args, **kwargs) - if "DISASM" in field_name: - print(ruler * width) + + if footer: + print(ruler * cols) + return wrapper return decorator @@ -173,7 +168,7 @@ def setup_ctx_manager(ql: Qiling) -> CtxManager: QL_ARCH.ARM_THUMB: CtxManager_ARM, QL_ARCH.CORTEX_M: CtxManager_ARM, QL_ARCH.MIPS: CtxManager_MIPS, - }.get(ql.archtype)(ql) + }.get(ql.arch.type)(ql) class CtxManager(object): @@ -184,7 +179,7 @@ class CtxManager(object): def print_asm(self, insn: CsInsn, to_jump: Optional[bool] = None, address: int = None) -> None: opcode = "".join(f"{b:02x}" for b in insn.bytes) - if self.ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + if self.ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): trace_line = f"0x{insn.address:08x} │ {opcode:20s} {insn.mnemonic:10} {insn.op_str:35s}" else: trace_line = f"0x{insn.address:08x} │ {opcode:10s} {insn.mnemonic:10} {insn.op_str:35s}" @@ -231,7 +226,7 @@ class CtxManager(object): print(f" ◂— 0x{self.ql.unpack(buf[0]):08x}", end="") print() - @context_printer("[ DISASM ]") + @context_printer("[ DISASM ]", footer=True) def context_asm(self): # assembly before current location past_list = [] @@ -424,7 +419,7 @@ class CtxManager_X86(CtxManager): print(lines.format(*cur_regs.values())) print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.reg.ef)), color.END, sep="") - @context_printer("[ DISASM ]") + @context_printer("[ DISASM ]", footer=True) def context_asm(self): past_list = [] cur_addr = self.ql.reg.arch_pc @@ -504,4 +499,3 @@ class CtxManager_CORTEX_M(CtxManager): if __name__ == "__main__": pass - diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index d6366254..3a2d9aa6 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -13,7 +13,7 @@ from qiling.const import QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger from .frontend import examine_mem, setup_ctx_manager -from .utils import is_thumb, parse_int, setup_branch_predictor, disasm +from .utils import parse_int, setup_branch_predictor, disasm from .utils import Breakpoint, TempBreakpoint, read_inst from .const import color @@ -68,7 +68,7 @@ class QlQdb(cmd.Cmd, QlDebugger): self.cur_addr = self.ql.loader.entry_point - if self.ql.archtype == QL_ARCH.CORTEX_M: + if self.ql.arch.type == QL_ARCH.CORTEX_M: self._run() else: @@ -83,7 +83,7 @@ class QlQdb(cmd.Cmd, QlDebugger): getter for current address of qiling instance """ - return self.ql.reg.arch_pc + return self.ql.arch.regs.arch_pc @cur_addr.setter def cur_addr(self: QlQdb, address: int) -> None: @@ -91,7 +91,7 @@ class QlQdb(cmd.Cmd, QlDebugger): setter for current address of qiling instance """ - self.ql.reg.arch_pc = address + self.ql.arch.regs.arch_pc = address def _save(self: QlQdb, *args) -> None: """ @@ -115,7 +115,7 @@ class QlQdb(cmd.Cmd, QlDebugger): if not address: address = self.cur_addr - if self.ql.archtype == QL_ARCH.CORTEX_M and self.ql.count != 0: + if self.ql.arch.type == QL_ARCH.CORTEX_M and self.ql.count != 0: while self.ql.count: @@ -132,7 +132,7 @@ class QlQdb(cmd.Cmd, QlDebugger): return - if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M) and is_thumb(self.ql.reg.cpsr): + if self.ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and self.ql.arch.is_thumb: address |= 1 self.ql.emu_start(begin=address, end=end, count=count) @@ -235,7 +235,7 @@ class QlQdb(cmd.Cmd, QlDebugger): if prophecy.where is True: return True - if self.ql.archtype == QL_ARCH.CORTEX_M: + if self.ql.arch.type == QL_ARCH.CORTEX_M: self.ql.arch.step() else: self._run(count=1) @@ -285,7 +285,7 @@ class QlQdb(cmd.Cmd, QlDebugger): restore qiling instance context to initial state """ - if self.ql.archtype != QL_ARCH.CORTEX_M: + if self.ql.arch.type != QL_ARCH.CORTEX_M: self.ql.restore(self._init_state) self.do_context() diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 9178a262..a6199c73 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -4,7 +4,11 @@ # from __future__ import annotations -from typing import Callable, Optional, Mapping +from typing import Callable, Optional, Mapping, Tuple + +from capstone import CsInsn + +from qiling import Qiling from qiling.const import * from collections import namedtuple @@ -37,7 +41,7 @@ def signed_val(val: int) -> int: return (val-1 << 32) if is_negative(val) else val -def get_cpsr(bits: int) -> (bool, bool, bool, bool): +def get_cpsr(bits: int) -> Tuple[bool, bool, bool, bool]: return ( bits & 0x10000000 != 0, # V, overflow flag bits & 0x20000000 != 0, # C, carry flag @@ -57,32 +61,22 @@ def get_x86_eflags(bits: int) -> Dict[str, bool]: } -def is_thumb(bits: int) -> bool: - return bits & 0x00000020 != 0 - - -def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: +def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[CsInsn]: """ helper function for disassembling """ - md = ql.disassembler + md = ql.arch.disassembler md.detail = detail - try: - ret = next(md.disasm(read_inst(ql, address), address)) - except StopIteration: - ret = None - - return ret + return next(md.disasm(read_inst(ql, address), address), None) def read_inst(ql: Qiling, addr: int) -> int: result = ql.mem.read(addr, 4) - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): - if is_thumb(ql.reg.cpsr): - + if ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): + if ql.arch.is_thumb: first_two = ql.unpack16(ql.mem.read(addr, 2)) result = ql.pack16(first_two) @@ -96,7 +90,7 @@ def read_inst(ql: Qiling, addr: int) -> int: latter_two = ql.unpack16(ql.mem.read(addr+2, 2)) result += ql.pack16(latter_two) - elif ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + elif ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): # due to the variadic lengh of x86 instructions ( 1~15 ) # always assume the maxium size for disassembler to tell # what is it exactly. @@ -115,7 +109,7 @@ def setup_branch_predictor(ql: Qiling) -> BranchPredictor: QL_ARCH.ARM_THUMB: BranchPredictor_ARM, QL_ARCH.CORTEX_M: BranchPredictor_CORTEX_M, QL_ARCH.MIPS: BranchPredictor_MIPS, - }.get(ql.archtype)(ql) + }.get(ql.arch.type)(ql) class Prophecy(object): def __init__(self): diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index ad6f3709..954b9948 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -51,7 +51,6 @@ from qiling.arch.x86_const import reg_map_st as x86_reg_map_st from qiling.arch.arm_const import reg_map as arm_reg_map from qiling.arch.arm64_const import reg_map as arm64_reg_map from qiling.arch.mips_const import reg_map as mips_reg_map -from qiling.utils import ql_get_arch_bits from qiling import __version__ as QLVERSION from qiling.os.filestruct import ql_file from keystone import * @@ -488,10 +487,6 @@ class QlEmuRegView(simplecustviewer_t): return True def SetReg(self, addr, ql:Qiling): - arch = ql.archtype - if arch == "": - return - #clear self.ClearLines() @@ -509,7 +504,7 @@ class QlEmuRegView(simplecustviewer_t): for regs in reglist: for reg in regs: line += COLSTR(" %4s: " % str(reg), SCOLOR_REG) - regvalue = ql.reg.read(reg) + regvalue = ql.arch.regs.read(reg) if arch in [QL_ARCH.X8664, QL_ARCH.ARM64]: value_format = "0x%.16X" else: @@ -547,16 +542,12 @@ class QlEmuStackView(simplecustviewer_t): if ql is None: return - sp = ql.reg.arch_sp + sp = ql.arch.regs.arch_sp self.AddLine('') self.AddLine(COLSTR(' Stack at 0x%X' % sp, SCOLOR_AUTOCMT)) self.AddLine('') - arch = ql.archtype - if arch == "": - return - - reg_bit_size = ql_get_arch_bits(arch) + reg_bit_size = ql.arch.bits reg_byte_size = reg_bit_size // 8 value_format = '% .16X' if reg_bit_size == 64 else '% .8X' @@ -832,18 +823,7 @@ class QlEmuMisc: QL_ARCH.MIPS : list({**mips_reg_map}.keys()), } - if ql.archtype == QL_ARCH.X86: - return tables[QL_ARCH.X86] - elif ql.archtype == QL_ARCH.X8664: - return tables[QL_ARCH.X8664] - elif ql.archtype == QL_ARCH.ARM: - return tables[QL_ARCH.ARM] - elif ql.archtype == QL_ARCH.ARM64: - return tables[QL_ARCH.ARM64] - elif ql.archtype == QL_ARCH.MIPS: - return tables[QL_ARCH.MIPS] - else: - return [] + return tables.get(ql.arch.type, []) @staticmethod def url_download(url): @@ -898,22 +878,19 @@ class QlEmuQiling: def __init__(self): self.path = None self.rootfs = None - self.ql = None + self.ql: Qiling self.status = None self.exit_addr = None self.baseaddr = None self.env = {} def start(self, *args, **kwargs): - if sys.platform != 'win32': - qlstdin = QlEmuMisc.QLStdIO('stdin', sys.__stdin__.fileno()) - qlstdout = QlEmuMisc.QLStdIO('stdout', sys.__stdout__.fileno()) - qlstderr = QlEmuMisc.QLStdIO('stderr', sys.__stderr__.fileno()) + self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, log_plain=True, *args, **kwargs) if sys.platform != 'win32': - self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, stdin=qlstdin, stdout=qlstdout, stderr=qlstderr, log_plain=True, *args, **kwargs) - else: - self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, log_plain=True, *args, **kwargs) + self.ql.os.stdin = QlEmuMisc.QLStdIO('stdin', sys.__stdin__.fileno()) + self.ql.os.stdout = QlEmuMisc.QLStdIO('stdout', sys.__stdout__.fileno()) + self.ql.os.stderr = QlEmuMisc.QLStdIO('stderr', sys.__stderr__.fileno()) self.exit_addr = self.ql.os.exit_point if self.ql.ostype == QL_OS.LINUX: @@ -923,9 +900,9 @@ class QlEmuQiling: if elf_header['e_type'] == 'ET_EXEC': self.baseaddr = self.ql.os.elf_mem_start elif elf_header['e_type'] == 'ET_DYN': - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.baseaddr = int(self.ql.os.profile.get("OS32", "load_address"), 16) - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.baseaddr = int(self.ql.os.profile.get("OS64", "load_address"), 16) else: self.baseaddr = 0x0 @@ -935,12 +912,12 @@ class QlEmuQiling: def set_reg(self): reglist = QlEmuMisc.get_reg_map(self.ql) - regs = [ [ row, int(self.ql.reg.read(row)), ql_get_arch_bits(self.ql.archtype) ] for row in reglist ] + regs = [ [ row, int(self.ql.arch.regs.read(row)), self.ql.arch.bits ] for row in reglist ] regs_len = len(regs) RegDig = QlEmuRegDialog(regs) if RegDig.show(): for idx, val in enumerate(RegDig.items[0:regs_len-1]): - self.ql.reg.write(reglist[idx], val[1]) + self.ql.arch.regs.write(reglist[idx], val[1]) return True else: return False @@ -1090,7 +1067,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): self.qlemu.ql.restore(self.qlemu.status) show_wait_box("Qiling is processing ...") try: - self.qlemu.run(begin=self.qlemu.ql.reg.arch_pc, end=self.qlemu.exit_addr) + self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=self.qlemu.exit_addr) finally: hide_wait_box() else: @@ -1103,7 +1080,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): if userhook and userhook is not None: for hook in userhook: self.qlemu.ql.hook_del(hook) - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1131,17 +1108,17 @@ class QlEmuPlugin(plugin_t, UI_Hooks): for hook in userhook: self.qlemu.ql.hook_del(hook) self.qlemu.status = self.qlemu.ql.save() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') def ql_set_pc(self): if self.qlinit: ea = IDA.get_current_address() - self.qlemu.ql.reg.arch_pc = ea + self.qlemu.ql.arch.regs.arch_pc = ea logging.info(f"QIling PC set to {hex(ea)}") self.qlemu.status = self.qlemu.ql.save() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1153,7 +1130,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): self.qlemu.ql.restore(self.qlemu.status) show_wait_box("Qiling is processing ...") try: - self.qlemu.run(begin=self.qlemu.ql.reg.arch_pc, end=curr_addr+self.qlemu.baseaddr-get_imagebase()) + self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=curr_addr+self.qlemu.baseaddr-get_imagebase()) finally: hide_wait_box() else: @@ -1166,7 +1143,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): set_color(curr_addr, CIC_ITEM, 0x00B3CBFF) self.qlemu.ql.hook_del(untillhook) self.qlemu.status = self.qlemu.ql.save() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1178,11 +1155,11 @@ class QlEmuPlugin(plugin_t, UI_Hooks): self.stephook = self.qlemu.ql.hook_code(callback=self.ql_step_hook) if self.userobj is not None: userhook = self.userobj.custom_step(self.qlemu.ql) - self.qlemu.run(begin=self.qlemu.ql.reg.arch_pc, end=self.qlemu.exit_addr) + self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=self.qlemu.exit_addr) if userhook and userhook is not None: for hook in userhook: self.qlemu.ql.hook_del(hook) - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1203,7 +1180,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): def ql_chang_reg(self): if self.qlinit: self.qlemu.set_reg() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) self.qlemu.status = self.qlemu.ql.save() else: logging.error('Qiling should be setup firstly.') @@ -1235,7 +1212,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): self.qlemuregview = QlEmuRegView(self) QlEmuRegView(self) self.qlemuregview.Create() - self.qlemuregview.SetReg(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.qlemuregview.SetReg(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) self.qlemuregview.Show() self.qlemuregview.Refresh() else: @@ -1461,9 +1438,9 @@ class QlEmuPlugin(plugin_t, UI_Hooks): ins_list = list(IDA.micro_code_from_mbb(next_mbb)) first_ins = ins_list[0] imm = first_ins.l.nnn.value - reg_name = ida_hexrays.get_mreg_name(first_ins.d.r, ql.pointersize) + reg_name = ida_hexrays.get_mreg_name(first_ins.d.r, ql.arch.pointersize) logging.info(f"Froce set {reg_name} to {hex(imm)}") - ql.reg.__setattr__(reg_name, imm) + ql.arch.regs.__setattr__(reg_name, imm) return True def _ida_address_after_branch(self, ida_addr): @@ -1489,9 +1466,9 @@ class QlEmuPlugin(plugin_t, UI_Hooks): if "x86" in IDA.get_ql_arch_string(): # cmovlg eax, ebx reg1 = IDA.print_operand(ida_addr, 0).lower() reg2 = IDA.print_operand(ida_addr, 1).lower() - reg2_val = ql.reg.__getattribute__(reg2) + reg2_val = ql.arch.regs.__getattribute__(reg2) logging.info(f"Force set {reg1} to {hex(reg2_val)}") - ql.reg.__setattr__(reg1, reg2_val) + ql.arch.regs.__setattr__(reg1, reg2_val) return True elif "arm" in IDA.get_ql_arch_string(): instr = IDA.get_instruction(ida_addr).lower() @@ -1504,14 +1481,14 @@ class QlEmuPlugin(plugin_t, UI_Hooks): reg = IDA.print_operand(ida_addr, 0).lower() val = (high << 16) + low logging.info(f"Force set {reg} to {hex(val)}") - ql.reg.__setattr__(reg, val) + ql.arch.regs.__setattr__(reg, val) return True elif "csel" in instr: # csel dst, src1, src2, cond dst = IDA.print_operand(ida_addr, 0).lower() src = IDA.print_operand(ida_addr, 2).lower() - src_val = ql.reg.__getattribute__(src) + src_val = ql.arch.regs.__getattribute__(src) logging.info(f"Force set {dst} to {hex(src_val)}") - ql.reg.__setattr__(dst, src_val) + ql.arch.regs.__setattr__(dst, src_val) return True return False @@ -1554,12 +1531,12 @@ class QlEmuPlugin(plugin_t, UI_Hooks): else: next_ida_addr = ida_addr + IDA.get_instruction_size(ida_addr) logging.info(f"Goto {hex(next_ida_addr)} after branch...") - ql.reg.arch_pc = self.deflatqlemu.ql_addr_from_ida(next_ida_addr) + self.append + ql.arch.regs.arch_pc = self.deflatqlemu.ql_addr_from_ida(next_ida_addr) + self.append ida_addr = next_ida_addr # TODO: Maybe we can detect whether the program will access unmapped # here so that we won't map the memory. if self._has_call_insn(ida_addr): - ql.reg.arch_pc += IDA.get_instruction_size(ida_addr) + self.append + ql.arch.regs.arch_pc += IDA.get_instruction_size(ida_addr) + self.append return if start_bb_id == cur_bb.id: return @@ -1617,10 +1594,10 @@ class QlEmuPlugin(plugin_t, UI_Hooks): def _log_verbose(self, ql, addr, size): logging.debug(f"addr: {hex(addr)} ida_addr: {hex(self.deflatqlemu.ida_addr_from_ql_addr(addr))}") - registers = [ k for k in ql.reg.register_mapping.keys() if type(k) is str ] + registers = [ k for k in ql.arch.regs.register_mapping.keys() if type(k) is str ] for idx in range(0, len(registers), 3): regs = registers[idx:idx+3] - s = "\t".join(map(lambda v: f"{v:4}: {ql.reg.__getattribute__(v):016x}", regs)) + s = "\t".join(map(lambda v: f"{v:4}: {ql.arch.regs.__getattribute__(v):016x}", regs)) logging.debug(s) # Q: Why we need emulation to help us find real control flow considering there are some @@ -1640,8 +1617,8 @@ class QlEmuPlugin(plugin_t, UI_Hooks): if IDA.get_ql_arch_string() == "arm32": if self._thumb_detect(first_block.start_ea): logging.info(f"Thumb detected, enable it.") - self.deflatqlemu.start(archtype=QL_ARCH.ARM_THUMB) - self.deflatqlemu.ql.reg.cpsr |= 0x20 + self.deflatqlemu.start(archtype=QL_ARCH.ARM, thumb=True) + self.deflatqlemu.ql.arch.regs.cpsr |= 0x20 self.append = 1 else: self.deflatqlemu.start() @@ -1693,7 +1670,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): # assembler implmentation with keystone. def _initialize_keystone(self): if self.ks is None: - self.ks = self.deflatqlemu.ql.create_assembler() + self.ks = self.deflatqlemu.ql.arch.assembler def _asm(self, *args, **kwargs): @@ -2003,7 +1980,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks): addr = addr - self.qlemu.baseaddr + get_imagebase() if self.stepflag: set_color(addr, CIC_ITEM, 0x00FFD700) - self.ql_update_views(self.qlemu.ql.reg.arch_pc, ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, ql) self.qlemu.status = ql.save() ql.os.stop() self.qlemu.ql.hook_del(self.stephook) diff --git a/qiling/extensions/report/report.py b/qiling/extensions/report/report.py index 91b022a9..59343165 100644 --- a/qiling/extensions/report/report.py +++ b/qiling/extensions/report/report.py @@ -12,11 +12,11 @@ class Report: def __init__(self, ql): self.filename = ql.argv self.rootfs = ql.rootfs - self.arch = list(arch_map.keys())[list(arch_map.values()).index(ql.archtype)] + self.arch = list(arch_map.keys())[list(arch_map.values()).index(ql.arch.type)] self.os = list(os_map.keys())[list(os_map.values()).index(ql.ostype)] self.env = ql.env self.strings = set() - for string in ql.os.utils.appeared_strings: + for string in ql.os.stats.appeared_strings: strings = string.split(" ") self.strings |= set(strings) self.profile = {} diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py index d2d7692d..878c6a4b 100644 --- a/qiling/extensions/trace.py +++ b/qiling/extensions/trace.py @@ -59,7 +59,7 @@ def __get_trace_records(ql: Qiling, address: int, size: int, md: Cs) -> Iterator for insn in md.disasm(buf, address): # BUG: insn.regs_read doesn't work well, so we use insn.regs_access()[0] - state = tuple((reg, ql.reg.read(CS_UC_REGS[reg])) for reg in insn.regs_access()[0]) + state = tuple((reg, ql.arch.regs.read(CS_UC_REGS[reg])) for reg in insn.regs_access()[0]) yield (insn, state) @@ -152,7 +152,7 @@ def enable_full_trace(ql: Qiling): """ # enable detailed disassembly info - md = ql.create_disassembler() + md = ql.arch.disassembler md.detail = True assert md.arch == CS_ARCH_X86, 'currently available only for intel architecture' @@ -189,7 +189,7 @@ def enable_history_trace(ql: Qiling, nrecords: int): """ # enable detailed disassembly info - md = ql.create_disassembler() + md = ql.arch.disassembler md.detail = True assert md.arch == CS_ARCH_X86, 'currently available only for intel architecture' diff --git a/qiling/host.py b/qiling/host.py new file mode 100644 index 00000000..5acf5063 --- /dev/null +++ b/qiling/host.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from functools import cached_property +from typing import Optional +import platform + +from qiling import utils +from qiling.const import QL_OS, QL_ARCH + +class QlHost: + """Interface to the hosting platform. + """ + + @cached_property + def os(self) -> Optional[QL_OS]: + """Hosting platform OS type. + """ + + system = platform.system() + + return utils.ostype_convert(system.lower()) + + @cached_property + def arch(self) -> Optional[QL_ARCH]: + """Hosting platform architecture type. + """ + + machine = platform.machine() + + return utils.arch_convert(machine.lower()) diff --git a/qiling/hw/peripheral.py b/qiling/hw/peripheral.py index 4ba5caa4..5443c1ac 100644 --- a/qiling/hw/peripheral.py +++ b/qiling/hw/peripheral.py @@ -36,7 +36,7 @@ class QlPeripheralUtils: retval = func(self, offset, size) if self.verbose: - self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.reg.pc)}] [R] {self.field_description(offset, size):{width}s} = {hex(retval)}') + self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.arch.regs.arch_pc)}] [R] {self.field_description(offset, size):{width}s} = {hex(retval)}') return retval @@ -49,7 +49,7 @@ class QlPeripheralUtils: if field.startswith('DR') and value <= 255: extra = f'({repr(chr(value))})' - self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.reg.pc)}] [W] {field:{width}s} = {hex(value)} {extra}') + self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.arch.regs.pc)}] [W] {field:{width}s} = {hex(value)} {extra}') return func(self, offset, size, value) diff --git a/qiling/loader/blob.py b/qiling/loader/blob.py index 787ba6a3..382dbb33 100644 --- a/qiling/loader/blob.py +++ b/qiling/loader/blob.py @@ -23,6 +23,6 @@ class QlLoaderBLOB(QlLoader): heap_size = int(self.ql.os.profile.get("CODE", "heap_size"), 16) self.ql.os.heap = QlMemoryHeap(self.ql, heap_address, heap_address + heap_size) - self.ql.reg.arch_sp = heap_address - 0x1000 + self.ql.arch.regs.arch_sp = heap_address - 0x1000 return diff --git a/qiling/loader/dos.py b/qiling/loader/dos.py index 69e8826a..5e3db8bb 100644 --- a/qiling/loader/dos.py +++ b/qiling/loader/dos.py @@ -87,16 +87,16 @@ class QlLoaderDOS(QlLoader): self.ql.os.fs_mapper.add_fs_mapping(0x80, QlDisk(path, 0x80)) # 0x80 -> first drive - self.ql.reg.dx = 0x80 + self.ql.arch.regs.dx = 0x80 else: raise NotImplementedError() - self.ql.reg.cs = cs - self.ql.reg.ds = cs - self.ql.reg.es = cs - self.ql.reg.ss = ss - self.ql.reg.ip = ip - self.ql.reg.sp = sp + self.ql.arch.regs.cs = cs + self.ql.arch.regs.ds = cs + self.ql.arch.regs.es = cs + self.ql.arch.regs.ss = ss + self.ql.arch.regs.ip = ip + self.ql.arch.regs.sp = sp self.stack_address = (ss << 4) + sp self.start_address = (cs << 4) + ip diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 80ab28f0..efb5e78c 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -70,13 +70,13 @@ class QlLoaderELF(QlLoader): self.ql.mem.map(self.ql.os.entry_point, self.ql.os.code_ram_size, info="[shellcode_stack]") self.ql.os.entry_point = (self.ql.os.entry_point + 0x200000 - 0x1000) self.ql.mem.write(self.ql.os.entry_point, self.ql.code) - self.ql.reg.arch_sp = self.ql.os.entry_point + self.ql.arch.regs.arch_sp = self.ql.os.entry_point return section = { 32 : 'OS32', 64 : 'OS64' - }[self.ql.archbit] + }[self.ql.arch.bits] self.profile = self.ql.os.profile[section] @@ -115,13 +115,13 @@ class QlLoaderELF(QlLoader): self.is_driver = (elftype == 'ET_REL') - self.ql.reg.arch_sp = self.stack_address + self.ql.arch.regs.arch_sp = self.stack_address # No idea why. if self.ql.ostype == QL_OS.FREEBSD: - # self.ql.reg.rbp = self.stack_address + 0x40 - self.ql.reg.rdi = self.stack_address - self.ql.reg.r14 = self.stack_address + # self.ql.arch.regs.rbp = self.stack_address + 0x40 + self.ql.arch.regs.rdi = self.stack_address + self.ql.arch.regs.r14 = self.stack_address @staticmethod def seg_perm_to_uc_prot(perm: int) -> int: @@ -216,7 +216,7 @@ class QlLoaderELF(QlLoader): # # this workaround unifies such "overlapping" segments, which may apply more permissive # protection flags to that memory region. - if self.ql.archtype == QL_ARCH.ARM64: + if self.ql.arch.type == QL_ARCH.ARM64: load_regions[-1] = (prev_lbound, ubound, prev_perms | perms) continue @@ -309,7 +309,7 @@ class QlLoaderELF(QlLoader): """ data = (s if isinstance(s, bytes) else s.encode("utf-8")) + b'\x00' - top = QlLoaderELF.align(top - len(data), self.ql.pointersize) + top = QlLoaderELF.align(top - len(data), self.ql.arch.pointersize) self.ql.mem.write(top, data) return top @@ -343,12 +343,12 @@ class QlLoaderELF(QlLoader): elf_phnum = elffile['e_phnum'] elf_entry = load_address + elffile['e_entry'] - if self.ql.archbit == 64: + if self.ql.arch.bits == 64: elf_hwcap = 0x078bfbfd - elif self.ql.archbit == 32: + elif self.ql.arch.bits == 32: elf_hwcap = 0x1fb8d7 - if self.ql.archendian == QL_ENDIAN.EB: + if self.ql.arch.endian == QL_ENDIAN.EB: # FIXME: considering this is a 32 bits value, it is not a big-endian version of the # value above like it is meant to be, since the one above has an implied leading zero # byte (i.e. 0x001fb8d7) which the EB value didn't take into account @@ -393,7 +393,7 @@ class QlLoaderELF(QlLoader): self.stack_address = new_stack self.load_address = load_address self.images.append(Image(load_address, load_address + mem_end, self.path)) - self.init_sp = self.ql.reg.arch_sp + self.init_sp = self.ql.arch.regs.arch_sp self.ql.os.entry_point = self.entry_point = entry_point self.ql.os.elf_mem_start = mem_start @@ -404,7 +404,7 @@ class QlLoaderELF(QlLoader): self.skip_exit_check = (self.elf_entry != self.entry_point) # map vsyscall section for some specific needs - if self.ql.archtype == QL_ARCH.X8664 and self.ql.ostype == QL_OS.LINUX: + if self.ql.arch.type == QL_ARCH.X8664 and self.ql.ostype == QL_OS.LINUX: _vsyscall_addr = int(self.profile.get('vsyscall_address'), 0) _vsyscall_size = int(self.profile.get('vsyscall_size'), 0) @@ -413,7 +413,7 @@ class QlLoaderELF(QlLoader): # 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') - assembler = self.ql.create_assembler() + assembler = self.ql.arch.assembler def __assemble(asm: str) -> bytes: bs, _ = assembler.asm(asm) @@ -506,19 +506,19 @@ class QlLoaderELF(QlLoader): # we need to lookup from address to symbol, so we can find the right callback # for sys_xxx handler for syscall, the address must be aligned to pointer size if symbol_name.startswith('sys_'): - self.ql.os.hook_addr = QlLoaderELF.align_up(self.ql.os.hook_addr, self.ql.pointersize) + self.ql.os.hook_addr = QlLoaderELF.align_up(self.ql.os.hook_addr, self.ql.arch.pointersize) self.import_symbols[self.ql.os.hook_addr] = symbol_name # FIXME: this is for rootkit to scan for syscall table from page_offset_base # write address of syscall table to this slot, so syscall scanner can quickly find it if symbol_name == "page_offset_base": - ql.mem.write(self.ql.os.hook_addr, self.ql.pack(SYSCALL_MEM)) + ql.mem.write_ptr(self.ql.os.hook_addr, SYSCALL_MEM) # we also need to do reverse lookup from symbol to address 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.pointersize + self.ql.os.hook_addr += self.ql.arch.pointersize else: # local symbol _section = elffile.get_section(_symbol['st_shndx']) @@ -539,48 +539,49 @@ class QlLoaderELF(QlLoader): if rel['r_addend']: val = sym_offset + rel['r_addend'] val += mem_start - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) else: - ql.mem.write(loc, ql.pack32(rev_reloc_symbols[symbol_name] & 0xFFFFFFFF)) + val = rev_reloc_symbols[symbol_name] + + ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4) elif desc == 'R_X86_64_64': val = sym_offset + rel['r_addend'] val += 0x2000000 # init_module position: FIXME - ql.mem.write(loc, ql.pack64(val)) + ql.mem.write_ptr(loc, val, 8) elif desc == 'R_X86_64_PC64': val = rel['r_addend'] - loc val += rev_reloc_symbols[symbol_name] - ql.mem.write(loc, ql.pack64(val)) + ql.mem.write_ptr(loc, val, 8) elif desc in ('R_X86_64_PC32', 'R_X86_64_PLT32'): val = rel['r_addend'] - loc val += rev_reloc_symbols[symbol_name] - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) + ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4) elif desc in ('R_386_PC32', 'R_386_PLT32'): val = ql.mem.read_ptr(loc, 4) val = rev_reloc_symbols[symbol_name] + val - loc - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) + 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 - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) + ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4) elif desc == 'R_MIPS_HI16': # actual relocation is done in R_MIPS_LO16 prev_mips_hi16_loc = loc elif desc == 'R_MIPS_LO16': - val = ql.unpack16(ql.mem.read(prev_mips_hi16_loc + 2, 2)) << 16 | ql.unpack16(ql.mem.read(loc + 2, 2)) + val = ql.mem.read_ptr(prev_mips_hi16_loc + 2, 2) << 16 | ql.mem.read_ptr(loc + 2, 2) val = rev_reloc_symbols[symbol_name] + val # *(word)(mips_lo16_loc + 2) is treated as signed if (val & 0xFFFF) >= 0x8000: val += (1 << 16) - ql.mem.write(prev_mips_hi16_loc + 2, ql.pack16(val >> 16)) - ql.mem.write(loc + 2, ql.pack16(val & 0xFFFF)) + ql.mem.write_ptr(prev_mips_hi16_loc + 2, (val >> 16), 2) + ql.mem.write_ptr(loc + 2, (val & 0xFFFF), 2) else: raise NotImplementedError(f'Relocation type {desc} not implemented') @@ -629,7 +630,7 @@ class QlLoaderELF(QlLoader): self.ql.os.entry_point = self.entry_point = entry_point self.elf_entry = self.ql.os.elf_entry = self.ql.os.entry_point - self.stack_address = QlLoaderELF.align(stack_addr, self.ql.pointersize) + self.stack_address = QlLoaderELF.align(stack_addr, self.ql.arch.pointersize) self.load_address = loadbase # remember address of syscall table, so external tools can access to it @@ -648,20 +649,20 @@ class QlLoaderELF(QlLoader): if hasattr(SYSCALL_NR, tmp_sc): syscall_id = getattr(SYSCALL_NR, tmp_sc).value - dest = SYSCALL_MEM + syscall_id * self.ql.pointersize + dest = SYSCALL_MEM + syscall_id * self.ql.arch.pointersize self.ql.log.debug(f'Writing syscall {tmp_sc} to {dest:#x}') - self.ql.mem.write(dest, self.ql.pack(addr)) + self.ql.mem.write_ptr(dest, addr) # write syscall addresses into syscall table - self.ql.mem.write(SYSCALL_MEM + 0 * self.ql.pointersize, self.ql.pack(self.ql.os.hook_addr + 0 * self.ql.pointersize)) - self.ql.mem.write(SYSCALL_MEM + 1 * self.ql.pointersize, self.ql.pack(self.ql.os.hook_addr + 1 * self.ql.pointersize)) - self.ql.mem.write(SYSCALL_MEM + 2 * self.ql.pointersize, self.ql.pack(self.ql.os.hook_addr + 2 * self.ql.pointersize)) + self.ql.mem.write_ptr(SYSCALL_MEM + 0 * self.ql.arch.pointersize, self.ql.os.hook_addr + 0 * self.ql.arch.pointersize) + self.ql.mem.write_ptr(SYSCALL_MEM + 1 * self.ql.arch.pointersize, self.ql.os.hook_addr + 1 * self.ql.arch.pointersize) + self.ql.mem.write_ptr(SYSCALL_MEM + 2 * self.ql.arch.pointersize, self.ql.os.hook_addr + 2 * self.ql.arch.pointersize) # setup hooks for read/write/open syscalls - self.import_symbols[self.ql.os.hook_addr + 0 * self.ql.pointersize] = hook_sys_read - self.import_symbols[self.ql.os.hook_addr + 1 * self.ql.pointersize] = hook_sys_write - self.import_symbols[self.ql.os.hook_addr + 2 * self.ql.pointersize] = hook_sys_open + self.import_symbols[self.ql.os.hook_addr + 0 * self.ql.arch.pointersize] = hook_sys_read + self.import_symbols[self.ql.os.hook_addr + 1 * self.ql.arch.pointersize] = hook_sys_write + self.import_symbols[self.ql.os.hook_addr + 2 * self.ql.arch.pointersize] = hook_sys_open def get_elfdata_mapping(self, elffile: ELFFile) -> bytes: elfdata_mapping = bytearray() diff --git a/qiling/loader/macho.py b/qiling/loader/macho.py index 813324ab..5c0e5c21 100644 --- a/qiling/loader/macho.py +++ b/qiling/loader/macho.py @@ -27,7 +27,7 @@ from qiling.os.macos.thread import QlMachoThreadManagement, QlMachoThread # commpage is a shared mem space which is in a static address def load_commpage(ql): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: COMM_PAGE_START_ADDRESS = X8664_COMM_PAGE_START_ADDRESS else: COMM_PAGE_START_ADDRESS = ARM64_COMM_PAGE_START_ADDRESS @@ -105,7 +105,7 @@ class QlLoaderMACHO(QlLoader): self.ql.mem.write(self.entry_point, self.ql.code) - self.ql.reg.arch_sp = self.ql.os.entry_point + self.ql.arch.regs.arch_sp = self.ql.os.entry_point return self.ql.os.macho_task = MachoTask() @@ -149,8 +149,8 @@ class QlLoaderMACHO(QlLoader): else: self.loadMacho() self.stack_address = (int(self.stack_sp)) - self.ql.reg.arch_sp = self.stack_address # self.stack_sp - self.init_sp = self.ql.reg.arch_sp + self.ql.arch.regs.arch_sp = self.stack_address # self.stack_sp + self.init_sp = self.ql.arch.regs.arch_sp self.ql.os.macho_task.min_offset = page_align_end(self.vm_end_addr, PAGE_SIZE) def loadDriver(self, stack_addr, loadbase = -1, argv = [], env = {}): @@ -420,7 +420,7 @@ class QlLoaderMACHO(QlLoader): self.load_address = self.macho_entry # load_commpage not wroking with ARM64, yet - if self.ql.archtype== QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: load_commpage(self.ql) return self.proc_entry diff --git a/qiling/loader/macho_parser/parser.py b/qiling/loader/macho_parser/parser.py index a8b0c29b..2b38914c 100644 --- a/qiling/loader/macho_parser/parser.py +++ b/qiling/loader/macho_parser/parser.py @@ -20,7 +20,7 @@ class MachoParser: self.ql = ql self.binary_file = self.readFile(path) self.raw_data = self.binary_file - self.archtype = ql.archtype + self.archtype = ql.arch.type self.parseFile() self.page_zero_size = 0 self.header_address = 0x0 diff --git a/qiling/loader/mcu.py b/qiling/loader/mcu.py index ae8f5741..78baaa23 100644 --- a/qiling/loader/mcu.py +++ b/qiling/loader/mcu.py @@ -104,7 +104,7 @@ class QlLoaderMCU(QlLoader): self.ql.arch.init_context() - self.entry_point = self.ql.reg.read('pc') + self.entry_point = self.ql.arch.regs.read('pc') def load_profile(self): self.ql.env.update(self.ql.profile) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 726fc10a..87251bb9 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -210,19 +210,19 @@ class Process(): if name == b"_acmdln": addr, packed_addr = self._alloc_cmdline(wide=False) cmdline_entry = {"name": name, "address": address} - memory[address:address + self.ql.pointersize] = packed_addr + memory[address:address + self.ql.arch.pointersize] = packed_addr self.ql.mem.write(addr, self.cmdline) elif name == b"_wcmdln": addr, packed_addr = self._alloc_cmdline(wide=True) cmdline_entry = {"name": name, "address": address} - memory[address:address + self.ql.pointersize] = packed_addr + memory[address:address + self.ql.arch.pointersize] = packed_addr encoded = self.cmdline.decode('ascii').encode('UTF-16LE') self.ql.mem.write(addr, encoded) return cmdline_entry def init_tib(self): - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: teb_addr = self.structure_last_addr else: gs = self.structure_last_addr @@ -243,11 +243,11 @@ class Process(): self.ql.mem.write(teb_addr, teb_data.bytes()) self.structure_last_addr += teb_size - if self.ql.archtype == QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: # TEB - self.ql.mem.write(gs + 0x30, self.ql.pack64(teb_addr)) + self.ql.mem.write_ptr(gs + 0x30, teb_addr) # PEB - self.ql.mem.write(gs + 0x60, self.ql.pack64(teb_addr + teb_size)) + self.ql.mem.write_ptr(gs + 0x60, teb_addr + teb_size) self.TEB = self.ql.TEB = teb_data @@ -277,16 +277,16 @@ class Process(): self.ql, base=ldr_addr, in_load_order_module_list={ - 'Flink': ldr_addr + 2 * self.ql.pointersize, - 'Blink': ldr_addr + 2 * self.ql.pointersize + 'Flink': ldr_addr + 2 * self.ql.arch.pointersize, + 'Blink': ldr_addr + 2 * self.ql.arch.pointersize }, in_memory_order_module_list={ - 'Flink': ldr_addr + 4 * self.ql.pointersize, - 'Blink': ldr_addr + 4 * self.ql.pointersize + 'Flink': ldr_addr + 4 * self.ql.arch.pointersize, + 'Blink': ldr_addr + 4 * self.ql.arch.pointersize }, in_initialization_order_module_list={ - 'Flink': ldr_addr + 6 * self.ql.pointersize, - 'Blink': ldr_addr + 6 * self.ql.pointersize + 'Flink': ldr_addr + 6 * self.ql.arch.pointersize, + 'Blink': ldr_addr + 6 * self.ql.arch.pointersize } ) self.ql.mem.write(ldr_addr, ldr_data.bytes()) @@ -316,8 +316,8 @@ class Process(): ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderModuleList['Flink'] flink.InLoadOrderModuleList['Flink'] = ldr_table_entry.base - flink.InMemoryOrderModuleList['Flink'] = ldr_table_entry.base + 2 * self.ql.pointersize - flink.InInitializationOrderModuleList['Flink'] = ldr_table_entry.base + 4 * self.ql.pointersize + flink.InMemoryOrderModuleList['Flink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize + flink.InInitializationOrderModuleList['Flink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize else: flink = self.ldr_list[-1] @@ -326,8 +326,8 @@ class Process(): ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderLinks['Flink'] flink.InLoadOrderLinks['Flink'] = ldr_table_entry.base - flink.InMemoryOrderLinks['Flink'] = ldr_table_entry.base + 2 * self.ql.pointersize - flink.InInitializationOrderLinks['Flink'] = ldr_table_entry.base + 4 * self.ql.pointersize + flink.InMemoryOrderLinks['Flink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize + flink.InInitializationOrderLinks['Flink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize # Blink blink = self.LDR @@ -336,8 +336,8 @@ class Process(): ldr_table_entry.InInitializationOrderLinks['Blink'] = blink.InInitializationOrderModuleList['Blink'] blink.InLoadOrderModuleList['Blink'] = ldr_table_entry.base - blink.InMemoryOrderModuleList['Blink'] = ldr_table_entry.base + 2 * self.ql.pointersize - blink.InInitializationOrderModuleList['Blink'] = ldr_table_entry.base + 4 * self.ql.pointersize + blink.InMemoryOrderModuleList['Blink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize + blink.InInitializationOrderModuleList['Blink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize self.ql.mem.write(flink.base, flink.bytes()) self.ql.mem.write(blink.base, blink.bytes()) @@ -370,9 +370,9 @@ class Process(): driver_object_addr = self.structure_last_addr self.ql.log.info("Driver object addr is 0x%x" %driver_object_addr) - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.driver_object = DRIVER_OBJECT32(self.ql, driver_object_addr) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.driver_object = DRIVER_OBJECT64(self.ql, driver_object_addr) driver_object_size = ctypes.sizeof(self.driver_object) @@ -386,9 +386,9 @@ class Process(): regitry_path_addr = self.structure_last_addr self.ql.log.info("Registry path addr is 0x%x" %regitry_path_addr) - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: regitry_path_data = UNICODE_STRING32(0, 0, regitry_path_addr) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: regitry_path_data = UNICODE_STRING64(0, 0, regitry_path_addr) regitry_path_size = ctypes.sizeof(regitry_path_data) @@ -402,9 +402,9 @@ class Process(): self.ql.log.info("EPROCESS is is 0x%x" %addr) - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.eprocess_object = EPROCESS32(self.ql, addr) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.eprocess_object = EPROCESS64(self.ql, addr) size = ctypes.sizeof(self.eprocess_object) @@ -420,9 +420,9 @@ class Process(): struct information: https://doxygen.reactos.org/d8/dae/modules_2rostests_2winetests_2ntdll_2time_8c_source.html ''' - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: KI_USER_SHARED_DATA = 0xFFDF0000 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: KI_USER_SHARED_DATA = 0xFFFFF78000000000 self.ql.log.info("KI_USER_SHARED_DATA is 0x%x" %KI_USER_SHARED_DATA) @@ -435,17 +435,13 @@ class Process(): class QlLoaderPE(QlLoader, Process): - def __init__(self, ql: Qiling): + def __init__(self, ql: Qiling, libcache: bool): super().__init__(ql) self.ql = ql self.path = self.ql.path self.is_driver = False - - if ql.libcache is True: - self.libcache = QlPeCache() - else: - self.libcache = ql.libcache or None + self.libcache = QlPeCache() if libcache else None def run(self): self.init_dlls = [b"ntdll.dll", b"kernel32.dll", b"user32.dll"] @@ -460,7 +456,7 @@ class QlLoaderPE(QlLoader, Process): self.init_dlls.append(b"ntoskrnl.exe") self.sys_dlls.append(b"ntoskrnl.exe") - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.stack_address = int(self.ql.os.profile.get("OS32", "stack_address"), 16) self.stack_size = int(self.ql.os.profile.get("OS32", "stack_size"), 16) self.image_address = int(self.ql.os.profile.get("OS32", "image_address"), 16) @@ -469,7 +465,7 @@ class QlLoaderPE(QlLoader, Process): self.ql.os.heap_base_address = int(self.ql.os.profile.get("OS32", "heap_address"), 16) self.ql.os.heap_base_size = int(self.ql.os.profile.get("OS32", "heap_size"), 16) self.structure_last_addr = FS_SEGMENT_ADDR - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.stack_address = int(self.ql.os.profile.get("OS64", "stack_address"), 16) self.stack_size = int(self.ql.os.profile.get("OS64", "stack_size"), 16) self.image_address = int(self.ql.os.profile.get("OS64", "image_address"), 16) @@ -535,9 +531,9 @@ class QlLoaderPE(QlLoader, Process): # Stack should not init at the very bottom. Will cause errors with Dlls sp = self.stack_address + self.stack_size - 0x1000 - if self.ql.archtype == QL_ARCH.X86: - self.ql.reg.esp = sp - self.ql.reg.ebp = sp + if self.ql.arch.type == QL_ARCH.X86: + self.ql.arch.regs.esp = sp + self.ql.arch.regs.ebp = sp if self.pe.is_dll(): self.ql.log.debug('Setting up DllMain args') @@ -549,18 +545,18 @@ class QlLoaderPE(QlLoader, Process): self.ql.log.debug('Writing 0x01 (DLL_PROCESS_ATTACH) to [ESP+8](0x%08X)' % (sp + 0x8)) self.ql.mem.write(sp + 0x8, int(1).to_bytes(length=4, byteorder='little')) - elif self.ql.archtype == QL_ARCH.X8664: - self.ql.reg.rsp = sp - self.ql.reg.rbp = sp + elif self.ql.arch.type == QL_ARCH.X8664: + self.ql.arch.regs.rsp = sp + self.ql.arch.regs.rbp = sp if self.pe.is_dll(): self.ql.log.debug('Setting up DllMain args') self.ql.log.debug('Setting RCX (arg1) to %16X (IMAGE_BASE)' % (self.pe_image_address)) - self.ql.reg.rcx = self.pe_image_address + self.ql.arch.regs.rcx = self.pe_image_address self.ql.log.debug('Setting RDX (arg2) to 1 (DLL_PROCESS_ATTACH)') - self.ql.reg.rdx = 1 + self.ql.arch.regs.rdx = 1 else: raise QlErrorArch("Unknown ql.arch") @@ -574,24 +570,24 @@ class QlLoaderPE(QlLoader, Process): super().init_ki_user_shared_data() # setup IRQ Level in CR8 to PASSIVE_LEVEL (0) - self.ql.reg.write(UC_X86_REG_CR8, 0) + self.ql.arch.regs.write(UC_X86_REG_CR8, 0) # setup CR4, some drivers may check this at initialized time - self.ql.reg.write(UC_X86_REG_CR4, 0x6f8) + self.ql.arch.regs.write(UC_X86_REG_CR4, 0x6f8) self.ql.log.debug('Setting up DriverEntry args') self.ql.stop_execution_pattern = 0xDEADC0DE - if self.ql.archtype == QL_ARCH.X86: # Win32 - if not self.ql.stop_options.any: + if self.ql.arch.type == QL_ARCH.X86: # Win32 + if not self.ql.stop_options: # We know that a driver will return, # so if the user did not configure stop options, write a sentinel return value self.ql.mem.write(sp, self.ql.stop_execution_pattern.to_bytes(length=4, byteorder='little')) self.ql.log.debug('Writing 0x%08X (PDRIVER_OBJECT) to [ESP+4](0x%08X)' % (self.ql.loader.driver_object_address, sp+0x4)) self.ql.log.debug('Writing 0x%08X (RegistryPath) to [ESP+8](0x%08X)' % (self.ql.loader.regitry_path_address, sp+0x8)) - elif self.ql.archtype == QL_ARCH.X8664: # Win64 - if not self.ql.stop_options.any: + elif self.ql.arch.type == QL_ARCH.X8664: # Win64 + if not self.ql.stop_options: # We know that a driver will return, # so if the user did not configure stop options, write a sentinel return value self.ql.mem.write(sp, self.ql.stop_execution_pattern.to_bytes(length=8, byteorder='little')) @@ -613,9 +609,9 @@ class QlLoaderPE(QlLoader, Process): # setup IMAGE_LOAD_CONFIG_DIRECTORY if self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']].VirtualAddress != 0: SecurityCookie_rva = self.pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SecurityCookie - self.pe.OPTIONAL_HEADER.ImageBase - SecurityCookie_value = default_security_cookie_value = self.ql.mem.read(self.pe_image_address+SecurityCookie_rva, self.ql.pointersize) + SecurityCookie_value = default_security_cookie_value = self.ql.mem.read(self.pe_image_address+SecurityCookie_rva, self.ql.arch.pointersize) while SecurityCookie_value == default_security_cookie_value: - SecurityCookie_value = secrets.token_bytes(self.ql.pointersize) + SecurityCookie_value = secrets.token_bytes(self.ql.arch.pointersize) # rol rcx, 10h (rcx: cookie) # test cx, 0FFFFh SecurityCookie_value_array = bytearray(SecurityCookie_value) @@ -653,7 +649,7 @@ class QlLoaderPE(QlLoader, Process): else: addr = self.import_address_table[dll_name][imp.ordinal] - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: address = self.ql.pack32(addr) else: address = self.ql.pack64(addr) @@ -665,12 +661,12 @@ class QlLoaderPE(QlLoader, Process): elif self.ql.code: self.filepath = b"" - if self.ql.archtype == QL_ARCH.X86: - self.ql.reg.esp = self.stack_address + 0x3000 - self.ql.reg.ebp = self.ql.reg.esp - elif self.ql.archtype == QL_ARCH.X8664: - self.ql.reg.rsp = self.stack_address + 0x3000 - self.ql.reg.rbp = self.ql.reg.rsp + if self.ql.arch.type == QL_ARCH.X86: + self.ql.arch.regs.esp = self.stack_address + 0x3000 + self.ql.arch.regs.ebp = self.ql.arch.regs.esp + elif self.ql.arch.type == QL_ARCH.X8664: + self.ql.arch.regs.rsp = self.stack_address + 0x3000 + self.ql.arch.regs.rbp = self.ql.arch.regs.rsp # load shellcode in self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode_base]") @@ -687,4 +683,4 @@ class QlLoaderPE(QlLoader, Process): # move entry_point to ql.os self.ql.os.entry_point = self.entry_point - self.init_sp = self.ql.reg.arch_sp + self.init_sp = self.ql.arch.regs.arch_sp diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 7bcaec50..9414ebf6 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -192,8 +192,8 @@ class QlLoaderPE_UEFI(QlLoader): self.ql.os.heap = context.heap # set stack and frame pointers - self.ql.reg.rsp = context.top_of_stack - self.ql.reg.rbp = context.top_of_stack + self.ql.arch.regs.rsp = context.top_of_stack + self.ql.arch.regs.rbp = context.top_of_stack self.ql.os.fcall.call_native(entry_point, ( (POINTER, ImageHandle), @@ -321,11 +321,11 @@ class QlLoaderPE_UEFI(QlLoader): ql = self.ql # intel architecture uefi implementation only - if ql.archtype not in (QL_ARCH.X86, QL_ARCH.X8664): + if ql.arch.type not in (QL_ARCH.X86, QL_ARCH.X8664): raise QlErrorArch("Unsupported architecture") # x86-64 arch only - if ql.archtype != QL_ARCH.X8664: + if ql.arch.type != QL_ARCH.X8664: raise QlErrorArch("Only 64-bit modules are supported at the moment") self.loaded_image_protocol_guid = ql.os.profile["LOADED_IMAGE_PROTOCOL"]["Guid"] diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index 22562a34..09be61e9 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -28,7 +28,7 @@ class QlOsBlob(QlOs): QL_ARCH.ARM : arm.aarch32, QL_ARCH.ARM64 : arm.aarch64, QL_ARCH.MIPS : mips.mipso32 - }[ql.archtype](ql) + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/os/dos/dos.py b/qiling/os/dos/dos.py index 170768ef..40cf5da1 100644 --- a/qiling/os/dos/dos.py +++ b/qiling/os/dos/dos.py @@ -56,10 +56,10 @@ class QlOsDos(QlOs): curses.endwin() def set_flag_value(self, fl: Flags, val: int) -> None: - self.ql.reg.ef = self.ql.reg.ef & (~fl) | (fl * val) + self.ql.arch.regs.ef = self.ql.arch.regs.ef & (~fl) | (fl * val) def test_flags(self, fl): - return self.ql.reg.ef & fl == fl + return self.ql.arch.regs.ef & fl == fl def set_cf(self): self.set_flag_value(Flags.CF, 0b1) @@ -76,7 +76,7 @@ class QlOsDos(QlOs): def hook_syscall(self): def cb(ql: Qiling, intno: int): - ah = ql.reg.ah + ah = ql.arch.regs.ah intinfo = (intno, ah) func = self.user_defined_api[QL_INTERCEPT.CALL].get(intinfo) or handlers.get(intno) diff --git a/qiling/os/dos/interrupts/int10.py b/qiling/os/dos/interrupts/int10.py index 837ddad6..9b887f0b 100644 --- a/qiling/os/dos/interrupts/int10.py +++ b/qiling/os/dos/interrupts/int10.py @@ -63,7 +63,7 @@ def __leaf_00(ql: Qiling): except: pass - al = ql.reg.al + al = ql.arch.regs.al resolution = { 0x00 : (25, 40), @@ -128,31 +128,31 @@ def __leaf_00(ql: Qiling): def __leaf_01(ql: Qiling): # limited support - ch = ql.reg.ch + ch = ql.arch.regs.ch if (ch & 0x20): curses.curs_set(0) def __leaf_02(ql: Qiling): # page number ignored - dh = ql.reg.dh # row - dl = ql.reg.dl # column + dh = ql.arch.regs.dh # row + dl = ql.arch.regs.dl # column ql.os.stdscr.move(dh, dl) def __leaf_05(ql: Qiling): # No idea how to implement, do nothing here. - ql.reg.al = 0 + ql.arch.regs.al = 0 def __leaf_06(ql: Qiling): stdscr = ql.os.stdscr - al = ql.reg.al # lines to scroll - ch = ql.reg.ch # row of upper-left cornner - cl = ql.reg.cl # column of upper-left corner - dh = ql.reg.dh # row of lower right corner - dl = ql.reg.dl # column of lower righ corner - bh = ql.reg.bh # color + al = ql.arch.regs.al # lines to scroll + ch = ql.arch.regs.ch # row of upper-left cornner + cl = ql.arch.regs.cl # column of upper-left corner + dh = ql.arch.regs.dh # row of lower right corner + dl = ql.arch.regs.dl # column of lower righ corner + bh = ql.arch.regs.bh # color y, x = stdscr.getmaxyx() cy, cx = stdscr.getyx() @@ -193,13 +193,13 @@ def __leaf_08(ql: Qiling): stdscr = ql.os.stdscr if stdscr is None: - ql.reg.ax = 0x0720 + ql.arch.regs.ax = 0x0720 else: cy, cx = stdscr.getyx() inch = stdscr.inch(cy, cx) attr = inch & curses.A_COLOR ch = inch & 0xFF - ql.reg.al = ch + ql.arch.regs.al = ch pair_number = curses.pair_number(attr) fg, bg = curses.pair_content(pair_number) @@ -209,10 +209,10 @@ def __leaf_08(ql: Qiling): if attr & curses.A_BLINK: orig_bg |= 0b1000 - ql.reg.ah = ((orig_bg << 4) & orig_fg) + ql.arch.regs.ah = ((orig_bg << 4) & orig_fg) def __leaf_0e(ql: Qiling): - al = ql.reg.al + al = ql.arch.regs.al ql.log.debug(f'echo: {al:02x} -> {curses.ascii.unctrl(al)}') @@ -247,7 +247,7 @@ def __leaf_0e(ql: Qiling): # https://stanislavs.org/helppc/idx_interrupt.html # implemented by curses def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int13.py b/qiling/os/dos/interrupts/int13.py index dc99e6eb..1714111a 100644 --- a/qiling/os/dos/interrupts/int13.py +++ b/qiling/os/dos/interrupts/int13.py @@ -45,43 +45,43 @@ def __leaf_00(ql: Qiling): ql.os.clear_cf() def __leaf_02(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.warning(f'Warning: No such disk: {idx:#x}') - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return - cylinder = ((ql.reg.cx & 0xff00) >> 8) | ((ql.reg.cx & 0xC0) << 2) - head = ql.reg.dh - sector = ql.reg.cx & 63 - cnt = ql.reg.al + cylinder = ((ql.arch.regs.cx & 0xff00) >> 8) | ((ql.arch.regs.cx & 0xC0) << 2) + head = ql.arch.regs.dh + sector = ql.arch.regs.cx & 63 + cnt = ql.arch.regs.al disk = ql.os.fs_mapper.open(idx, None) content = disk.read_chs(cylinder, head, sector, cnt) - ql.mem.write(utils.linaddr(ql.reg.es, ql.reg.bx), content) + ql.mem.write(utils.linaddr(ql.arch.regs.es, ql.arch.regs.bx), content) ql.os.clear_cf() - ql.reg.ah = 0 - ql.reg.al = sector + ql.arch.regs.ah = 0 + ql.arch.regs.al = sector # @see: https://stanislavs.org/helppc/int_13-8.html def __leaf_08(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.warning(f'Warning: No such disk: {idx:#x}') - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return disk = ql.os.fs_mapper.open(idx, None) - ql.reg.dl = ql.os.fs_mapper.mapping_count() - ql.reg.dh = disk.n_heads - 1 - ql.reg.bl = 0x4 - ql.reg.di = 0 - ql.reg.ds = 0 + ql.arch.regs.dl = ql.os.fs_mapper.mapping_count() + ql.arch.regs.dh = disk.n_heads - 1 + ql.arch.regs.bl = 0x4 + ql.arch.regs.di = 0 + ql.arch.regs.ds = 0 n_sectors = min(disk.n_sectors, 63) n_cylinders = min(disk.n_cylinders, 1023) @@ -90,28 +90,28 @@ def __leaf_08(ql: Qiling): cx |= ((n_cylinders & 0b11) << 6) cx |= (((n_cylinders & 0b1111111100) >> 2) << 8) - ql.reg.cx = cx - ql.reg.ah = 0 + ql.arch.regs.cx = cx + ql.arch.regs.ah = 0 ql.os.clear_cf() def __leaf_41(ql: Qiling): - ql.reg.ah = 0 + ql.arch.regs.ah = 0 # 1 -> Device Access using the packet structure. # 2 -> Drive locking and ejecting. # 4 -> Enhanced Disk Drive Support. - ql.reg.bx = 0xaa55 - ql.reg.cx = 7 + ql.arch.regs.bx = 0xaa55 + ql.arch.regs.cx = 7 def __leaf_42(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.warning(f'Warning: No such disk: {idx:#x}') - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return - dapbs = ql.mem.read(utils.linaddr(ql.reg.ds, ql.reg.si), 16) + dapbs = ql.mem.read(utils.linaddr(ql.arch.regs.ds, ql.arch.regs.si), 16) _, _, cnt, offset, segment, lba = parse_dap(dapbs) ql.log.info(f'Reading {cnt} sectors from disk {idx:#x} with LBA {lba}') @@ -120,18 +120,18 @@ def __leaf_42(ql: Qiling): ql.mem.write(utils.linaddr(segment, offset), content) ql.os.clear_cf() - ql.reg.ah = 0 + ql.arch.regs.ah = 0 def __leaf_43(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.info(f"Warning: No such disk: {hex(idx)}") - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return - dapbs = ql.mem.read(utils.linaddr(ql.reg.ds, ql.reg.si), 16) + dapbs = ql.mem.read(utils.linaddr(ql.arch.regs.ds, ql.arch.regs.si), 16) _, _, cnt, offset, segment, lba = parse_dap(dapbs) ql.log.info(f'Writing {cnt} sectors to disk {idx:#x} with LBA {lba}') @@ -140,11 +140,11 @@ def __leaf_43(ql: Qiling): disk.write_sectors(lba, cnt, buffer) ql.os.clear_cf() - ql.reg.ah = 0 + ql.arch.regs.ah = 0 # @see: https://en.wikipedia.org/wiki/INT_13H def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int15.py b/qiling/os/dos/interrupts/int15.py index e237c5f7..87110d28 100644 --- a/qiling/os/dos/interrupts/int15.py +++ b/qiling/os/dos/interrupts/int15.py @@ -16,23 +16,23 @@ def __leaf_01(ql: Qiling): pass def __leaf_53(ql: Qiling): - al = ql.reg.al + al = ql.arch.regs.al if al == 0x01: ql.os.clear_cf() elif al == 0x0e: - ql.reg.ax = 0x0102 + ql.arch.regs.ax = 0x0102 ql.os.clear_cf() elif al == 0x07: - if (ql.reg.bx == 1) and (ql.reg.cx == 3): + if (ql.arch.regs.bx == 1) and (ql.arch.regs.cx == 3): ql.log.info("Emulation Stop") ql.emu_stop() else: raise NotImplementedError() def __leaf_86(ql: Qiling): - dx = ql.reg.dx - cx = ql.reg.cx + dx = ql.arch.regs.dx + cx = ql.arch.regs.cx full_secs = ((cx << 16) + dx) / 1000000 ql.log.info(f"Goint to sleep for {full_secs} seconds") @@ -41,10 +41,10 @@ def __leaf_86(ql: Qiling): # Note: Since we are in a single thread environment, we assume # that no one will wait at the same time. ql.os.clear_cf() - ql.reg.ah = 0x80 + ql.arch.regs.ah = 0x80 def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int16.py b/qiling/os/dos/interrupts/int16.py index 84278412..215d20c2 100644 --- a/qiling/os/dos/interrupts/int16.py +++ b/qiling/os/dos/interrupts/int16.py @@ -136,10 +136,10 @@ def __leaf_00(ql: Qiling): key = parse_key(ql.os.stdscr.getch()) ql.log.debug(f"Get key: {hex(key)}") if curses.ascii.isascii(key): - ql.reg.al = key + ql.arch.regs.al = key else: - ql.reg.al = 0 - ql.reg.ah = get_scan_code(key) + ql.arch.regs.al = 0 + ql.arch.regs.ah = get_scan_code(key) curses.nl() def __leaf_01(ql: Qiling): @@ -150,11 +150,11 @@ def __leaf_01(ql: Qiling): if key == -1: ql.os.set_zf() - ql.reg.ax = 0 + ql.arch.regs.ax = 0 else: ql.log.debug(f"Has key: {hex(key)} ({curses.ascii.unctrl(key)})") - ql.reg.al = key - ql.reg.ah = get_scan_code(key) + ql.arch.regs.al = key + ql.arch.regs.ah = get_scan_code(key) ql.os.clear_zf() # Buffer shouldn't be removed in this interrupt. curses.ungetch(key) @@ -163,7 +163,7 @@ def __leaf_01(ql: Qiling): curses.nl() def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int19.py b/qiling/os/dos/interrupts/int19.py index fe31a3fc..4513e020 100644 --- a/qiling/os/dos/interrupts/int19.py +++ b/qiling/os/dos/interrupts/int19.py @@ -7,7 +7,7 @@ from qiling import Qiling def handler(ql: Qiling): # Note: Memory is not cleaned. - dl = ql.reg.dl + dl = ql.arch.regs.dl if ql.os.fs_mapper.has_mapping(dl): disk = ql.os.fs_mapper.open(dl, None) @@ -19,5 +19,5 @@ def handler(ql: Qiling): ql.mem.write(0x7C00, mbr) - ql.reg.cs = 0x07C0 - ql.reg.ip = 0x0000 + ql.arch.regs.cs = 0x07C0 + ql.arch.regs.ip = 0x0000 diff --git a/qiling/os/dos/interrupts/int1a.py b/qiling/os/dos/interrupts/int1a.py index ed301e03..4f7edc1f 100644 --- a/qiling/os/dos/interrupts/int1a.py +++ b/qiling/os/dos/interrupts/int1a.py @@ -13,13 +13,13 @@ def __set_elapsed_ticks(ql: Qiling): now = datetime.now() ticks = int((now - ql.os.start_time).total_seconds() * ql.os.ticks_per_second) - ql.reg.cx = (ticks >> 16) & 0xffff - ql.reg.dx = (ticks >> 0) & 0xffff + ql.arch.regs.cx = (ticks >> 16) & 0xffff + ql.arch.regs.dx = (ticks >> 0) & 0xffff def __leaf_00(ql: Qiling): __set_elapsed_ticks(ql) - ql.reg.al = 0 + ql.arch.regs.al = 0 def __leaf_01(ql: Qiling): __set_elapsed_ticks(ql) @@ -27,10 +27,10 @@ def __leaf_01(ql: Qiling): def __leaf_02_03(ql: Qiling): now = datetime.now() - ql.reg.ch = utils.BIN2BCD(now.hour) - ql.reg.cl = utils.BIN2BCD(now.minute) - ql.reg.dh = utils.BIN2BCD(now.second) - ql.reg.dl = 0 + ql.arch.regs.ch = utils.BIN2BCD(now.hour) + ql.arch.regs.cl = utils.BIN2BCD(now.minute) + ql.arch.regs.dh = utils.BIN2BCD(now.second) + ql.arch.regs.dl = 0 ql.os.clear_cf() @@ -38,10 +38,10 @@ def __leaf_04_05(ql: Qiling): now = datetime.now() # See https://sites.google.com/site/liangweiqiang/Home/e5006/e5006classnote/jumptiming/int1ahclockservice - ql.reg.ch = utils.BIN2BCD((now.year - 1) // 100) - ql.reg.cl = utils.BIN2BCD(now.year % 100) - ql.reg.dh = utils.BIN2BCD(now.month) - ql.reg.dl = utils.BIN2BCD(now.day) + ql.arch.regs.ch = utils.BIN2BCD((now.year - 1) // 100) + ql.arch.regs.cl = utils.BIN2BCD(now.year % 100) + ql.arch.regs.dh = utils.BIN2BCD(now.month) + ql.arch.regs.dl = utils.BIN2BCD(now.day) ql.os.clear_cf() @@ -55,13 +55,13 @@ def __leaf_08(ql: Qiling): def __leaf_0a(ql: Qiling): now = datetime.now() - ql.reg.cx = (now - datetime(1980, 1, 1)).days + ql.arch.regs.cx = (now - datetime(1980, 1, 1)).days def __leaf_0b(ql: Qiling): pass def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int20.py b/qiling/os/dos/interrupts/int20.py index 59480d51..14bc51fd 100644 --- a/qiling/os/dos/interrupts/int20.py +++ b/qiling/os/dos/interrupts/int20.py @@ -9,7 +9,7 @@ def __leaf_13(self): pass def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x13 : __leaf_13 diff --git a/qiling/os/dos/interrupts/int21.py b/qiling/os/dos/interrupts/int21.py index 153bcce2..a8709494 100644 --- a/qiling/os/dos/interrupts/int21.py +++ b/qiling/os/dos/interrupts/int21.py @@ -16,8 +16,8 @@ def __leaf_4c(ql: Qiling): # write a character to screen def __leaf_02(ql: Qiling): - ch = ql.reg.dl - ql.reg.al = ch + ch = ql.arch.regs.dl + ql.arch.regs.al = ch print(f'{ch:c}', end='') @@ -39,7 +39,7 @@ def __leaf_26(ql: Qiling): # get dos version def __leaf_30(ql: Qiling): - ql.reg.ax = ql.os.dos_ver + ql.arch.regs.ax = ql.os.dos_ver # get or set ctrl-break def __leaf_33(ql: Qiling): @@ -56,7 +56,7 @@ def __leaf_3c(ql: Qiling): fpath = ql.os.path.transform_to_real_path(fname) ql.os.handles[ql.os.handle_next] = open(fpath, "wb") - ql.reg.ax = ql.os.handle_next + ql.arch.regs.ax = ql.os.handle_next ql.os.handle_next += 1 ql.os.clear_cf() @@ -66,13 +66,13 @@ def __leaf_3d(ql: Qiling): fpath = ql.os.path.transform_to_real_path(fname) ql.os.handles[ql.os.handle_next] = open(fpath, "rb") - ql.reg.ax = ql.os.handle_next + ql.arch.regs.ax = ql.os.handle_next ql.os.handle_next += 1 ql.os.clear_cf() # close file def __leaf_3e(ql: Qiling): - hd = ql.reg.bx + hd = ql.arch.regs.bx if hd in ql.os.handles: f = ql.os.handles.pop(hd) @@ -80,39 +80,39 @@ def __leaf_3e(ql: Qiling): ql.os.clear_cf() else: - ql.reg.ax = 0x06 + ql.arch.regs.ax = 0x06 ql.os.set_cf() # read from file def __leaf_3f(ql: Qiling): - hd = ql.reg.bx + hd = ql.arch.regs.bx if hd in ql.os.handles: f = ql.os.handles[hd] - buffer = utils.linaddr(ql.reg.ds, ql.reg.dx) - sz = ql.reg.cx + buffer = utils.linaddr(ql.arch.regs.ds, ql.arch.regs.dx) + sz = ql.arch.regs.cx rd = f.read(sz) ql.mem.write(buffer, rd) ql.os.clear_cf() - ql.reg.ax = len(rd) + ql.arch.regs.ax = len(rd) else: - ql.reg.ax = 0x06 + ql.arch.regs.ax = 0x06 ql.os.set_cf() # write to file def __leaf_40(ql: Qiling): - hd = ql.reg.bx + hd = ql.arch.regs.bx if hd in ql.os.handles: f = ql.os.handles[hd] - buffer = utils.linaddr(ql.reg.ds, ql.reg.dx) - sz = ql.reg.cx + buffer = utils.linaddr(ql.arch.regs.ds, ql.arch.regs.dx) + sz = ql.arch.regs.cx rd = ql.mem.read(buffer, sz) f.write(bytes(rd)) ql.os.clear_cf() - ql.reg.ax = len(rd) + ql.arch.regs.ax = len(rd) else: - ql.reg.ax = 0x06 + ql.arch.regs.ax = 0x06 ql.os.set_cf() # delete file @@ -124,15 +124,15 @@ def __leaf_41(ql: Qiling): os.remove(fpath) ql.os.clear_cf() except OSError: - ql.reg.ax = 0x05 + ql.arch.regs.ax = 0x05 ql.os.set_cf() def __leaf_43(ql: Qiling): - ql.reg.cx = 0xffff + ql.arch.regs.cx = 0xffff ql.os.clear_cf() def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x02 : __leaf_02, diff --git a/qiling/os/dos/utils.py b/qiling/os/dos/utils.py index 0d51dd3f..fe4d4474 100644 --- a/qiling/os/dos/utils.py +++ b/qiling/os/dos/utils.py @@ -53,6 +53,6 @@ def read_dos_string(ql: Qiling, address: int): return ba.decode('ascii') def read_dos_string_from_ds_dx(ql: Qiling): - address = linaddr(ql.reg.ds, ql.reg.dx) + address = linaddr(ql.arch.regs.ds, ql.arch.regs.dx) return read_dos_string(ql, address) diff --git a/qiling/os/fcall.py b/qiling/os/fcall.py index f8aa1960..4484e8de 100644 --- a/qiling/os/fcall.py +++ b/qiling/os/fcall.py @@ -131,7 +131,7 @@ class QlFunctionCall: """ ql = self.ql - pc = ql.reg.arch_pc + pc = ql.arch.regs.arch_pc # if set, fire up the on-enter hook and let it override original args set if hook_onenter: @@ -187,11 +187,11 @@ class QlFunctionCall: nslots = self.__count_slots(atype for atype, _ in args) self.cc.reserve(nslots) - # set arguments values - self.writeParams(args) - if ret is not None: self.cc.setReturnAddress(ret) + # set arguments values + self.writeParams(args) + # call - self.ql.reg.arch_pc = addr + self.ql.arch.regs.arch_pc = addr diff --git a/qiling/os/freebsd/freebsd.py b/qiling/os/freebsd/freebsd.py index e865416d..800d31f0 100644 --- a/qiling/os/freebsd/freebsd.py +++ b/qiling/os/freebsd/freebsd.py @@ -4,9 +4,9 @@ # from unicorn import UcError +from unicorn.x86_const import UC_X86_INS_SYSCALL -from qiling.arch.x86 import GDTManager, ql_x86_register_cs, ql_x86_register_ds_ss_es -from qiling.arch.x86_const import UC_X86_INS_SYSCALL +from qiling.arch.x86_utils import GDTManager, SegmentManager86 from qiling.os.posix.posix import QlOsPosix class QlOsFreebsd(QlOsPosix): @@ -18,10 +18,13 @@ class QlOsFreebsd(QlOsPosix): def load(self): + gdtm = GDTManager(self.ql) + + # setup gdt and segments selectors + segm = SegmentManager86(self.ql.arch, gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) - self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) def hook_syscall(self, intno= None): diff --git a/qiling/os/freebsd/map_syscall.py b/qiling/os/freebsd/map_syscall.py index d9b67f6d..b09e7eee 100644 --- a/qiling/os/freebsd/map_syscall.py +++ b/qiling/os/freebsd/map_syscall.py @@ -7,7 +7,7 @@ from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF def map_syscall(ql, syscall_num): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' x8664_syscall_table = { diff --git a/qiling/os/freebsd/syscall.py b/qiling/os/freebsd/syscall.py index 46d9624f..77691936 100644 --- a/qiling/os/freebsd/syscall.py +++ b/qiling/os/freebsd/syscall.py @@ -40,19 +40,14 @@ def ql_syscall_sysarch(ql, op, parms, *args, **kw): wild guess, of cause not working """ - regreturn = 0 - ql.GS_SEGMENT_ADDR = 0x6000 - ql.GS_SEGMENT_SIZE = 0x8000 - - - #ql.mem.map(ql.GS_SEGMENT_ADDR, ql.GS_SEGMENT_SIZE) - #ql.reg.msr(GSMSR, ql.GS_SEGMENT_ADDR) - ql.reg.msr(FSMSR, parms) + #ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) + #ql.arch.msr.write(IA32_GS_BASE_MSR, GS_SEGMENT_ADDR) + ql.arch.msr.write(IA32_FS_BASE_MSR, parms) #op_buf = ql.pack32(op) #ql.mem.write(parms, op_buf) - return regreturn + return 0 def ql_syscall_sigprocmask(ql, how, mask, omask, *args, **kw): ql.log.debug("sigprocmask(how: 0x%x, mask: 0x%x, omask: 0x%x)" % (how, mask, omask)) diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index 608a02e2..7b07c2e5 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -68,78 +68,78 @@ class HookFunc: def get_ret_pc(self): # ARM - if self.ql.archtype== QL_ARCH.ARM: - return self.ql.reg.lr + if self.ql.arch.type == QL_ARCH.ARM: + return self.ql.arch.regs.lr # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: - return self.ql.reg.ra + elif self.ql.arch.type == QL_ARCH.MIPS: + return self.ql.arch.regs.ra # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: - return self.ql.reg.x30 + elif self.ql.arch.type == QL_ARCH.ARM64: + return self.ql.arch.regs.x30 # X86 - elif self.ql.archtype== QL_ARCH.X86: - return self.ql.unpack(self.ql.mem.read(self.ql.reg.esp, self.ql.pointersize)) + elif self.ql.arch.type == QL_ARCH.X86: + return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.esp, self.ql.arch.pointersize)) # X8664 - elif self.ql.archtype== QL_ARCH.X8664: - return self.ql.unpack(self.ql.mem.read(self.ql.reg.rsp, self.ql.pointersize)) + elif self.ql.arch.type == QL_ARCH.X8664: + return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.rsp, self.ql.arch.pointersize)) else: raise def context_fixup(self): # ARM - if self.ql.archtype== QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: pass # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: pass # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: pass # X86 - elif self.ql.archtype== QL_ARCH.X86: - self.ql.reg.esp = self.ql.reg.esp + self.ql.pointersize + elif self.ql.arch.type == QL_ARCH.X86: + self.ql.arch.regs.esp = self.ql.arch.regs.esp + self.ql.arch.pointersize # X8664 - elif self.ql.archtype== QL_ARCH.X8664: - self.ql.reg.rsp = self.ql.reg.rsp + self.ql.pointersize + elif self.ql.arch.type == QL_ARCH.X8664: + self.ql.arch.regs.rsp = self.ql.arch.regs.rsp + self.ql.arch.pointersize else: raise def set_ret(self, addr): # ARM - if self.ql.archtype== QL_ARCH.ARM: - self.ql.reg.lr = addr + if self.ql.arch.type == QL_ARCH.ARM: + self.ql.arch.regs.lr = addr # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: - self.ql.reg.ra = addr + elif self.ql.arch.type == QL_ARCH.MIPS: + self.ql.arch.regs.ra = addr # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: - self.ql.mem.write(self.ql.reg.sp, self.ql.pack(addr)) + elif self.ql.arch.type == QL_ARCH.ARM64: + self.ql.arch.stack_write(0, addr) # X86 - elif self.ql.archtype== QL_ARCH.X86: - self.ql.mem.write(self.ql.reg.esp, self.ql.pack(addr)) + elif self.ql.arch.type == QL_ARCH.X86: + self.ql.arch.stack_write(0, addr) # X8664 - elif self.ql.archtype== QL_ARCH.X8664: - self.ql.mem.write(self.ql.reg.rsp, self.ql.pack(addr)) + elif self.ql.arch.type == QL_ARCH.X8664: + self.ql.arch.stack_write(0, addr) else: raise def call_enter(self): - # if self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: - # self.ql.reg.arch_pc = self.ql.reg.arch_pc + 4 + # if self.ql.arch.type == QL_ARCH.ARM or self.ql.arch.type == QL_ARCH.ARM64: + # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 - next_pc = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.pointersize)) + next_pc = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.arch.pointersize)) self.ret_pc = self.get_ret_pc() onenter_cb = None onenter_userdata = None @@ -168,37 +168,37 @@ class HookFunc: cb(self.ql, userdata) else: self.set_ret(self.exit_addr) - self.ql.reg.arch_pc = next_pc + self.ql.arch.regs.arch_pc = next_pc else: self.context_fixup() def ret(self): # ARM - if self.ql.archtype== QL_ARCH.ARM: - self.ql.reg.arch_pc = self.ret_pc + if self.ql.arch.type == QL_ARCH.ARM: + self.ql.arch.regs.arch_pc = self.ret_pc # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: - self.ql.reg.arch_pc = self.ret_pc + elif self.ql.arch.type == QL_ARCH.MIPS: + self.ql.arch.regs.arch_pc = self.ret_pc # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: - self.ql.reg.arch_pc = self.ret_pc + elif self.ql.arch.type == QL_ARCH.ARM64: + self.ql.arch.regs.arch_pc = self.ret_pc # X86 - elif self.ql.archtype== QL_ARCH.X86: - self.ql.reg.arch_pc = self.ret_pc + elif self.ql.arch.type == QL_ARCH.X86: + self.ql.arch.regs.arch_pc = self.ret_pc # X8664 - elif self.ql.archtype== QL_ARCH.X8664: - self.ql.reg.arch_pc = self.ret_pc + elif self.ql.arch.type == QL_ARCH.X8664: + self.ql.arch.regs.arch_pc = self.ret_pc else: raise def call_exit(self): - # if self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: - # self.ql.reg.arch_pc = self.ql.reg.arch_pc + 4 + # if self.ql.arch.type == QL_ARCH.ARM or self.ql.arch.type == QL_ARCH.ARM64: + # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 onexit_cb = None onexit_userdata = None @@ -241,10 +241,10 @@ class HookFuncRel(HookFunc): self.ori_offest = self.rel.r_offset self.rel.r_offset = self.hook_data_ptr - self.load_base - self.ori_data = self.ql.mem.read(self.ori_offest + self.load_base, self.ql.pointersize) + self.ori_data = self.ql.mem.read(self.ori_offest + self.load_base, self.ql.arch.pointersize) self.ql.mem.write(self.rel.ptr, self.rel.pack()) - self.ql.mem.write(self.ori_offest + self.load_base, self.ql.pack(self.hook_fuc_ptr)) + self.ql.mem.write_ptr(self.ori_offest + self.load_base, self.hook_fuc_ptr) self.ql.mem.write(self.hook_data_ptr, bytes(self.ori_data)) class HookFuncMips(HookFunc): @@ -255,21 +255,21 @@ class HookFuncMips(HookFunc): self.gotidx = gotidx def _hook_fuc_enter(self, ql): - self.ql.reg.t9 = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.pointersize)) + self.ql.arch.regs.t9 = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.arch.pointersize)) self.call_enter() def _hook_fuc_exit(self, ql): self.call_exit() - tmp = self.ql.unpack(self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pointersize)) + tmp = self.ql.unpack(self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.arch.pointersize)) if tmp != self.hook_fuc_ptr: - self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pack(self.hook_fuc_ptr)) - self.ql.mem.write(self.hook_data_ptr, self.ql.pack(tmp)) + self.ql.mem.write_ptr(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.hook_fuc_ptr) + self.ql.mem.write_ptr(self.hook_data_ptr, tmp) def _hook_got(self): - self.ori_data = self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pointersize) + self.ori_data = self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.arch.pointersize) - self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pack(self.hook_fuc_ptr)) + self.ql.mem.write_ptr(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.hook_fuc_ptr) self.ql.mem.write(self.hook_data_ptr, bytes(self.ori_data)) def enable(self): @@ -491,7 +491,7 @@ class ELF_Symtab: self.symtab = symtab self.endian = endian - self.symclass = ELF32_Sym if self.ql.archbit == 32 else ELF64_Sym + self.symclass = ELF32_Sym if self.ql.arch.bits == 32 else ELF64_Sym def __getitem__(self, idx): buf = self.ql.mem.read(self.symtab + idx * self.symclass.Sym_SIZE, self.symclass.Sym_SIZE) @@ -536,19 +536,19 @@ class FunctionHook: self.strtab_size = None self.symtab = None - self.syment = ELF32_Sym.Sym_SIZE if ql.archbit == 32 else ELF64_Sym.Sym_SIZE + self.syment = ELF32_Sym.Sym_SIZE if ql.arch.bits == 32 else ELF64_Sym.Sym_SIZE self.plt_rel_size = None self.plt_rel = None - self.plt_rel_type = DT_REL if ql.archbit == 32 else DT_RELA + self.plt_rel_type = DT_REL if ql.arch.bits == 32 else DT_RELA self.rela = None self.rela_size = None - self.relaent = ELF32_Rela.Rela_SIZE if ql.archbit == 32 else ELF64_Rela.Rela_SIZE + self.relaent = ELF32_Rela.Rela_SIZE if ql.arch.bits == 32 else ELF64_Rela.Rela_SIZE self.rel = None self.rel_size = None - self.relent = ELF32_Rel.Rel_SIZE if ql.archbit == 32 else ELF64_Rel.Rel_SIZE + self.relent = ELF32_Rel.Rel_SIZE if ql.arch.bits == 32 else ELF64_Rel.Rel_SIZE self.plt_got = None self.mips_local_gotno = None @@ -556,10 +556,10 @@ class FunctionHook: self.mips_gotsym = None self.rel_list = [] - self.endian = 0 if ql.archendian == QL_ENDIAN.EL else 1 + self.endian = 0 if ql.arch.endian == QL_ENDIAN.EL else 1 # ARM - if self.ql.archtype in [QL_ARCH.ARM, QL_ARCH.ARM_THUMB]: + if self.ql.arch.type == QL_ARCH.ARM: self.GLOB_DAT = 21 self.JMP_SLOT = 22 # orr r1, r1, r1 @@ -567,7 +567,7 @@ class FunctionHook: self.add_function_hook = self.add_function_hook_relocation # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: self.GLOB_DAT = 21 self.JMP_SLOT = 22 # add $t9, $t9, $zero @@ -575,7 +575,7 @@ class FunctionHook: self.add_function_hook = self.add_function_hook_mips # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: self.GLOB_DAT = 1025 self.JMP_SLOT = 1026 # orr x1,x1,x1 @@ -583,7 +583,7 @@ class FunctionHook: self.add_function_hook = self.add_function_hook_relocation # X86 - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: self.GLOB_DAT = 6 self.JMP_SLOT = 7 # nop @@ -591,14 +591,14 @@ class FunctionHook: self.add_function_hook = self.add_function_hook_relocation # X8664 - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.GLOB_DAT = 6 self.JMP_SLOT = 7 # nop ins = b'\x90' self.add_function_hook = self.add_function_hook_relocation - elif self.ql.archtype== QL_ARCH.RISCV: + elif self.ql.arch.type == QL_ARCH.RISCV: self.GLOB_DAT = 21 self.JMP_SLOT = 22 @@ -606,7 +606,7 @@ class FunctionHook: ins = b'\x00\x01' self.add_function_hook = self.add_function_hook_relocation - elif self.ql.archtype== QL_ARCH.RISCV64: + elif self.ql.arch.type == QL_ARCH.RISCV64: self.GLOB_DAT = 21 self.JMP_SLOT = 22 @@ -626,7 +626,7 @@ class FunctionHook: self.rel_list += self.plt_rel self.show_relocation(self.plt_rel) - if self.ql.archtype == QL_ARCH.MIPS and self.plt_got != None and self.mips_gotsym != None and self.mips_local_gotno != None and self.mips_symtabno != None: + if self.ql.arch.type == QL_ARCH.MIPS and self.plt_got != None and self.mips_gotsym != None and self.mips_local_gotno != None and self.mips_symtabno != None: self.show_dynsym_name(self.mips_gotsym, self.mips_symtabno) self.ql.mem.map(hook_mem, 0x2000, perms=7, info="[hook_mem]") @@ -700,9 +700,9 @@ class FunctionHook: return def parse_program_header(self): - if self.ql.archbit == 64: + if self.ql.arch.bits == 64: return self.parse_program_header64() - elif self.ql.archbit == 32: + elif self.ql.arch.bits == 32: return self.parse_program_header32() def parse_dynamic64(self): @@ -768,9 +768,9 @@ class FunctionHook: return def parse_dynamic(self): - if self.ql.archbit == 64: + if self.ql.arch.bits == 64: return self.parse_dynamic64() - elif self.ql.archbit == 32: + elif self.ql.arch.bits == 32: return self.parse_dynamic32() def _parse(self): @@ -786,19 +786,19 @@ class FunctionHook: if d.d_tag == DT_NULL: break elif d.d_tag == DT_HASH: - # self.hash_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.pointersize)) - # self.hash_nchain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize, self.ql.pointersize)) - # self.hash_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 2, self.ql.pointersize * self.hash_nbucket) - # self.hash_chain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 2 + self.ql.pointersize * self.hash_nbucket, self.ql.pointersize)) + # self.hash_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.arch.pointersize)) + # self.hash_nchain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize, self.ql.arch.pointersize)) + # self.hash_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 2, self.ql.arch.pointersize * self.hash_nbucket) + # self.hash_chain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 2 + self.ql.arch.pointersize * self.hash_nbucket, self.ql.arch.pointersize)) pass elif d.d_tag == DT_GNU_HASH: - # self.gnu_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.pointersize)) - # self.gnu_symbias = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize, self.ql.pointersize)))) - # self.gnu_maskwords = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 2, self.ql.pointersize)) - # self.gnu_shift2 = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 3, self.ql.pointersize)) - # self.gnu_bloom_filter = self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 4, self.gnu_maskwords) - # self.gnu_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 4 + self.gnu_maskwords, self.ql.pointersize * self.gnu_nbucket) - # self.gnu_chain = self.load_base + d.d_un + self.ql.pointersize * 4 + self.gnu_maskwords + self.ql.pointersize * self.gnu_nbucket - self.ql.pointersize * self.gnu_symbias + # self.gnu_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.arch.pointersize)) + # self.gnu_symbias = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize, self.ql.arch.pointersize)))) + # self.gnu_maskwords = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 2, self.ql.arch.pointersize)) + # self.gnu_shift2 = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 3, self.ql.arch.pointersize)) + # self.gnu_bloom_filter = self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 4, self.gnu_maskwords) + # self.gnu_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 4 + self.gnu_maskwords, self.ql.arch.pointersize * self.gnu_nbucket) + # self.gnu_chain = self.load_base + d.d_un + self.ql.arch.pointersize * 4 + self.gnu_maskwords + self.ql.arch.pointersize * self.gnu_nbucket - self.ql.arch.pointersize * self.gnu_symbias pass elif d.d_tag == DT_STRTAB: @@ -859,31 +859,31 @@ class FunctionHook: if self.rela != None and self.rela_size != None: rela_buf = self.ql.mem.read(self.rela, self.rela_size) rela_ptr = self.rela - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.rela = [ELF32_Rela(rela_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, rela_ptr + _ * self.relaent) for _ in range(self.rela_size // self.relaent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.rela = [ELF64_Rela(rela_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, rela_ptr + _ * self.relaent) for _ in range(self.rela_size // self.relaent)] if self.rel != None and self.rel_size != None: rel_buf = self.ql.mem.read(self.rel, self.rel_size) rel_ptr = self.rel - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.rel = [ELF32_Rel(rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, rel_ptr + _ * self.relent) for _ in range(self.rel_size // self.relent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.rel = [ELF64_Rel(rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, rel_ptr + _ * self.relent) for _ in range(self.rel_size // self.relent)] if self.plt_rel != None and self.plt_rel_size != None: plt_rel_buf = self.ql.mem.read(self.plt_rel, self.plt_rel_size) plt_rel_ptr = self.plt_rel if self.plt_rel_type == DT_REL: - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.plt_rel = [ELF32_Rel(plt_rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, plt_rel_ptr + _ * self.relent) for _ in range(self.plt_rel_size // self.relent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.plt_rel = [ELF64_Rel(plt_rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, plt_rel_ptr + _ * self.relent) for _ in range(self.plt_rel_size // self.relent)] else: - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.plt_rel = [ELF32_Rela(plt_rel_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, plt_rel_ptr + _ * self.relaent) for _ in range(self.plt_rel_size // self.relaent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.plt_rel = [ELF64_Rela(plt_rel_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, plt_rel_ptr + _ * self.relaent) for _ in range(self.plt_rel_size // self.relaent)] if self.symtab != None: @@ -902,7 +902,7 @@ class FunctionHook: self.ql.log.debug('dynsym name ' + str(rel_name)) def _hook_int(self, ql, intno): - idx = (self.ql.reg.arch_pc - self.hook_mem) // 0x10 + idx = (self.ql.arch.regs.arch_pc - self.hook_mem) // 0x10 if idx not in self.use_list.keys(): raise @@ -932,9 +932,9 @@ class FunctionHook: hf.enable() # if self.hook_int == False: - # if self.ql.archtype == QL_ARCH.X86 or self.ql.archtype == QL_ARCH.X8664: + # if self.ql.arch.type == QL_ARCH.X86 or self.ql.arch.type == QL_ARCH.X8664: # self.ql.hook_intno(self._hook_int, 0xa0) - # elif self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: + # elif self.ql.arch.type == QL_ARCH.ARM or self.ql.arch.type == QL_ARCH.ARM64: # self.ql.hook_intno(self._hook_int, 7) # self.hook_int = True diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 00d1de00..75b4f581 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -5,10 +5,12 @@ from typing import Callable from unicorn import UcError +from unicorn.x86_const import UC_X86_INS_SYSCALL from qiling import Qiling -from qiling.arch.x86_const import UC_X86_INS_SYSCALL -from qiling.arch.x86 import GDTManager, ql_x8664_set_gs, ql_x86_register_cs, ql_x86_register_ds_ss_es +from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE +from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 +from qiling.arch import arm_utils from qiling.cc import QlCC, intel, arm, mips, riscv from qiling.const import QL_ARCH, QL_INTERCEPT from qiling.os.fcall import QlFunctionCall @@ -33,7 +35,7 @@ class QlOsLinux(QlOsPosix): QL_ARCH.MIPS : mips.mipso32, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64: riscv.riscv, - }[ql.archtype](ql) + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) @@ -44,61 +46,67 @@ class QlOsLinux(QlOsPosix): self.elf_mem_start = 0x0 self.load() - if self.ql.archtype == QL_ARCH.X8664: - ql_x8664_set_gs(self.ql) def load(self): self.futexm = futex.QlLinuxFutexManagement() # ARM - if self.ql.archtype == QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.thread_class = thread.QlLinuxARMThread - self.ql.arch.init_get_tls() + arm_utils.init_get_tls(self.ql, self.ql.arch.arm_get_tls_addr) # MIPS32 - elif self.ql.archtype == QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: self.ql.hook_intno(self.hook_syscall, 17) self.thread_class = thread.QlLinuxMIPS32Thread # ARM64 - elif self.ql.archtype == QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.thread_class = thread.QlLinuxARM64Thread # X86 - elif self.ql.archtype == QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) + + # setup gdt and segments selectors + segm = SegmentManager86(self.ql.arch, self.gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + self.ql.hook_intno(self.hook_syscall, 0x80) self.thread_class = thread.QlLinuxX86Thread # X8664 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) + + # setup gdt and segments selectors + segm = SegmentManager64(self.ql.arch, self.gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + segm.setup_gs(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) + self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) # Keep test for _cc #self.ql.hook_insn(hook_posix_api, UC_X86_INS_SYSCALL) self.thread_class = thread.QlLinuxX8664Thread - elif self.ql.archtype == QL_ARCH.RISCV: + elif self.ql.arch.type == QL_ARCH.RISCV: self.ql.arch.enable_float() self.ql.hook_intno(self.hook_syscall, 8) self.thread_class = None - elif self.ql.archtype == QL_ARCH.RISCV64: + elif self.ql.arch.type == QL_ARCH.RISCV64: self.ql.arch.enable_float() self.ql.hook_intno(self.hook_syscall, 8) self.thread_class = None - - for i in range(NR_OPEN): + + # on fork or execve, do not inherit opened files tagged as 'close on exec' + for i in range(len(self.fd)): if getattr(self.fd[i], 'close_on_exec', 0): - self.fd[i] = 0 + self.fd[i] = None def hook_syscall(self, ql, intno = None): return self.load_syscall() @@ -138,7 +146,7 @@ class QlOsLinux(QlOsPosix): elif self.ql.loader.elf_entry != self.ql.loader.entry_point: entry_address = self.ql.loader.elf_entry - if self.ql.archtype == QL_ARCH.ARM and entry_address & 1 == 1: + if self.ql.arch.type == QL_ARCH.ARM and entry_address & 1 == 1: entry_address -= 1 self.ql.emu_start(self.ql.loader.entry_point, entry_address, self.ql.timeout) self.ql.enable_lib_patch() diff --git a/qiling/os/linux/map_syscall.py b/qiling/os/linux/map_syscall.py index a4e4c591..87bd3552 100644 --- a/qiling/os/linux/map_syscall.py +++ b/qiling/os/linux/map_syscall.py @@ -20,8 +20,8 @@ def map_syscall(ql, syscall_num): QL_ARCH.MIPS: mips_syscall_table, QL_ARCH.RISCV: riscv32_syscall_table, QL_ARCH.RISCV64: riscv64_syscall_table, - }[ql.archtype] - + }[ql.arch.type] + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' arm_syscall_table = { diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index 840c38ce..ae5a465a 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -21,7 +21,7 @@ class timespec(ctypes.Structure): # Temporary dirty fix. -# TODO: Pack ctypes.Structure according to ql.archtype and ql.ostype? +# TODO: Pack ctypes.Structure according to ql.arch.type and ql.ostype? class timespec32(ctypes.Structure): _fields_ = [ ("tv_sec", ctypes.c_uint32), @@ -30,11 +30,8 @@ class timespec32(ctypes.Structure): _pack_ = 4 -def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): - if ql.archtype == QL_ARCH.X86: - GDT_ENTRY_TLS_MIN = 12 - GDT_ENTRY_TLS_MAX = 14 - +def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): + if ql.arch.type == QL_ARCH.X86: u_info = ql.mem.read(u_info_addr, 4 * 4) index = ql.unpack32s(u_info[0 : 4]) base = ql.unpack32(u_info[4 : 8]) @@ -45,37 +42,38 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): if index == -1: index = ql.os.gdtm.get_free_idx(12) - if index == -1 or index < GDT_ENTRY_TLS_MIN or index > GDT_ENTRY_TLS_MAX: + if index in (12, 13, 14): + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT + + ql.os.gdtm.register_gdt_segment(index, base, limit, access) + ql.mem.write_ptr(u_info_addr, index, 4) + else: ql.log.warning(f"Wrong index {index} from address {hex(u_info_addr)}") return -1 - else: - ql.os.gdtm.register_gdt_segment(index, base, limit, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) - ql.mem.write(u_info_addr, ql.pack32(index)) - return 0 - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: CONFIG3_ULR = (1 << 13) - ql.reg.cp0_config3 = CONFIG3_ULR - ql.reg.cp0_userlocal = u_info_addr - ql.reg.v0 = 0 - ql.reg.a3 = 0 + ql.arch.regs.cp0_config3 = CONFIG3_ULR + ql.arch.regs.cp0_userlocal = u_info_addr + ql.arch.regs.v0 = 0 + ql.arch.regs.a3 = 0 ql.log.debug ("set_thread_area(0x%x)" % u_info_addr) return 0 def ql_syscall_set_tls(ql, address, *args, **kw): - if ql.archtype == QL_ARCH.ARM: - ql.reg.c13_c0_3 = address - ql.mem.write(ql.arch.arm_get_tls_addr + 12, ql.pack32(address)) - ql.reg.r0 = address + if ql.arch.type == QL_ARCH.ARM: + ql.arch.regs.c13_c0_3 = address + ql.mem.write_ptr(ql.arch.arm_get_tls_addr + 12, address, 4) + ql.arch.regs.r0 = address ql.log.debug("settls(0x%x)" % address) def ql_syscall_clock_gettime(ql, clock_gettime_clock_id, clock_gettime_timespec, *args, **kw): now = datetime.now().timestamp() tv_sec = floor(now) tv_nsec = floor((now - floor(now)) * 1e6) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) else: tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) @@ -89,7 +87,7 @@ def ql_syscall_gettimeofday(ql, gettimeofday_tv, gettimeofday_tz, *args, **kw): now = datetime.now().timestamp() tv_sec = floor(now) tv_nsec = floor((now - floor(now)) * 1e6) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) else: tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index b4a7f273..32d15a02 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -5,11 +5,12 @@ import gevent, os -from typing import Callable +from typing import Callable, Sequence from abc import abstractmethod from unicorn.unicorn import UcError +from qiling import Qiling from qiling.os.thread import * from qiling.arch.x86_const import * from qiling.exception import QlErrorExecutionStop @@ -25,7 +26,7 @@ THREAD_STATUS_STOPPED = 4 THREAD_STATUS_SUSPEND = 5 class QlLinuxThread(QlThread): - def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_addr = None, thread_id = None): + def __init__(self, ql: Qiling, start_address: int, exit_point: int, context = None, set_child_tid_addr = None, thread_id: int = None): super(QlLinuxThread, self).__init__(ql) if not thread_id: self.new_thread_id() @@ -84,7 +85,7 @@ class QlLinuxThread(QlThread): self._robust_list_head_len = None if self._set_child_tid_address != None: - self.ql.mem.write(self._set_child_tid_address, ql.pack32(self.id)) + self.ql.mem.write_ptr(self._set_child_tid_address, self.id, 4) @property def ql(self): @@ -222,7 +223,7 @@ class QlLinuxThread(QlThread): # In this context, we do: # - Call gevent functions to switch threads. # - Forward blocking syscalls to gevent. - self.ql.reg.arch_pc = self.start_address + self.ql.arch.regs.arch_pc = self.start_address if not self._saved_context: self.save() @@ -235,11 +236,11 @@ class QlLinuxThread(QlThread): self.restore() # Sanity check - if self.ql.reg.arch_pc == self.exit_point: + if self.ql.arch.regs.arch_pc == self.exit_point: self.ql.log.warning(f"Nothing to do but still get scheduled!") # Run and log the run event - start_address = self.ql.arch.get_pc() # For arm thumb. + start_address = getattr(self.ql.arch, 'effective_pc', self.ql.arch.regs.arch_pc) # For arm thumb. self.sched_cb = QlLinuxThread._default_sched_cb self.ql.log.debug(f"Scheduled from {hex(start_address)}.") @@ -250,7 +251,7 @@ class QlLinuxThread(QlThread): self.ql.os.emu_error() self.ql.log.exception("") raise e - self.ql.log.debug(f"Suspended at {hex(self.ql.reg.arch_pc)}") + self.ql.log.debug(f"Suspended at {hex(self.ql.arch.regs.arch_pc)}") self.save() # Note that this callback may be set by UC callbacks. @@ -261,7 +262,7 @@ class QlLinuxThread(QlThread): self.ql.log.debug(f"Call sched_cb: {self.sched_cb}") self.sched_cb(self) - if self.status == THREAD_STATUS_TERMINATED or self.ql.reg.arch_pc == self.exit_point: + if self.status == THREAD_STATUS_TERMINATED or self.ql.arch.regs.arch_pc == self.exit_point: break self._on_stop() @@ -303,18 +304,18 @@ class QlLinuxThread(QlThread): return new_thread def save_context(self): - self.saved_context = self.ql.arch.context_save() + self.saved_context = self.ql.arch.save() def restore_context(self): - self.ql.arch.context_restore(self.saved_context) + self.ql.arch.restore(self.saved_context) def set_start_address(self, addr): # We can't modify UcContext directly. - old_context = self.ql.arch.context_save() + old_context = self.ql.arch.save() self.restore_context() - self.ql.reg.arch_pc = addr + self.ql.arch.regs.arch_pc = addr self.save_context() - self.ql.arch.context_restore(old_context) + self.ql.arch.restore(old_context) def set_clear_child_tid_addr(self, addr): self.clear_child_tid_address = addr @@ -332,7 +333,7 @@ class QlLinuxThread(QlThread): if self.clear_child_tid_address is not None: self.ql.log.debug(f"Perform CLONE_CHILD_CLEARTID at {hex(self.clear_child_tid_address)}") - self.ql.mem.write(self.clear_child_tid_address, self.ql.pack32(0)) + self.ql.mem.write_ptr(self.clear_child_tid_address, 0, 4) wakes = self.ql.os.futexm.get_futex_wake_list(self.ql, self.clear_child_tid_address, 1) self.clear_child_tid_address = None # When the thread is to stop, we don't have chance for next sched_cb, so @@ -368,12 +369,18 @@ class QlLinuxX86Thread(QlLinuxThread): """docstring for X86Thread""" def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_addr = None, thread_id = None): super(QlLinuxX86Thread, self).__init__(ql, start_address, exit_point, context, set_child_tid_addr, thread_id) - self.tls = bytes(b'\x00' * (8 * 3)) + self.tls = [b'\x00' * 8] * 3 + + def __read_tls(self) -> Sequence[bytes]: + return [self.ql.os.gdtm.get_entry(i) for i in (12, 13, 14)] + + def __write_tls(self, tls: Sequence[bytes]) -> None: + for i, entry in zip((12, 13, 14), tls): + self.ql.os.gdtm.set_entry(i, entry) def set_thread_tls(self, tls_addr): - old_tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) - - self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, self.tls) + old_tls = self.__read_tls() + self.__write_tls(self.tls) u_info = self.ql.mem.read(tls_addr, 4 * 4) index = self.ql.unpack32s(u_info[0 : 4]) @@ -383,25 +390,30 @@ class QlLinuxX86Thread(QlLinuxThread): if index == -1: index = self.ql.os.gdtm.get_free_idx(12) - if index == -1 or index < 12 or index > 14: - raise - else: - self.ql.os.gdtm.register_gdt_segment(index, base, limit, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) - self.ql.mem.write(tls_addr, self.ql.pack32(index)) + if index in (12, 13, 14): + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT - self.tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) - self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, old_tls) - self.ql.log.debug(f"Set tls to index={hex(index)} base={hex(base)} limit={hex(limit)} fs={hex(self.ql.reg.fs)} gs={hex(self.ql.reg.gs)} gdt_buf={self.tls}") + self.ql.os.gdtm.register_gdt_segment(index, base, limit, access) + self.ql.mem.write_ptr(tls_addr, index, 4) + else: + raise + + self.tls = self.__read_tls() + self.__write_tls(old_tls) + + self.ql.log.debug(f'Set TLS to index={index:d} base={base:#x} limit={limit:#x} fs={self.ql.arch.regs.fs:#06x} gs={self.ql.arch.regs.gs:#06x}') def save(self): self.save_context() - self.tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) - self.ql.log.debug(f"Saved context. fs={hex(self.ql.reg.fs)} gs={hex(self.ql.reg.gs)} gdt_buf={self.tls}") + self.tls = self.__read_tls() + + self.ql.log.debug(f'Context saved (fs={self.ql.arch.regs.fs:#06x} gs={self.ql.arch.regs.gs:#06x} gdt_buf=[{" ".join(ent.hex() for ent in self.tls)}])') def restore(self): self.restore_context() - self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, self.tls) - self.ql.log.debug(f"Restored context. fs={hex(self.ql.reg.fs)} gs={hex(self.ql.reg.gs)} gdt_buf={self.tls}") + self.__write_tls(self.tls) + + self.ql.log.debug(f'Context restored (fs={self.ql.arch.regs.fs:#06x} gs={self.ql.arch.regs.gs:#06x} gdt_buf=[{" ".join(ent.hex() for ent in self.tls)}])') def clone(self): new_thread = super(QlLinuxX86Thread, self).clone() @@ -416,7 +428,7 @@ class QlLinuxX8664Thread(QlLinuxThread): def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.reg.msr(FSMSR, self.tls) + self.ql.arch.msr.write(IA32_FS_BASE_MSR, self.tls) self.ql.log.debug(f"Set fsbase to {hex(tls_addr)} for {str(self)}") # Some notes: @@ -424,13 +436,13 @@ class QlLinuxX8664Thread(QlLinuxThread): # - https://stackoverflow.com/questions/11497563/detail-about-msr-gs-base-in-linux-x86-64 def save(self): self.save_context() - self.tls = self.ql.reg.msr(FSMSR) - self.ql.log.debug(f"Saved context: fs={hex(self.ql.reg.fsbase)} tls={hex(self.tls)}") + self.tls = self.ql.arch.msr.read(IA32_FS_BASE_MSR) + self.ql.log.debug(f"Saved context: fs={hex(self.ql.arch.regs.fsbase)} tls={hex(self.tls)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context: fs={hex(self.ql.reg.fsbase)} tls={hex(self.tls)}") + self.ql.log.debug(f"Restored context: fs={hex(self.ql.arch.regs.fsbase)} tls={hex(self.tls)}") def clone(self): new_thread = super(QlLinuxX8664Thread, self).clone() @@ -447,19 +459,19 @@ class QlLinuxMIPS32Thread(QlLinuxThread): def set_thread_tls(self, tls_addr): self.tls = tls_addr CONFIG3_ULR = (1 << 13) - self.ql.reg.cp0_config3 = CONFIG3_ULR - self.ql.reg.cp0_userlocal = self.tls - self.ql.log.debug(f"Set cp0 to {hex(self.ql.reg.cp0_userlocal)}") + self.ql.arch.regs.cp0_config3 = CONFIG3_ULR + self.ql.arch.regs.cp0_userlocal = self.tls + self.ql.log.debug(f"Set cp0 to {hex(self.ql.arch.regs.cp0_userlocal)}") def save(self): self.save_context() - self.tls = self.ql.reg.cp0_userlocal - self.ql.log.debug(f"Saved context. cp0={hex(self.ql.reg.cp0_userlocal)}") + self.tls = self.ql.arch.regs.cp0_userlocal + self.ql.log.debug(f"Saved context. cp0={hex(self.ql.arch.regs.cp0_userlocal)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context. cp0={hex(self.ql.reg.cp0_userlocal)}") + self.ql.log.debug(f"Restored context. cp0={hex(self.ql.arch.regs.cp0_userlocal)}") def clone(self): new_thread = super(QlLinuxMIPS32Thread, self).clone() @@ -475,19 +487,19 @@ class QlLinuxARMThread(QlLinuxThread): def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.reg.c13_c0_3 = self.tls - self.ql.log.debug(f"Set c13_c0_3 to {hex(self.ql.reg.c13_c0_3)}") + self.ql.arch.regs.c13_c0_3 = self.tls + self.ql.log.debug(f"Set c13_c0_3 to {hex(self.ql.arch.regs.c13_c0_3)}") def save(self): self.save_context() - self.tls = self.ql.reg.c13_c0_3 - self.ql.log.debug(f"Saved context. c13_c0_3={hex(self.ql.reg.c13_c0_3)}") + self.tls = self.ql.arch.regs.c13_c0_3 + self.ql.log.debug(f"Saved context. c13_c0_3={hex(self.ql.arch.regs.c13_c0_3)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context. c13_c0_3={hex(self.ql.reg.c13_c0_3)}") + self.ql.log.debug(f"Restored context. c13_c0_3={hex(self.ql.arch.regs.c13_c0_3)}") def clone(self): new_thread = super(QlLinuxARMThread, self).clone() @@ -503,18 +515,18 @@ class QlLinuxARM64Thread(QlLinuxThread): def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.reg.tpidr_el0 = self.tls - self.ql.log.debug(f"Set tpidr_el0 to {hex(self.ql.reg.tpidr_el0)}") + self.ql.arch.regs.tpidr_el0 = self.tls + self.ql.log.debug(f"Set tpidr_el0 to {hex(self.ql.arch.regs.tpidr_el0)}") def save(self): self.save_context() - self.tls = self.ql.reg.tpidr_el0 - self.ql.log.debug(f"Saved context. tpidr_el0={hex(self.ql.reg.tpidr_el0)}") + self.tls = self.ql.arch.regs.tpidr_el0 + self.ql.log.debug(f"Saved context. tpidr_el0={hex(self.ql.arch.regs.tpidr_el0)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context. tpidr_el0={hex(self.ql.reg.tpidr_el0)}") + self.ql.log.debug(f"Restored context. tpidr_el0={hex(self.ql.arch.regs.tpidr_el0)}") def clone(self): new_thread = super(QlLinuxARM64Thread, self).clone() @@ -565,14 +577,14 @@ class QlLinuxThreadManagement: def _prepare_lib_patch(self): if self.ql.loader.elf_entry != self.ql.loader.entry_point: entry_address = self.ql.loader.elf_entry - if self.ql.archtype == QL_ARCH.ARM and entry_address & 1 == 1: + if self.ql.arch.type == QL_ARCH.ARM and entry_address & 1 == 1: entry_address -= 1 self.main_thread = self.ql.os.thread_class.spawn(self.ql, self.ql.loader.entry_point, entry_address) self.cur_thread = self.main_thread self._clear_queued_msg() gevent.joinall([self.main_thread], raise_error=True) - if self.ql.reg.arch_pc != entry_address: - self.ql.log.error(f"{self.cur_thread} Expect {hex(self.ql.loader.elf_entry)} but get {hex(self.ql.reg.arch_pc)} when running loader.") + if self.ql.arch.regs.arch_pc != entry_address: + self.ql.log.error(f"{self.cur_thread} Expect {hex(self.ql.loader.elf_entry)} but get {hex(self.ql.arch.regs.arch_pc)} when running loader.") raise QlErrorExecutionStop('Dynamic library .init() failed!') self.ql.enable_lib_patch() self.ql.os.run_function_after_load() diff --git a/qiling/os/macos/events/macos.py b/qiling/os/macos/events/macos.py index 635f706e..83145f6c 100644 --- a/qiling/os/macos/events/macos.py +++ b/qiling/os/macos/events/macos.py @@ -269,10 +269,10 @@ class QlMacOSEvManager: remains = 0 if len(params) <= 6: for idx, p in enumerate(params): - self.ql.reg.write(reg_list[idx], p) + self.ql.arch.regs.write(reg_list[idx], p) else: for idx, p in enumerate(params[:6]): - self.ql.reg.write(reg_list[idx], p) + self.ql.arch.regs.write(reg_list[idx], p) remains = len(params) - 6 for i in range(remains): self.ql.stack_push(params[6 + i]) @@ -298,9 +298,9 @@ class QlMacOSEvManager: uap.newlen = newlen uap.updateToMem() - self.ql.reg.rdi = self.proc_find(0x1337).base - self.ql.reg.rsi = uap_addr # uap - self.ql.reg.rdx = 0 # unused retval + self.ql.arch.regs.rdi = self.proc_find(0x1337).base + self.ql.arch.regs.rsi = uap_addr # uap + self.ql.arch.regs.rdx = 0 # unused retval self.ql.os.savedrip=self.deadcode self.ql.run(self.ql.loader.kernel_extrn_symbols_detail[b"_sysctlbyname"]["n_value"]) @@ -645,9 +645,9 @@ class QlMacOSEvManager: sysent = self.ql.loader.kernel_local_symbols_detail[b"_sysent"]["n_value"] system_table = [sysent_t(self.ql, sysent + x * ctypes.sizeof(sysent_t)).loadFromMem() for x in range(nsyscall)] - self.ql.reg.write(UC_X86_REG_RAX, sysnum) + self.ql.arch.regs.write(UC_X86_REG_RAX, sysnum) reg_list = [UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9] for idx, p in enumerate(params): - self.ql.reg.write(reg_list[idx], p) + self.ql.arch.regs.write(reg_list[idx], p) self.ql.os.savedrip=self.deadcode self.ql.run(begin=system_table[sysnum].sy_call.value) diff --git a/qiling/os/macos/kernel_func.py b/qiling/os/macos/kernel_func.py index ff32c17c..b967e0b4 100644 --- a/qiling/os/macos/kernel_func.py +++ b/qiling/os/macos/kernel_func.py @@ -19,10 +19,10 @@ def vm_shared_region_enter(ql): def map_commpage(ql): - if ql.archtype== QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: addr_base = X8664_COMM_PAGE_START_ADDRESS addr_size = 0x100000 - elif ql.archtype== QL_ARCH.ARM64: + elif ql.arch.type == QL_ARCH.ARM64: addr_base = ARM64_COMM_PAGE_START_ADDRESS addr_size = 0x1000 ql.mem.map(addr_base, addr_size, info="[commpage]") diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index 15fb449e..ec826825 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -4,11 +4,12 @@ # from ctypes import sizeof + from unicorn import UcError +from unicorn.x86_const import UC_X86_INS_SYSCALL from qiling import Qiling -from qiling.arch.x86 import GDTManager, ql_x86_register_cs, ql_x86_register_ds_ss_es -from qiling.arch.x86_const import UC_X86_INS_SYSCALL +from qiling.arch.x86_utils import GDTManager, SegmentManager64 from qiling.cc import intel from qiling.const import QL_ARCH, QL_VERBOSE from qiling.os.fcall import QlFunctionCall @@ -23,7 +24,7 @@ class QlOsMacos(QlOsPosix): super(QlOsMacos, self).__init__(ql) self.ql = ql - self.fcall = QlFunctionCall(ql, intel.macosx64(ql)) + self.fcall = QlFunctionCall(ql, intel.macosx64(ql.arch)) self.ql.counter = 0 self.ev_manager = QlMacOSEvManager(self.ql) @@ -54,59 +55,59 @@ class QlOsMacos(QlOsPosix): self.ql.stack_push(0) self.savedrip=0xffffff8000a163bd self.ql.run(begin=self.ql.loader.kext_alloc) - self.kext_object = self.ql.reg.rax + self.kext_object = self.ql.arch.regs.rax self.ql.log.debug("Created kext object at 0x%x" % self.kext_object) - self.ql.reg.rdi = self.kext_object - self.ql.reg.rsi = 0 # NULL option + self.ql.arch.regs.rdi = self.kext_object + self.ql.arch.regs.rsi = 0 # NULL option self.savedrip=0xffffff8000a16020 self.ql.run(begin=self.ql.loader.kext_init) - if self.ql.reg.rax == 0: + if self.ql.arch.regs.rax == 0: self.ql.log.debug("Failed to initialize kext object") return self.ql.log.debug("Initialized kext object") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a16102 self.ql.run(begin=self.ql.loader.kext_attach) - if self.ql.reg.rax == 0: + if self.ql.arch.regs.rax == 0: self.ql.log.debug("Failed to attach kext object") return self.ql.log.debug("Attached kext object 1st time") - self.ql.reg.rdi = self.kext_object - self.ql.reg.rdi = 0 + self.ql.arch.regs.rdi = self.kext_object + self.ql.arch.regs.rdi = 0 # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? tmp = self.heap.alloc(8) - self.ql.reg.rdx = tmp + self.ql.arch.regs.rdx = tmp self.savedrip=0xffffff8000a16184 self.ql.run(begin=self.ql.loader.kext_probe) self.heap.free(tmp) self.ql.log.debug("Probed kext object") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a16198 self.ql.run(begin=self.ql.loader.kext_detach) self.ql.log.debug("Detached kext object") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a168a3 self.ql.run(begin=self.ql.loader.kext_attach) - if self.ql.reg.rax == 0: + if self.ql.arch.regs.rax == 0: self.ql.log.debug("Failed to attach kext object") return self.ql.log.debug("Attached kext object 2nd time") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a168ed self.ql.run(begin=self.ql.loader.kext_start) else: @@ -131,8 +132,8 @@ class QlOsMacos(QlOsPosix): kmod_info.updateToMem() self.ql.log.debug("Initialized kmod_info") - self.ql.reg.rdi = kmod_info_addr - self.ql.reg.rsi = 0 + self.ql.arch.regs.rdi = kmod_info_addr + self.ql.arch.regs.rsi = 0 self.savedrip=0xffffff80009c2c16 self.ql.run(begin=self.ql.loader.kext_start) @@ -141,16 +142,19 @@ class QlOsMacos(QlOsPosix): if self.ql.code: return - if self.ql.archtype== QL_ARCH.ARM64: + if self.ql.arch.type == QL_ARCH.ARM64: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.ql.hook_intno(self.hook_sigtrap, 7) - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: + gdtm = GDTManager(self.ql) + + # setup gdt and segments selectors + segm = SegmentManager64(self.ql.arch, gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) - self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) def hook_syscall(self, intno= None, int = None): @@ -179,7 +183,7 @@ class QlOsMacos(QlOsPosix): """ self.ql.stack_push(self.savedrip) def callback_ret(ql): - ql.reg.arch_pc = 0 + ql.arch.regs.arch_pc = 0 if self.savedrip not in self.hook_ret: tmp = self.ql.hook_address(callback_ret, self.savedrip) diff --git a/qiling/os/macos/map_syscall.py b/qiling/os/macos/map_syscall.py index e7ab802f..36f57c07 100644 --- a/qiling/os/macos/map_syscall.py +++ b/qiling/os/macos/map_syscall.py @@ -9,15 +9,13 @@ from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF def map_syscall(ql, syscall_num): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: if syscall_num >= 0x2000000 and syscall_num <= 0x3000000: syscall_num = syscall_num - 0x2000000 - print("") - print(syscall_num) return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' - elif ql.archtype == QL_ARCH.ARM64: + elif ql.arch.type == QL_ARCH.ARM64: if syscall_num >= 0xffffffffffffff00: syscall_num = syscall_num - 0xffffffffffffff00 diff --git a/qiling/os/macos/syscall.py b/qiling/os/macos/syscall.py index 36c1fd79..96cc099f 100644 --- a/qiling/os/macos/syscall.py +++ b/qiling/os/macos/syscall.py @@ -204,10 +204,12 @@ def ql_syscall_pread(ql, fd, buf, nbyte, offset, *args, **kw): ql.log.debug("pread(fd: 0x%x, buf: 0x%x, nbyte: 0x%x, offset: 0x%x)" % ( fd, buf, nbyte, offset )) - if fd >= 0 and fd <= MAX_FD_SIZE: + + if fd in range(MAX_FD_SIZE + 1): ql.os.fd[fd].seek(offset) data = ql.os.fd[fd].read(nbyte) ql.mem.write(buf, data) + set_eflags_cf(ql, 0x0) return nbyte @@ -299,7 +301,7 @@ def ql_syscall_shared_region_check_np(ql, p, uap, retvalp, *args, **kw): # 0x150 def ql_syscall_proc_info(ql, callnum, pid, flavor, arg, buff, buffer_size): - retval = struct.unpack(" bool: return fm in self._mapping - def mapping_count(self): + def mapping_count(self) -> int: return len(self._mapping) - def open_ql_file(self, path, openflags, openmode): + def open_ql_file(self, path: str, openflags: int, openmode: int): if self.has_mapping(path): - self.ql.log.info(f"mapping {path}") return self._open_mapping_ql_file(path, openflags, openmode) - else: - real_path = self.ql.os.path.transform_to_real_path(path) - return ql_file.open(real_path, openflags, openmode) - def open(self, path, openmode): + real_path = self.path.transform_to_real_path(path) + return ql_file.open(real_path, openflags, openmode) + + def open(self, path: str, openmode: str): if self.has_mapping(path): - self.ql.log.info(f"mapping {path}") return self._open_mapping(path, openmode) - else: - real_path = self.ql.os.path.transform_to_real_path(path) - return open(real_path, openmode) - def _parse_path(self, p): - if "__fspath__" in dir(p): # p is a os.PathLike object. - p = p.__fspath__() + real_path = self.path.transform_to_real_path(path) + return open(real_path, openmode) + + def _parse_path(self, p: Union[os.PathLike, str]) -> str: + fspath = getattr(p, '__fspath__', None) + + # p is an `os.PathLike` object + if fspath is not None: + p = fspath() + if isinstance(p, bytes): # os.PathLike.__fspath__ may return bytes. p = p.decode("utf-8") + return p - # 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. - # 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. - def add_fs_mapping(self, ql_path, real_dest): + def add_fs_mapping(self, ql_path: Union[os.PathLike, str], real_dest: Any) -> 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. + 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 + """ + ql_path = self._parse_path(ql_path) real_dest = self._parse_path(real_dest) + self._mapping[ql_path] = real_dest - \ No newline at end of file diff --git a/qiling/os/mcu/mcu.py b/qiling/os/mcu/mcu.py index 04d84141..ef248f04 100644 --- a/qiling/os/mcu/mcu.py +++ b/qiling/os/mcu/mcu.py @@ -13,10 +13,6 @@ class QlOsMcu(QlOs): self.runable = True - def step(self): - self.ql.emu_start(self.ql.arch.get_pc(), 0, count=1) - self.ql.hw.step() - def stop(self): self.ql.emu_stop() self.runable = False @@ -27,13 +23,17 @@ class QlOsMcu(QlOs): count = self.ql.count end = self.ql.exit_point if self.ql.exit_point is not None else -1 - if isinstance(self.ql.arch, QlArchARM): - end |= self.ql.arch.thumb - - while self.runable and \ - self.ql.arch.get_pc() != end: + while self.runable: + current_address = self.ql.arch.regs.arch_pc + if isinstance(self.ql.arch, QlArchARM): + current_address |= int(self.ql.arch.is_thumb) + + if current_address == end: + break + + self.ql.emu_start(current_address, 0, count=1) + self.ql.hw.step() - self.step() count -= 1 if count == 0: diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 80f29205..394b121c 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -35,10 +35,10 @@ class QlMemoryManager: 16 : (1 << 20) - 1 # 20bit address line } - if ql.archbit not in bit_stuff: + if ql.arch.bits not in bit_stuff: raise QlErrorStructConversion("Unsupported Qiling archtecture for memory manager") - max_addr = bit_stuff[ql.archbit] + max_addr = bit_stuff[ql.arch.bits] self.max_addr = max_addr self.max_mem_addr = max_addr @@ -288,6 +288,7 @@ class QlMemoryManager: def read_ptr(self, addr: int, size: int=None) -> int: """Read an integer value from a memory address. + Bytes read will be unpacked using emulated architecture properties. Args: addr: memory address to read @@ -297,7 +298,7 @@ class QlMemoryManager: """ if not size: - size = self.ql.pointersize + size = self.ql.arch.pointersize __unpack = { 1 : self.ql.unpack8, @@ -306,10 +307,10 @@ class QlMemoryManager: 8 : self.ql.unpack64 }.get(size) - if __unpack: - return __unpack(self.read(addr, size)) + if __unpack is None: + raise QlErrorStructConversion(f"Unsupported pointer size: {size}") - raise QlErrorStructConversion(f"Unsupported pointer size: {size}") + return __unpack(self.read(addr, size)) def write(self, addr: int, data: bytes) -> None: """Write bytes to a memory. @@ -321,6 +322,31 @@ class QlMemoryManager: self.ql.uc.mem_write(addr, data) + def write_ptr(self, addr: int, value: int, size: int=None) -> None: + """Write an integer value to a memory address. + Bytes written will be packed using emulated architecture properties. + + Args: + addr: target memory address + value: integer value to write + size: pointer size (in bytes): either 1, 2, 4, 8, or None for arch native size + """ + + if not size: + size = self.ql.arch.pointersize + + __pack = { + 1 : self.ql.pack8, + 2 : self.ql.pack16, + 4 : self.ql.pack32, + 8 : self.ql.pack64 + }.get(size) + + if __pack is None: + raise QlErrorStructConversion(f"Unsupported pointer size: {size}") + + self.write(addr, __pack(value)) + def search(self, needle: bytes, begin: int = None, end: int = None) -> Sequence[int]: """Search for a sequence of bytes in memory. diff --git a/qiling/os/os.py b/qiling/os/os.py index b5824b92..e9b84e96 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -15,7 +15,7 @@ from qiling.os.fcall import QlFunctionCall, TypedArg from .filestruct import ql_file from .mapper import QlFsMapper -from .utils import QlOsUtils +from .utils import QlOsStats, QlOsUtils from .path import QlPathManager class QlOs: @@ -30,14 +30,19 @@ class QlOs: self._stderr: TextIO self.utils = QlOsUtils(ql) + self.stats = QlOsStats() self.fcall: QlFunctionCall - self.fs_mapper = QlFsMapper(ql) self.child_processes = False self.thread_management = None self.profile = self.ql.profile - self.path = None if self.ql.baremetal else QlPathManager(ql, self.ql.profile.get("MISC", "current_path")) self.exit_code = 0 + if not ql.baremetal: + cwd = self.profile.get("MISC", "current_path") + + self.path = QlPathManager(ql, cwd) + self.fs_mapper = QlFsMapper(self.path) + self.user_defined_api = { QL_INTERCEPT.CALL : {}, QL_INTERCEPT.ENTER: {}, @@ -61,7 +66,7 @@ class QlOs: 16: 0xfffff, # 20bit address lane 32: 0x8fffffff, 64: 0xffffffffffffffff - }.get(self.ql.archbit, None) + }.get(self.ql.arch.bits, None) if self.ql.code: self.code_ram_size = int(self.profile.get("CODE", "ram_size"), 16) @@ -79,8 +84,6 @@ class QlOs: # let the user override default resolvers or add custom ones self.resolvers.update(resolvers) - self.ql.arch.utils.setup_output() - def save(self) -> Mapping[str, Any]: return {} @@ -187,26 +190,33 @@ class QlOs: self.utils.print_function(pc, func.__name__, pargs, retval, passthru) # append syscall to list - self.utils._call_api(pc, func.__name__, args, retval, retaddr) + self.stats.log_api_call(pc, func.__name__, args, retval, retaddr) # [Windows and UEFI] if emulation has stopped, do not update the return address if hasattr(self, 'PE_RUN') and not self.PE_RUN: passthru = True if not passthru: - self.ql.reg.arch_pc = retaddr + self.ql.arch.regs.arch_pc = retaddr return retval # TODO: separate this method into os-specific functionalities, instead of 'if-else' - def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INTERCEPT): + def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + """Either replace or hook OS API with a custom one. + + Args: + api_name: target API name + intercept_function: function to call + intercept: + `QL_INTERCEPT.CALL` : run handler instead of the existing target implementation + `QL_INTERCEPT.ENTER`: run handler before the target API is called + `QL_INTERCEPT.EXIT` : run handler after the target API is called + """ + if self.ql.ostype == QL_OS.UEFI: api_name = f'hook_{api_name}' - # BUG: workaround missing arg - if intercept is None: - intercept = QL_INTERCEPT.CALL - if (self.ql.ostype in (QL_OS.WINDOWS, QL_OS.UEFI, QL_OS.DOS)) or (self.ql.ostype in (QL_OS_POSIX) and self.ql.loader.is_driver): self.user_defined_api[intercept][api_name] = intercept_function else: @@ -229,11 +239,11 @@ class QlOs: def emu_error(self): self.ql.log.error(f'CPU Context:') - for reg in self.ql.reg.register_mapping: + for reg in self.ql.arch.regs.register_mapping: if isinstance(reg, str): - self.ql.log.error(f'{reg}\t: {self.ql.reg.read(reg):#x}') + self.ql.log.error(f'{reg}\t: {self.ql.arch.regs.read(reg):#x}') - pc = self.ql.reg.arch_pc + pc = self.ql.arch.regs.arch_pc try: data = self.ql.mem.read(pc, size=8) @@ -249,7 +259,7 @@ class QlOs: containing_image = self.find_containing_image(pc) pc_info = f' ({containing_image.path} + {pc - containing_image.base:#x})' if containing_image else '' finally: - self.ql.log.error(f'PC = {pc:#0{self.ql.pointersize * 2 + 2}x}{pc_info}\n') + self.ql.log.error(f'PC = {pc:#0{self.ql.arch.pointersize * 2 + 2}x}{pc_info}\n') self.ql.log.info(f'Memory map:') self.ql.mem.show_mapinfo() diff --git a/qiling/os/path.py b/qiling/os/path.py index b3914bca..85c67d9f 100644 --- a/qiling/os/path.py +++ b/qiling/os/path.py @@ -107,7 +107,7 @@ class QlPathManager: def convert_path(self, rootfs: Union[str, Path], cwd: str, path: str) -> Path: emulated_os = self.ql.ostype - hosting_os = self.ql.platform_os + hosting_os = self.ql.host.os # emulated os and hosting platform are of the same type if (emulated_os == hosting_os) or (emulated_os in QL_OS_POSIX and hosting_os in QL_OS_POSIX): diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index 42673b30..02756b97 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -46,21 +46,21 @@ def ql_open_flag_mapping(ql: Qiling, flags): f = {} t = {} - if ql.platform_os == None: + if ql.host.os == None: return flags if ql.ostype == QL_OS.LINUX: - if ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): f = linux_x86_open_flags - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.ARM64): + elif ql.arch.type in (QL_ARCH.ARM, QL_ARCH.ARM64): f = linux_arm_open_flags - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: f = linux_mips_open_flags - elif ql.archtype in (QL_ARCH.RISCV, QL_ARCH.RISCV64): + elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): f = linux_riscv_open_flags elif ql.ostype == QL_OS.MACOS: - if ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): f = macos_x86_open_flags elif ql.ostype == QL_OS.FREEBSD: f = freebsd_x86_open_flags @@ -69,13 +69,13 @@ def ql_open_flag_mapping(ql: Qiling, flags): elif ql.ostype == QL_OS.QNX: f = qnx_arm64_open_flags - if ql.platform_os == QL_OS.LINUX: + if ql.host.os == QL_OS.LINUX: t = linux_x86_open_flags - elif ql.platform_os == QL_OS.MACOS: + elif ql.host.os == QL_OS.MACOS: t = macos_x86_open_flags - elif ql.platform_os == QL_OS.FREEBSD: + elif ql.host.os == QL_OS.FREEBSD: t = freebsd_x86_open_flags - elif ql.platform_os == QL_OS.WINDOWS: + elif ql.host.os == QL_OS.WINDOWS: t = windows_x86_open_flags if f == t: @@ -130,7 +130,6 @@ def socket_type_mapping(t, archtype, ostype): QL_ARCH.X86: linux_x86_socket_types, QL_ARCH.X8664: linux_x86_socket_types, QL_ARCH.ARM: linux_arm_socket_types, - QL_ARCH.ARM_THUMB: linux_arm_socket_types, QL_ARCH.ARM64: linux_arm_socket_types, QL_ARCH.MIPS: linux_mips_socket_types, }[archtype] @@ -150,7 +149,6 @@ def socket_domain_mapping(p, archtype, ostype): QL_ARCH.X86: linux_x86_socket_domain, QL_ARCH.X8664: linux_x86_socket_domain, QL_ARCH.ARM: linux_arm_socket_domain, - QL_ARCH.ARM_THUMB: linux_arm_socket_domain, QL_ARCH.ARM64: linux_arm_socket_domain, QL_ARCH.MIPS: linux_mips_socket_domain, }[archtype] @@ -167,7 +165,6 @@ def socket_level_mapping(t, archtype, ostype): QL_ARCH.X86: linux_x86_socket_level, QL_ARCH.X8664: linux_x86_socket_level, QL_ARCH.ARM: linux_arm_socket_level, - QL_ARCH.ARM_THUMB: linux_arm_socket_level, QL_ARCH.ARM64: linux_arm_socket_level, QL_ARCH.MIPS: linux_mips_socket_level, }[archtype] @@ -185,7 +182,6 @@ def socket_ip_option_mapping(t, archtype, ostype): QL_ARCH.X86: linux_socket_ip_options, QL_ARCH.X8664: linux_socket_ip_options, QL_ARCH.ARM: linux_socket_ip_options, - QL_ARCH.ARM_THUMB: linux_socket_ip_options, QL_ARCH.ARM64: linux_socket_ip_options, QL_ARCH.MIPS: linux_socket_ip_options, }[archtype] @@ -203,7 +199,6 @@ def socket_option_mapping(t, archtype, ostype): QL_ARCH.X86: linux_x86_socket_options, QL_ARCH.X8664: linux_x86_socket_options, QL_ARCH.ARM: linux_arm_socket_options, - QL_ARCH.ARM_THUMB: linux_arm_socket_options, QL_ARCH.ARM64: linux_arm_socket_options, QL_ARCH.MIPS: linux_mips_socket_options, }[archtype] diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 22330f8b..1c9b1259 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -17,7 +17,7 @@ from qiling.cc import QlCC, intel, arm, mips, riscv from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs -from qiling.os.posix.const import errors, NR_OPEN +from qiling.os.posix.const import errors from qiling.utils import QlFileDes, ostype_convert_str, ql_get_module_function, ql_syscall_mapping_function from qiling.os.posix.syscall import * @@ -49,8 +49,8 @@ class mipso32(mips.mipso32): else: a3return = 0 - self.ql.reg.v0 = value - self.ql.reg.a3 = a3return + self.arch.regs.v0 = value + self.arch.regs.a3 = a3return class riscv32(riscv.riscv): pass @@ -86,13 +86,13 @@ class QlOsPosix(QlOs): QL_ARCH.X86 : UC_X86_REG_EAX, QL_ARCH.X8664: UC_X86_REG_RAX, QL_ARCH.RISCV: UC_RISCV_REG_A7, - QL_ARCH.RISCV64: UC_RISCV_REG_A7, - }[self.ql.archtype] + QL_ARCH.RISCV64: UC_RISCV_REG_A7 + }[self.ql.arch.type] # handle a special case - if (self.ql.archtype == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS): + if (self.ql.arch.type == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS): self.__syscall_id_reg = UC_ARM64_REG_X16 - if (self.ql.archtype == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX): + if (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX): self.__syscall_id_reg = UC_ARM_REG_R12 # TODO: use abstract to access __syscall_cc and __syscall_id_reg by defining a system call class @@ -104,9 +104,9 @@ class QlOsPosix(QlOs): QL_ARCH.X8664: intel64, QL_ARCH.RISCV: riscv32, QL_ARCH.RISCV64: riscv64, - }[self.ql.archtype](ql) + }[self.ql.arch.type](self.ql.arch) - self._fd = QlFileDes([0] * NR_OPEN) + self._fd = QlFileDes() # the QlOs constructor cannot assign the standard streams using their designated properties since # it runs before the _fd array is declared. instead, it assigns them to the private members and here @@ -145,14 +145,21 @@ class QlOsPosix(QlOs): def syscall(self): return self.get_syscall() - def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT): + def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT=QL_INTERCEPT.CALL): + """Either hook or replace a system call with a custom one. + + Args: + target: either syscall name or number. a name may be used only if target syscall is implemented + handler: function to call + intercept: + `QL_INTERCEPT.CALL` : run handler instead of the existing target implementation + `QL_INTERCEPT.ENTER`: run handler before the target syscall is called + `QL_INTERCEPT.EXIT` : run handler after the target syscall is called + """ + if type(target) is str: target = f'{SYSCALL_PREF}{target}' - # BUG: workaround missing arg - if intercept is None: - intercept = QL_INTERCEPT.CALL - self.posix_syscall_hooks[intercept][target] = handler @staticmethod @@ -195,7 +202,7 @@ class QlOsPosix(QlOs): # look in os-specific and posix syscall hooks if syscall_name: - self.ql.log.debug("syscall hooked 0x%x: %s()" % (self.ql.reg.arch_pc, syscall_name)) + self.ql.log.debug("syscall hooked 0x%x: %s()" % (self.ql.arch.regs.arch_pc, syscall_name)) syscall_hook = getattr(os_syscalls, syscall_name, None) or getattr(posix_syscalls, syscall_name, None) if syscall_hook: @@ -252,37 +259,38 @@ class QlOsPosix(QlOs): args.append((name, f'{value:#x}')) sret = QlOsPosix.getNameFromErrorCode(retval) - self.utils.print_function(self.ql.reg.arch_pc, syscall_basename, args, sret, False) + self.utils.print_function(self.ql.arch.regs.arch_pc, syscall_basename, args, sret, False) # record syscall statistics - self.utils.syscalls.setdefault(syscall_name, []).append({ - "params": dict(zip(param_names, params)), - "result": retval, - "address": self.ql.reg.arch_pc, - "return_address": None, - "position": self.utils.syscalls_counter - }) - - self.utils.syscalls_counter += 1 + self.stats.log_api_call(self.ql.arch.regs.arch_pc, syscall_name, dict(zip(param_names, params)), retval, None) else: - self.ql.log.warning(f'{self.ql.reg.arch_pc:#x}: syscall {syscall_name} number = {syscall_id:#x}({syscall_id:d}) not implemented') + self.ql.log.warning(f'{self.ql.arch.regs.arch_pc:#x}: syscall {syscall_name} number = {syscall_id:#x}({syscall_id:d}) not implemented') if self.ql.debug_stop: raise QlErrorSyscallNotFound(f'Syscall not found: {syscall_name}') def get_syscall(self) -> int: - if self.ql.archtype == QL_ARCH.ARM: - # When ARM-OABI - # svc_imm = 0x900000 + syscall_nr - # syscall_nr = svc_imm - 0x900000 - # Ref1: https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html - # Ref2: https://github.com/rootkiter/Reverse-bins/blob/master/syscall_header/armv4l_unistd.h - # Ref3: https://github.com/unicorn-engine/unicorn/issues/1137 - code_val = self.ql.mem.read_ptr(self.ql.reg.arch_pc-4, 4) - svc_imm = code_val & 0x00ffffff - if (svc_imm >= 0x900000): - return svc_imm - 0x900000 - return self.ql.reg.read(self.__syscall_id_reg) + if self.ql.arch.type == QL_ARCH.ARM: + # support arm-oabi + # @see: https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html + # @see: https://github.com/rootkiter/Reverse-bins/blob/master/syscall_header/armv4l_unistd.h + # @see: https://github.com/unicorn-engine/unicorn/issues/1137 + + # read the instruction we have just emulated + isize = 2 if self.ql.arch.is_thumb else self.ql.arch.pointersize + ibytes = self.ql.mem.read_ptr(self.ql.arch.regs.arch_pc - isize, isize) + + # mask off the opcode, which is the most significant byte + svc_imm = ibytes & ((1 << ((isize - 1) * 8)) - 1) + + # arm-oabi + if svc_imm >= 0x900000: + return svc_imm - 0x900000 + + if svc_imm > 0: + return svc_imm + + return self.ql.arch.regs.read(self.__syscall_id_reg) def set_syscall_return(self, retval: int): self.__syscall_cc.setReturnValue(retval) diff --git a/qiling/os/posix/syscall/fcntl.py b/qiling/os/posix/syscall/fcntl.py index fe672ce9..97681f54 100644 --- a/qiling/os/posix/syscall/fcntl.py +++ b/qiling/os/posix/syscall/fcntl.py @@ -21,16 +21,15 @@ def ql_syscall_open(ql: Qiling, filename: int, flags: int, mode: int): flags &= 0xffffffff mode &= 0xffffffff - idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] == 0), -1) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) if idx == -1: regreturn = -EMFILE else: try: - if ql.archtype== QL_ARCH.ARM and ql.ostype!= QL_OS.QNX: + if ql.arch.type == QL_ARCH.ARM and ql.ostype != QL_OS.QNX: mode = 0 - #flags = ql_open_flag_mapping(ql, flags) flags = ql_open_flag_mapping(ql, flags) ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(path, flags, mode) regreturn = idx @@ -57,13 +56,13 @@ def ql_syscall_creat(ql: Qiling, filename: int, mode: int): flags &= 0xffffffff mode &= 0xffffffff - idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] == 0), -1) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) if idx == -1: regreturn = -ENOMEM else: try: - if ql.archtype== QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: mode = 0 flags = ql_open_flag_mapping(ql, flags) @@ -89,13 +88,13 @@ def ql_syscall_openat(ql: Qiling, fd: int, path: int, flags: int, mode: int): flags &= 0xffffffff mode &= 0xffffffff - idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] == 0), -1) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) if idx == -1: regreturn = -EMFILE else: try: - if ql.archtype== QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: mode = 0 flags = ql_open_flag_mapping(ql, flags) @@ -120,24 +119,26 @@ def ql_syscall_openat(ql: Qiling, fd: int, path: int, flags: int, mode: int): def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int): - if not (0 <= fd < NR_OPEN) or ql.os.fd[fd] == 0: + if fd not in range(NR_OPEN): return -EBADF f = ql.os.fd[fd] + if f is None: + return -EBADF + if cmd == F_DUPFD: - if 0 <= arg < NR_OPEN: - for idx, val in enumerate(ql.os.fd): - if val == 0 and idx >= arg: - new_fd = ql.os.fd[fd].dup() - ql.os.fd[idx] = new_fd - regreturn = idx - break - else: - regreturn = -EMFILE - else: + if arg not in range(NR_OPEN): regreturn = -EINVAL + for idx, val in enumerate(ql.os.fd, arg): + if val is None: + ql.os.fd[idx] = f.dup() + regreturn = idx + break + else: + regreturn = -EMFILE + elif cmd == F_GETFD: regreturn = getattr(f, "close_on_exec", 0) @@ -146,10 +147,10 @@ def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int): regreturn = 0 elif cmd == F_GETFL: - regreturn = ql.os.fd[fd].fcntl(cmd, arg) + regreturn = f.fcntl(cmd, arg) elif cmd == F_SETFL: - ql.os.fd[fd].fcntl(cmd, arg) + f.fcntl(cmd, arg) regreturn = 0 else: @@ -159,28 +160,31 @@ def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int): def ql_syscall_fcntl64(ql: Qiling, fd: int, cmd: int, arg: int): + if fd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None: + return -1 # https://linux.die.net/man/2/fcntl64 if cmd == F_DUPFD: - if 0 <= arg < NR_OPEN and 0 <= fd < NR_OPEN: - if ql.os.fd[fd] != 0: - new_fd = ql.os.fd[fd].dup() - for idx, val in enumerate(ql.os.fd): - if val == 0 and idx >= arg: - ql.os.fd[idx] = new_fd - regreturn = idx - break - else: - regreturn = -1 - else: + if arg not in range(NR_OPEN): regreturn = -1 + for idx, val in enumerate(ql.os.fd, arg): + if val is None: + ql.os.fd[idx] = f.dup() + regreturn = idx + break + elif cmd == F_GETFL: regreturn = 2 elif cmd == F_SETFL: - if isinstance(ql.os.fd[fd], ql_socket): - ql.os.fd[fd].fcntl(cmd, arg) + if isinstance(f, ql_socket): + f.fcntl(cmd, arg) regreturn = 0 elif cmd == F_GETFD: diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index 233b229c..cf7585e9 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -65,15 +65,15 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f 2 : 'mmap2' }[ver] - if ql.archbit == 64: + if ql.arch.bits == 64: fd = ql.unpack64(ql.pack64(fd)) - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: MAP_ANONYMOUS = 2048 if ver == 2: pgoffset = pgoffset * pagesize - elif ql.archtype == QL_ARCH.ARM and ql.ostype== QL_OS.QNX: + elif ql.arch.type == QL_ARCH.ARM and ql.ostype == QL_OS.QNX: MAP_ANONYMOUS = 0x00080000 fd = ql.unpack32s(ql.pack32s(fd)) @@ -123,21 +123,24 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f except Exception as e: raise QlMemoryMappedError("Error: trying to zero memory") - if ((flags & MAP_ANONYMOUS) == 0) and 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - ql.os.fd[fd].seek(pgoffset) - data = ql.os.fd[fd].read(mlen) - mem_info = str(ql.os.fd[fd].name) - ql.os.fd[fd]._is_map_shared = flags & MAP_SHARED - ql.os.fd[fd]._mapped_offset = pgoffset - ql.log.debug("mem write : " + hex(len(data))) - ql.log.debug("mem mmap : " + mem_info) + if ((flags & MAP_ANONYMOUS) == 0) and fd in range(NR_OPEN): + f = ql.os.fd[fd] - ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=("[%s] " % api_name) + mem_info) - try: - ql.mem.write(mmap_base, data) - except Exception as e: - ql.log.debug(e) - raise QlMemoryMappedError("Error: trying to write memory: ") + if f is not None: + f.seek(pgoffset) + data = f.read(mlen) + mem_info = str(f.name) + f._is_map_shared = flags & MAP_SHARED + f._mapped_offset = pgoffset + ql.log.debug("mem write : " + hex(len(data))) + ql.log.debug("mem mmap : " + mem_info) + + ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=("[%s] " % api_name) + mem_info) + try: + ql.mem.write(mmap_base, data) + except Exception as e: + ql.log.debug(e) + raise QlMemoryMappedError("Error: trying to write memory: ") return mmap_base diff --git a/qiling/os/posix/syscall/net.py b/qiling/os/posix/syscall/net.py index 151262b4..01dd7c0e 100644 --- a/qiling/os/posix/syscall/net.py +++ b/qiling/os/posix/syscall/net.py @@ -54,6 +54,6 @@ def ql_syscall_socketcall(ql: Qiling, call: int, args: int): handler, nargs = handlers[call] # read 'nargs' arguments from the specified base pointer 'args' - params = (ql.unpack(ql.mem.read(args + i * ql.pointersize, ql.pointersize)) for i in range(nargs)) + params = (ql.unpack(ql.mem.read(args + i * ql.arch.pointersize, ql.arch.pointersize)) for i in range(nargs)) return handler(ql, *params) diff --git a/qiling/os/posix/syscall/prctl.py b/qiling/os/posix/syscall/prctl.py index 3652c2bd..c4811a5c 100644 --- a/qiling/os/posix/syscall/prctl.py +++ b/qiling/os/posix/syscall/prctl.py @@ -4,7 +4,7 @@ # from qiling import Qiling -from qiling.arch.x86_const import FSMSR, GSMSR +from qiling.arch.x86_const import IA32_FS_BASE_MSR, IA32_GS_BASE_MSR def ql_syscall_arch_prctl(ql: Qiling, code: int, addr: int): ARCH_SET_GS = 0x1001 @@ -13,10 +13,10 @@ def ql_syscall_arch_prctl(ql: Qiling, code: int, addr: int): ARCH_GET_GS = 0x1004 handlers = { - ARCH_SET_GS : lambda : ql.reg.msr(GSMSR, addr), - ARCH_SET_FS : lambda : ql.reg.msr(FSMSR, addr), - ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.reg.msr(FSMSR))), - ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.reg.msr(GSMSR))) + ARCH_SET_GS : lambda : ql.arch.msr.write(IA32_GS_BASE_MSR, addr), + ARCH_SET_FS : lambda : ql.arch.msr.write(IA32_FS_BASE_MSR, addr), + ARCH_GET_FS : lambda : ql.mem.write_ptr(addr, ql.arch.msr.read(IA32_FS_BASE_MSR), 8), + ARCH_GET_GS : lambda : ql.mem.write_ptr(addr, ql.arch.msr.read(IA32_GS_BASE_MSR), 8) } if code not in handlers: diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index dbc493f3..9648182d 100644 --- a/qiling/os/posix/syscall/sched.py +++ b/qiling/os/posix/syscall/sched.py @@ -38,7 +38,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in CLONE_IO = 0x80000000 # X8664 flags, child_stack, parent_tidptr, child_tidptr, newtls - if ql.archtype== QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: ori_newtls = child_tidptr child_tidptr = newtls newtls = ori_newtls @@ -49,7 +49,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in # Shared virtual memory if not (flags & CLONE_VM): # FIXME: need a proper os.fork() for Windows - if ql.platform_os == QL_OS.WINDOWS: + if ql.host.os == QL_OS.WINDOWS: try: pid = Process() pid = 0 @@ -71,7 +71,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in f_th.set_clear_child_tid_addr(child_tidptr) if child_stack != 0: - ql.arch.set_sp(child_stack) + ql.arch.regs.arch_sp = child_stack # ql.log.debug(f'clone(new_stack = {child_stack:#x}, flags = {flags:#x}, tls = {newtls:#x}, ptidptr = {parent_tidptr:#x}, ctidptr = {child_tidptr:#x}) = {regreturn:d}') ql.emu_stop() @@ -81,12 +81,12 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in if flags & CLONE_CHILD_SETTID == CLONE_CHILD_SETTID: set_child_tid_addr = child_tidptr - th = ql.os.thread_class.spawn(ql, ql.reg.arch_pc + 2, ql.os.exit_point, set_child_tid_addr = set_child_tid_addr) + th = ql.os.thread_class.spawn(ql, ql.arch.regs.arch_pc + 2, ql.os.exit_point, set_child_tid_addr = set_child_tid_addr) th.path = f_th.path ql.log.debug(f'{str(th)} created') if flags & CLONE_PARENT_SETTID == CLONE_PARENT_SETTID: - ql.mem.write(parent_tidptr, ql.pack32(th.id)) + ql.mem.write_ptr(parent_tidptr, th.id, 4) ctx = ql.save(reg=True, mem=False) # Whether to set a new tls @@ -101,12 +101,12 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in # (the return value of the child thread is 0, and the return value of the parent thread is the tid of the child thread) # and save the current context. regreturn = 0 - ql.reg.arch_sp = child_stack + ql.arch.regs.arch_sp = child_stack # We have to find next pc manually for some archs since the pc is current instruction (like `syscall`). - if ql.archtype in (QL_ARCH.X8664, ): - ql.reg.arch_pc += list(ql.disassembler.disasm_lite(bytes(ql.mem.read(ql.reg.arch_pc, 4)), ql.reg.arch_pc))[0][1] - ql.log.debug(f"Fix pc for child thread to {hex(ql.reg.arch_pc)}") + if ql.arch.type == QL_ARCH.X8664: + ql.arch.regs.arch_pc += list(ql.arch.disassembler.disasm_lite(bytes(ql.mem.read(ql.arch.regs.arch_pc, 4)), ql.arch.regs.arch_pc))[0][1] + ql.log.debug(f"Fix pc for child thread to {hex(ql.arch.regs.arch_pc)}") ql.os.set_syscall_return(0) th.save() diff --git a/qiling/os/posix/syscall/select.py b/qiling/os/posix/syscall/select.py index 6d1aab71..28368052 100644 --- a/qiling/os/posix/syscall/select.py +++ b/qiling/os/posix/syscall/select.py @@ -49,7 +49,7 @@ def ql_syscall__newselect(ql: Qiling, nfds: int, readfds: int, writefds: int, ex tmp_w_fd, tmp_w_map = parse_fd_set(ql, nfds, writefds) tmp_e_fd, tmp_e_map = parse_fd_set(ql, nfds, exceptfds) - n = ql.pointersize + n = ql.arch.pointersize if timeout: sec = ql.unpack(ql.mem.read(timeout + n * 0, n)) diff --git a/qiling/os/posix/syscall/sendfile.py b/qiling/os/posix/syscall/sendfile.py index 0c8c092e..6d4b81ca 100644 --- a/qiling/os/posix/syscall/sendfile.py +++ b/qiling/os/posix/syscall/sendfile.py @@ -14,26 +14,25 @@ def ql_syscall_sendfile64(ql: Qiling, out_fd: int, in_fd: int, offset: int, coun def ql_syscall_sendfile(ql: Qiling, out_fd: int, in_fd: int, offset: int, count: int): # https://man7.org/linux/man-pages/man2/sendfile.2.html - if 0 <= out_fd < NR_OPEN and 0 <= in_fd < NR_OPEN \ - and ql.os.fd[out_fd] != 0 and ql.os.fd[in_fd] != 0: - in_fd_pos = ql.os.fd[in_fd].tell() - if offset: - # Handle sendfile64 and sendfile offset_ptr - offset = ql.unpack(ql.mem.read(offset, ql.pointersize)) - else: - offset = in_fd_pos + if in_fd not in range(NR_OPEN) or out_fd not in range(NR_OPEN): + return -1 - ql.os.fd[in_fd].lseek(offset) - buf = ql.os.fd[in_fd].read(count) - if offset: - current_offset = ql.os.fd[in_fd].tell() - ql.mem.write(offset, ql.pack(current_offset)) - ql.os.fd[in_fd].lseek(in_fd_pos) + ifile = ql.os.fd[in_fd] + ofile = ql.os.fd[out_fd] - regreturn = ql.os.fd[out_fd].write(buf) + if ifile is None or ofile is None: + return -1 - else: - regreturn = -1 + ifile_pos = ifile.tell() + offset = ql.mem.read_ptr(offset) if offset else ifile_pos - return regreturn + ifile.lseek(offset) + buf = ifile.read(count) + + if offset: + current_offset = ifile.tell() + ql.mem.write_ptr(offset, current_offset) + ifile.lseek(ifile_pos) + + return ofile.write(buf) diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index b48152dc..be01291d 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -68,46 +68,35 @@ def ql_bin_to_ip(ip): def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): - idx = -1 - for i in range(NR_OPEN): - if ql.os.fd[i] == 0: - idx = i - break - try: - if idx == -1: - regreturn = -1 - else: - # 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.archtype, ql.ostype) - socket_type = getattr(socket, emu_socket_type) - ql.log.debug("Convert emu_socket_type {}:{} to host platform based socket_type {}:{}".format( - emu_socket_type, emu_socket_value, emu_socket_type, socket_type)) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) - except AttributeError: - ql.log.error("Can't convert emu_socket_type {}:{} to host platform based socket_type".format( - emu_socket_type, emu_socket_value)) - raise - - except Exception: - ql.log.error("Can't convert emu_socket_type {} to host platform based socket_type".format( - emu_socket_value)) - raise - - 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) - - regreturn = (idx) - - except Exception: - ql.log.exception("") + if idx == -1: regreturn = -1 + else: + # 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.ostype) + 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}') - socket_type = socket_type_mapping(socket_type, ql.archtype, ql.ostype) - socket_domain = socket_domain_mapping(socket_domain, ql.archtype, ql.ostype) + except AttributeError: + ql.log.error(f'Cannot convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type') + raise + + except Exception: + ql.log.error(f'Cannot convert emu_socket_type {emu_socket_value} to host platform based socket_type') + raise + + 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) + + regreturn = idx + + socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) + socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.ostype) ql.log.debug("socket(%s, %s, %s) = %d" % (socket_domain, socket_type, socket_protocol, regreturn)) return regreturn @@ -151,7 +140,7 @@ def ql_syscall_connect(ql: Qiling, connect_sockfd, connect_addr, connect_addrlen def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optlen_addr): - if not (0 <= sockfd < NR_OPEN) or ql.os.fd[sockfd] == 0: + if sockfd not in range(NR_OPEN) or ql.os.fd[sockfd] is None: return -EBADF try: @@ -161,7 +150,7 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_level = level - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) 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)) @@ -178,15 +167,15 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_opt = optname - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.ostype) else: - emu_opt_name = socket_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.ostype) # Fix for mips - if ql.archtype == QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: if emu_opt_name.endswith("_NEW") or emu_opt_name.endswith("_OLD"): emu_opt_name = emu_opt_name[:-4] @@ -212,8 +201,7 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optlen): - if not (0 <= sockfd < NR_OPEN) or\ - ql.os.fd[sockfd] == 0: + if sockfd not in range(NR_OPEN) or ql.os.fd[sockfd] is None: return -EBADF regreturn = 0 @@ -223,7 +211,7 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: try: emu_level = level - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) 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)) @@ -240,15 +228,15 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_opt = optname - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.ostype) else: - emu_opt_name = socket_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.ostype) # Fix for mips - if ql.archtype == QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: if emu_opt_name.endswith("_NEW") or emu_opt_name.endswith("_OLD"): emu_opt_name = emu_opt_name[:-4] @@ -277,15 +265,17 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle return regreturn -def ql_syscall_shutdown(ql: Qiling, shutdown_fd, shutdown_how): - +def ql_syscall_shutdown(ql: Qiling, fd: int, how: int): regreturn = 0 - - if 0 <= shutdown_fd < NR_OPEN and ql.os.fd[shutdown_fd] != 0: - try: - ql.os.fd[shutdown_fd].shutdown(shutdown_how) - except: - regreturn = -1 + + if fd in range(NR_OPEN): + sock = ql.os.fd[fd] + + if sock is not None: + try: + sock.shutdown(how) + except: + regreturn = -1 return regreturn @@ -293,7 +283,7 @@ def ql_syscall_shutdown(ql: Qiling, shutdown_fd, shutdown_how): def ql_syscall_bind(ql: Qiling, bind_fd, bind_addr, bind_addrlen): regreturn = 0 - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: data = ql.mem.read(bind_addr, 8) else: data = ql.mem.read(bind_addr, bind_addrlen) @@ -387,18 +377,24 @@ def ql_syscall_getpeername(ql: Qiling, sockfd: int, addr: int, addrlenptr: int): return regreturn -def ql_syscall_listen(ql: Qiling, listen_sockfd, listen_backlog): - if 0 <= listen_sockfd < NR_OPEN and ql.os.fd[listen_sockfd] != 0: - try: - ql.os.fd[listen_sockfd].listen(listen_backlog) - regreturn = 0 - except: - if ql.verbose >= QL_VERBOSE.DEBUG: - raise - regreturn = -1 - else: - regreturn = -1 - return regreturn +def ql_syscall_listen(ql: Qiling, sockfd: int, backlog: int): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + try: + sock.listen(backlog) + except: + if ql.verbose >= QL_VERBOSE.DEBUG: + raise + + return -1 + + return 0 def ql_syscall_accept(ql: Qiling, accept_sockfd, accept_addr, accept_addrlen): @@ -412,14 +408,12 @@ def ql_syscall_accept(ql: Qiling, accept_sockfd, accept_addr, accept_addrlen): return ret try: conn, address = ql.os.fd[accept_sockfd].accept() - if conn == None: + + if conn is None: return -1 - idx = -1 - for i in range(NR_OPEN): - if ql.os.fd[i] == 0: - idx = i - break + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) + if idx == -1: regreturn = -1 else: @@ -432,7 +426,7 @@ def ql_syscall_accept(ql: Qiling, accept_sockfd, accept_addr, accept_addrlen): tmp_buf += inet_addr(address[0]) tmp_buf += b'\x00' * 8 ql.mem.write(accept_addr, tmp_buf) - ql.mem.write(accept_addrlen, ql.pack32(16)) + ql.mem.write_ptr(accept_addrlen, 16, 4) except: if ql.verbose >= QL_VERBOSE.DEBUG: raise @@ -441,44 +435,51 @@ def ql_syscall_accept(ql: Qiling, accept_sockfd, accept_addr, accept_addrlen): return regreturn -def ql_syscall_recv(ql: Qiling, recv_sockfd, recv_buf, recv_len, recv_flags): - if 0 <= recv_sockfd < NR_OPEN and ql.os.fd[recv_sockfd] != 0: - tmp_buf = ql.os.fd[recv_sockfd].recv(recv_len, recv_flags) - if tmp_buf: - ql.log.debug("recv() CONTENT:") - ql.log.debug("%s" % tmp_buf) - ql.mem.write(recv_buf, tmp_buf) - regreturn = len(tmp_buf) - else: - regreturn = -1 +def ql_syscall_recv(ql: Qiling, sockfd: int, buf: int, length: int, flags: int): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + content = sock.recv(length, flags) + + if content: + ql.log.debug("recv() CONTENT:") + ql.log.debug("%s" % content) + + ql.mem.write(buf, content) + + return len(content) + + +def ql_syscall_send(ql: Qiling, sockfd: int, buf: int, length: int, flags: int): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + try: + content = bytes(ql.mem.read(buf, length)) + regreturn = sock.send(content, flags) + except: + regreturn = 0 + ql.log.info(sys.exc_info()[0]) + + if ql.verbose >= QL_VERBOSE.DEBUG: + raise + return regreturn -def ql_syscall_send(ql: Qiling, send_sockfd, send_buf, send_len, send_flags): +def ql_syscall_recvmsg(ql: Qiling, sockfd: int, msg_addr: int, flags: int): regreturn = 0 - if 0 <= send_sockfd < NR_OPEN and ql.os.fd[send_sockfd] != 0: - try: - ql.log.debug("debug send() start") - tmp_buf = ql.mem.read(send_buf, send_len) - ql.log.debug("fd is " + str(send_sockfd)) - ql.log.debug("send() CONTENT:") - ql.log.debug("%s" % str(tmp_buf)) - ql.log.debug("send() flag is " + str(send_flags)) - ql.log.debug("send() len is " + str(send_len)) - regreturn = ql.os.fd[send_sockfd].send(bytes(tmp_buf), send_flags) - ql.log.debug("debug send end") - except: - ql.log.info(sys.exc_info()[0]) - if ql.verbose >= QL_VERBOSE.DEBUG: - raise - else: - regreturn = -1 - return regreturn - - -def ql_syscall_recvmsg(ql: Qiling, sockfd, msg_addr, flags): - regreturn = 0 - if 0 <= sockfd < NR_OPEN and ql.os.fd[sockfd] != 0: + if sockfd not in range(NR_OPEN) and ql.os.fd[sockfd] is not None: msg = msghdr.load(ql, msg_addr) try: @@ -521,81 +522,96 @@ def ql_syscall_recvmsg(ql: Qiling, sockfd, msg_addr, flags): return regreturn -def ql_syscall_recvfrom(ql: Qiling, recvfrom_sockfd, recvfrom_buf, recvfrom_len, recvfrom_flags, recvfrom_addr, recvfrom_addrlen): +def ql_syscall_recvfrom(ql: Qiling, sockfd: int, buf: int, length: int, flags: int, addr: int, addrlen: int): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + SOCK_STREAM = 1 + # For x8664, recvfrom() is called finally when calling recv() in TCP communications - SOCK_STREAM = 1 - if ql.os.fd[recvfrom_sockfd].socktype == SOCK_STREAM: - return ql_syscall_recv(ql, recvfrom_sockfd, recvfrom_buf, recvfrom_len, recvfrom_flags) + if sock.socktype == SOCK_STREAM: + return ql_syscall_recv(ql, sockfd, buf, length, flags) + + tmp_buf, tmp_addr = sock.recvfrom(length, flags) + + if tmp_buf: + ql.log.debug("recvfrom() CONTENT:") + ql.log.debug("%s" % tmp_buf) + + sin_family = int(sock.family) + data = struct.pack("H", tmp_addr[1]) + data += ipaddress.ip_address(tmp_addr[0]).packed + addrlen = ql.mem.read_ptr(addrlen) + data = data[:addrlen] - sin_family = int(ql.os.fd[recvfrom_sockfd].family) - data = struct.pack("H", tmp_addr[1]) - data += ipaddress.ip_address(tmp_addr[0]).packed - addrlen = ql.unpack(ql.mem.read(recvfrom_addrlen, ql.pointersize)) - data = data[:addrlen] - ql.mem.write(recvfrom_addr, data) + ql.mem.write(addr, data) + ql.mem.write(buf, tmp_buf) - ql.mem.write(recvfrom_buf, tmp_buf) - regreturn = len(tmp_buf) - else: - regreturn = -1 - - return regreturn + return len(tmp_buf) -def ql_syscall_sendto(ql: Qiling, sendto_sockfd, sendto_buf, sendto_len, sendto_flags, sendto_addr, sendto_addrlen): +def ql_syscall_sendto(ql: Qiling, sockfd: int, sendto_buf, sendto_len, sendto_flags, sendto_addr, sendto_addrlen): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + SOCK_STREAM = 1 + # For x8664, sendto() is called finally when calling send() in TCP communications - SOCK_STREAM = 1 - if ql.os.fd[sendto_sockfd].socktype == SOCK_STREAM: - return ql_syscall_send(ql, sendto_sockfd, sendto_buf, sendto_len, sendto_flags) - else: - regreturn = 0 - if 0 <= sendto_sockfd < NR_OPEN and ql.os.fd[sendto_sockfd] != 0: - try: - ql.log.debug("debug sendto() start") - tmp_buf = ql.mem.read(sendto_buf, sendto_len) + if sock.socktype == SOCK_STREAM: + return ql_syscall_send(ql, sockfd, sendto_buf, sendto_len, sendto_flags) - if ql.archtype== QL_ARCH.X8664: - data = ql.mem.read(sendto_addr, 8) - else: - data = ql.mem.read(sendto_addr, sendto_addrlen) + regreturn = 0 - sin_family, = struct.unpack("HI", data[2:8]) - host = ql_bin_to_ip(host) + try: + ql.log.debug("debug sendto() start") + tmp_buf = ql.mem.read(sendto_buf, sendto_len) - if sin_family == 1: - path = data[2 : ].split(b'\x00')[0] - path = ql.os.path.transform_to_real_path(path.decode()) - - ql.log.debug("fd is " + str(sendto_sockfd)) - ql.log.debug("sendto() CONTENT:") - ql.log.debug("%s" % tmp_buf) - ql.log.debug("sendto() flag is " + str(sendto_flags)) - ql.log.debug("sendto() len is " + str(sendto_len)) - if sin_family == 1: - ql.log.debug("sendto() path is " + str(path)) - regreturn = ql.os.fd[sendto_sockfd].sendto(bytes(tmp_buf), sendto_flags, path) - else: - ql.log.debug("sendto() addr is %s:%d" % (host, port)) - regreturn = ql.os.fd[sendto_sockfd].sendto(bytes(tmp_buf), sendto_flags, (host, port)) - ql.log.debug("debug sendto end") - except: - ql.log.debug(sys.exc_info()[0]) - if ql.verbose >= QL_VERBOSE.DEBUG: - raise + if ql.arch.type== QL_ARCH.X8664: + data = ql.mem.read(sendto_addr, 8) else: - regreturn = -1 + data = ql.mem.read(sendto_addr, sendto_addrlen) - return regreturn + sin_family, = struct.unpack("HI", data[2:8]) + host = ql_bin_to_ip(host) + + ql.log.debug("fd is " + str(sockfd)) + ql.log.debug("sendto() CONTENT:") + ql.log.debug("%s" % tmp_buf) + ql.log.debug("sendto() flag is " + str(sendto_flags)) + 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()) + + ql.log.debug("sendto() path is " + str(path)) + regreturn = sock.sendto(bytes(tmp_buf), sendto_flags, path) + else: + ql.log.debug("sendto() addr is %s:%d" % (host, port)) + regreturn = sock.sendto(bytes(tmp_buf), sendto_flags, (host, port)) + ql.log.debug("debug sendto end") + except: + ql.log.debug(sys.exc_info()[0]) + + if ql.verbose >= QL_VERBOSE.DEBUG: + raise + + return regreturn diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 2b8d8fc8..0ca5fd6c 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -929,63 +929,63 @@ class QNXARMStat64(ctypes.Structure): _pack_ = 8 def get_stat64_struct(ql: Qiling): - if ql.archbit == 64: - ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.ostype} and {ql.archtype}!") + if ql.arch.bits == 64: + ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.ostype} and {ql.arch.type}!") if ql.ostype == QL_OS.LINUX: - if ql.archtype == QL_ARCH.X86: + if ql.arch.type == QL_ARCH.X86: return LinuxX86Stat64() - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: return LinuxMips32Stat64() - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + elif ql.arch.type == QL_ARCH.ARM: return LinuxARMStat64() - elif ql.archtype in (QL_ARCH.RISCV, QL_ARCH.RISCV64): + elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() elif ql.ostype == QL_OS.MACOS: return MacOSStat64() elif ql.ostype == QL_OS.QNX: return QNXARMStat64() - ql.log.warning(f"Unrecognized arch && os with {ql.archtype} and {ql.ostype} for stat64! Fallback to Linux x86.") + ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.ostype} for stat64! Fallback to Linux x86.") return LinuxX86Stat64() def get_stat_struct(ql: Qiling): if ql.ostype == QL_OS.FREEBSD: - if ql.archtype == QL_ARCH.X8664 or ql.archbit == 64: + if ql.arch.type == QL_ARCH.X8664 or ql.arch.bits == 64: return FreeBSDX8664Stat() else: return FreeBSDX86Stat() elif ql.ostype == QL_OS.MACOS: return MacOSStat() elif ql.ostype == QL_OS.LINUX: - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: return LinuxX8664Stat() - elif ql.archtype == QL_ARCH.X86: + elif ql.arch.type == QL_ARCH.X86: return LinuxX86Stat() - elif ql.archtype == QL_ARCH.MIPS: - if ql.archbit == 64: + elif ql.arch.type == QL_ARCH.MIPS: + if ql.arch.bits == 64: return LinuxMips64Stat() else: return LinuxMips32Stat() - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): - if ql.archendian == QL_ENDIAN.EL: + elif ql.arch.type == QL_ARCH.ARM: + if ql.arch.endian == QL_ENDIAN.EL: return LinuxARMStat() else: return LinuxARMEBStat() - elif ql.archtype == QL_ARCH.ARM64: - if ql.archendian == QL_ENDIAN.EL: + elif ql.arch.type == QL_ARCH.ARM64: + if ql.arch.endian == QL_ENDIAN.EL: return LinuxARM64Stat() else: return LinuxARM64EBStat() - elif ql.archtype in (QL_ARCH.RISCV, QL_ARCH.RISCV64): + elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() elif ql.ostype == QL_OS.QNX: - if ql.archtype == QL_ARCH.ARM64: + if ql.arch.type == QL_ARCH.ARM64: return QNXARM64Stat() - elif ql.archtype == QL_ARCH.ARM: - if ql.archendian == QL_ENDIAN.EL: + elif ql.arch.type == QL_ARCH.ARM: + if ql.arch.endian == QL_ENDIAN.EL: return QNXARMStat() else: return QNXARMEBStat() - ql.log.warning(f"Unrecognized arch && os with {ql.archtype} and {ql.ostype} for stat! Fallback to Linux x86.") + ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.ostype} for stat! Fallback to Linux x86.") return LinuxX86Stat() def __common_pack_stat_struct(stat, info) -> bytes: @@ -1064,7 +1064,7 @@ def ql_syscall_chmod(ql: Qiling, filename: int, mode: int): return 0 def ql_syscall_fchmod(ql: Qiling, fd: int, mode: int): - if not (0 < fd < NR_OPEN) or ql.os.fd[fd] == 0: + if fd not in range(NR_OPEN) or ql.os.fd[fd] is None: return -EBADF return 0 @@ -1095,46 +1095,40 @@ def ql_syscall_newfstatat(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flag: return regreturn -def ql_syscall_fstat64(ql: Qiling, fd, buf_ptr): - if not hasattr(ql.os.fd[fd], "fstat"): - regreturn = -1 - elif ql.os.fd[fd].fstat() == -1: - regreturn = 0 - elif 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - buf = pack_stat64_struct(ql, ql.os.fd[fd].fstat()) +def ql_syscall_fstat64(ql: Qiling, fd: int, buf_ptr: int): + if fd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None or not hasattr(f, "fstat"): + return -1 + + fstat = f.fstat() + + if fstat != -1: + buf = pack_stat64_struct(ql, fstat) ql.mem.write(buf_ptr, buf) - regreturn = 0 - else: - regreturn = -1 - - if regreturn == 0: - ql.log.debug("fstat64 write completed") - else: - ql.log.debug("fstat64 read/write fail") - - return regreturn + return 0 -def ql_syscall_fstat(ql: Qiling, fd, buf_ptr): - if not hasattr(ql.os.fd[fd], "fstat"): - regreturn = -1 - # elif ql.os.fd[fd].fstat() == -1: - # regreturn = 0 - elif 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - buf = pack_stat_struct(ql, ql.os.fd[fd].fstat()) +def ql_syscall_fstat(ql: Qiling, fd: int, buf_ptr: int): + if fd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None or not hasattr(f, "fstat"): + return -1 + + fstat = f.fstat() + + if fstat != -1: + buf = pack_stat_struct(ql, fstat) ql.mem.write(buf_ptr, buf) - regreturn = 0 - else: - regreturn = -1 - - if regreturn == 0: - ql.log.debug("fstat write completed") - else: - ql.log.debug("fstat read/write fail") - - return regreturn + return 0 # int stat(const char *path, struct stat *buf); @@ -1233,7 +1227,7 @@ def ql_syscall_statx(ql: Qiling, dirfd: int, path: int, flags: int, mask: int, b tv_sec = struct.unpack('i', struct.pack('f', tv_sec))[0] tv_nsec = struct.unpack('i', struct.pack('f', tv_nsec))[0] - if ql.archbit == 32: + if ql.arch.bits == 32: return StatxTimestamp32(tv_sec=tv_sec, tv_nsec=tv_nsec) else: return StatxTimestamp64(tv_sec=tv_sec, tv_nsec=tv_nsec) @@ -1253,7 +1247,7 @@ def ql_syscall_statx(ql: Qiling, dirfd: int, path: int, flags: int, mask: int, b else: st = Stat(real_path, fd) - if ql.archbit == 32: + if ql.arch.bits == 32: Statx = Statx32 else: Statx = Statx64 diff --git a/qiling/os/posix/syscall/time.py b/qiling/os/posix/syscall/time.py index ea63f5c6..e7819ff2 100644 --- a/qiling/os/posix/syscall/time.py +++ b/qiling/os/posix/syscall/time.py @@ -13,7 +13,7 @@ def ql_syscall_time(ql: Qiling): return int(time.time()) def __sleep_common(ql: Qiling, req: int, rem: int) -> int: - n = ql.pointersize + n = ql.arch.pointersize tv_sec = ql.unpack(ql.mem.read(req, n)) tv_sec += ql.unpack(ql.mem.read(req + n, n)) / 1000000000 diff --git a/qiling/os/posix/syscall/uio.py b/qiling/os/posix/syscall/uio.py index fda88cc8..f07346a0 100644 --- a/qiling/os/posix/syscall/uio.py +++ b/qiling/os/posix/syscall/uio.py @@ -7,7 +7,7 @@ from qiling import Qiling def ql_syscall_writev(ql: Qiling, fd: int, vec: int, vlen: int): regreturn = 0 - size_t_len = ql.pointersize + size_t_len = ql.arch.pointersize iov = ql.mem.read(vec, vlen * size_t_len * 2) ql.log.debug('writev() CONTENT:') @@ -27,7 +27,7 @@ def ql_syscall_writev(ql: Qiling, fd: int, vec: int, vlen: int): def ql_syscall_readv(ql: Qiling, fd: int, vec: int, vlen: int): regreturn = 0 - size_t_len = ql.pointersize + size_t_len = ql.arch.pointersize iov = ql.mem.read(vec, vlen * size_t_len * 2) ql.log.debug('readv() CONTENT:') diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index e1650076..44b1a01b 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -137,18 +137,23 @@ def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int): return regreturn -def ql_syscall_lseek(ql: Qiling, fd: int, offset: int, lseek_origin: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - offset = ql.unpacks(ql.pack(offset)) +def ql_syscall_lseek(ql: Qiling, fd: int, offset: int, origin: int): + if fd not in range(NR_OPEN): + return -EBADF - try: - regreturn = ql.os.fd[fd].seek(offset, lseek_origin) - except OSError: - regreturn = -1 - else: - regreturn = -EBADF + f = ql.os.fd[fd] - # ql.log.debug("lseek(fd = %d, ofset = 0x%x, origin = 0x%x) = %d" % (lseek_fd, lseek_ofset, lseek_origin, regreturn)) + if f is None: + return -EBADF + + offset = ql.unpacks(ql.pack(offset)) + + try: + regreturn = f.seek(offset, origin) + except OSError: + regreturn = -1 + + # ql.log.debug("lseek(fd = %d, ofset = 0x%x, origin = 0x%x) = %d" % (fd, offset, origin, regreturn)) return regreturn @@ -163,7 +168,7 @@ def ql_syscall__llseek(ql: Qiling, fd: int, offset_high: int, offset_low: int, r except OSError: regreturn = -1 else: - ql.mem.write(result, ql.pack64(ret)) + ql.mem.write_ptr(result, ret, 8) regreturn = 0 # ql.log.debug("_llseek(%d, 0x%x, 0x%x, 0x%x) = %d" % (fd, offset_high, offset_low, origin, regreturn)) @@ -207,53 +212,67 @@ def ql_syscall_access(ql: Qiling, path: int, mode: int): def ql_syscall_close(ql: Qiling, fd: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - ql.os.fd[fd].close() - ql.os.fd[fd] = 0 - regreturn = 0 - else: - regreturn = -1 + if fd not in range(NR_OPEN): + return -1 - return regreturn + f = ql.os.fd[fd] + + if f is None: + return -1 + + f.close() + ql.os.fd[fd] = None + + return 0 def ql_syscall_pread64(ql: Qiling, fd: int, buf: int, length: int, offt: int): + if fd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None: + return -1 + # https://chromium.googlesource.com/linux-syscall-support/+/2c73abf02fd8af961e38024882b9ce0df6b4d19b # https://chromiumcodereview.appspot.com/10910222 - if ql.archtype == QL_ARCH.MIPS: - offt = ql.unpack64(ql.mem.read(ql.reg.arch_sp + 0x10, 8)) + if ql.arch.type == QL_ARCH.MIPS: + offt = ql.mem.read_ptr(ql.arch.regs.arch_sp + 0x10, 8) - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - try: - pos = ql.os.fd[fd].tell() - ql.os.fd[fd].seek(offt) + try: + pos = f.tell() + f.seek(offt) - data = ql.os.fd[fd].read(length) - ql.os.fd[fd].seek(pos) + data = f.read(length) + f.seek(pos) - ql.mem.write(buf, data) - regreturn = len(data) - except: - regreturn = -1 - else: + ql.mem.write(buf, data) + except: regreturn = -1 + else: + regreturn = len(data) return regreturn def ql_syscall_read(ql: Qiling, fd, buf: int, length: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - try: - data = ql.os.fd[fd].read(length) - ql.mem.write(buf, data) - except: - regreturn = -EBADF - else: - ql.log.debug(f'read() CONTENT: {data!r}') - regreturn = len(data) + if fd not in range(NR_OPEN): + return -EBADF - else: + f = ql.os.fd[fd] + + if f is None: + return -EBADF + + try: + data = f.read(length) + ql.mem.write(buf, data) + except: regreturn = -EBADF + else: + ql.log.debug(f'read() CONTENT: {data!r}') + regreturn = len(data) return regreturn @@ -365,7 +384,7 @@ def ql_syscall_getppid(ql: Qiling): def ql_syscall_vfork(ql: Qiling): - if ql.platform_os == QL_OS.WINDOWS: + if ql.host.os == QL_OS.WINDOWS: try: pid = Process() pid = 0 @@ -407,7 +426,7 @@ def ql_syscall_execve(ql: Qiling, pathname: int, argv: int, envp: int): break yield ql.os.utils.read_cstring(elem) - addr += ql.pointersize + addr += ql.arch.pointersize args = [s for s in __read_str_array(argv)] @@ -427,13 +446,23 @@ def ql_syscall_execve(ql: Qiling, pathname: int, argv: int, envp: int): ql.clear_ql_hooks() # Clean debugger to prevent port conflicts - ql.debugger = None + # ql.debugger = None if ql.code: return - ql._uc = ql.arch.init_uc - QlCoreHooks.__init__(ql, ql._uc) + # recreate cached uc + del ql.arch.uc + uc = ql.arch.uc + + # propagate new uc to arch internals + ql.arch.regs.uc = uc + + if hasattr(ql.arch, 'msr'): + ql.arch.msr.uc = uc + + ql.uc = uc + QlCoreHooks.__init__(ql, uc) ql.os.load() ql.loader.run() @@ -441,39 +470,50 @@ def ql_syscall_execve(ql: Qiling, pathname: int, argv: int, envp: int): def ql_syscall_dup(ql: Qiling, oldfd: int): - regreturn = -EBADF + if oldfd not in range(NR_OPEN): + return -EBADF - if oldfd in range(256): - if ql.os.fd[oldfd] != 0: - newfd = ql.os.fd[oldfd].dup() + f = ql.os.fd[oldfd] - for i, val in enumerate(ql.os.fd): - if val == 0: - ql.os.fd[i] = newfd - regreturn = i - break - else: - regreturn = -EMFILE + if f is None: + return -EBADF - return regreturn + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) + + if idx == -1: + return -EMFILE + + ql.os.fd[idx] = f.dup() + + return idx def ql_syscall_dup2(ql: Qiling, fd: int, newfd: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - if 0 <= newfd < NR_OPEN: - ql.os.fd[newfd] = ql.os.fd[fd].dup() - return newfd + if fd not in range(NR_OPEN) or newfd not in range(NR_OPEN): + return -EBADF - return -EBADF + f = ql.os.fd[fd] + + if f is None: + return -EBADF + + ql.os.fd[newfd] = f.dup() + + return newfd -def ql_syscall_dup3(ql: Qiling, fd, newfd: int, flags: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - if 0 <= newfd < NR_OPEN: - ql.os.fd[newfd] = ql.os.fd[fd].dup() - return newfd +def ql_syscall_dup3(ql: Qiling, fd: int, newfd: int, flags: int): + if fd not in range(NR_OPEN) or newfd not in range(NR_OPEN): + return -1 - return -1 + f = ql.os.fd[fd] + + if f is None: + return -1 + + ql.os.fd[newfd] = f.dup() + + return newfd def ql_syscall_set_tid_address(ql: Qiling, tidptr: int): if ql.os.thread_management: @@ -489,37 +529,23 @@ def ql_syscall_set_tid_address(ql: Qiling, tidptr: int): def ql_syscall_pipe(ql: Qiling, pipefd: int): rd, wd = ql_pipe.open() - idx1 = -1 - idx2 = -1 + unpopulated_fd = (i for i in range(NR_OPEN) if ql.os.fd[i] is None) + idx1 = next(unpopulated_fd, -1) + idx2 = next(unpopulated_fd, -1) - for i in range(NR_OPEN): - if ql.os.fd[i] == 0: - idx1 = i - break + if (idx1 == -1) or (idx2 == -1): + return -1 - if idx1 == -1: - regreturn = -1 + ql.os.fd[idx1] = rd + ql.os.fd[idx2] = wd + + if ql.arch.type == QL_ARCH.MIPS: + ql.arch.regs.v1 = idx2 + regreturn = idx1 else: - for i in range(NR_OPEN): - if ql.os.fd[i] == 0 and i != idx1: - idx2 = i - break - - if idx2 == -1: - regreturn = -1 - else: - ql.os.fd[idx1] = rd - ql.os.fd[idx2] = wd - - if ql.archtype== QL_ARCH.MIPS: - ql.reg.v1 = idx2 - regreturn = idx1 - else: - ql.mem.write(pipefd + 0, ql.pack32(idx1)) - ql.mem.write(pipefd + 4, ql.pack32(idx2)) - regreturn = 0 - - ql.log.debug("pipe(%x, [%d, %d]) = %d" % (pipefd, idx1, idx2, regreturn)) + ql.mem.write_ptr(pipefd + 0, idx1, 4) + ql.mem.write_ptr(pipefd + 4, idx2, 4) + regreturn = 0 return regreturn @@ -579,7 +605,7 @@ def ql_syscall_unlink(ql: Qiling, pathname: int): file_path = ql.os.utils.read_cstring(pathname) real_path = ql.os.path.transform_to_real_path(file_path) - opened_fds = [getattr(ql.os.fd[i], 'name', None) for i in range(NR_OPEN) if ql.os.fd[i] != 0] + opened_fds = [getattr(ql.os.fd[i], 'name', None) for i in range(NR_OPEN) if ql.os.fd[i] is not None] path = pathlib.Path(real_path) if any((real_path not in opened_fds, path.is_block_device(), path.is_fifo(), path.is_socket(), path.is_symlink())): @@ -673,7 +699,7 @@ def __getdents_common(ql: Qiling, fd: int, dirp: int, count: int, *, is_64: bool return bytes([t]) if ql.os.fd[fd].tell() == 0: - n = ql.pointersize + n = ql.arch.pointersize total_size = 0 results = os.scandir(ql.os.fd[fd].name) _ent_count = 0 diff --git a/qiling/os/posix/syscall/wait.py b/qiling/os/posix/syscall/wait.py index 8902f23c..8315b294 100644 --- a/qiling/os/posix/syscall/wait.py +++ b/qiling/os/posix/syscall/wait.py @@ -16,7 +16,7 @@ def ql_syscall_wait4(ql: Qiling, pid: int, wstatus: int, options: int, rusage: i spid, status, _ = os.wait4(pid, options) if wstatus: - ql.mem.write(wstatus, ql.pack32(status)) + ql.mem.write_ptr(wstatus, status, 4) retval = spid except ChildProcessError: diff --git a/qiling/os/qnx/map_msgtype.py b/qiling/os/qnx/map_msgtype.py index 7208b851..48f674cd 100644 --- a/qiling/os/qnx/map_msgtype.py +++ b/qiling/os/qnx/map_msgtype.py @@ -6,7 +6,7 @@ from qiling.const import QL_ARCH def map_msgtype(ql, msgtype): - if ql.archtype == QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: for k, v in msgtype_table.items(): if v == msgtype: return f'ql_qnx_msg_{k}' diff --git a/qiling/os/qnx/map_syscall.py b/qiling/os/qnx/map_syscall.py index 774ca17b..d668579f 100644 --- a/qiling/os/qnx/map_syscall.py +++ b/qiling/os/qnx/map_syscall.py @@ -7,7 +7,7 @@ from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF def map_syscall(ql, syscall_num): - if ql.archtype == QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: return f'{SYSCALL_PREF}{arm_syscall_table[syscall_num]}' # Source: https://github.com/vocho/openqnx diff --git a/qiling/os/qnx/message.py b/qiling/os/qnx/message.py index f0437335..e4621fcf 100644 --- a/qiling/os/qnx/message.py +++ b/qiling/os/qnx/message.py @@ -146,7 +146,7 @@ def ql_qnx_msg_io_lseek(ql:Qiling, coid, smsg, sparts, rmsg, rparts, *args, **kw ql.log.debug(f'msg_io_lseek(coid = {coid} => fd = {fd}, offset = {offset}, whence = {lseek_whence[whence]})') # lseek file regreturn = ql_syscall_lseek(ql, fd, offset, whence) - ql.mem.write(rmsg, ql.pack64(regreturn)) + ql.mem.write_ptr(rmsg, regreturn, 8) return 0 # lib/c/1/fstat.c diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 55bcc10d..82a0483a 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -10,6 +10,7 @@ from typing import Callable from unicorn import UcError from qiling import Qiling +from qiling.arch import arm_utils from qiling.os.posix.posix import QlOsPosix from qiling.os.qnx.const import NTO_SIDE_CHANNEL, SYSMGR_PID, SYSMGR_CHID, SYSMGR_COID from qiling.os.qnx.helpers import QnxConn @@ -36,7 +37,7 @@ class QlOsQnx(QlOsPosix): QL_ARCH.MIPS : mips.mipso32, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64: riscv.riscv, - }[ql.archtype](ql) + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) @@ -64,11 +65,11 @@ class QlOsQnx(QlOsPosix): return # ARM - if self.ql.archtype == QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) #self.thread_class = thread.QlLinuxARMThread - self.ql.arch.init_get_tls() + arm_utils.init_get_tls(self.ql, self.ql.arch.arm_get_tls_addr) def hook_syscall(self, intno= None, int = None): @@ -114,7 +115,7 @@ class QlOsQnx(QlOsPosix): self.ql.mem.write(self.syspage_addr, sp.read()) # Address of struct _thread_local_storage for our thread - self.ql.mem.write(self.cpupage_addr, self.ql.pack32(self.cpupage_tls_addr)) + self.ql.mem.write_ptr(self.cpupage_addr, self.cpupage_tls_addr, 4) tls = _thread_local_storage(self.ql, self.cpupage_tls_addr) # Fill TLS structure with proper values @@ -126,7 +127,7 @@ class QlOsQnx(QlOsPosix): tls.updateToMem() # Address of the system page - self.ql.mem.write(self.cpupage_addr + 8, self.ql.pack32(self.syspage_addr)) + self.ql.mem.write_ptr(self.cpupage_addr + 8, self.syspage_addr, 4) try: if self.ql.code: @@ -134,7 +135,7 @@ class QlOsQnx(QlOsPosix): else: if self.ql.loader.elf_entry != self.ql.loader.entry_point: entry_address = self.ql.loader.elf_entry - if self.ql.archtype == QL_ARCH.ARM and entry_address & 1 == 1: + if self.ql.arch.type == QL_ARCH.ARM and entry_address & 1 == 1: entry_address -= 1 self.ql.emu_start(self.ql.loader.entry_point, entry_address, self.ql.timeout) self.run_function_after_load() diff --git a/qiling/os/qnx/syscall.py b/qiling/os/qnx/syscall.py index cdf4d9c6..9a8d0200 100644 --- a/qiling/os/qnx/syscall.py +++ b/qiling/os/qnx/syscall.py @@ -49,7 +49,7 @@ def ql_syscall_clock_time(ql:Qiling, id, new, old, *args, **kw): if old != 0: clock_old = ql.unpack64(ql.mem.read(old, 8)) ql.log.debug(f'syscall_clock_time(id = {clock_types[id]}, old = {clock_old})') - ql.mem.write(old, ql.pack64(time_ns())) + ql.mem.write_ptr(old, time_ns(), 8) return 0 @@ -107,7 +107,7 @@ def ql_syscall_sys_cpupage_get(ql:Qiling, index, *args, **kw): return ql.os.cpupage_addr # CPUPAGE_PLS elif index == 1: - return ql.unpack32(ql.mem.read(ql.os.cpupage_addr + 4, 4)) + return ql.mem.read_ptr(ql.os.cpupage_addr + 4, 4) # CPUPAGE_SYSPAGE elif index == 2: return ql.os.syspage_addr @@ -118,7 +118,7 @@ def ql_syscall_sys_cpupage_set(ql:Qiling, index, value, *args, **kw): # CPUPAGE_PLS if index == 1: - ql.mem.write(ql.os.cpupage_addr + 4, ql.pack32(value)) + ql.mem.write_ptr(ql.os.cpupage_addr + 4, value, 4) return EOK ql.log.warning(f'ql_syscall_sys_cpupage_get (index {index:d}) not implemented') diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index d80ac385..3532a159 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -399,7 +399,7 @@ def hook_LocateHandleBuffer(ql: Qiling, address: int, params): for handle in handles: write_int64(ql, address, handle) - address += ql.pointersize + address += ql.arch.pointersize return EFI_SUCCESS @@ -419,7 +419,7 @@ def hook_InstallMultipleProtocolInterfaces(ql: Qiling, address: int, params): handle = read_int64(ql, params["Handle"]) if handle == 0: - handle = ql.loader.dxe_context.heap.alloc(ql.pointersize) + handle = ql.loader.dxe_context.heap.alloc(ql.arch.pointersize) dic = ql.loader.dxe_context.protocols.get(handle, {}) @@ -504,7 +504,6 @@ def hook_SetMem(ql: Qiling, address: int, params): value: int = params["Value"] & 0xff size = params["Size"] - byteorder = 'little' if ql.archendian == QL_ENDIAN.EL else 'big' ql.mem.write(buffer, bytes([value]) * size) @dxeapi(params = { diff --git a/qiling/os/uefi/fncc.py b/qiling/os/uefi/fncc.py index 78838a12..83f999bf 100644 --- a/qiling/os/uefi/fncc.py +++ b/qiling/os/uefi/fncc.py @@ -11,7 +11,7 @@ from qiling.const import QL_INTERCEPT def dxeapi(params: Mapping[str, Any] = {}): def decorator(func): def wrapper(ql: Qiling): - pc = ql.reg.arch_pc + pc = ql.arch.regs.arch_pc fname = func.__name__ f = ql.os.user_defined_api[QL_INTERCEPT.CALL].get(fname) or func diff --git a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py index cda5e946..22f92537 100644 --- a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py @@ -61,7 +61,7 @@ def hook_Register(ql: Qiling, address: int, params): # a value of -1 indicates that the swsmi index for this handler is flexible and # should be assigned by the protocol - if idx == ((1 << ql.archbit) - 1): + if idx == ((1 << ql.arch.bits) - 1): idx = next((i for i in range(1, MAXIMUM_SWI_VALUE) if i not in handlers), None) if idx is None: @@ -80,7 +80,7 @@ def hook_Register(ql: Qiling, address: int, params): return EFI_INVALID_PARAMETER # allocate handle and return it through out parameter - Handle = ql.loader.smm_context.heap.alloc(ql.pointersize) + Handle = ql.loader.smm_context.heap.alloc(ql.arch.pointersize) utils.write_int64(ql, DispatchHandle, Handle) args = { diff --git a/qiling/os/uefi/protocols/common.py b/qiling/os/uefi/protocols/common.py index fe582b54..a729f5f4 100644 --- a/qiling/os/uefi/protocols/common.py +++ b/qiling/os/uefi/protocols/common.py @@ -22,7 +22,7 @@ def LocateHandles(context, params): else: handles = [] - return len(handles) * context.ql.pointersize, handles + return len(handles) * context.ql.arch.pointersize, handles def InstallProtocolInterface(context, params): handle = read_int64(context.ql, params["Handle"]) @@ -100,7 +100,7 @@ def LocateHandle(context, params): for handle in handles: write_int64(context.ql, ptr, handle) - ptr += context.ql.pointersize + ptr += context.ql.arch.pointersize ret = EFI_SUCCESS diff --git a/qiling/os/uefi/smm.py b/qiling/os/uefi/smm.py index c1cee75d..15e80e48 100644 --- a/qiling/os/uefi/smm.py +++ b/qiling/os/uefi/smm.py @@ -155,7 +155,7 @@ class SmmEnv: # write cpu state to ssa (partially) # that can take place only after smram ranges have been unlocked for ucreg, (width, regidx) in SmmEnv.SSA_REG_MAP.items(): - val = self.ql.reg.read(ucreg) + val = self.ql.arch.regs.read(ucreg) pack = { 8 : self.ql.pack64, @@ -189,7 +189,7 @@ class SmmEnv: 1 : self.ql.unpack8 }[width] - self.ql.reg.write(ucreg, unpack(data)) + self.ql.arch.regs.write(ucreg, unpack(data)) # lock smram ranges for access for lbound, ubound in self.__mapped_smram_ranges(): @@ -217,7 +217,7 @@ class SmmEnv: DispatchHandle = args['DispatchHandle'] Context = heap.alloc(EFI_SMM_SW_REGISTER_CONTEXT.sizeof()) CommBuffer = heap.alloc(EFI_SMM_SW_CONTEXT.sizeof()) - CommBufferSize = heap.alloc(ql.pointersize) + CommBufferSize = heap.alloc(ql.arch.pointersize) # setup Context args['RegisterContext'].saveTo(ql, Context) @@ -237,7 +237,7 @@ class SmmEnv: ql.log.info(f'Leaving SWSMI handler {idx:#04x}') # unwind ms64 shadow space - ql.reg.arch_sp += (4 * ql.pointersize) + ql.arch.regs.arch_sp += (4 * ql.arch.pointersize) # release handler resources heap.free(DispatchHandle) @@ -256,7 +256,7 @@ class SmmEnv: onexit(ql) # hook returning from swsmi handler - cleanup_trap = heap.alloc(ql.pointersize) + cleanup_trap = heap.alloc(ql.arch.pointersize) hret = ql.hook_address(__cleanup, cleanup_trap) ql.log.info(f'Entering SWSMI handler {idx:#04x}') diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 1ee19fbb..709000db 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -33,7 +33,7 @@ class QlOsUefi(QlOs): cc: QlCC = { 32: intel.cdecl, 64: intel.ms64 - }[ql.archbit](ql) + }[ql.arch.bits](ql.arch) self.fcall = QlFunctionCall(ql, cc) @@ -116,7 +116,7 @@ class QlOsUefi(QlOs): p = re.compile(r'^((?:00)+)') def __emit_reg(size: int, reg: str): - val = f'{self.ql.reg.read(reg):0{size * 2}x}' + val = f'{self.ql.arch.regs.read(reg):0{size * 2}x}' padded = p.sub("\x1b[90m\\1\x1b[39m", val, 1) return f'{reg:3s} = {padded}' @@ -146,7 +146,7 @@ class QlOsUefi(QlOs): def emit_disasm(self, address: int, data: bytearray, num_insns: int = 8): - md = self.ql.create_disassembler() + md = self.ql.arch.disassembler self.ql.log.error('Disassembly:') @@ -160,21 +160,21 @@ class QlOsUefi(QlOs): self.ql.log.error('Stack:') for i in range(-nitems, nitems + 1): - offset = i * self.ql.pointersize + offset = i * self.ql.arch.pointersize try: item = self.ql.arch.stack_read(offset) except UcError: data = '(unavailable)' else: - data = f'{item:0{self.ql.pointersize * 2}x}' + data = f'{item:0{self.ql.arch.pointersize * 2}x}' - self.ql.log.error(f'{self.ql.reg.arch_sp + offset:08x} : {data}{" <=" if i == 0 else ""}') + self.ql.log.error(f'{self.ql.arch.regs.arch_sp + offset:08x} : {data}{" <=" if i == 0 else ""}') self.ql.log.error('') def emu_error(self): - pc = self.ql.reg.arch_pc + pc = self.ql.arch.regs.arch_pc try: data = self.ql.mem.read(pc, size=64) diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 8c66631c..fe357674 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -26,11 +26,11 @@ def execute_protocol_notifications(ql: Qiling, from_hook: bool = False) -> bool: if not ql.loader.notify_list: return False - next_hook = ql.loader.context.heap.alloc(ql.pointersize) + next_hook = ql.loader.context.heap.alloc(ql.arch.pointersize) def __notify_next(ql: Qiling): # discard previous callback's shadow space - ql.reg.arch_sp += (4 * ql.pointersize) + ql.arch.regs.arch_sp += (4 * ql.arch.pointersize) if ql.loader.notify_list: event_id, notify_func, callback_args = ql.loader.notify_list.pop(0) @@ -44,22 +44,22 @@ def execute_protocol_notifications(ql: Qiling, from_hook: bool = False) -> bool: ql.loader.context.heap.free(next_hook) hret.remove() - ql.reg.rax = EFI_SUCCESS - ql.reg.arch_pc = ql.stack_pop() + ql.arch.regs.rax = EFI_SUCCESS + ql.arch.regs.arch_pc = ql.stack_pop() hret = ql.hook_address(__notify_next, next_hook) # __notify_next unwinds the previous callback shadow space allocated by call_function. however, on its first invocation # there is no such shadow space. to maintain stack consistency we set here a bogus shadow space that may be discarded # safely - ql.reg.arch_sp -= (4 * ql.pointersize) + ql.arch.regs.arch_sp -= (4 * ql.arch.pointersize) # To avoid having two versions of the code the first notify function will also be called from the __notify_next hook. if from_hook: ql.stack_push(next_hook) else: ql.stack_push(ql.loader.context.end_of_execution_ptr) - ql.reg.arch_pc = next_hook + ql.arch.regs.arch_pc = next_hook return True @@ -67,49 +67,49 @@ def ptr_read8(ql: Qiling, addr: int) -> int: """Read BYTE data from a pointer """ - return ql.unpack8(ql.mem.read(addr, 1)) + return ql.mem.read_ptr(addr, 1) def ptr_write8(ql: Qiling, addr: int, val: int) -> None: """Write BYTE data to a pointer """ - ql.mem.write(addr, ql.pack8(val)) + ql.mem.write_ptr(addr, val, 1) def ptr_read16(ql: Qiling, addr: int) -> int: """Read WORD data from a pointer """ - return ql.unpack16(ql.mem.read(addr, 2)) + return ql.mem.read_ptr(addr, 2) def ptr_write16(ql: Qiling, addr: int, val: int) -> None: """Write WORD data to a pointer """ - ql.mem.write(addr, ql.pack16(val)) + ql.mem.write_ptr(addr, val, 2) def ptr_read32(ql: Qiling, addr: int) -> int: """Read DWORD data from a pointer """ - return ql.unpack32(ql.mem.read(addr, 4)) + return ql.mem.read_ptr(addr, 4) def ptr_write32(ql: Qiling, addr: int, val: int) -> None: """Write DWORD data to a pointer """ - ql.mem.write(addr, ql.pack32(val)) + ql.mem.write_ptr(addr, val, 4) def ptr_read64(ql: Qiling, addr: int) -> int: """Read QWORD data from a pointer """ - return ql.unpack64(ql.mem.read(addr, 8)) + return ql.mem.read_ptr(addr, 8) def ptr_write64(ql: Qiling, addr: int, val: int) -> None: """Write QWORD data to a pointer """ - ql.mem.write(addr, ql.pack64(val)) + ql.mem.write_ptr(addr, val, 8) # backward comptability read_int8 = ptr_read8 diff --git a/qiling/os/utils.py b/qiling/os/utils.py index a5c3294c..47b20686 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -7,25 +7,19 @@ This module is intended for general purpose functions that are only used in qiling.os """ -from typing import Any, MutableMapping, Mapping, Union, Sequence, MutableSequence, Tuple +from typing import Any, List, MutableMapping, Mapping, Set, Union, Sequence, MutableSequence, Tuple from uuid import UUID from qiling import Qiling from qiling.const import QL_VERBOSE -class QlOsUtils: - - ELLIPSIS_PREF = r'__qlva_' - - def __init__(self, ql: Qiling): - self.ql = ql - - # We can save every syscall called - self.syscalls = {} +class QlOsStats: + def __init__(self): + self.syscalls: MutableMapping[str, List] = {} self.syscalls_counter = 0 - self.appeared_strings = {} + self.appeared_strings: MutableMapping[str, Set] = {} - def clear_syscalls(self): + def clear(self): """Reset API and string appearance stats. """ @@ -33,7 +27,7 @@ class QlOsUtils: self.syscalls_counter = 0 self.appeared_strings = {} - def _call_api(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: + def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: """Record API calls along with their details. Args: @@ -48,16 +42,16 @@ class QlOsUtils: name = name[5:] self.syscalls.setdefault(name, []).append({ - 'params': params, - 'retval': retval, - 'address': address, - 'retaddr': retaddr, - 'position': self.syscalls_counter + 'params' : params, + 'retval' : retval, + 'address' : address, + 'retaddr' : retaddr, + 'position' : self.syscalls_counter }) self.syscalls_counter += 1 - def string_appearance(self, s: str) -> None: + def log_string(self, s: str) -> None: """Record strings appearance as they are encountered during emulation. Args: @@ -67,6 +61,13 @@ class QlOsUtils: for token in s.split(' '): self.appeared_strings.setdefault(token, set()).add(self.syscalls_counter) +class QlOsUtils: + + ELLIPSIS_PREF = r'__qlva_' + + def __init__(self, ql: Qiling): + self.ql = ql + @staticmethod def read_string(ql: Qiling, address: int, terminator: bytes) -> str: result = bytearray() @@ -86,14 +87,14 @@ class QlOsUtils: # We need to remove \x00 inside the string. Compares do not work otherwise s = s.replace("\x00", "") - self.string_appearance(s) + self.ql.os.stats.log_string(s) return s def read_cstring(self, address: int) -> str: s = QlOsUtils.read_string(self.ql, address, b'\x00') - self.string_appearance(s) + self.ql.os.stats.log_string(s) return s @@ -139,7 +140,7 @@ class QlOsUtils: # optional prefixes and suffixes fret = f' = {ret}' if ret is not None else '' fpass = f' (PASSTHRU)' if passthru else '' - faddr = f'{address:#0{self.ql.archbit // 4 + 2}x}: ' if self.ql.verbose >= QL_VERBOSE.DEBUG else '' + faddr = f'{address:#0{self.ql.arch.bits // 4 + 2}x}: ' if self.ql.verbose >= QL_VERBOSE.DEBUG else '' log = f'{faddr}{fname}({fargs}){fret}{fpass}' @@ -164,7 +165,7 @@ class QlOsUtils: def va_list(self, format: str, ptr: int) -> MutableSequence[int]: count = format.count("%") - return [self.ql.unpack(self.ql.mem.read(ptr + i * self.ql.pointersize, self.ql.pointersize)) for i in range(count)] + return [self.ql.mem.read_ptr(ptr + i * self.ql.arch.pointersize) for i in range(count)] def sprintf(self, buff: int, format: str, args: MutableSequence, wstring: bool = False) -> int: out = self.__common_printf(format, args, wstring) diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index b4d244db..baf247be 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -36,7 +36,7 @@ def __RegOpenKey(ql: Qiling, address: int, params): new_handle = Handle(obj=key) ql.os.handle_manager.append(new_handle) if phkResult != 0: - ql.mem.write(phkResult, ql.pack(new_handle.id)) + ql.mem.write_ptr(phkResult, new_handle.id) return ERROR_SUCCESS def __RegQueryValue(ql: Qiling, address: int, params): @@ -78,7 +78,7 @@ def __RegQueryValue(ql: Qiling, address: int, params): length = ql.os.registry_manager.write_reg_value_into_mem(value, reg_type, lpData) # set lpcbData max_size = int.from_bytes(ql.mem.read(lpcbData, 4), byteorder="little") - ql.mem.write(lpcbData, ql.pack(length)) + ql.mem.write_ptr(lpcbData, length) if max_size < length: ret = ERROR_MORE_DATA @@ -106,7 +106,7 @@ def __RegCreateKey(ql: Qiling, address: int, params): new_handle = Handle(obj=s_hKey + "\\" + lpSubKey) ql.os.handle_manager.append(new_handle) if phkResult != 0: - ql.mem.write(phkResult, ql.pack(new_handle.id)) + ql.mem.write_ptr(phkResult, new_handle.id) else: # elicn: is this even reachable? new_handle = 0 @@ -494,7 +494,7 @@ def hook_GetSidSubAuthorityCount(ql: Qiling, address: int, params): 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.pointersize * num) + addr_authority = sid.addr + 8 + (ql.arch.pointersize * num) return addr_authority @@ -672,7 +672,7 @@ def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params): handle = Handle(obj=sid, id=sid_addr) ql.os.handle_manager.append(handle) dest = params["pSid"] - ql.mem.write(dest, ql.pack(sid_addr)) + ql.mem.write_ptr(dest, sid_addr) return 1 @@ -744,7 +744,7 @@ def hook_CheckTokenMembership(ql: Qiling, address: int, params): assert False, 'unimplemented' else: assert False, 'unimplemented' - ql.mem.write(params['IsMember'], ql.pack(IsMember)) + ql.mem.write_ptr(params['IsMember'], IsMember) return 1 diff --git a/qiling/os/windows/dlls/kernel32/errhandlingapi.py b/qiling/os/windows/dlls/kernel32/errhandlingapi.py index 3926a273..5dbc32d7 100644 --- a/qiling/os/windows/dlls/kernel32/errhandlingapi.py +++ b/qiling/os/windows/dlls/kernel32/errhandlingapi.py @@ -97,9 +97,9 @@ def hook_AddVectoredExceptionHandler(ql: Qiling, address: int, params): # https://github.com/LordNoteworthy/al-khaser/wiki/Anti-Debugging-Tricks#interrupt-0x2d pointer = ql.os.heap.alloc(0x4) # the value has just to be different from 0x80000003 - ql.mem.write(pointer, ql.pack32(0)) + ql.mem.write_ptr(pointer, 0, 4) double_pointer = ql.os.heap.alloc(0x4) - ql.mem.write(double_pointer, ql.pack32(pointer)) + ql.mem.write_ptr(double_pointer, pointer, 4) # arg ql.stack_push(double_pointer) @@ -112,12 +112,12 @@ def hook_AddVectoredExceptionHandler(ql: Qiling, address: int, params): # FIXME: probably this works only with al-khaser. pointer = ql.os.heap.alloc(0x4) # the value has just to be different from 0x80000003 - ql.mem.write(pointer, ql.pack32(0)) + ql.mem.write_ptr(pointer, 0, 4) double_pointer = ql.os.heap.alloc(0x4) - ql.mem.write(double_pointer, ql.pack32(pointer)) + ql.mem.write_ptr(double_pointer, pointer, 4) - ql.reg.eax = double_pointer - ql.reg.esi = user_data + ql.arch.regs.eax = double_pointer + ql.arch.regs.esi = user_data addr = params["Handler"] diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 514bc7a3..d4811027 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -171,12 +171,12 @@ def hook_ReadFile(ql: Qiling, address: int, params): read_len = nNumberOfBytesToRead ql.mem.write(lpBuffer, s) - ql.mem.write(lpNumberOfBytesRead, ql.pack32(read_len)) + ql.mem.write_ptr(lpNumberOfBytesRead, read_len, 4) else: f = ql.os.handle_manager.get(hFile).obj data = f.read(nNumberOfBytesToRead) ql.mem.write(lpBuffer, data) - ql.mem.write(lpNumberOfBytesRead, ql.pack32(len(data))) + ql.mem.write_ptr(lpNumberOfBytesRead, len(data), 4) return 1 @@ -203,8 +203,8 @@ def hook_WriteFile(ql: Qiling, address: int, params): if hFile == STD_OUTPUT_HANDLE: s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) - ql.os.utils.string_appearance(s.decode()) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.os.stats.log_string(s.decode()) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) else: f = ql.os.handle_manager.get(hFile) @@ -217,7 +217,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) nNumberOfBytesWritten = f.write(bytes(buffer)) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesWritten)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesWritten, 4) return 1 @@ -402,7 +402,7 @@ def hook_GetVolumeInformationW(ql: Qiling, address: int, params): lpMaximumComponentLength = params["lpMaximumComponentLength"] if lpMaximumComponentLength != 0: - ql.mem.write(lpMaximumComponentLength, ql.pack16(255)) + ql.mem.write_ptr(lpMaximumComponentLength, 255, 2) pt_serial_number = params["lpVolumeSerialNumber"] if pt_serial_number != 0: @@ -415,7 +415,7 @@ def hook_GetVolumeInformationW(ql: Qiling, address: int, params): if pt_flag != 0: # TODO implement - ql.mem.write(pt_flag, ql.pack32(0x00020000)) + ql.mem.write_ptr(pt_flag, 0x00020000, 4) if pt_system_type != 0: system_type = (ql.os.profile["VOLUME"]["type"] + "\x00").encode("utf-16le") diff --git a/qiling/os/windows/dlls/kernel32/handleapi.py b/qiling/os/windows/dlls/kernel32/handleapi.py index 4caf87ab..11e7291b 100644 --- a/qiling/os/windows/dlls/kernel32/handleapi.py +++ b/qiling/os/windows/dlls/kernel32/handleapi.py @@ -31,7 +31,7 @@ def hook_DuplicateHandle(ql: Qiling, address: int, params): content = params["hSourceHandle"] dst = params["lpTargetHandle"] - ql.mem.write(dst, ql.pack(content)) + ql.mem.write_ptr(dst, content) return 1 diff --git a/qiling/os/windows/dlls/kernel32/libloaderapi.py b/qiling/os/windows/dlls/kernel32/libloaderapi.py index bfc415d4..2d229ac8 100644 --- a/qiling/os/windows/dlls/kernel32/libloaderapi.py +++ b/qiling/os/windows/dlls/kernel32/libloaderapi.py @@ -63,7 +63,7 @@ def hook_GetModuleHandleExW(ql: Qiling, address: int, params): res = _GetModuleHandle(ql, address, params) dst = params["phModule"] - ql.mem.write(dst, ql.pack(res)) + ql.mem.write_ptr(dst, res) return res diff --git a/qiling/os/windows/dlls/kernel32/memoryapi.py b/qiling/os/windows/dlls/kernel32/memoryapi.py index b1277676..b7a416da 100644 --- a/qiling/os/windows/dlls/kernel32/memoryapi.py +++ b/qiling/os/windows/dlls/kernel32/memoryapi.py @@ -107,6 +107,6 @@ def hook_VirtualQuery(ql: Qiling, address: int, params): ) for i, v in enumerate(values): - ql.mem.write(mbi + i * ql.pointersize, ql.pack(v)) + ql.mem.write_ptr(mbi + i * ql.arch.pointersize, v) - return ql.pointersize * len(values) + return ql.arch.pointersize * len(values) diff --git a/qiling/os/windows/dlls/kernel32/processthreadsapi.py b/qiling/os/windows/dlls/kernel32/processthreadsapi.py index 6825e0e8..b01aa182 100644 --- a/qiling/os/windows/dlls/kernel32/processthreadsapi.py +++ b/qiling/os/windows/dlls/kernel32/processthreadsapi.py @@ -189,7 +189,7 @@ def hook_CreateThread(ql: Qiling, address: int, params): # set lpThreadId # FIXME: Temporary fix for the crash # if lpThreadId != 0: - # ql.mem.write(lpThreadId, ql.pack(thread_id)) + # ql.mem.write_ptr(lpThreadId, thread_id) # set thread handle return new_handle.id @@ -279,7 +279,7 @@ def hook_OpenProcessToken(ql: Qiling, address: int, params): new_handle = Handle(obj=token) ql.os.handle_manager.append(new_handle) - ql.mem.write(token_pointer, ql.pack(new_handle.id)) + ql.mem.write_ptr(token_pointer, new_handle.id) return 1 @@ -313,7 +313,7 @@ def hook_OpenThreadToken(ql: Qiling, address: int, params): new_handle = Handle(obj=token) ql.os.handle_manager.append(new_handle) - ql.mem.write(token_pointer, ql.pack(new_handle.id)) + ql.mem.write_ptr(token_pointer, new_handle.id) return 1 diff --git a/qiling/os/windows/dlls/kernel32/profileapi.py b/qiling/os/windows/dlls/kernel32/profileapi.py index 6aadc12a..53757851 100644 --- a/qiling/os/windows/dlls/kernel32/profileapi.py +++ b/qiling/os/windows/dlls/kernel32/profileapi.py @@ -25,6 +25,6 @@ def hook_QueryPerformanceCounter(ql: Qiling, address: int, params): def hook_QueryPerformanceFrequency(ql: Qiling, address: int, params): lpFrequency = params['lpFrequency'] - ql.mem.write(lpFrequency, ql.pack64(10000000)) + ql.mem.write_ptr(lpFrequency, 10000000, 8) return 1 diff --git a/qiling/os/windows/dlls/kernel32/synchapi.py b/qiling/os/windows/dlls/kernel32/synchapi.py index 47310b05..50ccd5e0 100644 --- a/qiling/os/windows/dlls/kernel32/synchapi.py +++ b/qiling/os/windows/dlls/kernel32/synchapi.py @@ -136,7 +136,7 @@ def hook_WaitForMultipleObjects(ql: Qiling, address: int, params): lpHandles = params["lpHandles"] for i in range(nCount): - handle_value = ql.unpack(ql.mem.read(lpHandles + i * ql.pointersize, ql.pointersize)) + handle_value = ql.unpack(ql.mem.read(lpHandles + i * ql.arch.pointersize, ql.arch.pointersize)) if handle_value: thread = ql.os.handle_manager.get(handle_value).obj diff --git a/qiling/os/windows/dlls/kernel32/winbase.py b/qiling/os/windows/dlls/kernel32/winbase.py index 66fa1d62..00f1d926 100644 --- a/qiling/os/windows/dlls/kernel32/winbase.py +++ b/qiling/os/windows/dlls/kernel32/winbase.py @@ -635,8 +635,8 @@ def __GetUserName(ql: Qiling, address: int, params, wstring: bool): enc = "utf-16le" if wstring else "utf-8" username = f'{ql.os.profile["USER"]["username"]}\x00'.encode(enc) - max_size = ql.unpack32(ql.mem.read(pcbBuffer, 4)) - ql.mem.write(pcbBuffer, ql.pack32(len(username))) + max_size = ql.mem.read_ptr(pcbBuffer, 4) + ql.mem.write_ptr(pcbBuffer, len(username), 4) if len(username) > max_size: ql.os.last_error = ERROR_INSUFFICIENT_BUFFER @@ -674,8 +674,8 @@ def __GetComputerName(ql: Qiling, address: int, params, wstring: bool): enc = "utf-16le" if wstring else "utf-8" computer = f'{ql.os.profile["SYSTEM"]["computername"]}\x00'.encode(enc) - max_size = ql.unpack32(ql.mem.read(nSize, 4)) - ql.mem.write(nSize, ql.pack32(len(computer))) + max_size = ql.mem.read_ptr(nSize, 4) + ql.mem.write_ptr(nSize, len(computer), 4) if len(computer) > max_size: ql.os.last_error = ERROR_BUFFER_OVERFLOW diff --git a/qiling/os/windows/dlls/kernel32/winnt.py b/qiling/os/windows/dlls/kernel32/winnt.py index 2c8ac1e5..ca59d5a8 100644 --- a/qiling/os/windows/dlls/kernel32/winnt.py +++ b/qiling/os/windows/dlls/kernel32/winnt.py @@ -21,7 +21,7 @@ def hook_InterlockedExchange(ql: Qiling, address: int, params): Value = params['Value'] old = ql.mem.read_ptr(Target, 4) - ql.mem.write(Target, ql.pack32(Value)) + ql.mem.write_ptr(Target, Value, 4) return old @@ -35,8 +35,8 @@ def hook_InterlockedIncrement(ql: Qiling, address: int, params): Target = params['Target'] Value = ql.mem.read_ptr(Target, 4) - Value = (Value + 1) % (1 << 32) # increment and handle overflow - ql.mem.write(Target, ql.pack32(Value)) + Value = (Value + 1) % (1 << 32) # increase and handle overflow + ql.mem.write_ptr(Target, Value, 4) return Value @@ -50,9 +50,8 @@ def hook_InterlockedDecrement(ql: Qiling, address: int, params): Target = params['Target'] Value = ql.mem.read_ptr(Target, 4) - Value = (Value - 1) % (1 << 32) # increment and handle underflow - - ql.mem.write(Target, ql.pack32(Value)) + Value = (Value - 1) % (1 << 32) # decrease and handle underflow + ql.mem.write_ptr(Target, Value, 4) return Value diff --git a/qiling/os/windows/dlls/kernel32/wow64apiset.py b/qiling/os/windows/dlls/kernel32/wow64apiset.py index b5097bb5..84600e9f 100644 --- a/qiling/os/windows/dlls/kernel32/wow64apiset.py +++ b/qiling/os/windows/dlls/kernel32/wow64apiset.py @@ -19,7 +19,7 @@ from qiling.os.windows.fncc import * def hook_IsWow64Process(ql: Qiling, address: int, params): Wow64Process = params["Wow64Process"] - if ql.archbit != 32: + if ql.arch.bits != 32: raise QlErrorNotImplemented("API not implemented") false = b'\x00' diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index 0b55bba2..7fa3ed49 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -41,13 +41,13 @@ def hook___getmainargs(ql: Qiling, address: int, params): # int* __p__fmode(); @winsdkapi(cc=CDECL, params={}) def hook___p__fmode(ql: Qiling, address: int, params): - addr = ql.os.heap.alloc(ql.pointersize) + addr = ql.os.heap.alloc(ql.arch.pointersize) return addr # int* __p__commode(); @winsdkapi(cc=CDECL, params={}) def hook___p__commode(ql: Qiling, address: int, params): - addr = ql.os.heap.alloc(ql.pointersize) + addr = ql.os.heap.alloc(ql.arch.pointersize) return addr # char** __p__acmdln(); @@ -87,17 +87,16 @@ def hook_atexit(ql: Qiling, address: int, params): # char*** __p__environ(void) @winsdkapi(cc=CDECL, params={}) def hook___p__environ(ql: Qiling, address: int, params): - ret = ql.os.heap.alloc(ql.pointersize * len(ql.os.env)) + ret = ql.os.heap.alloc(ql.arch.pointersize * len(ql.os.env)) for i, (k, v) in enumerate(ql.os.env.items()): entry = bytes(f'{k}={v}', 'ascii') + b'\x00' p_entry = ql.os.heap.alloc(len(entry)) ql.mem.write(p_entry, entry) - pp_entry = ql.os.heap.alloc(ql.pointersize) - ql.mem.write(pp_entry, ql.pack(p_entry)) - - ql.mem.write(ret + i * ql.pointersize, ql.pack(pp_entry)) + pp_entry = ql.os.heap.alloc(ql.arch.pointersize) + ql.mem.write_ptr(pp_entry, p_entry) + ql.mem.write_ptr(ret + i * ql.arch.pointersize, pp_entry) return ret @@ -155,26 +154,26 @@ def hook__initterm_e(ql: Qiling, address: int, params): @winsdkapi(cc=CDECL, params={}) def hook___p___argv(ql: Qiling, address: int, params): # allocate argv pointers array - p_argv = ql.os.heap.alloc(ql.pointersize * len(ql.os.argv)) + p_argv = ql.os.heap.alloc(ql.arch.pointersize * len(ql.os.argv)) for i, each in enumerate(ql.os.argv): entry = bytes(each, 'ascii') + b'\x00' p_entry = ql.os.heap.alloc(len(entry)) ql.mem.write(p_entry, entry) - ql.mem.write(p_argv + i * ql.pointersize, ql.pack(p_entry)) + ql.mem.write_ptr(p_argv + i * ql.arch.pointersize, p_entry) - ret = ql.os.heap.alloc(ql.pointersize) - ql.mem.write(ret, ql.pack(p_argv)) + ret = ql.os.heap.alloc(ql.arch.pointersize) + ql.mem.write_ptr(ret, p_argv) return ret # int* __p___argc(void) @winsdkapi(cc=CDECL, params={}) def hook___p___argc(ql: Qiling, address: int, params): - ret = ql.os.heap.alloc(ql.pointersize) + ret = ql.os.heap.alloc(ql.arch.pointersize) - ql.mem.write(ret, ql.pack(len(ql.argv))) + ql.mem.write_ptr(ret, len(ql.argv)) return ret @@ -440,8 +439,8 @@ def hook_free(ql: Qiling, address: int, params): def hook__onexit(ql: Qiling, address: int, params): function = params['function'] - addr = ql.os.heap.alloc(ql.pointersize) - ql.mem.write(addr, ql.pack(function)) + addr = ql.os.heap.alloc(ql.arch.pointersize) + ql.mem.write_ptr(addr, function) return addr @@ -532,7 +531,7 @@ def hook__wfopen_s(ql: Qiling, address: int, params): f = ql.os.fs_mapper.open(filename, mode) new_handle = Handle(obj=f) ql.os.handle_manager.append(new_handle) - ql.mem.write(pFile, ql.pack(new_handle.id)) + ql.mem.write_ptr(pFile, new_handle.id) return 1 diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index f506bcf3..42728e7a 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -63,7 +63,7 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): addr = ql.os.heap.alloc(pbi.size) pbi.write(addr) - value = addr.to_bytes(ql.pointersize, "little") + value = addr.to_bytes(ql.arch.pointersize, "little") else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") @@ -125,7 +125,7 @@ def _QuerySystemInformation(ql: Qiling, address: int, params): max_uaddr = { QL_ARCH.X86 : 0x7FFEFFFF, QL_ARCH.X8664: 0x7FFFFFFEFFFF - }[ql.archtype] + }[ql.arch.type] sbi = structs.SystemBasicInforation( ql, @@ -320,7 +320,7 @@ def _SetInformationProcess(ql: Qiling, address: int, params): 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 = addr.to_bytes(ql.pointersize, "little") + value = addr.to_bytes(ql.arch.pointersize, "little") else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") @@ -367,7 +367,7 @@ def hook_LdrGetProcedureAddress(ql: Qiling, address: int, params): if identifier in ql.loader.import_address_table[dll_name]: addr = ql.loader.import_address_table[dll_name][identifier] - ql.mem.write(addr.to_bytes(length=ql.pointersize, byteorder='little'), FunctionAddress) + ql.mem.write(addr.to_bytes(length=ql.arch.pointersize, byteorder='little'), FunctionAddress) return 0 return 0xFFFFFFFF diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 82eecbea..59ef9460 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -11,6 +11,7 @@ from qiling.os.windows.api import * from qiling.os.windows.const import * from qiling.os.windows.fncc import * from qiling.os.windows.structs import * +from qiling.os.windows.wdk_const import DO_DEVICE_INITIALIZING, DO_EXCLUSIVE from qiling.utils import verify_ret # typedef struct _OSVERSIONINFOW { @@ -67,7 +68,7 @@ def hook_ZwSetInformationThread(ql: Qiling, address: int, params): ql.log.debug("The target is checking debugger via SetInformationThread") if dst != 0: - ql.mem.write(dst, ql.pack8(0)) + ql.mem.write_ptr(dst, 0, 1) else: raise QlErrorNotImplemented(f'API not implemented {information}') @@ -164,7 +165,7 @@ def __IoCreateDevice(ql: Qiling, address: int, params): objcls = { QL_ARCH.X86 : DEVICE_OBJECT32, QL_ARCH.X8664 : DEVICE_OBJECT64 - }[ql.archtype] + }[ql.arch.type] addr = ql.os.heap.alloc(ctypes.sizeof(objcls)) @@ -186,7 +187,7 @@ def __IoCreateDevice(ql: Qiling, address: int, params): device_object.Characteristics = DeviceCharacteristics ql.mem.write(addr, bytes(device_object)[:]) - ql.mem.write(DeviceObject, ql.pack(addr)) + ql.mem.write_ptr(DeviceObject, addr) # update DriverObject.DeviceObject ql.loader.driver_object.DeviceObject = addr @@ -643,7 +644,7 @@ def hook_MmMapLockedPagesSpecifyCache(ql: Qiling, address: int, params): mdl_class: ctypes.Structure = { QL_ARCH.X8664 : MDL64, QL_ARCH.X86 : MDL32 - }[ql.archtype] + }[ql.arch.type] mdl_buffer = ql.mem.read(MemoryDescriptorList, ctypes.sizeof(mdl_class)) mdl = mdl_class.from_buffer(mdl_buffer) @@ -756,7 +757,7 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): # if SystemInformationLength = 0, we return the total size in ReturnLength NumberOfModules = 1 - if ql.archbit == 64: + if ql.arch.bits == 64: # only 1 module for ntoskrnl.exe # FIXME: let users customize this? size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION64) * NumberOfModules @@ -764,13 +765,13 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION32) * NumberOfModules if params["ReturnLength"] != 0: - ql.mem.write(params["ReturnLength"], ql.pack(size)) + ql.mem.write_ptr(params["ReturnLength"], size) if params["SystemInformationLength"] < size: return STATUS_INFO_LENGTH_MISMATCH else: # return all the loaded modules - if ql.archbit == 64: + if ql.arch.bits == 64: module = RTL_PROCESS_MODULE_INFORMATION64() else: module = RTL_PROCESS_MODULE_INFORMATION32() @@ -898,7 +899,7 @@ def hook_IoCreateDriver(ql: Qiling, address: int, params): # print("\n\n>>> IoCreateDriver at %x, going to execute function at %x, RET = %x\n" %(address, init_func, ret_addr)) # save SP & init_sp - sp = ql.reg.sp + sp = ql.arch.regs.sp init_sp = ql.os.init_sp ql.os.fcall = ql.os.fcall_select(STDCALL) @@ -915,7 +916,7 @@ def hook_IoCreateDriver(ql: Qiling, address: int, params): verify_ret(ql, err) # reset SP since emulated function does not cleanup - ql.reg.sp = sp + ql.arch.regs.sp = sp ql.os.init_sp = init_sp # ret_addr = ql.stack_read(0) @@ -1072,13 +1073,13 @@ def hook_PsLookupProcessByProcessId(ql: Qiling, address: int, params): ProcessId = params["ProcessId"] Process = params["Process"] - if ql.archbit == 64: + if ql.arch.bits == 64: obj = EPROCESS64 else: obj = EPROCESS32 addr = ql.os.heap.alloc(ctypes.sizeof(obj)) - ql.mem.write(Process, ql.pack(addr)) + ql.mem.write_ptr(Process, addr) ql.log.info(f'PID = {ProcessId:#x}, addrof(EPROCESS) == {addr:#x}') return STATUS_SUCCESS @@ -1159,12 +1160,12 @@ def hook_PsCreateSystemThread(ql: Qiling, address: int, params): # set lpThreadId if lpThreadId != 0: - ql.mem.write(lpThreadId, ql.pack(UniqueProcess)) - ql.mem.write(lpThreadId + ql.pointersize, ql.pack(thread_id)) + ql.mem.write_ptr(lpThreadId, UniqueProcess) + ql.mem.write_ptr(lpThreadId + ql.arch.pointersize, thread_id) # set lpThreadId if ThreadHandle != 0: - ql.mem.write(ThreadHandle, ql.pack(handle_value)) + ql.mem.write_ptr(ThreadHandle, handle_value) # set thread handle return STATUS_SUCCESS @@ -1271,7 +1272,7 @@ def hook_ObOpenObjectByPointer(ql: Qiling, address: int, params): new_handle = Handle(name=f'p={Object:x}') ql.os.handle_manager.append(new_handle) - ql.mem.write(point_to_new_handle, ql.pack(new_handle.id)) + ql.mem.write_ptr(point_to_new_handle, new_handle.id) ql.log.info(f'New handle of {Object:#x} is {new_handle.id:#x}') return STATUS_SUCCESS diff --git a/qiling/os/windows/dlls/oleaut32.py b/qiling/os/windows/dlls/oleaut32.py index 69e1ff48..f19cb479 100644 --- a/qiling/os/windows/dlls/oleaut32.py +++ b/qiling/os/windows/dlls/oleaut32.py @@ -69,7 +69,7 @@ def hook_SysReAllocStringLen(ql: Qiling, address: int, params): addr = ql.os.heap.alloc(size + 1) ql.mem.write(addr, content[:size].encode("utf-16le")) - ql.mem.write(params["pbstr"], ql.pack(addr)) + ql.mem.write_ptr(params["pbstr"], addr) return 1 diff --git a/qiling/os/windows/fiber.py b/qiling/os/windows/fiber.py index 51715c42..e8bda865 100644 --- a/qiling/os/windows/fiber.py +++ b/qiling/os/windows/fiber.py @@ -32,21 +32,21 @@ class FiberManager: self.ql.log.debug(f'Skipping emulation of callback function {fiber.cb:#x} for fiber {fiber.idx:#x}') """ - ret_addr = self.ql.reg.read(UC_X86_REG_RIP + 6 ) #FIXME, use capstone to get addr of next instr? + ret_addr = self.ql.arch.regs.read(UC_X86_REG_RIP + 6 ) #FIXME, use capstone to get addr of next instr? # Write Fls data to memory to be accessed by cb - addr = self.ql.os.heap.alloc(self.ql.pointersize) - data = fiber.data.to_bytes(self.ql.pointersize, byteorder='little') + addr = self.ql.os.heap.alloc(self.ql.arch.pointersize) + data = fiber.data.to_bytes(self.ql.arch.pointersize, byteorder='little') self.ql.mem.write(addr, data) # set up params and return address then jump to callback - if self.ql.pointersize == 8: - self.ql.reg.write(UC_X86_REG_RCX, addr) + if self.ql.arch.pointersize == 8: + self.ql.arch.regs.write(UC_X86_REG_RCX, addr) else: self.ql.stack_push(ret_addr) self.ql.stack_push(ret_addr) self.ql.log.debug("Jumping to callback @ 0x%X" % fiber.cb) - self.ql.reg.write(UC_X86_REG_RIP, fiber.cb) + self.ql.arch.regs.write(UC_X86_REG_RIP, fiber.cb) # All of this gets overwritten by the rest of the code in fncc.py # Not sure how to actually make unicorn emulate the callback function due to that """ diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index 5a69b81d..abdff9f3 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -154,7 +154,7 @@ class RegistryManager: "value_name": value_name, "value": value, "type": type, - "position": self.ql.os.utils.syscalls_counter + "position": self.ql.os.stats.syscalls_counter }) # we don't have to increase the counter since we are technically inside a hook diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index 9f05a664..4b7705fa 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -118,7 +118,7 @@ class PEB: self.AtlThunkSListPtr = alt_thunk_s_list_ptr self.IFEOKey = ifeo_key self.numberOfProcessors = number_processors - if self.ql.archtype == 32: + if self.ql.arch.type == 32: self.size = 0x0468 else: self.size = 0x07B0 @@ -1600,12 +1600,12 @@ class LdrDataTableEntry: s += self.ql.pack(self.SizeOfImage) # 0x20 s += self.ql.pack16(self.FullDllName['Length']) # 0x24 s += self.ql.pack16(self.FullDllName['MaximumLength']) # 0x26 - if self.ql.archtype == QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: s += self.ql.pack32(0) s += self.ql.pack(self.FullDllName['BufferPtr']) # 0x28 s += self.ql.pack16(self.BaseDllName['Length']) s += self.ql.pack16(self.BaseDllName['MaximumLength']) - if self.ql.archtype == QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: s += self.ql.pack32(0) s += self.ql.pack(self.BaseDllName['BufferPtr']) s += self.ql.pack(self.Flags) @@ -1635,7 +1635,7 @@ class WindowsStruct: self.addr = None self.ULONG_SIZE = 8 self.LONG_SIZE = 4 - self.POINTER_SIZE = self.ql.pointersize + self.POINTER_SIZE = self.ql.arch.pointersize self.INT_SIZE = 2 self.DWORD_SIZE = 4 self.WORD_SIZE = 2 @@ -2145,7 +2145,7 @@ class StartupInfo(WindowsStruct): def __init__(self, ql, desktop=None, title=None, x=None, y=None, x_size=None, y_size=None, x_chars=None, y_chars=None, fill_attribute=None, flags=None, show=None, std_input=None, output=None, error=None): super().__init__(ql) - self.size = 53 + 3 * self.ql.pointersize + self.size = 53 + 3 * self.ql.arch.pointersize self.cb = [self.size, self.DWORD_SIZE, "little", int] self.reserved = [0, self.POINTER_SIZE, "little", int] self.desktop = [desktop, self.POINTER_SIZE, "little", int] @@ -2281,7 +2281,7 @@ class UnicodeString(AlignedWindowsStruct): super().__init__(ql) # on x64, self.buffer is aligned to 8 - if (ql.archtype == 32): + if ql.arch.bits == 32: self.size = self.USHORT_SIZE * 2 + self.POINTER_SIZE else: self.size = self.USHORT_SIZE * 2 + 4 + self.POINTER_SIZE diff --git a/qiling/os/windows/thread.py b/qiling/os/windows/thread.py index c49f166f..cbbb6651 100644 --- a/qiling/os/windows/thread.py +++ b/qiling/os/windows/thread.py @@ -11,7 +11,7 @@ from .utils import * def thread_scheduler(ql, address, size): - if ql.reg.arch_pc == ql.os.thread_manager.THREAD_RET_ADDR: + if ql.arch.regs.arch_pc == ql.os.thread_manager.THREAD_RET_ADDR: ql.os.thread_manager.cur_thread.stop() ql.os.thread_manager.do_schedule() else: @@ -84,22 +84,22 @@ class QlWindowsThread(QlThread): stack_size = 1024 new_stack = self.ql.os.heap.alloc(stack_size) + stack_size - self.saved_context = self.ql.reg.save() + self.saved_context = self.ql.arch.regs.save() # set return address, parameters - if self.ql.archtype == QL_ARCH.X86: - self.ql.mem.write(new_stack - 4, self.ql.pack32(self.ql.os.thread_manager.THREAD_RET_ADDR)) - self.ql.mem.write(new_stack, self.ql.pack32(func_params)) - elif self.ql.archtype == QL_ARCH.X8664: - self.ql.mem.write(new_stack - 8, self.ql.pack64(self.ql.os.thread_manager.THREAD_RET_ADDR)) + if self.ql.arch.type == QL_ARCH.X86: + self.ql.mem.write_ptr(new_stack - 4, self.ql.os.thread_manager.THREAD_RET_ADDR) + self.ql.mem.write_ptr(new_stack, func_params) + elif self.ql.arch.type == QL_ARCH.X8664: + self.ql.mem.write_ptr(new_stack - 8, self.ql.os.thread_manager.THREAD_RET_ADDR) self.saved_context["rcx"] = func_params # set eip/rip, ebp/rbp, esp/rsp - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.saved_context["eip"] = func_addr self.saved_context["ebp"] = new_stack - 4 self.saved_context["esp"] = new_stack - 4 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.saved_context["rip"] = func_addr self.saved_context["rbp"] = new_stack - 8 self.saved_context["rsp"] = new_stack - 8 @@ -111,10 +111,10 @@ class QlWindowsThread(QlThread): return self.id def suspend(self): - self.saved_context = self.ql.reg.save() + self.saved_context = self.ql.arch.regs.save() def resume(self): - self.ql.reg.restore(self.saved_context) + self.ql.arch.regs.restore(self.saved_context) self.status = QlWindowsThread.RUNNING def stop(self): diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index 6b6cf6a7..6ec9f6c7 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -10,6 +10,7 @@ from typing import Tuple, TypeVar from unicorn import UcError from qiling import Qiling +from qiling.const import QL_OS from qiling.os.const import POINTER from qiling.os.windows.fncc import STDCALL from qiling.os.windows.wdk_const import * @@ -22,10 +23,6 @@ Comparable = TypeVar('Comparable', str, int) def cmp(a: Comparable, b: Comparable) -> int: return (a > b) - (a < b) -def ql_x86_windows_hook_mem_error(ql: Qiling, access, addr: int, size: int, value: int): - ql.log.debug(f'ERROR: unmapped memory access at {addr:#x}') - return False - def is_file_library(string: str) -> bool: string = string.lower() @@ -47,7 +44,7 @@ def io_Write(ql: Qiling, in_buffer: bytes): # raise error? return (False, None) - if ql.archbit == 32: + if ql.arch.bits == 32: buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(DEVICE_OBJECT32)) device_object = DEVICE_OBJECT32.from_buffer(buf) else: @@ -56,7 +53,7 @@ def io_Write(ql: Qiling, in_buffer: bytes): alloc_addr = [] def build_mdl(buffer_size, data=None): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl = MDL64() else: mdl = MDL32() @@ -73,7 +70,7 @@ def io_Write(ql: Qiling, in_buffer: bytes): return mdl # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_addr = heap.alloc(ctypes.sizeof(IRP64)) alloc_addr.append(irp_addr) irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) @@ -107,7 +104,7 @@ def io_Write(ql: Qiling, in_buffer: bytes): elif device_object.Flags & DO_DIRECT_IO: # DIRECT_IO mdl = build_mdl(len(in_buffer)) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) else: mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) @@ -139,7 +136,7 @@ def io_Write(ql: Qiling, in_buffer: bytes): verify_ret(ql, err) # read current IRP state - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) irp = IRP64.from_buffer(irp_buffer) else: @@ -172,7 +169,7 @@ def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple: alloc_addr = [] def build_mdl(buffer_size, data=None): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl = MDL64() else: mdl = MDL32() @@ -211,7 +208,7 @@ def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple: alloc_addr.append(output_buffer_addr) # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_addr = heap.alloc(ctypes.sizeof(IRP64)) alloc_addr.append(irp_addr) irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) @@ -267,7 +264,7 @@ def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple: # Create MDL structure for output data # used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT mdl = build_mdl(output_buffer_size) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) else: mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) @@ -293,7 +290,7 @@ def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple: verify_ret(ql, err) # read current IRP state - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) irp = IRP64.from_buffer(irp_buffer) else: diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index c7558bde..e7f69108 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -9,7 +9,8 @@ from typing import Callable from unicorn import UcError from qiling import Qiling -from qiling.arch.x86 import GDTManager, ql_x86_register_cs, ql_x86_register_ds_ss_es, ql_x86_register_fs, ql_x86_register_gs, ql_x8664_set_gs +from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, FS_SEGMENT_ADDR, FS_SEGMENT_SIZE +from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.cc import intel from qiling.const import QL_ARCH, QL_INTERCEPT from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound @@ -44,9 +45,9 @@ class QlOsWindows(QlOs): """ __fcall_objs = { - fncc.STDCALL: QlFunctionCall(ql, intel.stdcall(ql)), - fncc.CDECL : QlFunctionCall(ql, intel.cdecl(ql)), - fncc.MS64 : QlFunctionCall(ql, intel.ms64(ql)) + fncc.STDCALL: QlFunctionCall(ql, intel.stdcall(ql.arch)), + fncc.CDECL : QlFunctionCall(ql, intel.cdecl(ql.arch)), + fncc.MS64 : QlFunctionCall(ql, intel.ms64(ql.arch)) } __selector = { @@ -56,7 +57,7 @@ class QlOsWindows(QlOs): return __selector[atype] - self.fcall_select = __make_fcall_selector(ql.archtype) + self.fcall_select = __make_fcall_selector(ql.arch.type) self.fcall = None self.PE_RUN = True @@ -67,7 +68,6 @@ class QlOsWindows(QlOs): self.argv = self.ql.argv self.env = self.ql.env self.pid = self.profile.getint("KERNEL","pid") - self.ql.hook_mem_unmapped(utils.ql_x86_windows_hook_mem_error) self.automatize_input = self.profile.getboolean("MISC","automatize_input") self.username = self.profile["USER"]["username"] self.windir = self.profile["PATH"]["systemdrive"] + self.profile["PATH"]["windir"] @@ -83,15 +83,24 @@ class QlOsWindows(QlOs): def setupGDT(self): - # setup gdt - if self.ql.archtype == QL_ARCH.X86: - self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) - ql_x86_register_fs(self) - ql_x86_register_gs(self) - elif self.ql.archtype == QL_ARCH.X8664: - ql_x8664_set_gs(self.ql) + gdtm = GDTManager(self.ql) + + segm_class = { + 32 : SegmentManager86, + 64 : SegmentManager64 + }[self.ql.arch.bits] + + # setup gdt and segments selectors + segm = segm_class(self.ql.arch, gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + segm.setup_fs(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE) + segm.setup_gs(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) + + if not self.ql.mem.is_mapped(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE): + self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]') + + if not self.ql.mem.is_mapped(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): + self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') def setupComponents(self): @@ -147,7 +156,7 @@ class QlOsWindows(QlOs): def post_report(self): self.ql.log.debug("Syscalls called:") - for key, values in self.utils.syscalls.items(): + for key, values in self.stats.syscalls.items(): self.ql.log.debug(f'{key}:') for value in values: @@ -161,7 +170,7 @@ class QlOsWindows(QlOs): self.ql.log.debug(f' {json.dumps(value):s}') self.ql.log.debug("Strings:") - for key, values in self.utils.appeared_strings.items(): + for key, values in self.stats.appeared_strings.items(): self.ql.log.debug(f'{key}: {" ".join(str(word) for word in values)}') diff --git a/qiling/utils.py b/qiling/utils.py index 060c8f69..47f27a59 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -8,18 +8,19 @@ This module is intended for general purpose functions that can be used thoughout the qiling framework """ -import importlib, os, copy, re, pefile, logging, sys, yaml +import importlib, os, copy, re, pefile, logging, yaml from configparser import ConfigParser from logging import LogRecord -from typing import Any, Container, Optional, Sequence, Tuple, Type +from typing import Any, Container, IO, List, Optional, Sequence, TextIO, Tuple, Type, Union from enum import Enum from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED -from .exception import * -from .const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER, QL_ARCH_1BIT, QL_ARCH_16BIT, QL_ARCH_32BIT, QL_ARCH_64BIT -from .const import debugger_map, arch_map, os_map, arch_os_map, loader_map +from qiling.exception import * +from qiling.const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER +from qiling.const import debugger_map, arch_map, os_map, arch_os_map, loader_map +from qiling.os.posix.const import NR_OPEN FMT_STR = "%(levelname)s\t%(message)s" @@ -37,65 +38,60 @@ class COLOR_CODE: CYAN = '\033[96m' ENDC = '\033[0m' -class QilingColoredFormatter(logging.Formatter): +class QlBaseFormatter(logging.Formatter): + __level_tag = { + 'WARNING' : '[!]', + 'INFO' : '[=]', + 'DEBUG' : '[+]', + 'CRITICAL' : '[x]', + 'ERROR' : '[x]' + } + def __init__(self, ql, *args, **kwargs): super().__init__(*args, **kwargs) self.ql = ql - def get_colored_level(self, record: LogRecord) -> str: - LEVEL_NAME = { - 'WARNING' : f"{COLOR_CODE.YELLOW}[!]{COLOR_CODE.ENDC}", - 'INFO' : f"{COLOR_CODE.BLUE}[=]{COLOR_CODE.ENDC}", - 'DEBUG' : f"{COLOR_CODE.MAGENTA}[+]{COLOR_CODE.ENDC}", - 'CRITICAL' : f"{COLOR_CODE.CRIMSON}[x]{COLOR_CODE.ENDC}", - 'ERROR' : f"{COLOR_CODE.RED}[x]{COLOR_CODE.ENDC}" - } + def get_level_tag(self, level: str) -> str: + return self.__level_tag[level] - return LEVEL_NAME[record.levelname] + def get_thread_tag(self, thread: str) -> str: + return thread def format(self, record: LogRecord): # In case we have multiple formatters, we have to keep a copy of the record. record = copy.copy(record) - record.levelname = self.get_colored_level(record) # early logging may access ql.os when it is not yet set try: cur_thread = self.ql.os.thread_management.cur_thread except AttributeError: - pass + tid = f'' else: - record.levelname = f"{record.levelname} {COLOR_CODE.GREEN}{str(cur_thread)}{COLOR_CODE.ENDC}" + tid = self.get_thread_tag(str(cur_thread)) + + level = self.get_level_tag(record.levelname) + record.levelname = f'{level} {tid}' return super().format(record) -class QilingPlainFormatter(logging.Formatter): - def __init__(self, ql, *args, **kwargs): - super().__init__(*args, **kwargs) - self.ql = ql +class QlColoredFormatter(QlBaseFormatter): + __level_color = { + 'WARNING' : COLOR_CODE.YELLOW, + 'INFO' : COLOR_CODE.BLUE, + 'DEBUG' : COLOR_CODE.MAGENTA, + 'CRITICAL' : COLOR_CODE.CRIMSON, + 'ERROR' : COLOR_CODE.RED + } - def get_level(self, record: LogRecord) -> str: - LEVEL_NAME = { - 'WARNING' : "[!]", - 'INFO' : "[=]", - 'DEBUG' : "[+]", - 'CRITICAL' : "[x]", - 'ERROR' : "[x]" - } + def get_level_tag(self, level: str) -> str: + s = super().get_level_tag(level) - return LEVEL_NAME[record.levelname] + return f'{self.__level_color[level]}{s}{COLOR_CODE.ENDC}' - def format(self, record: LogRecord): - record.levelname = self.get_level(record) + def get_thread_tag(self, tid: str) -> str: + s = super().get_thread_tag(tid) - # early logging may access ql.os when it is not yet set - try: - cur_thread = self.ql.os.thread_management.cur_thread - except AttributeError: - pass - else: - record.levelname = f"{record.levelname} {str(cur_thread)}" - - return super().format(record) + return f'{COLOR_CODE.GREEN}{s}{COLOR_CODE.ENDC}' class RegexFilter(logging.Filter): def __init__(self, regexp): @@ -111,13 +107,16 @@ class RegexFilter(logging.Filter): return re.match(self._filter, msg) is not None class QlFileDes: - def __init__(self, init): - self.__fds = init + def __init__(self): + self.__fds: List[Optional[IO]] = [None] * NR_OPEN - def __getitem__(self, idx): + def __len__(self): + return len(self.__fds) + + def __getitem__(self, idx: int): return self.__fds[idx] - def __setitem__(self, idx, val): + def __setitem__(self, idx: int, val: Optional[IO]): self.__fds[idx] = val def __iter__(self): @@ -133,25 +132,6 @@ class QlFileDes: self.__fds = fds -class QlStopOptions(object): - def __init__(self, stackpointer=False, exit_trap=False): - super().__init__() - self._stackpointer = stackpointer - self._exit_trap = exit_trap - - @property - def stackpointer(self) -> bool: - return self._stackpointer - - @property - def exit_trap(self) -> bool: - return self._exit_trap - - @property - def any(self) -> bool: - return self.stackpointer or self.exit_trap - - def catch_KeyboardInterrupt(ql): def decorator(func): def wrapper(*args, **kw): @@ -165,21 +145,6 @@ def catch_KeyboardInterrupt(ql): return decorator -def ql_get_arch_bits(arch: QL_ARCH) -> int: - if arch in QL_ARCH_1BIT: - return 1 - - if arch in QL_ARCH_16BIT: - return 16 - - if arch in QL_ARCH_32BIT: - return 32 - - if arch in QL_ARCH_64BIT: - return 64 - - raise QlErrorArch("Invalid Arch Bit") - def enum_values(e: Type[Enum]) -> Container: return e.__members__.values() @@ -246,66 +211,105 @@ def ql_get_module_function(module_name: str, function_name: str): return module_function def ql_elf_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: - with open(path, "rb") as f: - size = os.fstat(f.fileno()).st_size + # instead of using full-blown elffile parsing, we perform a simple parsing to avoid + # external dependencies for target systems that do not need them. + # + # see: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html - ident = f.read(512 if size >= 512 else 20) + # ei_class + ELFCLASS32 = 1 # 32-bit + ELFCLASS64 = 2 # 64-bit - arch = None + #ei_data + ELFDATA2LSB = 1 # little-endian + ELFDATA2MSB = 2 # big-endian + + # ei_osabi + ELFOSABI_SYSV = 0 + ELFOSABI_LINUX = 3 + ELFOSABI_FREEBSD = 9 + ELFOSABI_ARM_AEABI = 64 + ELFOSABI_ARM = 97 + ELFOSABI_STANDALONE = 255 + + # e_machine + EM_386 = 3 + EM_MIPS = 8 + EM_ARM = 40 + EM_X86_64 = 62 + EM_AARCH64 = 183 + EM_RISCV = 243 + + endianess = { + ELFDATA2LSB : (QL_ENDIAN.EL, 'little'), + ELFDATA2MSB : (QL_ENDIAN.EB, 'big') + } + + machines32 = { + EM_386 : QL_ARCH.X86, + EM_MIPS : QL_ARCH.MIPS, + EM_ARM : QL_ARCH.ARM, + EM_RISCV : QL_ARCH.RISCV + } + + machines64 = { + EM_X86_64 : QL_ARCH.X8664, + EM_AARCH64 : QL_ARCH.ARM64, + EM_RISCV : QL_ARCH.RISCV64 + } + + classes = { + ELFCLASS32 : machines32, + ELFCLASS64 : machines64 + } + + abis = { + ELFOSABI_SYSV : QL_OS.LINUX, + ELFOSABI_LINUX : QL_OS.LINUX, + ELFOSABI_FREEBSD : QL_OS.FREEBSD, + ELFOSABI_ARM_AEABI : QL_OS.LINUX, + ELFOSABI_ARM : QL_OS.LINUX, + ELFOSABI_STANDALONE : QL_OS.BLOB + } + + archtype = None ostype = None archendian = None - if ident[:4] == b'\x7fELF': - elfbit = ident[0x4] - endian = ident[0x5] - osabi = ident[0x7] - e_machine = ident[0x12:0x14] + with open(path, 'rb') as binfile: + e_ident = binfile.read(16) + e_type = binfile.read(2) + e_machine = binfile.read(2) - if osabi == 0x09: - ostype = QL_OS.FREEBSD - elif osabi in (0x0, 0x03) or osabi >= 0x11: - if b"ldqnx.so" in ident: - ostype = QL_OS.QNX - else: - ostype = QL_OS.LINUX + # qnx may be detected by the interpreter name: 'ldqnx.so'. + # instead of properly parsing the file to locate the pt_interp + # segment, we detect qnx fuzzily by looking for that string in + # the first portion of the file. + blob = binfile.read(0x200 - 20) - if e_machine == b"\x03\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.X86 + if e_ident[:4] == b'\x7fELF': + ei_class = e_ident[4] # arch bits + ei_data = e_ident[5] # arch endianess + ei_osabi = e_ident[7] - elif e_machine == b"\x08\x00" and endian == 1 and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.MIPS + if ei_class in classes: + machines = classes[ei_class] - elif e_machine == b"\x00\x08" and endian == 2 and elfbit == 1: - archendian = QL_ENDIAN.EB - arch = QL_ARCH.MIPS + if ei_data in endianess: + archendian, endian = endianess[ei_data] - elif e_machine == b"\x28\x00" and endian == 1 and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.ARM + machine = int.from_bytes(e_machine, endian) - elif e_machine == b"\x00\x28" and endian == 2 and elfbit == 1: - archendian = QL_ENDIAN.EB - arch = QL_ARCH.ARM + if machine in machines: + archtype = machines[machine] - elif e_machine == b"\xB7\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.ARM64 + if ei_osabi in abis: + ostype = abis[ei_osabi] - elif e_machine == b"\x3E\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.X8664 - - elif e_machine == b"\xf3\x00" and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.RISCV - - elif e_machine == b"\xf3\x00" and elfbit == 2: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.RISCV64 - - return arch, ostype, archendian + if blob and b'ldqnx.so' in blob: + ostype = QL_OS.QNX + + return archtype, ostype, archendian def ql_macho_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: macho_macos_sig64 = b'\xcf\xfa\xed\xfe' @@ -351,9 +355,7 @@ def ql_pe_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_AMD64'] : QL_ARCH.X8664, pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_ARM'] : QL_ARCH.ARM, pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_THUMB'] : QL_ARCH.ARM, - # pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_ARM64'] : QL_ARCH.ARM64 #pefile does not have the definition - # for IMAGE_FILE_MACHINE_ARM64 - 0xAA64: QL_ARCH.ARM64 # Temporary workaround for Issues #21 till pefile gets updated + pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_ARM64'] : QL_ARCH.ARM64 } # get arch @@ -405,18 +407,28 @@ def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Opt return arch, ostype, endian -def loader_setup(ostype: QL_OS, ql): - loadertype_str = loadertype_convert_str(ostype) - function_name = "QlLoader" + loadertype_str - return ql_get_module_function(f"qiling.loader.{loadertype_str.lower()}", function_name)(ql) +def loader_setup(ql, ostype: QL_OS, libcache: bool): + args = [libcache] if ostype == QL_OS.WINDOWS else [] + + qlloader_name = loadertype_convert_str(ostype) + qlloader_path = f'qiling.loader.{qlloader_name.lower()}' + qlloader_class = f'QlLoader{qlloader_name.upper()}' + + obj = ql_get_module_function(qlloader_path, qlloader_class) + + return obj(ql, *args) -def component_setup(component_type, component_name, ql): - function_name = "Ql" + component_name.capitalize() + "Manager" - return ql_get_module_function(f"qiling.{component_type}.{component_name}", function_name)(ql) +def component_setup(component_type: str, component_name: str, ql): + component_path = f'qiling.{component_type}.{component_name}' + component_class = f'Ql{component_name.capitalize()}Manager' + + obj = ql_get_module_function(component_path, component_class) + + return obj(ql) -def debugger_setup(options, ql): +def debugger_setup(options: Union[str, bool], ql): if options is True: options = 'gdb' @@ -432,66 +444,83 @@ def debugger_setup(options, ql): return None -def arch_setup(archtype, ql): - if not ql_is_valid_arch(archtype): - raise QlErrorArch("Invalid Arch") - - if archtype == QL_ARCH.ARM_THUMB: - archtype = QL_ARCH.ARM +def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): + # set endianess and thumb mode for arm-based archs + if archtype == QL_ARCH.ARM: + args = [endian, thumb] - archmanager = f'QlArch{arch_convert_str(archtype).upper()}' + # set endianess for mips arch + elif archtype == QL_ARCH.MIPS: + args = [endian] - if archtype in (QL_ARCH.X8664, QL_ARCH.A8086): - arch_str = "x86" else: - arch_str = arch_convert_str(archtype) + args = [] - if ql.interpreter: - return ql_get_module_function(f"qiling.arch.{arch_str.lower()}.{arch_str.lower()}", archmanager)(ql) - else: - return ql_get_module_function(f"qiling.arch.{arch_str.lower()}", archmanager)(ql) + module = { + QL_ARCH.A8086 : r'x86', + QL_ARCH.X86 : r'x86', + QL_ARCH.X8664 : r'x86', + QL_ARCH.ARM : r'arm', + QL_ARCH.ARM64 : r'arm64', + QL_ARCH.MIPS : r'mips', + QL_ARCH.EVM : r'evm.evm', + QL_ARCH.CORTEX_M : r'cortex_m', + QL_ARCH.RISCV : r'riscv', + QL_ARCH.RISCV64 : r'riscv64' + }[archtype] + + qlarch_path = f'qiling.arch.{module}' + qlarch_class = f'QlArch{arch_convert_str(archtype).upper()}' + + obj = ql_get_module_function(qlarch_path, qlarch_class) + + return obj(ql, *args) -# This function is extracted from os_setup so I put it here. -def ql_syscall_mapping_function(ostype): - ostype_str = ostype_convert_str(ostype) - return ql_get_module_function(f"qiling.os.{ostype_str.lower()}.map_syscall", "map_syscall") +# This function is extracted from os_setup (QlOsPosix) so I put it here. +def ql_syscall_mapping_function(ostype: QL_OS): + qlos_name = ostype_convert_str(ostype) + qlos_path = f'qiling.os.{qlos_name.lower()}.map_syscall' + qlos_func = 'map_syscall' + + func = ql_get_module_function(qlos_path, qlos_func) + + return func -def os_setup(archtype: QL_ARCH, ostype: QL_OS, ql): - if not ql_is_valid_ostype(ostype): - raise QlErrorOsType("Invalid OSType") +def os_setup(ostype: QL_OS, ql): + qlos_name = ostype_convert_str(ostype) + qlos_path = f'qiling.os.{qlos_name.lower()}.{qlos_name.lower()}' + qlos_class = f'QlOs{qlos_name.capitalize()}' - if not ql_is_valid_arch(archtype): - raise QlErrorArch("Invalid Arch %s" % archtype) + obj = ql_get_module_function(qlos_path, qlos_class) - ostype_str = ostype_convert_str(ostype) - ostype_str = ostype_str.capitalize() - function_name = "QlOs" + ostype_str - return ql_get_module_function(f"qiling.os.{ostype_str.lower()}.{ostype_str.lower()}", function_name)(ql) + return obj(ql) -def profile_setup(ql): - _profile = "Default" - - if ql.profile != None: - _profile = ql.profile - debugmsg = "Profile: %s" % _profile +def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): + ql.log.debug(f'Profile: {filename or "default"}') if ql.baremetal: - config = {} - if ql.profile: - with open(ql.profile) as f: + if filename: + with open(filename) as f: config = yaml.load(f, Loader=yaml.Loader) + else: + config = {} else: - profile_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "profiles", ostype_convert_str(ql.ostype).lower() + ".ql") - profiles = [profile_path, ql.profile] if ql.profile else [profile_path] + qiling_home = os.path.dirname(os.path.abspath(__file__)) + os_profile = os.path.join(qiling_home, 'profiles', f'{ostype_convert_str(ostype).lower()}.ql') + + profiles = [os_profile] + + if filename: + profiles.append(filename) config = ConfigParser() config.read(profiles) - return config, debugmsg + return config def ql_resolve_logger_level(verbose: QL_VERBOSE) -> int: return { @@ -505,6 +534,46 @@ def ql_resolve_logger_level(verbose: QL_VERBOSE) -> int: QL_INSTANCE_ID = 114514 +def __is_color_terminal(stream: TextIO) -> bool: + """Determine whether standard output is attached to a color terminal. + + see: https://stackoverflow.com/questions/53574442/how-to-reliably-test-color-capability-of-an-output-terminal-in-python3 + """ + + def __handle_nt(fd: int) -> bool: + import ctypes + import msvcrt + + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + hstdout = msvcrt.get_osfhandle(fd) + mode = ctypes.c_ulong() + + return kernel32.GetConsoleMode(hstdout, ctypes.byref(mode)) and (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) + + def __handle_posix(fd: int) -> bool: + import curses + + try: + curses.setupterm(fd=fd) + except curses.error: + return True + else: + return curses.tigetnum('colors') > 0 + + def __default(_: int) -> bool: + return True + + handlers = { + 'nt' : __handle_nt, + 'posix' : __handle_posix + } + + handler = handlers.get(os.name, __default) + + return handler(stream.fileno()) + # TODO: qltool compatibility def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optional[Sequence], log_override: Optional[logging.Logger], log_plain: bool): global QL_INSTANCE_ID @@ -527,20 +596,21 @@ def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optiona if console: handler = logging.StreamHandler() - if not log_plain and not sys.platform == "win32": - formatter = QilingColoredFormatter(ql, FMT_STR) + if log_plain or not __is_color_terminal(handler.stream): + formatter = QlBaseFormatter(ql, FMT_STR) else: - formatter = QilingPlainFormatter(ql, FMT_STR) + formatter = QlColoredFormatter(ql, FMT_STR) handler.setFormatter(formatter) log.addHandler(handler) else: - log.setLevel(logging.CRITICAL) + handler = logging.NullHandler() + log.addHandler(handler) # Do we have to write log to a file? if log_file is not None: handler = logging.FileHandler(log_file) - formatter = QilingPlainFormatter(ql, FMT_STR) + formatter = QlBaseFormatter(ql, FMT_STR) handler.setFormatter(formatter) log.addHandler(handler) @@ -548,10 +618,10 @@ def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optiona # If there aren't any filters, we do add the filters until users specify any. log_filter = None - if filters is not None and len(filters) != 0: + if filters: log_filter = RegexFilter(filters) log.addFilter(log_filter) - + log.setLevel(logging.INFO) return log, log_filter @@ -559,8 +629,7 @@ def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optiona # verify if emulator returns properly def verify_ret(ql, err): - ql.log.debug("Got exception %u: init SP = %x, current SP = %x, PC = %x" %(err.errno, ql.os.init_sp, ql.reg.arch_sp, ql.reg.arch_pc)) - # print("Got exception %u: init SP = %x, current SP = %x, PC = %x" %(err.errno, ql.os.init_sp, self.reg.arch_sp, self.reg.arch_pc)) + 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 @@ -569,20 +638,20 @@ def verify_ret(ql, err): if ql.ostype == QL_OS.MACOS: if ql.loader.kext_name: # FIXME: Should I push saved RIP before every method callings of IOKit object? - if ql.os.init_sp == ql.reg.arch_sp - 8: + if ql.os.init_sp == ql.arch.regs.arch_sp - 8: pass else: raise - if ql.archtype == QL_ARCH.X8664: # Win64 - if ql.os.init_sp == ql.reg.arch_sp or ql.os.init_sp + 8 == ql.reg.arch_sp or ql.os.init_sp + 0x10 == ql.reg.arch_sp: # FIXME + if ql.arch.type == QL_ARCH.X8664: # Win64 + if ql.os.init_sp == ql.arch.regs.arch_sp or ql.os.init_sp + 8 == ql.arch.regs.arch_sp or ql.os.init_sp + 0x10 == ql.arch.regs.arch_sp: # FIXME # 0x11626 c3 ret # print("OK, stack balanced!") pass else: raise else: # Win32 - if ql.os.init_sp + 12 == ql.reg.arch_sp: # 12 = 8 + 4 + if ql.os.init_sp + 12 == ql.arch.regs.arch_sp: # 12 = 8 + 4 # 0x114dd c2 08 00 ret 8 pass else: diff --git a/qltool b/qltool index 4dbaa1ca..108f0843 100755 --- a/qltool +++ b/qltool @@ -28,21 +28,24 @@ def read_file(fname: str): class __arg_env(argparse.Action): def __call__(self, parser, namespace, values, option_string): - env = {} - if os.path.exists(values): with open(values, 'rb') as f: env = pickle.load(f) else: env = ast.literal_eval(values) - setattr(namespace, self.dest, env) + setattr(namespace, self.dest, env or {}) class __arg_verbose(argparse.Action): def __call__(self, parser, namespace, values, option_string): setattr(namespace, self.dest, verbose_map[values]) def handle_code(options: argparse.Namespace): + archendian = { + 'little': QL_ENDIAN.EL, + 'big' : QL_ENDIAN.EB + }[options.endian] + if options.format == 'hex': if options.input is not None: print ("Load HEX from ARGV") @@ -64,12 +67,7 @@ def handle_code(options: argparse.Namespace): assembly = read_file(options.filename) archtype = arch_convert(options.arch) - archendian = { - 'little': QL_ENDIAN.EL, - 'big' : QL_ENDIAN.EB - }[options.endian] - - assembler = arch_utils.assembler(archtype, archendian) + assembler = arch_utils.assembler(archtype, archendian, options.thumb) code, _ = assembler.asm(assembly) code = bytes(code) @@ -87,10 +85,10 @@ def handle_code(options: argparse.Namespace): code=code, ostype=options.os, archtype=options.arch, - bigendian=(options.endian == 'big'), verbose=options.verbose, profile=options.profile, - filter=options.filter + filter=options.filter, + endian=archendian, ) return ql @@ -184,7 +182,8 @@ 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('--endian', choices=('little', 'big'), default='little') + code_parser.add_argument('--thumb', action='store_true', 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', help='emulated root filesystem, that is where all libraries reside') code_parser.add_argument('--format', choices=('asm', 'hex', 'bin'), default='bin', help='input file format') @@ -199,7 +198,7 @@ if __name__ == '__main__': # set common options comm_parser.add_argument('-v', '--verbose', choices=verbose_map, default=QL_VERBOSE.DEFAULT, action=__arg_verbose, help='set verbosity level') - comm_parser.add_argument('--env', metavar="FILE", action=__arg_env, help="pickle file containing an environment dictionary") + comm_parser.add_argument('--env', metavar="FILE", action=__arg_env, default={}, help="pickle file containing an environment dictionary") comm_parser.add_argument('-g', '--gdb', nargs='?', metavar='SERVER:PORT', const='gdb', help='enable gdb server') comm_parser.add_argument('--qdb', action='store_true', help='attach Qdb at entry point, it\'s MIPS, ARM(THUMB) supported only for now') comm_parser.add_argument('--rr', action='store_true', help='switch on record and replay feature in qdb, only works with --qdb') diff --git a/tests/test_android.py b/tests/test_android.py index 7d838376..79d18ea6 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -6,7 +6,7 @@ import os, platform, sys, unittest sys.path.append("..") -from qiling import * +from qiling import Qiling from qiling.const import QL_VERBOSE diff --git a/tests/test_blob.py b/tests/test_blob.py index 281c998e..1a702f01 100644 --- a/tests/test_blob.py +++ b/tests/test_blob.py @@ -20,31 +20,31 @@ class BlobTest(unittest.TestCase): value_addr = ql.os.heap.alloc(len(value)) ql.mem.write(value_addr, value) - ql.reg.r0 = value_addr - ql.reg.arch_pc = ql.reg.lr + ql.arch.regs.r0 = value_addr + ql.arch.regs.arch_pc = ql.arch.regs.lr def check_password(ql, *args, **kwargs): - passwd_output = ql.mem.read(ql.reg.r0, ql.reg.r2) - passwd_input = ql.mem.read(ql.reg.r1, ql.reg.r2) + passwd_output = ql.mem.read(ql.arch.regs.r0, ql.arch.regs.r2) + passwd_input = ql.mem.read(ql.arch.regs.r1, ql.arch.regs.r2) self.assertEqual(passwd_output, passwd_input) def partial_run_init(ql): # argv prepare - ql.reg.arch_sp -= 0x30 - arg0_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x30 + arg0_ptr = ql.arch.regs.arch_sp ql.mem.write(arg0_ptr, b"kaimendaji") - ql.reg.arch_sp -= 0x10 - arg1_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x10 + arg1_ptr = ql.arch.regs.arch_sp ql.mem.write(arg1_ptr, b"013f1f") - ql.reg.arch_sp -= 0x20 - argv_ptr = ql.reg.arch_sp - ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) - ql.mem.write(argv_ptr + ql.pointersize, ql.pack(arg1_ptr)) + ql.arch.regs.arch_sp -= 0x20 + argv_ptr = ql.arch.regs.arch_sp + ql.mem.write_ptr(argv_ptr, arg0_ptr) + ql.mem.write_ptr(argv_ptr + ql.arch.pointersize, arg1_ptr) - ql.reg.r2 = 2 - ql.reg.r3 = argv_ptr + ql.arch.regs.r2 = 2 + ql.arch.regs.r3 = argv_ptr print("ARM uboot bin") diff --git a/tests/test_dos.py b/tests/test_dos.py index 2c2b0803..449b19a0 100644 --- a/tests/test_dos.py +++ b/tests/test_dos.py @@ -26,8 +26,8 @@ class DOSTest(unittest.TestCase): def onexit(ql: Qiling): ck.visited_onexit = True - ql.set_api((0x21, 0x09), onexit, QL_INTERCEPT.EXIT) - ql.set_api((0x21, 0x4c), onenter, QL_INTERCEPT.ENTER) + ql.os.set_api((0x21, 0x09), onexit, QL_INTERCEPT.EXIT) + ql.os.set_api((0x21, 0x4c), onenter, QL_INTERCEPT.ENTER) ql.run() diff --git a/tests/test_edl.py b/tests/test_edl.py index e14546d7..7f86845a 100644 --- a/tests/test_edl.py +++ b/tests/test_edl.py @@ -15,8 +15,8 @@ from unicorn.arm64_const import * def replace_function(ql,addr,callback): def runcode(ql): ret=callback(ql) - ql.reg.x0=ret - ql.reg.pc=ql.reg.x30 #lr + ql.arch.regs.x0=ret + ql.arch.regs.pc=ql.arch.regs.x30 #lr ql.hook_address(runcode,addr) def hook_mem_invalid(uc, access, address, size, value, user_data): @@ -55,12 +55,12 @@ class TestAndroid(unittest.TestCase): def devprg_time_usec(ql): current_milli_time = int(round(time.time() * 1000)) - ql.reg.x0 = current_milli_time + ql.arch.regs.x0 = current_milli_time return current_milli_time def devprg_tx_blocking(ql): - ptr = ql.reg.x0 - plen = ql.reg.x1 + ptr = ql.arch.regs.x0 + plen = ql.arch.regs.x1 data = ql.mem.read(ptr, plen) res=bytes(data) if b"response" in res: @@ -75,13 +75,13 @@ class TestAndroid(unittest.TestCase): replace_function(ql, 0x148595A0, devprg_time_usec) # Register 0xC221000 replace_function(ql, 0x1485C614, devprg_tx_blocking) # Function being used by UART in DP_LOGI - ql.reg.sp = 0x146B2000 # SP from main + ql.arch.regs.sp = 0x146B2000 # SP from main xml_buffer_addr = 0x14684E80 # We extracted that from devprg_get_xml_buffer device_serial_addr = 0x148A8A8C device_serial = pack("\n\n\x00" - ql.reg.x0 = xml_buffer_addr - ql.reg.x1 = len(uart_data) + ql.arch.regs.x0 = xml_buffer_addr + ql.arch.regs.x1 = len(uart_data) ql.mem.write(xml_buffer_addr, uart_data) ql.mem.write(device_serial_addr, device_serial) handle_xml_addr_start=0x14857C94 diff --git a/tests/test_elf.py b/tests/test_elf.py index e4f84378..96bb9702 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -7,7 +7,7 @@ import sys, unittest, string, random, os, io, re sys.path.append("..") from qiling import Qiling -from qiling.const import QL_OS, QL_INTERCEPT, QL_VERBOSE +from qiling.const import QL_OS, QL_INTERCEPT, QL_STOP, QL_VERBOSE from qiling.exception import * from qiling.extensions import pipe from qiling.os.const import STRING @@ -19,7 +19,7 @@ class ELFTest(unittest.TestCase): def test_libpatch_elf_linux_x8664(self): ql = Qiling(["../examples/rootfs/x8664_linux/bin/patch_test.bin"], "../examples/rootfs/x8664_linux") - ql.patch(0x0000000000000575, b'qiling\x00', file_name = b'libpatch_test.so') + ql.patch(0x0000000000000575, b'qiling\x00', target='libpatch_test.so') ql.run() del ql @@ -74,21 +74,21 @@ class ELFTest(unittest.TestCase): nonlocal snapshot nonlocal reg nonlocal ctx - ql.save(reg=reg, cpu_context=ctx, os_context=True, loader=True, snapshot=snapshot) + ql.save(reg=reg, cpu_context=ctx, os=True, loader=True, snapshot=snapshot) ql.emu_stop() ql.hook_address(dump, hook_address) ql.run() # make sure that the ending PC is the same as the hook address because dump stops the emulater - assert ql.reg.arch_pc == hook_address, f"0x{ql.reg.arch_pc:x} != 0x{hook_address:x}" + assert ql.arch.regs.arch_pc == hook_address, f"0x{ql.arch.regs.arch_pc:x} != 0x{hook_address:x}" del ql ql = Qiling(cmdline, rootfs, verbose=QL_VERBOSE.DEBUG) ql.restore(snapshot=snapshot) # ensure that the starting PC is same as the PC we stopped on when taking the snapshot - assert ql.reg.arch_pc == hook_address, f"0x{ql.reg.arch_pc:x} != 0x{hook_address:x}" + assert ql.arch.regs.arch_pc == hook_address, f"0x{ql.arch.regs.arch_pc:x} != 0x{hook_address:x}" ql.run(begin=hook_address) del ql @@ -108,7 +108,7 @@ class ELFTest(unittest.TestCase): def my_puts(ql: Qiling): params = ql.os.resolve_fcall_params(ELFTest.PARAMS_PUTS) print(f'puts("{params["s"]}")') - reg = ql.reg.read("rax") + reg = ql.arch.regs.read("rax") print("reg : %#x" % reg) self.set_api = reg @@ -127,9 +127,9 @@ class ELFTest(unittest.TestCase): return str_len + 1 ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_args","1234test", "12345678", "bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(1, write_onEnter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts) - ql.set_syscall(1, write_onexit, QL_INTERCEPT.EXIT) + ql.os.set_syscall(1, write_onEnter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts) + ql.os.set_syscall(1, write_onexit, QL_INTERCEPT.EXIT) ql.mem.map(0x1000, 0x1000) ql.mem.write(0x1000, b"\xFF\xFE\xFD\xFC\xFB\xFA\xFB\xFC\xFC\xFE\xFD") ql.mem.map(0x2000, 0x1000) @@ -153,11 +153,11 @@ class ELFTest(unittest.TestCase): self.test_enter_str = params["s"] def my_puts_exit(ql): - self.test_exit_rdi = ql.reg.rdi + self.test_exit_rdi = ql.arch.regs.rdi ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_puts"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts_enter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts_exit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_enter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_exit, QL_INTERCEPT.EXIT) ql.run() @@ -187,7 +187,7 @@ class ELFTest(unittest.TestCase): def hook_main(ql: Qiling): # set up fopen hook when reaching main - ql.set_api('fopen', onenter_fopen, QL_INTERCEPT.ENTER) + ql.os.set_api('fopen', onenter_fopen, QL_INTERCEPT.ENTER) ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_fetch_urandom"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEFAULT) @@ -233,7 +233,7 @@ class ELFTest(unittest.TestCase): real_path = ql.os.fd[read_fd].name with open(real_path) as fd: assert fd.read() == ql.mem.read(read_buf, read_count).decode() - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -252,7 +252,7 @@ class ELFTest(unittest.TestCase): real_path = ql.os.fd[write_fd].name with open(real_path) as fd: assert fd.read() == 'Hello testing\x00' - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -270,7 +270,7 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.path.isfile(real_path) == True - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -304,7 +304,7 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -313,9 +313,9 @@ class ELFTest(unittest.TestCase): target = False pathname = ql.os.fd[ftrunc_fd].name.split('/')[-1] - reg = ql.reg.read("eax") + reg = ql.arch.regs.read("eax") print("reg : 0x%x" % reg) - ql.reg.eax = reg + ql.arch.regs.eax = reg if pathname == "test_syscall_ftruncate.txt": print("test => ftruncate(%d, 0x%x)" % (ftrunc_fd, ftrunc_length)) @@ -326,18 +326,18 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0x10 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_posix_syscall"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(0x3, test_syscall_read) - ql.set_syscall(0x4, test_syscall_write) - ql.set_syscall(0x127, test_syscall_openat) - ql.set_syscall(0xa, test_syscall_unlink) - ql.set_syscall(0x5c, test_syscall_truncate) - ql.set_syscall(0x5d, test_syscall_ftruncate) + ql.os.set_syscall(0x3, test_syscall_read) + ql.os.set_syscall(0x4, test_syscall_write) + ql.os.set_syscall(0x127, test_syscall_openat) + ql.os.set_syscall(0xa, test_syscall_unlink) + ql.os.set_syscall(0x5c, test_syscall_truncate) + ql.os.set_syscall(0x5d, test_syscall_ftruncate) ql.run() del ql @@ -351,7 +351,7 @@ class ELFTest(unittest.TestCase): 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.set_api('puts', my_puts) + ql.os.set_api('puts', my_puts) ql.run() del ql @@ -458,12 +458,12 @@ class ELFTest(unittest.TestCase): # os.remove(real_path) # ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_posix_syscall"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG) - # ql.set_syscall(0x3, test_syscall_read) - # ql.set_syscall(0x4, test_syscall_write) - # ql.set_syscall(0x5, test_syscall_open) - # ql.set_syscall(0xa, test_syscall_unlink) - # ql.set_syscall(0x5c, test_syscall_truncate) - # ql.set_syscall(0x5d, test_syscall_ftruncate) + # ql.os.set_syscall(0x3, test_syscall_read) + # ql.os.set_syscall(0x4, test_syscall_write) + # ql.os.set_syscall(0x5, test_syscall_open) + # ql.os.set_syscall(0xa, test_syscall_unlink) + # ql.os.set_syscall(0x5c, test_syscall_truncate) + # ql.os.set_syscall(0x5d, test_syscall_ftruncate) # ql.run() # del ql @@ -507,7 +507,7 @@ class ELFTest(unittest.TestCase): return 2 ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_double_hello"], "../examples/rootfs/mips32el_linux") - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) ql.run() self.assertEqual(4196680, self.my_puts_onenter_addr) @@ -520,9 +520,9 @@ class ELFTest(unittest.TestCase): target = False pathname = ql.os.fd[read_fd].name.split('/')[-1] - reg = ql.reg.read("x0") + reg = ql.arch.regs.read("x0") print("reg : 0x%x" % reg) - ql.reg.x0 = reg + ql.arch.regs.x0 = reg if pathname == "test_syscall_read.txt": print("test => read(%d, %s, %d)" % (read_fd, pathname, read_count)) @@ -534,7 +534,7 @@ class ELFTest(unittest.TestCase): real_path = ql.os.fd[read_fd].name with open(real_path) as fd: assert fd.read() == ql.mem.read(read_buf, read_count).decode() - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -554,7 +554,7 @@ class ELFTest(unittest.TestCase): real_path = ql.os.fd[write_fd].name with open(real_path) as fd: assert fd.read() == 'Hello testing\x00' - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -573,7 +573,7 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.path.isfile(real_path) == True - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -609,7 +609,7 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -628,18 +628,18 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0x10 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_posix_syscall"], "../examples/rootfs/arm64_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(0x3f, test_syscall_read) - ql.set_syscall(0x40, test_syscall_write) - ql.set_syscall(0x38, test_syscall_openat) - ql.set_syscall(0x402, test_syscall_unlink) - ql.set_syscall(0x2d, test_syscall_truncate) - ql.set_syscall(0x2e, test_syscall_ftruncate) + ql.os.set_syscall(0x3f, test_syscall_read) + ql.os.set_syscall(0x40, test_syscall_write) + ql.os.set_syscall(0x38, test_syscall_openat) + ql.os.set_syscall(0x402, test_syscall_unlink) + ql.os.set_syscall(0x2d, test_syscall_truncate) + ql.os.set_syscall(0x2e, test_syscall_ftruncate) ql.run() del ql @@ -667,9 +667,9 @@ class ELFTest(unittest.TestCase): target = False pathname = ql.os.fd[read_fd].name.split('/')[-1] - reg = ql.reg.read("v0") + reg = ql.arch.regs.read("v0") print("reg : 0x%x" % reg) - ql.reg.v0 = reg + ql.arch.regs.v0 = reg if pathname == "test_syscall_read.txt": print("test => read(%d, %s, %d)" % (read_fd, pathname, read_count)) @@ -681,7 +681,7 @@ class ELFTest(unittest.TestCase): real_path = ql.os.fd[read_fd].name with open(real_path) as fd: assert fd.read() == ql.mem.read(read_buf, read_count).decode() - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -701,7 +701,7 @@ class ELFTest(unittest.TestCase): real_path = ql.os.fd[write_fd].name with open(real_path) as fd: assert fd.read() == 'Hello testing\x00' - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -719,7 +719,7 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.path.isfile(real_path) == True - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -753,7 +753,7 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -771,18 +771,18 @@ class ELFTest(unittest.TestCase): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0x10 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_posix_syscall"], "../examples/rootfs/mips32el_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(4003, test_syscall_read) - ql.set_syscall(4004, test_syscall_write) - ql.set_syscall(4005, test_syscall_open) - ql.set_syscall(4010, test_syscall_unlink) - ql.set_syscall(4092, test_syscall_truncate) - ql.set_syscall(4093, test_syscall_ftruncate) + ql.os.set_syscall(4003, test_syscall_read) + ql.os.set_syscall(4004, test_syscall_write) + ql.os.set_syscall(4005, test_syscall_open) + ql.os.set_syscall(4010, test_syscall_unlink) + ql.os.set_syscall(4092, test_syscall_truncate) + ql.os.set_syscall(4093, test_syscall_ftruncate) ql.run() del ql @@ -794,9 +794,9 @@ class ELFTest(unittest.TestCase): mapaddr = ql.mem.map_anywhere(0x100000) ql.log.info("0x%x" % mapaddr) - reg = ql.reg.read("r0") + reg = ql.arch.regs.read("r0") print("reg : 0x%x" % reg) - ql.reg.r0 = reg + ql.arch.regs.r0 = reg try: @@ -813,7 +813,7 @@ class ELFTest(unittest.TestCase): return regreturn ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux") - ql.set_syscall(0x04, my_syscall_write) + ql.os.set_syscall(0x04, my_syscall_write) ql.run() self.assertEqual(1, self.set_syscall) @@ -830,15 +830,18 @@ class ELFTest(unittest.TestCase): pass def run_one_round(payload): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) - ql = Qiling(["../examples/rootfs/x86_linux/bin/crackme_linux"], "../examples/rootfs/x86_linux", console=False, stdin=mock_stdin) + ql = Qiling(["../examples/rootfs/x86_linux/bin/crackme_linux"], "../examples/rootfs/x86_linux", console=False) + ins_count = [0] ql.hook_code(instruction_count, ins_count) - ql.set_syscall("_llseek", my__llseek) + ql.os.set_syscall("_llseek", my__llseek) + + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) ql.os.stdin.write(payload) + ql.run() - del mock_stdin del ql + return ins_count[0] @@ -897,8 +900,8 @@ class ELFTest(unittest.TestCase): def check_exit_code(ql, exit_code, *args, **kw): ql.exit_code = exit_code - ql.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) - ql.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) ql.run() self.assertEqual(0, ql.exit_code) @@ -934,8 +937,8 @@ class ELFTest(unittest.TestCase): def check_exit_code(ql, exit_code, *args, **kw): ql.exit_code = exit_code - ql.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) - ql.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) ql.run() self.assertEqual(0, ql.exit_code) @@ -956,8 +959,8 @@ class ELFTest(unittest.TestCase): def check_exit_code(ql, exit_code, *args, **kw): ql.exit_code = exit_code - ql.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) - ql.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) ql.run() @@ -979,40 +982,42 @@ class ELFTest(unittest.TestCase): del ql def test_x8664_absolute_path(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x8664_linux/bin/absolutepath"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/absolutepath"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertEqual(ql.os.stdout.read(), b'test_complete\n\ntest_complete\n\n') del ql def test_x8664_getcwd(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x8664_linux/bin/testcwd"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/testcwd"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertEqual(ql.os.stdout.read(), b'/\n/lib\n/bin\n/\n') del ql def test_elf_linux_x86_return_from_main_stackpointer(self): - ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop=QL_STOP.STACK_POINTER) ql.run() del ql def test_elf_linux_x86_return_from_main_exit_trap(self): - ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop=QL_STOP.EXIT_TRAP) ql.run() del ql def test_elf_linux_x8664_return_from_main_stackpointer(self): - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop=QL_STOP.STACK_POINTER) ql.run() del ql def test_elf_linux_x8664_return_from_main_exit_trap(self): - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop=QL_STOP.EXIT_TRAP) ql.run() del ql @@ -1022,11 +1027,14 @@ class ELFTest(unittest.TestCase): del ql def test_elf_linux_x8664_getdents(self): - output = io.BytesIO() - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_getdents"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=output) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_getdents"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + + ql.os.stdout = io.BytesIO() ql.run() - output.seek(0) - self.assertTrue("bin\n" in output.read().decode("utf-8")) + + ql.os.stdout.seek(0) + self.assertTrue("bin\n" in ql.os.stdout.read().decode("utf-8")) + del ql # TODO: Disable for now @@ -1046,10 +1054,11 @@ class ELFTest(unittest.TestCase): del ql def test_elf_linux_x86_getdents64(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_getdents64"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_getdents64"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertTrue("bin\n" in ql.os.stdout.read().decode("utf-8")) del ql @@ -1093,10 +1102,11 @@ class ELFTest(unittest.TestCase): del ql def test_elf_linux_x8664_path_traversion(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x8664_linux/bin/path_traverse_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/path_traverse_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertTrue("root\n" not in ql.os.stdout.read().decode("utf-8")) del ql diff --git a/tests/test_elf_ko.py b/tests/test_elf_ko.py index b3a80b75..c7286e14 100644 --- a/tests/test_elf_ko.py +++ b/tests/test_elf_ko.py @@ -32,7 +32,7 @@ class ELF_KO_Test(unittest.TestCase): try: procfile_read_func_begin = ql.loader.load_address + 0x11e0 procfile_read_func_end = ql.loader.load_address + 0x11fa - ql.set_api("printk", my_printk) + ql.os.set_api("printk", my_printk) ql.run(begin=procfile_read_func_begin, end=procfile_read_func_end) except UcError as e: print(e) @@ -55,7 +55,7 @@ class ELF_KO_Test(unittest.TestCase): try: procfile_read_func_begin = ql.loader.load_address + 0x1064 procfile_read_func_end = ql.loader.load_address + 0x107e - ql.set_api("printk", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("printk", my_onenter, QL_INTERCEPT.ENTER) ql.run(begin=procfile_read_func_begin, end=procfile_read_func_end) except UcError as e: print(e) @@ -76,7 +76,7 @@ class ELF_KO_Test(unittest.TestCase): ql = Qiling(["../examples/rootfs/mips32_linux/kernel/hello.ko"], "../examples/rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG) begin = ql.loader.load_address + 0x1060 end = ql.loader.load_address + 0x1084 - ql.set_api("printk", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("printk", my_onexit, QL_INTERCEPT.EXIT) ql.run(begin=begin, end=end) self.assertEqual("\x016Hello, World!\n", self.set_api_onexit) diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index 5b2a4037..3c9a3f43 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -5,15 +5,10 @@ import platform, sys, unittest, os, threading, time -from unicorn import UcError, UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED - sys.path.append("..") -from qiling import * +from qiling import Qiling from qiling.const import * from qiling.exception import * -from qiling.os.posix import syscall -from qiling.os.mapper import QlFsMappedObject -from qiling.os.posix.stat import Fstat from qiling.os.filestruct import ql_file class ELFTest(unittest.TestCase): @@ -39,13 +34,13 @@ class ELFTest(unittest.TestCase): with open('../examples/rootfs/x8664_linux/testfile', 'wb') as f: f.write(b'0123456789') - err = ql_file.open('output.txt', os.O_RDWR | os.O_CREAT, 0o777) ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_cloexec_test"], "../examples/rootfs/x8664_linux", - verbose=QL_VERBOSE.DEBUG, - stderr=err, + verbose=QL_VERBOSE.DEBUG, multithread=True) + err = ql_file.open('output.txt', os.O_RDWR | os.O_CREAT, 0o777) + ql.os.stderr = err ql.run() os.close(err.fileno()) with open('output.txt', 'rb') as f: @@ -65,7 +60,7 @@ class ELFTest(unittest.TestCase): pass buf_out = None ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_multithreading"], "../examples/rootfs/x86_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -84,7 +79,7 @@ class ELFTest(unittest.TestCase): pass buf_out = None ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_multithreading"], "../examples/rootfs/arm64_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -103,7 +98,7 @@ class ELFTest(unittest.TestCase): 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.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -122,7 +117,7 @@ class ELFTest(unittest.TestCase): pass buf_out = None ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_multithreading"], "../examples/rootfs/mips32el_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -141,7 +136,7 @@ class ELFTest(unittest.TestCase): pass buf_out = None ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_multithreading"], "../examples/rootfs/arm_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -159,7 +154,7 @@ class ELFTest(unittest.TestCase): except: pass ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_tcp_test","20001"], "../examples/rootfs/x86_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server send() 14 return 14.\n", ql.buf_out) @@ -177,7 +172,7 @@ class ELFTest(unittest.TestCase): except: pass ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_tcp_test","20002"], "../examples/rootfs/x8664_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server send() 14 return 14.\n", ql.buf_out) @@ -195,7 +190,7 @@ class ELFTest(unittest.TestCase): except: pass ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_tcp_test","20003"], "../examples/rootfs/arm_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server write() 14 return 14.\n", ql.buf_out) @@ -213,7 +208,7 @@ class ELFTest(unittest.TestCase): except: pass ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_tcp_test","20004"], "../examples/rootfs/arm64_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server send() 14 return 14.\n", ql.buf_out) @@ -238,7 +233,7 @@ class ELFTest(unittest.TestCase): pass ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_udp_test","20007"], "../examples/rootfs/x86_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) @@ -257,7 +252,7 @@ class ELFTest(unittest.TestCase): pass ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_udp_test","20008"], "../examples/rootfs/x8664_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) @@ -275,7 +270,7 @@ class ELFTest(unittest.TestCase): pass ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_udp_test","20009"], "../examples/rootfs/arm64_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) diff --git a/tests/test_macho_kext.py b/tests/test_macho_kext.py index eb8bbca8..879b676f 100644 --- a/tests/test_macho_kext.py +++ b/tests/test_macho_kext.py @@ -82,9 +82,9 @@ class MACHOTest(unittest.TestCase): return ql = Qiling(["../examples/rootfs/x8664_macos/kext/SuperRootkit.kext"], "../examples/rootfs/x8664_macos", verbose=QL_VERBOSE.DISASM) - ql.set_api("_ipf_addv4", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("_strncmp", my_onexit, QL_INTERCEPT.EXIT) - ql.set_api("_strlen", my__strlen) + ql.os.set_api("_ipf_addv4", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("_strncmp", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("_strlen", my__strlen) ql.hook_address(hook_stop, 0xffffff8000854800) try: diff --git a/tests/test_mcu.py b/tests/test_mcu.py index 416c3ee7..78a9ca6c 100644 --- a/tests/test_mcu.py +++ b/tests/test_mcu.py @@ -215,7 +215,7 @@ class MCUTest(unittest.TestCase): ql.run(count=400000, end=0x8003225) - return ql.arch.get_pc() == 0x8003225 + return ql.arch.effective_pc == 0x8003225 self.assertTrue(crack('618618')) self.assertTrue(crack('778899')) @@ -286,7 +286,7 @@ class MCUTest(unittest.TestCase): delay_start = 0x8002936 delay_end = 0x8002955 def skip_delay(ql): - ql.reg.pc = delay_end + ql.arch.regs.pc = delay_end ql.hook_address(skip_delay, delay_start) @@ -307,7 +307,7 @@ class MCUTest(unittest.TestCase): delay_cycles_end = 0x800018c def skip_delay(ql): - ql.reg.pc = delay_cycles_end + ql.arch.regs.pc = delay_cycles_end count = 0 def counter(): diff --git a/tests/test_pathutils.py b/tests/test_pathutils.py index 396206fc..3954203c 100644 --- a/tests/test_pathutils.py +++ b/tests/test_pathutils.py @@ -70,7 +70,7 @@ class TestPathUtils(unittest.TestCase): def test_convert_for_native_os(self): ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - if ql.platform_os == QL_OS.WINDOWS: + if ql.host.os == QL_OS.WINDOWS: rootfs = pathlib.Path("../examples/rootfs/x8664_windows").resolve() self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) diff --git a/tests/test_pe.py b/tests/test_pe.py index b3e3981b..a0848fd0 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -62,6 +62,7 @@ class TestOut: self.output[key] = value return len(string) +IS_FAST_TEST = 'QL_FAST_TEST' in os.environ class PETest(unittest.TestCase): @@ -111,8 +112,6 @@ class PETest(unittest.TestCase): def test_pe_win_x86_uselessdisk(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return class Fake_Drive(QlFsMappedObject): def read(self, size): @@ -135,13 +134,11 @@ class PETest(unittest.TestCase): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x86_gandcrab(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return def stop(ql, default_values): print("Ok for now") ql.emu_stop() @@ -193,7 +190,7 @@ class PETest(unittest.TestCase): randomize_config_value(ql, "USER", "username") randomize_config_value(ql, "SYSTEM", "computername") randomize_config_value(ql, "VOLUME", "serial_number") - num_syscalls_admin = ql.os.utils.syscalls_counter + num_syscalls_admin = ql.os.stats.syscalls_counter ql.run() del ql @@ -201,7 +198,7 @@ class PETest(unittest.TestCase): ql = Qiling(["../examples/rootfs/x86_windows/bin/GandCrab502.bin"], "../examples/rootfs/x86_windows", profile="profiles/windows_gandcrab_user.ql") ql.run() - num_syscalls_user = ql.os.utils.syscalls_counter + num_syscalls_user = ql.os.stats.syscalls_counter # let's check that gandcrab behave takes a different path if a different environment is found if num_syscalls_admin == num_syscalls_user: @@ -210,7 +207,7 @@ class PETest(unittest.TestCase): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x86_multithread(self): def _t(): @@ -221,7 +218,7 @@ class PETest(unittest.TestCase): return address, params ql = Qiling(["../examples/rootfs/x86_windows/bin/MultiThread.exe"], "../examples/rootfs/x86_windows") - ql.set_api("GetCurrentThreadId", ThreadId_onEnter, QL_INTERCEPT.ENTER) + ql.os.set_api("GetCurrentThreadId", ThreadId_onEnter, QL_INTERCEPT.ENTER) ql.run() if not ( 1<= thread_id < 255): @@ -285,7 +282,7 @@ class PETest(unittest.TestCase): def test_pe_win_x86_return_from_main_stackpointer(self): def _t(): - ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", libcache=True, stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", stop=QL_STOP.STACK_POINTER, libcache=True) ql.run() del ql return True @@ -295,7 +292,7 @@ class PETest(unittest.TestCase): def test_pe_win_x86_return_from_main_exit_trap(self): def _t(): - ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", libcache=True, stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", stop=QL_STOP.EXIT_TRAP, libcache=True) ql.run() del ql return True @@ -305,7 +302,7 @@ class PETest(unittest.TestCase): def test_pe_win_x8664_return_from_main_stackpointer(self): def _t(): - ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", libcache=True, stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", stop=QL_STOP.STACK_POINTER, libcache=True) ql.run() del ql return True @@ -315,7 +312,7 @@ class PETest(unittest.TestCase): def test_pe_win_x8664_return_from_main_exit_trap(self): def _t(): - ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", libcache=True, stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", stop=QL_STOP.EXIT_TRAP, libcache=True) ql.run() del ql return True @@ -325,8 +322,6 @@ class PETest(unittest.TestCase): def test_pe_win_x86_wannacry(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return def stop(ql): ql.log.info("killerswtichfound") ql.log.setLevel(logging.CRITICAL) @@ -339,7 +334,7 @@ class PETest(unittest.TestCase): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x86_NtQueryInformationSystem(self): @@ -356,18 +351,16 @@ class PETest(unittest.TestCase): def test_pe_win_al_khaser(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return ql = Qiling(["../examples/rootfs/x86_windows/bin/al-khaser.bin"], "../examples/rootfs/x86_windows") # The hooks are to remove the prints to file. It crashes. will debug why in the future def results(ql): - if ql.reg.ebx == 1: + if ql.arch.regs.ebx == 1: print("BAD") else: print("GOOD ") - ql.reg.eip = 0x402ee4 + ql.arch.regs.eip = 0x402ee4 #ql.hook_address(results, 0x00402e66) # the program alloc 4 bytes and then tries to write 0x2cc bytes. @@ -384,7 +377,7 @@ class PETest(unittest.TestCase): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x8664_customapi(self): @@ -421,9 +414,9 @@ class PETest(unittest.TestCase): def my_sandbox(path, rootfs): nonlocal set_api, set_api_onenter, set_api_onexit ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG) - ql.set_api("puts", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("puts", my_puts64, QL_INTERCEPT.CALL) - ql.set_api("puts", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("puts", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("puts", my_puts64, QL_INTERCEPT.CALL) + ql.os.set_api("puts", my_onexit, QL_INTERCEPT.EXIT) ql.run() if 12 != set_api_onenter: @@ -462,7 +455,7 @@ class PETest(unittest.TestCase): arglist = params['_ArgList'] count = format.count("%") - fargs = [ql.unpack(ql.mem.read(arglist + i * ql.pointersize, ql.pointersize)) for i in range(count)] + fargs = [ql.unpack(ql.mem.read(arglist + i * ql.arch.pointersize, ql.arch.pointersize)) for i in range(count)] target_txt = "" @@ -474,7 +467,7 @@ class PETest(unittest.TestCase): return address, params ql = Qiling(["../examples/rootfs/x86_windows/bin/argv.exe"], "../examples/rootfs/x86_windows") - ql.set_api('__stdio_common_vfprintf', check_print, QL_INTERCEPT.ENTER) + ql.os.set_api('__stdio_common_vfprintf', check_print, QL_INTERCEPT.ENTER) ql.run() if target_txt.find("argv.exe"): @@ -493,7 +486,7 @@ class PETest(unittest.TestCase): def _t(): def force_call_dialog_func(ql): # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) + lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) # setup stack for DialogFunc ql.stack_push(0) ql.stack_push(1001) @@ -501,7 +494,7 @@ class PETest(unittest.TestCase): ql.stack_push(0) ql.stack_push(0x0401018) # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + ql.arch.regs.eip = lpDialogFunc def our_sandbox(path, rootfs): ql = Qiling(path, rootfs) diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index 56c7f5ae..ae2ef439 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -9,7 +9,7 @@ from unicorn import UcError sys.path.append("..") from qiling import Qiling -from qiling.const import QL_VERBOSE +from qiling.const import QL_STOP, QL_VERBOSE from qiling.os.const import POINTER, DWORD, STRING, HANDLE from qiling.os.windows import utils from qiling.os.windows.wdk_const import * @@ -23,7 +23,7 @@ if platform.system() == "Darwin" and platform.machine() == "arm64": class PETest(unittest.TestCase): def hook_third_stop_address(self, ql): - print(" >>>> Third Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> Third Stop address: 0x%08x" % ql.arch.regs.arch_pc) self.third_stop = True ql.emu_stop() @@ -87,7 +87,7 @@ class PETest(unittest.TestCase): return ret - def _WriteFile(ql, address, params): + def _WriteFile(ql: Qiling, address: int, params): ret = 1 hFile = params["hFile"] lpBuffer = params["lpBuffer"] @@ -98,19 +98,18 @@ class PETest(unittest.TestCase): if hFile == 0xfffffff5: s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) - ql.os.utils.string_appearance(s.decode()) - ql.mem.write(lpNumberOfBytesWritten, ql.pack(nNumberOfBytesToWrite)) + ql.os.stats.log_string(s.decode()) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite) else: f = ql.os.handle_manager.get(hFile) if f is None: # Invalid handle ql.os.last_error = 0xffffffff return 0 - else: - f = f.obj + buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - f.write(bytes(buffer)) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + f.obj.write(bytes(buffer)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) return ret @winsdkapi(cc=STDCALL, params={ @@ -130,7 +129,7 @@ class PETest(unittest.TestCase): buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) try: r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) except Exception: print("Error") r = 1 @@ -177,29 +176,28 @@ class PETest(unittest.TestCase): def hook_first_stop_address(ql): - print(" >>>> First Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> First Stop address: 0x%08x" % ql.arch.regs.arch_pc) ql.first_stop = True ql.emu_stop() def hook_second_stop_address(ql): - print(" >>>> Second Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> Second Stop address: 0x%08x" % ql.arch.regs.arch_pc) ql.second_stop = True ql.emu_stop() ql = Qiling(["../examples/rootfs/x86_windows/bin/sality.dll"], "../examples/rootfs/x86_windows", verbose=QL_VERBOSE.DEBUG) - ql.libcache = False ql.first_stop = False ql.second_stop = False self.third_stop = False # for this module ql.amsint32_driver = None # emulate some Windows API - ql.set_api("CreateThread", hook_CreateThread) - ql.set_api("CreateFileA", hook_CreateFileA) - ql.set_api("WriteFile", hook_WriteFile) - ql.set_api("StartServiceA", hook_StartServiceA) + ql.os.set_api("CreateThread", hook_CreateThread) + ql.os.set_api("CreateFileA", hook_CreateFileA) + ql.os.set_api("WriteFile", hook_WriteFile) + ql.os.set_api("StartServiceA", hook_StartServiceA) #init sality ql.hook_address(hook_first_stop_address, 0x40EFFB) ql.run() @@ -234,7 +232,7 @@ class PETest(unittest.TestCase): def test_pe_win_x8664_driver(self): # Compiled sample from https://github.com/microsoft/Windows-driver-samples/tree/master/general/ioctl/wdm/sys - ql = Qiling(["../examples/rootfs/x8664_windows/bin/sioctl.sys"], "../examples/rootfs/x8664_windows", libcache=True, stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x8664_windows/bin/sioctl.sys"], "../examples/rootfs/x8664_windows", stop=QL_STOP.STACK_POINTER, libcache=True) driver_object = ql.loader.driver_object @@ -257,7 +255,7 @@ class PETest(unittest.TestCase): # And a DriverUnload self.assertNotEqual(driver_object.DriverUnload, 0) - ql.os.utils.clear_syscalls() + ql.os.stats.clear() IOCTL_SIOCTL_METHOD_OUT_DIRECT = (40000, 0x901, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) output_buffer_size = 0x1000 diff --git a/tests/test_qnx.py b/tests/test_qnx.py index 43f9b252..0303c7fb 100644 --- a/tests/test_qnx.py +++ b/tests/test_qnx.py @@ -58,11 +58,11 @@ class QNXTest(unittest.TestCase): return QL_CALL_BLOCK ql = Qiling(["../examples/rootfs/arm_qnx/bin/hello_sqrt"], "../examples/rootfs/arm_qnx", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) - ql.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) - - # ql.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) - ql.set_api('printf', my_printf_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) + + # ql.os.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('printf', my_printf_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/tests/test_riscv.py b/tests/test_riscv.py index 365f2bd4..f1013fbe 100644 --- a/tests/test_riscv.py +++ b/tests/test_riscv.py @@ -14,11 +14,12 @@ class RISCVTest(unittest.TestCase): def test_riscv32_hello_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv32_linux/bin/hello'], '../examples/rootfs/riscv32_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) - + verbose=QL_VERBOSE.DEFAULT) + def close(ql, fd): return 0 - ql.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') @@ -27,11 +28,12 @@ class RISCVTest(unittest.TestCase): def test_riscv64_hello_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv64_linux/bin/hello'], '../examples/rootfs/riscv64_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) + verbose=QL_VERBOSE.DEFAULT) def close(ql, fd): return 0 - ql.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') @@ -40,8 +42,9 @@ class RISCVTest(unittest.TestCase): def test_riscv64_hello_dyn_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv64_linux/bin/hello-linux'], '../examples/rootfs/riscv64_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) + verbose=QL_VERBOSE.DEFAULT) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') @@ -50,8 +53,9 @@ class RISCVTest(unittest.TestCase): def test_riscv32_hello_dyn_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv32_linux/bin/hello-linux'], '../examples/rootfs/riscv32_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) + verbose=QL_VERBOSE.DEFAULT) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') diff --git a/tests/test_uefi.py b/tests/test_uefi.py index b5180c36..f29420d9 100644 --- a/tests/test_uefi.py +++ b/tests/test_uefi.py @@ -101,9 +101,9 @@ class Test_UEFI(unittest.TestCase): ql = Qiling([f'{ROOTFS_UEFI}/bin/TcgPlatformSetupPolicy'], ROOTFS_UEFI, env=env, verbose=QL_VERBOSE.DEBUG) self.ck = Checklist() - ql.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) - ql.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("LocateProtocol", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) + ql.os.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("LocateProtocol", my_onexit, QL_INTERCEPT.EXIT) ql.run()