diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 36523a470..c28d542a3 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: #os: [windows-2019, macos-10.15, ubuntu-18.04, ubuntu-20.04] - os: [windows-2019, ubuntu-18.04, ubuntu-20.04] + os: [windows-latest, ubuntu-18.04, ubuntu-20.04] python-version: [3.8, 3.9] exclude: - os: ubuntu-18.04 diff --git a/.github/workflows/giteesync.yml b/.github/workflows/giteesync.yml index 4e7104ba4..dbf797497 100644 --- a/.github/workflows/giteesync.yml +++ b/.github/workflows/giteesync.yml @@ -5,6 +5,7 @@ jobs: deploy: runs-on: ubuntu-latest + if: github.repository_owner == 'qilingframework' steps: - uses: actions/checkout@v2 with: diff --git a/CREDITS.md b/CREDITS.md index 09fc255b1..9ea9f0edf 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -3,27 +3,27 @@ #### Founder -- LAU kaijern (xwings) +- LAU kaijern (xwings) #### Advisor -- NGUYEN Anh Quynh +- NGUYEN Anh Quynh #### Core Developers Crew -- Earl MARCUS (klks84) klks84@gmail.com -- WU chenxu (kabeor) -- KONG ziqiao (lazymio) -- YU zheng (dataisland) -- Eli Cohen Nehemia (elicn) +- Earl MARCUS (klks84) +- WU chenxu (kabeor) +- KONG ziqiao (lazymio) +- YU zheng (dataisland) +- Eli Cohen Nehemia (elicn) #### CI, Website,Documentations, Logo & Swags -- FOO Kevin (chfl4gs) -- SU muchen (Mirai Suu) +- FOO Kevin (chfl4gs) +- SU muchen (miraisuu) #### Key Contributors (in no particular order) @@ -52,14 +52,17 @@ - bambu - madprogrammer - danielmoos +- sigeryang +- bet4it +- nullableVoidPtr #### Legacy Core Developers -- DING tianze (D1iv3) -- SUN bowen (w1tcher) -- CHEN huitao (null) -- YU tong (sp1ke) +- DING tianze (D1iv3) +- SUN bowen (w1tcher) +- CHEN huitao (null) +- YU tong (sp1ke) #### Demigod team (https://groundx.io/demigod) diff --git a/ChangeLog b/ChangeLog index 078fadbc4..b178267bc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,58 @@ This file details the changelog of Qiling Framework. +------------------------------------ +[Version 1.4.4]: July XX, 2022 + + +------------------------------------ +[Version 1.4.3]: June 1st, 2022 + +New features: +- Introduce PowerPC architecture support (#1140) + +Improvements: +- Fix fuzzing for tendaac15 (#1096) +- Update unicorn version to 2.0-rc6 (#1100) +- Implemented a few more Windows msvcrt functions (#1102) +- Minor PE Loader fix (#1104) +- Minor quality changes (#1106) +- Fix cacheflush syscall typo (#1115) +- Improvements and fixes for Windows and PE (#1118) +- Add vm_context to EVM hooks (#1119) +- Load interpreter segments with correct perms and vaddr (#1120) +- Fix mistakes in fuzz_x8664_linux binary (#1121) +- Add EVM ABI helpers, fix EVM DBG stack view (#1123) +- Fix regression caused by missing exception handling when opening socket (#1124) +- CI improvement (#1128 #1134) +- Add macho load command 'LC_LOAD_WEAK_DYLIB' support (#1133) +- Fix breakage of non-Windows binary emulation on Windows host (#1143) +- Remove misused region bound check of unmap_all (#1144) +- Change deprecated interfaces of IDA (#1145) +- Use importlib to retrieve package version (#1146) +- New and improved gdbserver (#1148) +- Rewrite package data reading (#1150) +- Misc improvements (#1154) +- Fix memory exhaustion problem caused by the logger (#1161) + +Contributors: +- wtdcode +- aquynh +- elicn +- xwings +- cq674350529 +- elicn +- TheZ3ro +- bet4it +- chinggg +- kabeor +- chfl4gs +- profiles +- OlfillasOdikno +- nmantan +- machinewu +- nullableVoidPtr +- Phat3 + ------------------------------------ [Version 1.4.2]: Feb 13th, 2022 diff --git a/Dockerfile b/Dockerfile index 2a1360b46..99a0a9bf6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ WORKDIR /qiling RUN apt-get update \ && apt-get install -y --no-install-recommends unzip apt-utils \ && rm -rf /var/lib/apt/lists/* \ - && pip3 install wheels/*.whl \ + && pip3 install --no-deps wheels/*.whl \ && rm -rf wheels ENV HOME /qiling diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index ead0863c9..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -recursive-include qiling/debugger/gdb/xml * -recursive-include qiling/extensions/windows_sdk/defs * -recursive-include qiling/profiles * -include qiling/os/uefi/guids.csv -include qiling/os/qnx/syspage.bin diff --git a/README.md b/README.md index 849c2dfc0..8abbe0ef6 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,12 @@

+[Qiling's usecase, blog and related work](https://github.com/qilingframework/qiling/issues/134) + Qiling is an advanced binary emulation framework, with the following features: -- Emulate multi-platforms: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine -- Emulate multi-architectures: X86, X86_64, Arm, Arm64, MIPS, 8086 +- Emulate multi-platforms: Windows, MacOS, Linux, Android, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine +- Emulate multi-architectures: 8086, X86, X86_64, ARM, ARM64, MIPS, RISCV, PowerPC - Support multiple file formats: PE, MachO, ELF, COM, MBR - Support Windows Driver (.sys), Linux Kernel Module (.ko) & MacOS Kernel (.kext) via [Demigod](https://groundx.io/demigod/) - Emulates & sandbox code in an isolated environment @@ -88,55 +90,55 @@ Please see [setup guide](https://docs.qiling.io/en/latest/install/) file for how #### Examples -- Below example shows how to use Qiling framework to emulate a Windows EXE on a Linux machine +- The example below shows how to use Qiling framework in the most striaghtforward way to emulate a Windows executable. ```python -from qiling import * - -# sandbox to emulate the EXE -def my_sandbox(path, rootfs): - # setup Qiling engine - ql = Qiling(path, rootfs) - # now emulate the EXE - ql.run() +from qiling import Qiling if __name__ == "__main__": - # execute Windows EXE under our rootfs - my_sandbox(["examples/rootfs/x86_windows/bin/x86_hello.exe"], "examples/rootfs/x86_windows") + # initialize Qiling instance, specifying the executable to emulate and the emulated system root. + # note that the current working directory is assumed to be Qiling home + ql = Qiling([r'examples/rootfs/x86_windows/bin/x86_hello.exe'], r'examples/rootfs/x86_windows') + + # start emulation + ql.run() ``` -- Below example shows how to use Qiling framework to dynamically patch a Windows crackme, make it always display "Congratulation" dialog +- The following example shows how a Windows crackme may be patched dynamically to make it always display the "Congratulation" dialog. ```python -from qiling import * +from qiling import Qiling + +def force_call_dialog_func(ql: Qiling): + # get DialogFunc address from current stack frame + lpDialogFunc = ql.stack_read(-8) -def force_call_dialog_func(ql): - # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) # setup stack memory for DialogFunc ql.stack_push(0) - ql.stack_push(1001) - ql.stack_push(273) + ql.stack_push(1001) # IDS_APPNAME + ql.stack_push(0x111) # WM_COMMAND ql.stack_push(0) + + # push return address ql.stack_push(0x0401018) - # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + + # resume emulation from DialogFunc address + ql.arch.regs.eip = lpDialogFunc -def my_sandbox(path, rootfs): - ql = Qiling(path, rootfs) +if __name__ == "__main__": + # initialize Qiling instance + ql = Qiling([r'rootfs/x86_windows/bin/Easy_CrackMe.exe'], r'rootfs/x86_windows') + # NOP out some code ql.patch(0x004010B5, b'\x90\x90') ql.patch(0x004010CD, b'\x90\x90') ql.patch(0x0040110B, b'\x90\x90') ql.patch(0x00401112, b'\x90\x90') + # hook at an address with a callback ql.hook_address(force_call_dialog_func, 0x00401016) ql.run() - - -if __name__ == "__main__": - my_sandbox(["rootfs/x86_windows/bin/Easy_CrackMe.exe"], "rootfs/x86_windows") ``` The below Youtube video shows how the above example works. @@ -221,6 +223,7 @@ Contact us at email info@qiling.io, or via Twitter [@qiling_io](https://twitter. Please refer to [CREDITS.md](https://github.com/qilingframework/qiling/blob/dev/CREDITS.md) + --- #### This is an awesome project! Can I donate? diff --git a/examples/crackme_x86_linux.py b/examples/crackme_x86_linux.py index 37a057c62..9373d133c 100644 --- a/examples/crackme_x86_linux.py +++ b/examples/crackme_x86_linux.py @@ -16,14 +16,11 @@ 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 @@ def __init__(self, invalid: bytes): # # 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, @@ -92,21 +89,26 @@ def replay(self, input: bytes) -> bool: return False +def progress(msg: str) -> None: + print(msg, end='\r', file=sys.stderr, flush=True) + def main(): - idx_list = (1, 4, 2, 0, 3) - flag = [0] * len(idx_list) + flag = bytearray(b'*****') + indices = (1, 4, 2, 0, 3) - solver = Solver(bytes(flag)) + # all possible flag characters (may be reduced to uppercase and digits to save time) + charset = string.printable - for idx in idx_list: + progress('Initializing...') + solver = Solver(flag) - # bruteforce all possible flag characters - for ch in string.printable: - flag[idx] = ord(ch) + for i in indices: + for ch in charset: + flag[i] = ord(ch) - print(f'Guessing... [{"".join(chr(ch) if ch else "_" for ch in flag)}]', end='\r', file=sys.stderr, flush=True) + progress(f'Guessing... {flag.decode()}') - if solver.replay(bytes(flag)): + if solver.replay(flag): break else: @@ -116,3 +118,5 @@ def main(): if __name__ == "__main__": main() + +# expected flag: L1NUX diff --git a/examples/crackme_x86_windows.py b/examples/crackme_x86_windows.py index e256ef730..c47da07eb 100644 --- a/examples/crackme_x86_windows.py +++ b/examples/crackme_x86_windows.py @@ -10,50 +10,111 @@ from qiling.const import QL_VERBOSE from qiling.extensions import pipe -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, - stdin=mock_stdin, - stdout=mock_stdout) - - ql.os.stdin.write(bytes("".join(flag) + "\n", 'utf-8')) - count = [0] - - ql.hook_code(instruction_count, count) - ql.run() - - print(ql.os.stdout.read().decode('utf-8'), end='') - print(" ============ count: %d ============ " % count[0]) - - return count[0] - -def solve(): - # BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53} - prefix = list("BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C") - flag = list("\x00" * 100) - base = get_count(prefix + flag) - i = 0 - - try: - for i in range(len(flag)): - for j in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-{}": - flag[i] = j - data = get_count(prefix + flag) - if data > base: - base = data - print("\n\n\n>>> FLAG: " + "".join(prefix + flag) + "\n\n\n") - break - if flag[i] == "}": +ROOTFS = r"rootfs/x86_windows" + +class Solver: + def __init__(self, invalid: bytes): + # create a silent qiling instance + self.ql = Qiling([rf"{ROOTFS}/bin/crackme.exe"], ROOTFS, verbose=QL_VERBOSE.OFF) + + self.ql.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 part that asks for input + self.ql.run(end=0x401030) + + # record replay starting and ending points. + # + # since the emulation halted upon entering a function, its return address is there on + # the stack. we use it to limit the emulation till function returns + 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, + # we will restore its state to the latest point possible, fast-forwarding a good + # amount of start-up code that is not affected by the input. + # + # here we save the state just when the read input function is about to be called so we + # could use it to jumpstart the initialization part and get to the read input immediately + self.jumpstart = self.ql.save() or {} + + # calibrate the replay instruction count by running the code with an invalid input + # first. the instruction count returned from the calibration process will be then + # used as a baseline for consequent replays + self.best_icount = self.__run(invalid) + + def __run(self, input: bytes) -> int: + icount = [0] + + def __count_instructions(ql: Qiling, address: int, size: int): + icount[0] += 1 + + # set a hook to fire up every time an instruction is about to execute + hobj = self.ql.hook_code(__count_instructions) + + # feed stdin with input + self.ql.os.stdin.write(input + b'\n') + + # resume emulation till function returns + self.ql.run(begin=self.replay_starts, end=self.replay_ends) + + hobj.remove() + + return icount[0] + + def replay(self, input: bytes) -> bool: + """Restore state and replay with a new input. + + Returns an indication to execution progress: `True` if a progress + was made, `False` otherwise + """ + + # restore program's state back to the starting point + self.ql.restore(self.jumpstart) + + # resume emulation and count emulated instructions + curr_icount = self.__run(input) + + # the larger part of the input is correct, the more instructions are expected to be executed. this is true + # for traditional loop-based validations like strcmp or memcmp which bails as soon as a mismatch is found: + # more correct characters mean more loop iterations - thus more executed instructions. + # + # if we got a higher instruction count, it means we made a progress in the right direction + if curr_icount > self.best_icount: + self.best_icount = curr_icount + + return True + + return False + +def progress(msg: str) -> None: + print(msg, end='\r', file=sys.stderr, flush=True) + +def main(): + flag = bytearray(b'BJWXB_CTF{********-****-****-****-************}') + indices = (i for i, ch in enumerate(flag) if ch == ord('*')) + + # uppercase hex digits + charset = '0123456789ABCDEF' + + progress('Initializing...') + solver = Solver(flag) + + for i in indices: + for ch in charset: + flag[i] = ord(ch) + + progress(f'Guessing... {flag.decode()}') + + if solver.replay(flag): break - print("SOLVED!!!") - except KeyboardInterrupt: - print("STOP: KeyboardInterrupt") + + else: + raise RuntimeError('no match found') + + print(f'\nFlag found!') if __name__ == "__main__": - solve() + main() + +# expected flag: BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53} diff --git a/examples/crackme_x86_windows_auto.py b/examples/crackme_x86_windows_auto.py index be5f517b8..8911199de 100644 --- a/examples/crackme_x86_windows_auto.py +++ b/examples/crackme_x86_windows_auto.py @@ -8,26 +8,64 @@ 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 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 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) -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 our_sandbox(path, rootfs): - ql = Qiling(path, rootfs, stdin=pipe.SimpleInStream(sys.stdin.fileno())) - - 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 06856c3e5..f873d7d83 100644 --- a/examples/crackme_x86_windows_setcallback.py +++ b/examples/crackme_x86_windows_setcallback.py @@ -10,7 +10,7 @@ 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 30bbee29b..1e43e50f6 100644 --- a/examples/crackme_x86_windows_unpatch.py +++ b/examples/crackme_x86_windows_unpatch.py @@ -10,7 +10,7 @@ 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 c76bd6f2d..35dc43b90 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 c58cfa5e2..78ffca21d 100644 --- a/examples/extensions/idaplugin/custom_script.py +++ b/examples/extensions/idaplugin/custom_script.py @@ -5,10 +5,10 @@ def __init__(self): 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.c b/examples/fuzzing/linux_x8664/fuzz.c index 3f7a284a6..068c45bdd 100644 --- a/examples/fuzzing/linux_x8664/fuzz.c +++ b/examples/fuzzing/linux_x8664/fuzz.c @@ -10,13 +10,13 @@ int fun(int i) char *buf = malloc(SIZE); char buf2[SIZE]; - while (*buf = getc(stdin) == 'A') + while ((*buf = getc(stdin)) == 'A') { buf[i++] = *buf; } strncpy(buf2, buf, i); - printf(buf2); + puts(buf2); return 0; } diff --git a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py index 83c8d0032..c7fb84db8 100755 --- a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py @@ -17,51 +17,54 @@ $ 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) + ql.hook_address(callback=lambda x: os.abort(), address=ba + 0x126e) - # set a hook on main() to let unicorn fork and start instrumentation - ql.hook_address(callback=start_afl, address=ba + 0x122c) + # set afl instrumentation [re]starting point. we set it to 'main' + ql.hook_address(callback=start_afl, address=ba + 0x1275) # okay, ready to roll ql.run() diff --git a/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py b/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py index ccb2cea0a..e8e3e56ff 100755 --- a/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py @@ -10,11 +10,7 @@ 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/linux_x8664/x8664_fuzz b/examples/fuzzing/linux_x8664/x8664_fuzz index e4028db2e..8878e1cca 100644 Binary files a/examples/fuzzing/linux_x8664/x8664_fuzz and b/examples/fuzzing/linux_x8664/x8664_fuzz differ diff --git a/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py b/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py index 3aace0d01..2e13e2294 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.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 start_afl(_ql: Qiling): 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/fuzz_tendaac15_httpd.py b/examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py index 35f142eac..d30d01de4 100644 --- a/examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py +++ b/examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py @@ -34,8 +34,8 @@ def main(input_file, enable_trace=False): fuzz_mem=ql.mem.search(b"CCCCAAAA") target_address = fuzz_mem[0] - def place_input_callback(uc, input, _, data): - ql.mem.write(target_address, input) + def place_input_callback(_ql: Qiling, input: bytes, _): + _ql.mem.write(target_address, input) def start_afl(_ql: Qiling): """ diff --git a/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py b/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py index ec46c6470..ca018af06 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 83855b068..6ae06db2c 100644 --- a/examples/hello_arm_linux_custom_syscall.py +++ b/examples/hello_arm_linux_custom_syscall.py @@ -9,30 +9,37 @@ from qiling import Qiling from qiling.const import QL_VERBOSE -def my_syscall_write(ql: Qiling, write_fd, write_buf, write_count, *args, **kw): - regreturn = 0 - +# customized system calls always use the same arguments list as the original ones, +# but with a Qiling instance as first argument +def my_syscall_write(ql: Qiling, fd: int, buf: int, count: int): try: - buf = ql.mem.read(write_buf, write_count) - ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn)) - ql.os.fd[write_fd].write(buf) - regreturn = write_count + # read data from emulated memory + data = ql.mem.read(buf, count) + + # select the emulated file object that corresponds to the requested + # file descriptor + fobj = ql.os.fd[fd] + + # write the data into the file object, if it supports write operations + if hasattr(fobj, 'write'): + fobj.write(data) except: - regreturn = -1 - ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn)) + ret = -1 + else: + ret = count - return regreturn + ql.log.info(f'my_syscall_write({fd}, {buf:#x}, {count}) = {ret}') + return ret if __name__ == "__main__": - ql = Qiling(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG) - # Custom syscall handler by syscall name or syscall number. - # 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) - - # set syscall by syscall name - #ql.set_syscall("write", my_syscall_write) + ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG) + + # replacing a system call with a custom implementation. + # note that system calls may be referred to either by their name or number. + ql.os.set_syscall(0x04, my_syscall_write) + + # an possible alternative: refer to a syscall by its name + #ql.os.set_syscall('write', my_syscall_write) ql.run() diff --git a/examples/hello_arm_linux_debug.py b/examples/hello_arm_linux_debug.py index a77b639f4..4c7e502c2 100644 --- a/examples/hello_arm_linux_debug.py +++ b/examples/hello_arm_linux_debug.py @@ -9,12 +9,13 @@ from qiling import Qiling from qiling.const import QL_VERBOSE -def run_sandbox(path, rootfs, verbose): - ql = Qiling(path, rootfs, verbose = verbose) +if __name__ == "__main__": + ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG) + ql.debugger = "qdb" # enable qdb without options + + # other possible alternatives: # ql.debugger = "qdb::rr" # switch on record and replay with rr # ql.debugger = "qdb:0x1030c" # enable qdb and setup breakpoin at 0x1030c - ql.run() -if __name__ == "__main__": - run_sandbox(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", QL_VERBOSE.DEBUG) + ql.run() diff --git a/examples/hello_arm_qnx_customapi.py b/examples/hello_arm_qnx_customapi.py index 9773450ff..7924afd7c 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 691318df3..9ed8fba51 100644 --- a/examples/hello_arm_uboot.py +++ b/examples/hello_arm_uboot.py @@ -19,11 +19,11 @@ def my_getenv(ql, *args, **kwargs): 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_password(ql, *args, **kwargs): 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 48df92d77..532970a47 100644 --- a/examples/hello_linuxx8664_intercept.py +++ b/examples/hello_linuxx8664_intercept.py @@ -12,16 +12,16 @@ 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_mips32_linux_customapi.py b/examples/hello_mips32_linux_customapi.py new file mode 100644 index 000000000..286d62faa --- /dev/null +++ b/examples/hello_mips32_linux_customapi.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys +sys.path.append("..") + +from qiling import Qiling +from qiling.os.const import STRING +from qiling.const import QL_VERBOSE + +def my_puts(ql: Qiling): + params = ql.os.resolve_fcall_params({'s': STRING}) + + print(f'puts("{params["s"]}")') + +if __name__ == "__main__": + ql = Qiling(["rootfs/mips32_linux/bin/mips32_hello"], "rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG) + ql.run() diff --git a/examples/hello_mips32el_linux_function_hook.py b/examples/hello_mips32el_linux_function_hook.py index 849b55f0a..74d32c49e 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 e624ff337..57892c11e 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 adfb22f75..9719828df 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 3f195aad4..522a8a393 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 8160167ab..ff2e49dc3 100644 --- a/examples/mcu/gd32vf103_blink.py +++ b/examples/mcu/gd32vf103_blink.py @@ -21,7 +21,7 @@ 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 64bde4738..17b4addbf 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 365a0005c..16338b77d 100644 --- a/examples/mcu/stm32f407_mnist_oled.py +++ b/examples/mcu/stm32f407_mnist_oled.py @@ -27,8 +27,8 @@ ## 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 5411874c6..3e09f11f9 100644 --- a/examples/mcu/stm32f411_i2c_lcd.py +++ b/examples/mcu/stm32f411_i2c_lcd.py @@ -42,7 +42,7 @@ def create(path, lcd): 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/multithreading_mips32_linux.py b/examples/multithreading_mips32_linux.py new file mode 100644 index 000000000..2c26e039a --- /dev/null +++ b/examples/multithreading_mips32_linux.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys +sys.path.append("..") + +from qiling import Qiling +from qiling.const import QL_VERBOSE + +def my_sandbox(path, rootfs): + ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True) + ql.run() + +if __name__ == "__main__": + my_sandbox(["rootfs/mips32_linux/bin/mips32_multithreading"], "rootfs/mips32_linux") diff --git a/examples/netgear_6220_mips32el_linux.py b/examples/netgear_6220_mips32el_linux.py index 323094bf6..06299773c 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 1735090a5..752202690 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/rootfs b/examples/rootfs index d8a9b0d6c..15c8d8428 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit d8a9b0d6c52a3c5bc627c055d5f711dacbb1a1f6 +Subproject commit 15c8d842876d709f013e4e42643d5d605cddbac9 diff --git a/examples/sality.py b/examples/sality.py index 330decaaf..b07c4273d 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)) @@ -151,8 +151,12 @@ def hook_StartServiceA(ql: Qiling, address: int, params): if service_handle.name in ql.os.services: service_path = ql.os.services[service_handle.name] service_path = ql.os.path.transform_to_real_path(service_path) + ql.amsint32_driver = Qiling([service_path], ql.rootfs, verbose=QL_VERBOSE.DEBUG) - init_unseen_symbols(ql.amsint32_driver, ql.amsint32_driver.loader.dlls["ntoskrnl.exe"]+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") + ntoskrnl = ql.amsint32_driver.loader.get_image_by_name("ntoskrnl.exe") + assert ntoskrnl, 'ntoskernl.exe was not loaded' + + init_unseen_symbols(ql.amsint32_driver, ntoskrnl.base+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") #ql.amsint32_driver.debugger= ":9999" try: ql.amsint32_driver.load() @@ -170,7 +174,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 +185,10 @@ def hook_stop_address(ql): 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/scripts/dllscollector.bat b/examples/scripts/dllscollector.bat index 40c9e307c..ce18c8613 100644 --- a/examples/scripts/dllscollector.bat +++ b/examples/scripts/dllscollector.bat @@ -1,90 +1,151 @@ -@echo off -REM -REM Example batch script to copy windows required DLLs and registry -REM - -mkdir examples\rootfs\x86_windows\Windows\System32 -mkdir examples\rootfs\x86_windows\Windows\System32\drivers -mkdir examples\rootfs\x86_windows\Windows\registry -mkdir examples\rootfs\x8664_windows\Windows\System32 -mkdir examples\rootfs\x8664_windows\Windows\SysWOW64 -mkdir examples\rootfs\x8664_windows\Windows\registry - -REM -REM Registry -REM -echo f | xcopy /f /y C:\Users\Default\NTUSER.DAT examples\rootfs\x8664_windows\Windows\registry\NTUSER.DAT -reg save hklm\system examples\rootfs\x8664_windows\Windows\registry\SYSTEM -reg save hklm\security examples\rootfs\x8664_windows\Windows\registry\SECURITY -reg save hklm\software examples\rootfs\x8664_windows\Windows\registry\SOFTWARE -reg save hklm\hardware examples\rootfs\x8664_windows\Windows\registry\HARDWARE -reg save hklm\SAM examples\rootfs\x8664_windows\Windows\registry\SAM -xcopy /d /y examples\rootfs\x8664_windows\Windows\registry\* examples\rootfs\x86_windows\Windows\registry\ - -REM -REM Dlls -REM -if exist %WINDIR%\SysWOW64\advapi32.dll xcopy /f /y %WINDIR%\SysWOW64\advapi32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\rpcrt4.dll xcopy /f /y %WINDIR%\SysWOW64\rpcrt4.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\crypt32.dll xcopy /f /y %WINDIR%\SysWOW64\crypt32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\iphlpapi.dll xcopy /f /y %WINDIR%\SysWOW64\iphlpapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\kernel32.dll xcopy /f /y %WINDIR%\SysWOW64\kernel32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\KernelBase.dll xcopy /f /y %WINDIR%\SysWOW64\KernelBase.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\mpr.dll xcopy /f /y %WINDIR%\SysWOW64\mpr.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\msvcp60.dll xcopy /f /y %WINDIR%\SysWOW64\msvcp60.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\msvcrt.dll xcopy /f /y %WINDIR%\SysWOW64\msvcrt.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\netapi32.dll xcopy /f /y %WINDIR%\SysWOW64\netapi32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ntdll.dll xcopy /f /y %WINDIR%\SysWOW64\ntdll.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ole32.dll xcopy /f /y %WINDIR%\SysWOW64\ole32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\urlmon.dll xcopy /f /y %WINDIR%\SysWOW64\urlmon.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\user32.dll xcopy /f /y %WINDIR%\SysWOW64\user32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\wsock32.dll xcopy /f /y %WINDIR%\SysWOW64\wsock32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\version.dll xcopy /f /y %WINDIR%\SysWOW64\version.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\winmm.dll xcopy /f /y %WINDIR%\SysWOW64\winmm.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\comctl32.dll xcopy /f /y %WINDIR%\SysWOW64\comctl32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\wininet.dll xcopy /f /y %WINDIR%\SysWOW64\wininet.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\psapi.dll xcopy /f /y %WINDIR%\SysWOW64\psapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\userenv.dll xcopy /f /y %WINDIR%\SysWOW64\userenv.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\uxtheme.dll xcopy /f /y %WINDIR%\SysWOW64\uxtheme.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\gdi32.dll xcopy /f /y %WINDIR%\SysWOW64\gdi32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\comdlg32.dll xcopy /f /y %WINDIR%\SysWOW64\comdlg32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\shell32.dll xcopy /f /y %WINDIR%\SysWOW64\shell32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\oleaut32.dll xcopy /f /y %WINDIR%\SysWOW64\oleaut32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\vcruntime140.dll xcopy /f /y %WINDIR%\SysWOW64\vcruntime140.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\winhttp.dll xcopy /f /y %WINDIR%\SysWOW64\winhttp.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\wininet.dll xcopy /f /y %WINDIR%\SysWOW64\wininet.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ws2_32.dll xcopy /f /y %WINDIR%\SysWOW64\ws2_32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\msvcr120_clr0400.dll echo f | xcopy /f /y %WINDIR%\SysWOW64\msvcr120_clr0400.dll "examples\rootfs\x86_windows\Windows\System32\msvcr110.dll" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-stdio-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-stdio-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-runtime-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-runtime-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-math-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-math-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-locale-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-locale-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-heap-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-heap-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-synch-l1-2-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-synch-l1-2-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-fibers-l1-1-1.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-fibers-l1-1-1.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-localization-l1-2-1.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-localization-l1-2-1.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-sysinfo-l1-2-1.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-sysinfo-l1-2-1.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\shlwapi.dll xcopy /f /y %WINDIR%\SysWOW64\shlwapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\setupapi.dll xcopy /f /y %WINDIR%\SysWOW64\setupapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\System32\ntoskrnl.exe xcopy /f /y %WINDIR%\System32\ntoskrnl.exe "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\winsxs\amd64_microsoft-windows-printing-xpsprint_31bf3856ad364e35_10.0.17763.194_none_20349c5a971eb293\XpsPrint.dll xcopy /f /y %WINDIR%\winsxs\amd64_microsoft-windows-printing-xpsprint_31bf3856ad364e35_10.0.17763.194_none_20349c5a971eb293\XpsPrint.dll "examples\rootfs\x86_windows\Windows\System32\" - -if exist %WINDIR%\System32\ntoskrnl.exe xcopy /f /y %WINDIR%\System32\ntoskrnl.exe "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\advapi32.dll xcopy /f /y %WINDIR%\System32\advapi32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\kernel32.dll xcopy /f /y %WINDIR%\System32\kernel32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\KernelBase.dll xcopy /f /y %WINDIR%\System32\KernelBase.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\msvcrt.dll xcopy /f /y %WINDIR%\System32\msvcrt.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\ntdll.dll xcopy /f /y %WINDIR%\System32\ntdll.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\urlmon.dll xcopy /f /y %WINDIR%\System32\urlmon.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\user32.dll xcopy /f /y %WINDIR%\System32\user32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\ws2_32.dll xcopy /f /y %WINDIR%\System32\ws2_32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\vcruntime140.dll xcopy /f /y %WINDIR%\System32\vcruntime140.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-stdio-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-stdio-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-runtime-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-runtime-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-math-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-math-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-locale-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-locale-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-heap-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-heap-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\vcruntime140d.dll xcopy /f /y %WINDIR%\System32\vcruntime140d.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\ucrtbased.dll xcopy /f /y %WINDIR%\System32\ucrtbased.dll "examples\rootfs\x8664_windows\Windows\System32\" - -exit /b +@ECHO OFF + +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: Create the emulated Windows directory structure and registry :: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: Host system directories +SET SYSDIR32="%WINDIR%\SysWOW64" +SET SYSDIR64="%WINDIR%\System32" + +:: Qiling rootfs directories +SET QL_WINDIR32="examples\rootfs\x86_windows\Windows" +SET QL_WINDIR64="examples\rootfs\x8664_windows\Windows" + +SET QL_SYSDIR32="%QL_WINDIR32%\System32" +SET QL_SYSDIR64="%QL_WINDIR64%\System32" + +SET QL_REGDIR32="%QL_WINDIR32%\registry" +SET QL_REGDIR64="%QL_WINDIR64%\registry" + +:: Create emulated Windows directory structure +MKDIR %QL_REGDIR32% +MKDIR %QL_SYSDIR32% +MKDIR "%QL_SYSDIR32%\drivers" +MKDIR "%QL_WINDIR32%\Temp" + +MKDIR %QL_REGDIR64% +MKDIR %QL_SYSDIR64% +MKDIR "%QL_SYSDIR64%\drivers" +MKDIR "%QL_WINDIR64%\Temp" + +:: Generate emulated Windows registry (requires Administrator privileges) +REG SAVE HKLM\SYSTEM %QL_REGDIR64%\SYSTEM /Y +REG SAVE HKLM\SECURITY %QL_REGDIR64%\SECURITY /Y +REG SAVE HKLM\SOFTWARE %QL_REGDIR64%\SOFTWARE /Y +REG SAVE HKLM\HARDWARE %QL_REGDIR64%\HARDWARE /Y +REG SAVE HKLM\SAM %QL_REGDIR64%\SAM /Y +COPY /B /Y C:\Users\Default\NTUSER.DAT "%QL_REGDIR64%\NTUSER.DAT" + +:: Duplicate generated registry +XCOPY /F /D /Y %QL_REGDIR64%\* %QL_REGDIR32% + +:: Collect 32-bit DLL files +CALL :collect_dll32 advapi32.dll +CALL :collect_dll32 bcrypt.dll +CALL :collect_dll32 cfgmgr32.dll +CALL :collect_dll32 ci.dll +CALL :collect_dll32 combase.dll +CALL :collect_dll32 comctl32.dll +CALL :collect_dll32 comdlg32.dll +CALL :collect_dll32 crypt32.dll +CALL :collect_dll32 cryptbase.dll +CALL :collect_dll32 gdi32.dll +CALL :collect_dll32 hal.dll +CALL :collect_dll32 iphlpapi.dll +CALL :collect_dll32 kdcom.dll +CALL :collect_dll32 kernel32.dll +CALL :collect_dll32 KernelBase.dll +CALL :collect_dll32 mpr.dll +CALL :collect_dll32 msvcp_win.dll +CALL :collect_dll32 msvcp60.dll +CALL :collect_dll32 msvcr120_clr0400.dll, msvcr110.dll +CALL :collect_dll32 msvcrt.dll +CALL :collect_dll32 netapi32.dll +CALL :collect_dll32 ntdll.dll +CALL :collect_dll32 ole32.dll +CALL :collect_dll32 oleaut32.dll +CALL :collect_dll32 psapi.dll +CALL :collect_dll32 rpcrt4.dll +CALL :collect_dll32 sechost.dll +CALL :collect_dll32 setupapi.dll +CALL :collect_dll32 shell32.dll +CALL :collect_dll32 shlwapi.dll +CALL :collect_dll32 sspicli.dll +CALL :collect_dll32 ucrtbase.dll +CALL :collect_dll32 ucrtbased.dll +CALL :collect_dll32 urlmon.dll +CALL :collect_dll32 user32.dll +CALL :collect_dll32 userenv.dll +CALL :collect_dll32 uxtheme.dll +CALL :collect_dll32 vcruntime140.dll +CALL :collect_dll32 version.dll +CALL :collect_dll32 win32u.dll +CALL :collect_dll32 winhttp.dll +CALL :collect_dll32 wininet.dll +CALL :collect_dll32 wininet.dll +CALL :collect_dll32 winmm.dll +CALL :collect_dll32 ws2_32.dll +CALL :collect_dll32 wsock32.dll + +CALL :collect_dll32 downlevel\api-ms-win-core-fibers-l1-1-1.dll +CALL :collect_dll32 downlevel\api-ms-win-core-localization-l1-2-1.dll +CALL :collect_dll32 downlevel\api-ms-win-core-synch-l1-2-0.dll +CALL :collect_dll32 downlevel\api-ms-win-core-sysinfo-l1-2-1.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-heap-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-locale-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-math-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-runtime-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-stdio-l1-1-0.dll + +:: Collect 64-bit DLL files +CALL :collect_dll64 advapi32.dll +CALL :collect_dll64 gdi32.dll +CALL :collect_dll64 kernel32.dll +CALL :collect_dll64 KernelBase.dll +CALL :collect_dll64 msvcrt.dll +CALL :collect_dll64 ntdll.dll +CALL :collect_dll64 ntoskrnl.exe +CALL :collect_dll64 ucrtbase.dll +CALL :collect_dll64 ucrtbased.dll +CALL :collect_dll64 urlmon.dll +CALL :collect_dll64 user32.dll +CALL :collect_dll64 vcruntime140.dll +CALL :collect_dll64 vcruntime140d.dll +CALL :collect_dll64 win32u.dll +CALL :collect_dll64 ws2_32.dll + +CALL :collect_dll64 downlevel\api-ms-win-crt-heap-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-locale-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-math-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-runtime-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-stdio-l1-1-0.dll + +:: Collect extras +CALL :collect %SYSDIR64%, ntoskrnl.exe, %QL_SYSDIR32% + +:: All done! +EXIT /B 0 + +:: Functions definitions +:normpath +SET %1=%~dpfn2 +EXIT /B + +:collect +CALL :normpath SRC, %~1\%~2 +CALL :normpath DST, %~3\%~4 + +IF EXIST %SRC% ( + ECHO %SRC% -^> %DST% + COPY /B /Y "%SRC%" "%DST%" >NUL +) +EXIT /B + +:collect_dll64 +CALL :collect %SYSDIR64%, %~1, %QL_SYSDIR64%, %~2 +EXIT /B + +:collect_dll32 +CALL :collect %SYSDIR32%, %~1, %QL_SYSDIR32%, %~2 +EXIT /B diff --git a/examples/simple_efi_x8664.py b/examples/simple_efi_x8664.py index 32c21a28d..c6410b0c8 100644 --- a/examples/simple_efi_x8664.py +++ b/examples/simple_efi_x8664.py @@ -40,7 +40,7 @@ def my_onenter(ql: Qiling, address: int, params): 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 0090b9449..819e8310c 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 c6b40ba13..8394e3f9d 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'): @@ -115,7 +115,6 @@ def emulate(path, rootfs, verbose=QL_VERBOSE.DEBUG, enable_trace=False): parser.add_argument("-t", "--trace", help="Enable full trace", action='store_true', default=False) parser.add_argument("-R", "--root", help="rootfs", default=None) parser.add_argument("-d", "--dump", help="Directory to dump memory regions to", default="dump") - #parser.add_argument("-a", "--automatize_input", help="Automatize writes on standard input", default=False) parser.add_argument("-p ", "--profile", help="customized profile", default="qiling/profiles/windows.ql") parser.add_argument('input', nargs='*') diff --git a/qiling/__init__.py b/qiling/__init__.py index 5a5ddbac9..96f113377 100644 --- a/qiling/__init__.py +++ b/qiling/__init__.py @@ -1,2 +1,9 @@ +import importlib.metadata from .core import Qiling -from .__version__ import __version__ \ No newline at end of file + +try: + __version__ = importlib.metadata.version(__package__ or __name__) +except importlib.metadata.PackageNotFoundError: + __version__ = "0.0.0" + +__all__ = ['Qiling'] diff --git a/qiling/__version__.py b/qiling/__version__.py deleted file mode 100644 index a7261f113..000000000 --- a/qiling/__version__.py +++ /dev/null @@ -1,3 +0,0 @@ -# NOTE: use "-dev" for dev branch -__version__ = "1.4.2" -#__version__ = "1.4.3" + "-dev" diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index f27925d1f..e1b84d186 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 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 + @property + @abstractmethod + def uc(self) -> Uc: + """Get unicorn instance bound to arch. + """ + + pass - # ql.init_Uc - initialized unicorn engine @property - def init_uc(self) -> Uc: - return self.get_init_uc() + @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 @@ def stack_push(self, value: int) -> int: 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 @@ def stack_pop(self) -> int: 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 @@ def stack_read(self, offset: int) -> int: 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 @@ def stack_write(self, offset: int, value: int) -> None: 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. + """ + + pass + + @property + @abstractmethod + def endian(self) -> QL_ENDIAN: + """Get processor endianess. """ - raise NotImplementedError(self.__class__.__name__) + pass \ No newline at end of file diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 7615d71a2..394258f79 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -3,125 +3,104 @@ # 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): - super().__init__(ql) + type = QL_ARCH.ARM + bits = 32 - reg_maps = ( - reg_map, - ) - - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + def __init__(self, ql: Qiling, endian: QL_ENDIAN, thumb: bool): + super().__init__(ql) - 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 = dict( + **arm_const.reg_map, + **arm_const.reg_vfp + ) + + pc_reg = 'pc' + sp_reg = 'sp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - # get PC - def get_pc(self) -> int: - append = 1 if self.check_thumb() == UC_MODE_THUMB else 0 + @property + def is_thumb(self) -> bool: + return bool(self.regs.cpsr & (1 << 5)) - return self.ql.reg.pc + append + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EB if self.regs.cpsr & (1 << 9) else QL_ENDIAN.EL - def __is_thumb(self) -> bool: - cpsr_v = { - QL_ENDIAN.EL : 0b100000, - QL_ENDIAN.EB : 0b100000 # FIXME: should be: 0b000000 - }[self.ql.archendian] + @property + def effective_pc(self) -> int: + """Get effective PC value, taking Thumb mode into account. + """ - return bool(self.ql.reg.cpsr & cpsr_v) + # append 1 to pc if in thumb mode, or 0 otherwise + return self.regs.pc + int(self.is_thumb) - def create_disassembler(self) -> Cs: + @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.__is_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.__is_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) - - if self.ql.archendian == QL_ENDIAN.EB: - self.ql.reg.fpexc = 0x40000000 - #self.ql.reg.fpexc = 0x00000040 - else: - self.ql.reg.fpexc = 0x40000000 - - def check_thumb(self): - return UC_MODE_THUMB if self.__is_thumb() else UC_MODE_ARM - - """ - 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)) + # set full access to cp10 and cp11 + self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0b11 << 20) | (0b11 << 22) - 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 6a08a50fc..659496694 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -3,44 +3,54 @@ # 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) - - reg_maps = ( - reg_map, - reg_map_w - ) + type = QL_ARCH.ARM64 + bits = 64 - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_ARM64, UC_MODE_ARM) - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **arm64_const.reg_map, + **arm64_const.reg_map_b, + **arm64_const.reg_map_d, + **arm64_const.reg_map_h, + **arm64_const.reg_map_q, + **arm64_const.reg_map_s, + **arm64_const.reg_map_w, + **arm64_const.reg_map_v + ) - # get initialized unicorn engine - def get_init_uc(self) -> Uc: - return Uc(UC_ARCH_ARM64, UC_MODE_ARM) + pc_reg = 'pc' + sp_reg = 'sp' - def create_disassembler(self) -> Cs: - if self._disasm is None: - self._disasm = Cs(CS_ARCH_ARM64, CS_MODE_ARM) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - return self._disasm + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL - def create_assembler(self) -> Ks: - if self._asm is None: - self._asm = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_ARM64, CS_MODE_ARM) - return self._asm + @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/arm64_const.py b/qiling/arch/arm64_const.py index c706d10c9..ee2b55167 100644 --- a/qiling/arch/arm64_const.py +++ b/qiling/arch/arm64_const.py @@ -1,79 +1,289 @@ #!/usr/bin/env python3 -# +# # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # from unicorn.arm64_const import * reg_map = { - "x0": UC_ARM64_REG_X0, - "x1": UC_ARM64_REG_X1, - "x2": UC_ARM64_REG_X2, - "x3": UC_ARM64_REG_X3, - "x4": UC_ARM64_REG_X4, - "x5": UC_ARM64_REG_X5, - "x6": UC_ARM64_REG_X6, - "x7": UC_ARM64_REG_X7, - "x8": UC_ARM64_REG_X8, - "x9": UC_ARM64_REG_X9, - "x10": UC_ARM64_REG_X10, - "x11": UC_ARM64_REG_X11, - "x12": UC_ARM64_REG_X12, - "x13": UC_ARM64_REG_X13, - "x14": UC_ARM64_REG_X14, - "x15": UC_ARM64_REG_X15, - "x16": UC_ARM64_REG_X16, - "x17": UC_ARM64_REG_X17, - "x18": UC_ARM64_REG_X18, - "x19": UC_ARM64_REG_X19, - "x20": UC_ARM64_REG_X20, - "x21": UC_ARM64_REG_X21, - "x22": UC_ARM64_REG_X22, - "x23": UC_ARM64_REG_X23, - "x24": UC_ARM64_REG_X24, - "x25": UC_ARM64_REG_X25, - "x26": UC_ARM64_REG_X26, - "x27": UC_ARM64_REG_X27, - "x28": UC_ARM64_REG_X28, - "x29": UC_ARM64_REG_X29, - "x30": UC_ARM64_REG_X30, - "sp": UC_ARM64_REG_SP, - "pc": UC_ARM64_REG_PC, - "lr": UC_ARM64_REG_LR, - "cpacr_el1": UC_ARM64_REG_CPACR_EL1, - "tpidr_el0": UC_ARM64_REG_TPIDR_EL0, + "x0": UC_ARM64_REG_X0, + "x1": UC_ARM64_REG_X1, + "x2": UC_ARM64_REG_X2, + "x3": UC_ARM64_REG_X3, + "x4": UC_ARM64_REG_X4, + "x5": UC_ARM64_REG_X5, + "x6": UC_ARM64_REG_X6, + "x7": UC_ARM64_REG_X7, + "x8": UC_ARM64_REG_X8, + "x9": UC_ARM64_REG_X9, + "x10": UC_ARM64_REG_X10, + "x11": UC_ARM64_REG_X11, + "x12": UC_ARM64_REG_X12, + "x13": UC_ARM64_REG_X13, + "x14": UC_ARM64_REG_X14, + "x15": UC_ARM64_REG_X15, + "x16": UC_ARM64_REG_X16, + "x17": UC_ARM64_REG_X17, + "x18": UC_ARM64_REG_X18, + "x19": UC_ARM64_REG_X19, + "x20": UC_ARM64_REG_X20, + "x21": UC_ARM64_REG_X21, + "x22": UC_ARM64_REG_X22, + "x23": UC_ARM64_REG_X23, + "x24": UC_ARM64_REG_X24, + "x25": UC_ARM64_REG_X25, + "x26": UC_ARM64_REG_X26, + "x27": UC_ARM64_REG_X27, + "x28": UC_ARM64_REG_X28, + "x29": UC_ARM64_REG_X29, + "x30": UC_ARM64_REG_X30, + "sp": UC_ARM64_REG_SP, + "pc": UC_ARM64_REG_PC, + "lr": UC_ARM64_REG_LR, + "cpacr_el1": UC_ARM64_REG_CPACR_EL1, + "tpidr_el0": UC_ARM64_REG_TPIDR_EL0 +} + +reg_map_b = { + "b0" : UC_ARM64_REG_B0, + "b1" : UC_ARM64_REG_B1, + "b2" : UC_ARM64_REG_B2, + "b3" : UC_ARM64_REG_B3, + "b4" : UC_ARM64_REG_B4, + "b5" : UC_ARM64_REG_B5, + "b6" : UC_ARM64_REG_B6, + "b7" : UC_ARM64_REG_B7, + "b8" : UC_ARM64_REG_B8, + "b9" : UC_ARM64_REG_B9, + "b10" : UC_ARM64_REG_B10, + "b11" : UC_ARM64_REG_B11, + "b12" : UC_ARM64_REG_B12, + "b13" : UC_ARM64_REG_B13, + "b14" : UC_ARM64_REG_B14, + "b15" : UC_ARM64_REG_B15, + "b16" : UC_ARM64_REG_B16, + "b17" : UC_ARM64_REG_B17, + "b18" : UC_ARM64_REG_B18, + "b19" : UC_ARM64_REG_B19, + "b20" : UC_ARM64_REG_B20, + "b21" : UC_ARM64_REG_B21, + "b22" : UC_ARM64_REG_B22, + "b23" : UC_ARM64_REG_B23, + "b24" : UC_ARM64_REG_B24, + "b25" : UC_ARM64_REG_B25, + "b26" : UC_ARM64_REG_B26, + "b27" : UC_ARM64_REG_B27, + "b28" : UC_ARM64_REG_B28, + "b29" : UC_ARM64_REG_B29, + "b30" : UC_ARM64_REG_B30, + "b31" : UC_ARM64_REG_B31 +} + +reg_map_d = { + "d0" : UC_ARM64_REG_D0, + "d1" : UC_ARM64_REG_D1, + "d2" : UC_ARM64_REG_D2, + "d3" : UC_ARM64_REG_D3, + "d4" : UC_ARM64_REG_D4, + "d5" : UC_ARM64_REG_D5, + "d6" : UC_ARM64_REG_D6, + "d7" : UC_ARM64_REG_D7, + "d8" : UC_ARM64_REG_D8, + "d9" : UC_ARM64_REG_D9, + "d10" : UC_ARM64_REG_D10, + "d11" : UC_ARM64_REG_D11, + "d12" : UC_ARM64_REG_D12, + "d13" : UC_ARM64_REG_D13, + "d14" : UC_ARM64_REG_D14, + "d15" : UC_ARM64_REG_D15, + "d16" : UC_ARM64_REG_D16, + "d17" : UC_ARM64_REG_D17, + "d18" : UC_ARM64_REG_D18, + "d19" : UC_ARM64_REG_D19, + "d20" : UC_ARM64_REG_D20, + "d21" : UC_ARM64_REG_D21, + "d22" : UC_ARM64_REG_D22, + "d23" : UC_ARM64_REG_D23, + "d24" : UC_ARM64_REG_D24, + "d25" : UC_ARM64_REG_D25, + "d26" : UC_ARM64_REG_D26, + "d27" : UC_ARM64_REG_D27, + "d28" : UC_ARM64_REG_D28, + "d29" : UC_ARM64_REG_D29, + "d30" : UC_ARM64_REG_D30, + "d31" : UC_ARM64_REG_D31 +} + +reg_map_h = { + "h0" : UC_ARM64_REG_H0, + "h1" : UC_ARM64_REG_H1, + "h2" : UC_ARM64_REG_H2, + "h3" : UC_ARM64_REG_H3, + "h4" : UC_ARM64_REG_H4, + "h5" : UC_ARM64_REG_H5, + "h6" : UC_ARM64_REG_H6, + "h7" : UC_ARM64_REG_H7, + "h8" : UC_ARM64_REG_H8, + "h9" : UC_ARM64_REG_H9, + "h10" : UC_ARM64_REG_H10, + "h11" : UC_ARM64_REG_H11, + "h12" : UC_ARM64_REG_H12, + "h13" : UC_ARM64_REG_H13, + "h14" : UC_ARM64_REG_H14, + "h15" : UC_ARM64_REG_H15, + "h16" : UC_ARM64_REG_H16, + "h17" : UC_ARM64_REG_H17, + "h18" : UC_ARM64_REG_H18, + "h19" : UC_ARM64_REG_H19, + "h20" : UC_ARM64_REG_H20, + "h21" : UC_ARM64_REG_H21, + "h22" : UC_ARM64_REG_H22, + "h23" : UC_ARM64_REG_H23, + "h24" : UC_ARM64_REG_H24, + "h25" : UC_ARM64_REG_H25, + "h26" : UC_ARM64_REG_H26, + "h27" : UC_ARM64_REG_H27, + "h28" : UC_ARM64_REG_H28, + "h29" : UC_ARM64_REG_H29, + "h30" : UC_ARM64_REG_H30, + "h31" : UC_ARM64_REG_H31 +} + +reg_map_q = { + "q0" : UC_ARM64_REG_Q0, + "q1" : UC_ARM64_REG_Q1, + "q2" : UC_ARM64_REG_Q2, + "q3" : UC_ARM64_REG_Q3, + "q4" : UC_ARM64_REG_Q4, + "q5" : UC_ARM64_REG_Q5, + "q6" : UC_ARM64_REG_Q6, + "q7" : UC_ARM64_REG_Q7, + "q8" : UC_ARM64_REG_Q8, + "q9" : UC_ARM64_REG_Q9, + "q10" : UC_ARM64_REG_Q10, + "q11" : UC_ARM64_REG_Q11, + "q12" : UC_ARM64_REG_Q12, + "q13" : UC_ARM64_REG_Q13, + "q14" : UC_ARM64_REG_Q14, + "q15" : UC_ARM64_REG_Q15, + "q16" : UC_ARM64_REG_Q16, + "q17" : UC_ARM64_REG_Q17, + "q18" : UC_ARM64_REG_Q18, + "q19" : UC_ARM64_REG_Q19, + "q20" : UC_ARM64_REG_Q20, + "q21" : UC_ARM64_REG_Q21, + "q22" : UC_ARM64_REG_Q22, + "q23" : UC_ARM64_REG_Q23, + "q24" : UC_ARM64_REG_Q24, + "q25" : UC_ARM64_REG_Q25, + "q26" : UC_ARM64_REG_Q26, + "q27" : UC_ARM64_REG_Q27, + "q28" : UC_ARM64_REG_Q28, + "q29" : UC_ARM64_REG_Q29, + "q30" : UC_ARM64_REG_Q30, + "q31" : UC_ARM64_REG_Q31 +} + +reg_map_s = { + "s0" : UC_ARM64_REG_S0, + "s1" : UC_ARM64_REG_S1, + "s2" : UC_ARM64_REG_S2, + "s3" : UC_ARM64_REG_S3, + "s4" : UC_ARM64_REG_S4, + "s5" : UC_ARM64_REG_S5, + "s6" : UC_ARM64_REG_S6, + "s7" : UC_ARM64_REG_S7, + "s8" : UC_ARM64_REG_S8, + "s9" : UC_ARM64_REG_S9, + "s10" : UC_ARM64_REG_S10, + "s11" : UC_ARM64_REG_S11, + "s12" : UC_ARM64_REG_S12, + "s13" : UC_ARM64_REG_S13, + "s14" : UC_ARM64_REG_S14, + "s15" : UC_ARM64_REG_S15, + "s16" : UC_ARM64_REG_S16, + "s17" : UC_ARM64_REG_S17, + "s18" : UC_ARM64_REG_S18, + "s19" : UC_ARM64_REG_S19, + "s20" : UC_ARM64_REG_S20, + "s21" : UC_ARM64_REG_S21, + "s22" : UC_ARM64_REG_S22, + "s23" : UC_ARM64_REG_S23, + "s24" : UC_ARM64_REG_S24, + "s25" : UC_ARM64_REG_S25, + "s26" : UC_ARM64_REG_S26, + "s27" : UC_ARM64_REG_S27, + "s28" : UC_ARM64_REG_S28, + "s29" : UC_ARM64_REG_S29, + "s30" : UC_ARM64_REG_S30, + "s31" : UC_ARM64_REG_S31 } reg_map_w = { - "w0" : UC_ARM64_REG_W0, - "w1" : UC_ARM64_REG_W1, - "w2" : UC_ARM64_REG_W2, - "w3" : UC_ARM64_REG_W3, - "w4" : UC_ARM64_REG_W4, - "w5" : UC_ARM64_REG_W5, - "w6" : UC_ARM64_REG_W6, - "w7" : UC_ARM64_REG_W7, - "w8" : UC_ARM64_REG_W8, - "w9" : UC_ARM64_REG_W9, - "w10" : UC_ARM64_REG_W10, - "w11" : UC_ARM64_REG_W11, - "w12" : UC_ARM64_REG_W12, - "w13" : UC_ARM64_REG_W13, - "w14" : UC_ARM64_REG_W14, - "w15" : UC_ARM64_REG_W15, - "w16" : UC_ARM64_REG_W16, - "w17" : UC_ARM64_REG_W17, - "w18" : UC_ARM64_REG_W18, - "w19" : UC_ARM64_REG_W19, - "w20" : UC_ARM64_REG_W20, - "w21" : UC_ARM64_REG_W21, - "w22" : UC_ARM64_REG_W22, - "w23" : UC_ARM64_REG_W23, - "w24" : UC_ARM64_REG_W24, - "w25" : UC_ARM64_REG_W25, - "w26" : UC_ARM64_REG_W26, - "w27" : UC_ARM64_REG_W27, - "w28" : UC_ARM64_REG_W28, - "w29" : UC_ARM64_REG_W29, - "w30" : UC_ARM64_REG_W30, -} \ No newline at end of file + "w0" : UC_ARM64_REG_W0, + "w1" : UC_ARM64_REG_W1, + "w2" : UC_ARM64_REG_W2, + "w3" : UC_ARM64_REG_W3, + "w4" : UC_ARM64_REG_W4, + "w5" : UC_ARM64_REG_W5, + "w6" : UC_ARM64_REG_W6, + "w7" : UC_ARM64_REG_W7, + "w8" : UC_ARM64_REG_W8, + "w9" : UC_ARM64_REG_W9, + "w10" : UC_ARM64_REG_W10, + "w11" : UC_ARM64_REG_W11, + "w12" : UC_ARM64_REG_W12, + "w13" : UC_ARM64_REG_W13, + "w14" : UC_ARM64_REG_W14, + "w15" : UC_ARM64_REG_W15, + "w16" : UC_ARM64_REG_W16, + "w17" : UC_ARM64_REG_W17, + "w18" : UC_ARM64_REG_W18, + "w19" : UC_ARM64_REG_W19, + "w20" : UC_ARM64_REG_W20, + "w21" : UC_ARM64_REG_W21, + "w22" : UC_ARM64_REG_W22, + "w23" : UC_ARM64_REG_W23, + "w24" : UC_ARM64_REG_W24, + "w25" : UC_ARM64_REG_W25, + "w26" : UC_ARM64_REG_W26, + "w27" : UC_ARM64_REG_W27, + "w28" : UC_ARM64_REG_W28, + "w29" : UC_ARM64_REG_W29, + "w30" : UC_ARM64_REG_W30 +} + +reg_map_v = { + "v0" : UC_ARM64_REG_V0, + "v1" : UC_ARM64_REG_V1, + "v2" : UC_ARM64_REG_V2, + "v3" : UC_ARM64_REG_V3, + "v4" : UC_ARM64_REG_V4, + "v5" : UC_ARM64_REG_V5, + "v6" : UC_ARM64_REG_V6, + "v7" : UC_ARM64_REG_V7, + "v8" : UC_ARM64_REG_V8, + "v9" : UC_ARM64_REG_V9, + "v10" : UC_ARM64_REG_V10, + "v11" : UC_ARM64_REG_V11, + "v12" : UC_ARM64_REG_V12, + "v13" : UC_ARM64_REG_V13, + "v14" : UC_ARM64_REG_V14, + "v15" : UC_ARM64_REG_V15, + "v16" : UC_ARM64_REG_V16, + "v17" : UC_ARM64_REG_V17, + "v18" : UC_ARM64_REG_V18, + "v19" : UC_ARM64_REG_V19, + "v20" : UC_ARM64_REG_V20, + "v21" : UC_ARM64_REG_V21, + "v22" : UC_ARM64_REG_V22, + "v23" : UC_ARM64_REG_V23, + "v24" : UC_ARM64_REG_V24, + "v25" : UC_ARM64_REG_V25, + "v26" : UC_ARM64_REG_V26, + "v27" : UC_ARM64_REG_V27, + "v28" : UC_ARM64_REG_V28, + "v29" : UC_ARM64_REG_V29, + "v30" : UC_ARM64_REG_V30, + "v31" : UC_ARM64_REG_V31 +} diff --git a/qiling/arch/arm_const.py b/qiling/arch/arm_const.py index e93393665..2a268cdfd 100644 --- a/qiling/arch/arm_const.py +++ b/qiling/arch/arm_const.py @@ -4,39 +4,63 @@ # from unicorn.arm_const import * -from enum import IntEnum reg_map = { - "r0": UC_ARM_REG_R0, - "r1": UC_ARM_REG_R1, - "r2": UC_ARM_REG_R2, - "r3": UC_ARM_REG_R3, - "r4": UC_ARM_REG_R4, - "r5": UC_ARM_REG_R5, - "r6": UC_ARM_REG_R6, - "r7": UC_ARM_REG_R7, - "r8": UC_ARM_REG_R8, - "r9": UC_ARM_REG_R9, - "r10": UC_ARM_REG_R10, - "r11": UC_ARM_REG_R11, - "r12": UC_ARM_REG_R12, - "sp": UC_ARM_REG_SP, - "lr": UC_ARM_REG_LR, - "pc": UC_ARM_REG_PC, - - # CPSR needs to be at offset 25 for GDB, see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l34 - # The fp registers inbetween have become obsolete - "f0": UC_ARM_REG_INVALID, - "f1": UC_ARM_REG_INVALID, - "f2": UC_ARM_REG_INVALID, - "f3": UC_ARM_REG_INVALID, - "f4": UC_ARM_REG_INVALID, - "f5": UC_ARM_REG_INVALID, - "f6": UC_ARM_REG_INVALID, - "f7": UC_ARM_REG_INVALID, - "fps": UC_ARM_REG_INVALID, - "cpsr": UC_ARM_REG_CPSR, - "c1_c0_2": UC_ARM_REG_C1_C0_2, - "c13_c0_3": UC_ARM_REG_C13_C0_3, - "fpexc": UC_ARM_REG_FPEXC, + "r0": UC_ARM_REG_R0, + "r1": UC_ARM_REG_R1, + "r2": UC_ARM_REG_R2, + "r3": UC_ARM_REG_R3, + "r4": UC_ARM_REG_R4, + "r5": UC_ARM_REG_R5, + "r6": UC_ARM_REG_R6, + "r7": UC_ARM_REG_R7, + "r8": UC_ARM_REG_R8, + "r9": UC_ARM_REG_R9, + "r10": UC_ARM_REG_R10, + "r11": UC_ARM_REG_R11, + "r12": UC_ARM_REG_R12, + "sp": UC_ARM_REG_SP, + "lr": UC_ARM_REG_LR, + "pc": UC_ARM_REG_PC, + + "cpsr": UC_ARM_REG_CPSR, + "c1_c0_2": UC_ARM_REG_C1_C0_2, + "c13_c0_3": UC_ARM_REG_C13_C0_3, + "fpexc": UC_ARM_REG_FPEXC +} + +reg_vfp = { + "d0" : UC_ARM_REG_D0, + "d1" : UC_ARM_REG_D1, + "d2" : UC_ARM_REG_D2, + "d3" : UC_ARM_REG_D3, + "d4" : UC_ARM_REG_D4, + "d5" : UC_ARM_REG_D5, + "d6" : UC_ARM_REG_D6, + "d7" : UC_ARM_REG_D7, + "d8" : UC_ARM_REG_D8, + "d9" : UC_ARM_REG_D9, + "d10" : UC_ARM_REG_D10, + "d11" : UC_ARM_REG_D11, + "d12" : UC_ARM_REG_D12, + "d13" : UC_ARM_REG_D13, + "d14" : UC_ARM_REG_D14, + "d15" : UC_ARM_REG_D15, + "d16" : UC_ARM_REG_D16, + "d17" : UC_ARM_REG_D17, + "d18" : UC_ARM_REG_D18, + "d19" : UC_ARM_REG_D19, + "d20" : UC_ARM_REG_D20, + "d21" : UC_ARM_REG_D21, + "d22" : UC_ARM_REG_D22, + "d23" : UC_ARM_REG_D23, + "d24" : UC_ARM_REG_D24, + "d25" : UC_ARM_REG_D25, + "d26" : UC_ARM_REG_D26, + "d27" : UC_ARM_REG_D27, + "d28" : UC_ARM_REG_D28, + "d29" : UC_ARM_REG_D29, + "d30" : UC_ARM_REG_D30, + "d31" : UC_ARM_REG_D31, + "fpscr" : UC_ARM_REG_FPSCR } diff --git a/qiling/arch/arm_utils.py b/qiling/arch/arm_utils.py new file mode 100644 index 000000000..3f7ff87f5 --- /dev/null +++ b/qiling/arch/arm_utils.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from qiling import Qiling +from qiling.const import QL_ENDIAN + +def init_linux_traps(ql: Qiling, address_map) -> None: + # If the compiler for the target does not provides some primitives for some + # reasons (e.g. target limitations), the kernel is responsible to assist + # with these operations. + # + # The following is some `kuser` helpers, which can be found here: + # https://elixir.bootlin.com/linux/latest/source/arch/arm/kernel/entry-armv.S#L899 + + trap_map = { + 'memory_barrier': + # @ 0xffff0fa0 + # mcr p15, 0, r0, c7, c10, 5 + # nop + # mov pc, lr + ''' + ba 0f 07 ee + 00 f0 20 e3 + 0e f0 a0 e1 + ''', + + 'cmpxchg': + # @ 0xffff0fc0 + # ldr r3, [r2] + # subs r3, r3, r0 + # streq r1, [r2] + # rsbs r0, r3, #0 + # mov pc, lr + ''' + 00 30 92 e5 + 00 30 53 e0 + 00 10 82 05 + 00 00 73 e2 + 0e f0 a0 e1 + ''', + + 'get_tls': + # @ 0xffff0fe0 + # ldr r0, [pc, #(16 - 8)] + # mov pc, lr + # mrc p15, 0, r0, c13, c0, 3 + # padding (e7 fd de f1) + # data: + # "\x00\x00\x00\x00" + # "\x00\x00\x00\x00" + # "\x00\x00\x00\x00" + ''' + 08 00 9f e5 + 0e f0 a0 e1 + 70 0f 1d ee + e7 fd de f1 + 00 00 00 00 + 00 00 00 00 + 00 00 00 00 + ''' + } + + if address_map: + # Find min / max address in address_map + lower_bound = min(address_map.values()) + # Get max address in address_map and its trap name + upper_trap = max(address_map, key=address_map.get) + base = ql.mem.align(lower_bound) + # size to map = start of upper_trap + len of upper_trap - start of lower_trap + size = ql.mem.align_up(address_map[upper_trap] - lower_bound + len(trap_map[upper_trap])) + + ql.mem.map(base, size, info="[arm_traps]") + + for trap_name, trap_hex in trap_map.items(): + trap_code = bytes.fromhex(trap_hex) + + if ql.arch.endian == QL_ENDIAN.EB: + trap_code = swap_endianness(trap_code) + + if trap_name in address_map: + ql.mem.write(address_map[trap_name], trap_code) + + ql.log.debug(f'Set kernel trap: {trap_name} at {address_map[trap_name]:#x}') + +def swap_endianness(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 d1b4c0bcd..a9e0a64ee 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -3,85 +3,99 @@ # 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) - - reg_maps = ( - reg_map, - ) + type = QL_ARCH.ARM + bits = 32 - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + def __init__(self, ql: Qiling): + super().__init__(ql, endian=QL_ENDIAN.EL, thumb=True) - 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) - - def check_thumb(self): - return UC_MODE_THUMB + + @property + def is_thumb(self) -> bool: + return True + + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL def step(self): - self.ql.emu_start(self.get_pc(), 0, count=1) + self.ql.emu_start(self.effective_pc, 0, count=1) self.ql.hw.step() def stop(self): @@ -95,22 +109,22 @@ def run(self, count=-1, end=None): end |= 1 while self.runable and count != 0: - if self.get_pc() == end: + if self.effective_pc == end: break self.step() count -= 1 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 soft_interrupt_handler(self, ql, intno): forward_mapper = { @@ -144,28 +158,28 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def hard_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/abi.py b/qiling/arch/evm/abi.py index 34ff28964..33620217e 100644 --- a/qiling/arch/evm/abi.py +++ b/qiling/arch/evm/abi.py @@ -1,12 +1,18 @@ #!/usr/bin/env python3 -from eth_abi import encode_abi +from eth_abi import encode_abi, decode_abi, encode_single, decode_single +from eth_utils.abi import collapse_if_tuple +from eth_utils import function_signature_to_4byte_selector, function_abi_to_4byte_selector, decode_hex, encode_hex from .vm.utils import bytecode_to_bytes class QlArchEVMABI: @staticmethod def convert(datatypes:list, values:list) -> str: + return QlArchEVMABI.encode_params(datatypes, values) + + @staticmethod + def encode_params(datatypes:list, values:list) -> str: for idx, item in enumerate(datatypes): if item == 'address': if isinstance(values[idx], int): @@ -14,4 +20,27 @@ def convert(datatypes:list, values:list) -> str: elif isinstance(values[idx], str): values[idx] = bytecode_to_bytes(values[idx]) - return encode_abi(datatypes, values).hex() \ No newline at end of file + return encode_abi(datatypes, values).hex() + + @staticmethod + def decode_params(datatypes:list, value:str) -> list: + return decode_abi(datatypes, value) + + @staticmethod + def encode_function_call(abi:str, params:list) -> str: + abi = abi.replace(' ', '') + if '(' not in abi or ')' not in abi: + raise ValueError(f'Function signature must contain "(" and ")": {abi}') + signature = function_signature_to_4byte_selector(abi) + inputs = abi[abi.index('('):] + params = encode_single(inputs, params) + return encode_hex(signature + params) + + @staticmethod + def encode_function_call_abi(abi:dict, params:list) -> str: + signature = function_abi_to_4byte_selector(abi) + inputs = ",".join( + [collapse_if_tuple(abi_input) for abi_input in abi.get("inputs", [])] + ) + params = encode_single(f"({inputs})", params) + return encode_hex(signature + params) \ No newline at end of file diff --git a/qiling/arch/evm/analysis/signatures.py b/qiling/arch/evm/analysis/signatures.py index aebe55f0d..e7eed5092 100644 --- a/qiling/arch/evm/analysis/signatures.py +++ b/qiling/arch/evm/analysis/signatures.py @@ -1,4 +1,5 @@ -import os +import inspect +from pathlib import Path import re import logging import json @@ -92,8 +93,8 @@ def analysis_func_sign(insns:list, engine_num=1): class signatures_engine_1: @staticmethod def find_signature(sign): - path = os.path.split(os.path.realpath(__file__))[0] + '/signatures.json' - with open(path) as data_file: + path = Path(inspect.getfile(inspect.getframe())).parent / 'signatures.json' + with path.open('r') as data_file: data = json.load(data_file) list_name = [name for name, hexa in data.items() if hexa == sign] diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index cfcc1abce..81ce642c5 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -3,16 +3,25 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from qiling.const import * -from ..arch import QlArch -from .vm.evm import QlArchEVMEmulator +import types +from qiling.arch.arch import QlArch +from qiling.arch.evm.hooks import monkeypatch_core_hooks +from qiling.arch.evm.vm.evm import QlArchEVMEmulator +from qiling.arch.evm.vm.message import Message +from qiling.const import * class QlArchEVM(QlArch): + type = QL_ARCH.EVM + bits = 1 + def __init__(self, ql) -> None: super(QlArchEVM, self).__init__(ql) self.evm = QlArchEVMEmulator(self.ql) + monkeypatch_core_hooks(self.ql) + monkeypatch_core_methods(self.ql) + def run(self, msg): return self.evm.vm.execute_message(msg) @@ -28,5 +37,20 @@ def stack_read(self, offset): 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 + + +def __evm_run(self, code: Message): + return self.arch.run(code) + +def monkeypatch_core_methods(ql): + """Monkeypatch core methods for evm + """ + + ql.run = types.MethodType(__evm_run, ql) diff --git a/qiling/arch/evm/hooks.py b/qiling/arch/evm/hooks.py index f796929ca..c2354c713 100644 --- a/qiling/arch/evm/hooks.py +++ b/qiling/arch/evm/hooks.py @@ -2,70 +2,72 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework +import types +from enum import IntEnum +from typing import MutableMapping, MutableSequence from qiling.core_hooks_types import Hook, HookAddr, HookIntr, HookRet +class EVM_HOOK(IntEnum): + CODE = (1 << 0) + ADDR = (1 << 1) + INSN = (1 << 2) class QlArchEVMHooks: def __init__(self) -> None: - super().__init__() - self.hook_code_list = [] - self.hook_insn_list = [] - self.hook_addr_dict = {} + self.hook_code_list: MutableSequence[Hook] = [] + self.hook_insn_list: MutableSequence[HookIntr] = [] + self.hook_addr_dict: MutableMapping[int, MutableSequence[HookAddr]] = {} evm_hooks_info = QlArchEVMHooks() +def __evm_hook_code(ql, callback, user_data=None, begin=1, end=0): + h = Hook(callback, user_data, begin, end) + evm_hooks_info.hook_code_list.append(h) -def _ql_evm_hook(ql, hook_type, h, *args): - base_type = [ - "HOOK_CODE", - "HOOK_INSN", - "HOOK_ADDR" - ] + return HookRet(ql, EVM_HOOK.CODE, h) - if hook_type in base_type: - if hook_type in ["HOOK_CODE"]: - evm_hooks_info.hook_code_list.append(h) - elif hook_type in ["HOOK_INSN"]: - evm_hooks_info.hook_insn_list.append(h) - elif hook_type in ["HOOK_ADDR"]: - address = args[0] +def __evm_hook_insn(ql, callback, intno, user_data=None, begin=1, end=0): + h = HookIntr(callback, intno, user_data) + evm_hooks_info.hook_insn_list.append(h) - if address not in evm_hooks_info.hook_addr_dict.keys(): - evm_hooks_info.hook_addr_dict[address] = [] - - evm_hooks_info.hook_addr_dict[address].append(h) + return HookRet(ql, EVM_HOOK.INSN, h) -def ql_evm_hooks(ql, hook_type, callback, user_data=None, begin=1, end=0, *args): - h = Hook(callback, user_data, begin, end) - _ql_evm_hook(ql, hook_type, h, *args) - return HookRet(ql, hook_type, h) +def __evm_hook_address(ql, callback, address, user_data=None): + h = HookAddr(callback, address, user_data) -def evm_hook_insn(ql, hook_type, callback, intno, user_data=None, begin=1, end=0): - h = HookIntr(callback, intno, user_data) - _ql_evm_hook(ql, hook_type, h) - return HookRet(ql, hook_type, h) - -def evm_hook_address(ql, hook_type, h, address): - _ql_evm_hook(ql, hook_type, h, address) - return HookRet(ql, hook_type, h) - -def evm_hook_del(hook_type, h): - base_type = [ - "HOOK_CODE", - "HOOK_INSN", - "HOOK_ADDR" - ] - - if isinstance(h, HookAddr): - if h.addr in evm_hooks_info.hook_addr_dict.keys(): - if h in evm_hooks_info.hook_addr_dict[h.addr]: - evm_hooks_info.hook_addr_dict[h.addr].remove(h) - if len(evm_hooks_info.hook_addr_dict[h.addr]) == 0: - del evm_hooks_info.hook_addr_dict[h.addr] - - if hook_type in base_type: - if hook_type in ["HOOK_CODE"]: - evm_hooks_info.hook_code_list.remove(h) - elif hook_type in ["HOOK_INSN"]: - evm_hooks_info.hook_insn_list.remove(h) \ No newline at end of file + if address not in evm_hooks_info.hook_addr_dict: + evm_hooks_info.hook_addr_dict[address] = [] + + evm_hooks_info.hook_addr_dict[address].append(h) + + return HookRet(ql, EVM_HOOK.ADDR, h) + +def __evm_hook_del(ql, hret): + h = hret.obj + hook_type = hret.type + + if hook_type == EVM_HOOK.CODE: + evm_hooks_info.hook_code_list.remove(h) + + elif hook_type == EVM_HOOK.INSN: + evm_hooks_info.hook_insn_list.remove(h) + + elif hook_type == EVM_HOOK.ADDR: + if h.addr in evm_hooks_info.hook_addr_dict: + hooks_list = evm_hooks_info.hook_addr_dict[h.addr] + + if h in hooks_list: + hooks_list.remove(h) + + if not hooks_list: + del evm_hooks_info.hook_addr_dict[h.addr] + +def monkeypatch_core_hooks(ql): + """Monkeypatch core hooks for evm + """ + + ql.hook_code = types.MethodType(__evm_hook_code, ql) + ql.hook_address = types.MethodType(__evm_hook_address, ql) + ql.hook_insn = types.MethodType(__evm_hook_insn, ql) + ql.hook_del = types.MethodType(__evm_hook_del, ql) diff --git a/qiling/arch/evm/vm/dbgcui.py b/qiling/arch/evm/vm/dbgcui.py index c9db85cff..b1c2daf8e 100644 --- a/qiling/arch/evm/vm/dbgcui.py +++ b/qiling/arch/evm/vm/dbgcui.py @@ -59,10 +59,12 @@ def hexdump(src, length=16, sep='.', minrows=8, start=0, prevsrc="", to_list=Fal return result return '\n'.join(result) -def stackdump(src, length=16, minrows=8, start=0, to_list=False): +def stackdump(src, minrows=8, to_list=False): result = [] - for i in range(start, min(minrows, len(src))): + # only the last N elements should be printed + start = len(src) - min(minrows, len(src)) + for i in range(min(minrows, len(src)) + start - 1, start - 1, -1): v_type = src[i][0] value = src[i][1] if v_type is bytes: diff --git a/qiling/arch/evm/vm/debug.py b/qiling/arch/evm/vm/debug.py index bf209ba77..319aba92d 100644 --- a/qiling/arch/evm/vm/debug.py +++ b/qiling/arch/evm/vm/debug.py @@ -6,7 +6,7 @@ import os, time import cmd2 from rich import print as rprint -from ..hooks import evm_hook_insn +from ..hooks import evm_hooks_info from .disassembler import EVMDisasm from ..analysis.signatures import analysis_func_sign from .utils import analysis_bytecode, bytecode_to_bytes @@ -38,7 +38,7 @@ def init(self, executor): load_bytecode, runtime_code, aux_data, constructor_args = analysis_bytecode(self.executor.vm_context.msg.code) - insns = EVMDisasm().disasm(bytecode_to_bytes(runtime_code), evm_hook_insn) + insns = EVMDisasm().disasm(bytecode_to_bytes(runtime_code), evm_hooks_info) self.func_sign = analysis_func_sign(insns, engine_num=2) self.cli_output() diff --git a/qiling/arch/evm/vm/exec.py b/qiling/arch/evm/vm/exec.py index d2237f4d9..883291261 100644 --- a/qiling/arch/evm/vm/exec.py +++ b/qiling/arch/evm/vm/exec.py @@ -50,13 +50,13 @@ def execute_once(self, opcode:int): if dis_insn.is_hook_code: for h in dis_insn.callback_list.hook_code_list: - h.call(self.vm_context.state.ql) + h.call(self.vm_context.state.ql, self.vm_context) if dis_insn.is_hook_insn: for h in dis_insn.callback_list.hook_insn_list: - h.call(self.vm_context.state.ql) + h.call(self.vm_context.state.ql, self.vm_context) if dis_insn.is_hook_addr: for h in dis_insn.callback_list.hook_addr_dict[pc]: - h.call(self.vm_context.state.ql) + h.call(self.vm_context.state.ql, self.vm_context) except KeyError: opcode_fn = InvalidOpcode(opcode) diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index ee26b6c4b..9368634f2 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -3,57 +3,67 @@ # 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): - super().__init__(ql) + type = QL_ARCH.MIPS + bits = 32 - reg_maps = ( - reg_map, - reg_map_afpr128 - ) - - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + def __init__(self, ql: Qiling, endian: QL_ENDIAN): + super().__init__(ql) - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) + self._init_endian = endian - # 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, + **mips_const.reg_map_fpu + ) + + pc_reg = 'pc' + sp_reg = 'sp' - self._disasm = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - return self._disasm + @cached_property + def disassembler(self) -> Cs: + endian = { + QL_ENDIAN.EL : CS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB : CS_MODE_BIG_ENDIAN + }[self.endian] - 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] + return Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) + + @cached_property + def assembler(self) -> Ks: + endian = { + QL_ENDIAN.EL : KS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN + }[self.endian] - self._asm = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + endian) + return Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + endian) - return self._asm + @property + def endian(self) -> QL_ENDIAN: + return self._init_endian diff --git a/qiling/arch/mips_const.py b/qiling/arch/mips_const.py index 8dd8d48a9..8e3141f4f 100644 --- a/qiling/arch/mips_const.py +++ b/qiling/arch/mips_const.py @@ -6,6 +6,39 @@ from unicorn.mips_const import * reg_map = { + "r0" : UC_MIPS_REG_0, + "r1" : UC_MIPS_REG_1, + "r2" : UC_MIPS_REG_2, + "r3" : UC_MIPS_REG_3, + "r4" : UC_MIPS_REG_4, + "r5" : UC_MIPS_REG_5, + "r6" : UC_MIPS_REG_6, + "r7" : UC_MIPS_REG_7, + "r8" : UC_MIPS_REG_8, + "r9" : UC_MIPS_REG_9, + "r10" : UC_MIPS_REG_10, + "r11" : UC_MIPS_REG_11, + "r12" : UC_MIPS_REG_12, + "r13" : UC_MIPS_REG_13, + "r14" : UC_MIPS_REG_14, + "r15" : UC_MIPS_REG_15, + "r16" : UC_MIPS_REG_16, + "r17" : UC_MIPS_REG_17, + "r18" : UC_MIPS_REG_18, + "r19" : UC_MIPS_REG_19, + "r20" : UC_MIPS_REG_20, + "r21" : UC_MIPS_REG_21, + "r22" : UC_MIPS_REG_22, + "r23" : UC_MIPS_REG_23, + "r24" : UC_MIPS_REG_24, + "r25" : UC_MIPS_REG_25, + "r26" : UC_MIPS_REG_26, + "r27" : UC_MIPS_REG_27, + "r28" : UC_MIPS_REG_28, + "r29" : UC_MIPS_REG_29, + "r30" : UC_MIPS_REG_30, + "r31" : UC_MIPS_REG_31, + "zero": UC_MIPS_REG_ZERO, "at": UC_MIPS_REG_AT, "v0": UC_MIPS_REG_V0, @@ -49,4 +82,39 @@ reg_map_afpr128 = { "cp0_config3" : UC_MIPS_REG_CP0_CONFIG3, "cp0_userlocal": UC_MIPS_REG_CP0_USERLOCAL, -} \ No newline at end of file +} + +reg_map_fpu = { + "f0" : UC_MIPS_REG_F0, + "f1" : UC_MIPS_REG_F1, + "f2" : UC_MIPS_REG_F2, + "f3" : UC_MIPS_REG_F3, + "f4" : UC_MIPS_REG_F4, + "f5" : UC_MIPS_REG_F5, + "f6" : UC_MIPS_REG_F6, + "f7" : UC_MIPS_REG_F7, + "f8" : UC_MIPS_REG_F8, + "f9" : UC_MIPS_REG_F9, + "f10" : UC_MIPS_REG_F10, + "f11" : UC_MIPS_REG_F11, + "f12" : UC_MIPS_REG_F12, + "f13" : UC_MIPS_REG_F13, + "f14" : UC_MIPS_REG_F14, + "f15" : UC_MIPS_REG_F15, + "f16" : UC_MIPS_REG_F16, + "f17" : UC_MIPS_REG_F17, + "f18" : UC_MIPS_REG_F18, + "f19" : UC_MIPS_REG_F19, + "f20" : UC_MIPS_REG_F20, + "f21" : UC_MIPS_REG_F21, + "f22" : UC_MIPS_REG_F22, + "f23" : UC_MIPS_REG_F23, + "f24" : UC_MIPS_REG_F24, + "f25" : UC_MIPS_REG_F25, + "f26" : UC_MIPS_REG_F26, + "f27" : UC_MIPS_REG_F27, + "f28" : UC_MIPS_REG_F28, + "f29" : UC_MIPS_REG_F29, + "f30" : UC_MIPS_REG_F30, + "f31" : UC_MIPS_REG_F31 +} diff --git a/qiling/arch/msr.py b/qiling/arch/msr.py new file mode 100644 index 000000000..08409cffe --- /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/ppc.py b/qiling/arch/ppc.py new file mode 100644 index 000000000..4e6d099db --- /dev/null +++ b/qiling/arch/ppc.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from functools import cached_property + +from unicorn import Uc, UC_ARCH_PPC, UC_MODE_PPC32, UC_MODE_BIG_ENDIAN +from capstone import Cs, CS_ARCH_PPC, CS_MODE_32, CS_MODE_BIG_ENDIAN +from keystone import Ks, KS_ARCH_PPC, KS_MODE_PPC32, KS_MODE_BIG_ENDIAN + +from qiling import Qiling +from qiling.arch.arch import QlArch +from qiling.arch import ppc_const +from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN + +class QlArchPPC(QlArch): + type = QL_ARCH.PPC + bits = 32 + + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_PPC, UC_MODE_PPC32 + UC_MODE_BIG_ENDIAN) + + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **ppc_const.reg_map, + **ppc_const.reg_float_map + ) + + pc_reg = 'pc' + sp_reg = 'r1' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_PPC, CS_MODE_32 + CS_MODE_BIG_ENDIAN) + + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_PPC, KS_MODE_PPC32 + KS_MODE_BIG_ENDIAN) + + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EB + + def enable_float(self): + self.regs.msr = self.regs.msr | ppc_const.MSR.FP diff --git a/qiling/arch/ppc_const.py b/qiling/arch/ppc_const.py new file mode 100644 index 000000000..732cba1c9 --- /dev/null +++ b/qiling/arch/ppc_const.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from unicorn.ppc_const import * +from enum import IntEnum + +reg_map = { + "r0": UC_PPC_REG_0, + "r1": UC_PPC_REG_1, + "r2": UC_PPC_REG_2, + "r3": UC_PPC_REG_3, + "r4": UC_PPC_REG_4, + "r5": UC_PPC_REG_5, + "r6": UC_PPC_REG_6, + "r7": UC_PPC_REG_7, + "r8": UC_PPC_REG_8, + "r9": UC_PPC_REG_9, + "r10": UC_PPC_REG_10, + "r11": UC_PPC_REG_11, + "r12": UC_PPC_REG_12, + "r13": UC_PPC_REG_13, + "r14": UC_PPC_REG_14, + "r15": UC_PPC_REG_15, + "r16": UC_PPC_REG_16, + "r17": UC_PPC_REG_17, + "r18": UC_PPC_REG_18, + "r19": UC_PPC_REG_19, + "r20": UC_PPC_REG_20, + "r21": UC_PPC_REG_21, + "r22": UC_PPC_REG_22, + "r23": UC_PPC_REG_23, + "r24": UC_PPC_REG_24, + "r25": UC_PPC_REG_25, + "r26": UC_PPC_REG_26, + "r27": UC_PPC_REG_27, + "r28": UC_PPC_REG_28, + "r29": UC_PPC_REG_29, + "r30": UC_PPC_REG_30, + "r31": UC_PPC_REG_31, + "pc": UC_PPC_REG_PC, + "msr": UC_PPC_REG_MSR, + "cr": UC_PPC_REG_CR0, + "lr": UC_PPC_REG_LR, + "ctr": UC_PPC_REG_CTR, + "xer": UC_PPC_REG_XER, +} + +reg_float_map = { + "f0": UC_PPC_REG_FPR0, + "f1": UC_PPC_REG_FPR1, + "f2": UC_PPC_REG_FPR2, + "f3": UC_PPC_REG_FPR3, + "f4": UC_PPC_REG_FPR4, + "f5": UC_PPC_REG_FPR5, + "f6": UC_PPC_REG_FPR6, + "f7": UC_PPC_REG_FPR7, + "f8": UC_PPC_REG_FPR8, + "f9": UC_PPC_REG_FPR9, + "f10": UC_PPC_REG_FPR10, + "f11": UC_PPC_REG_FPR11, + "f12": UC_PPC_REG_FPR12, + "f13": UC_PPC_REG_FPR13, + "f14": UC_PPC_REG_FPR14, + "f15": UC_PPC_REG_FPR15, + "f16": UC_PPC_REG_FPR16, + "f17": UC_PPC_REG_FPR17, + "f18": UC_PPC_REG_FPR18, + "f19": UC_PPC_REG_FPR19, + "f20": UC_PPC_REG_FPR20, + "f21": UC_PPC_REG_FPR21, + "f22": UC_PPC_REG_FPR22, + "f23": UC_PPC_REG_FPR23, + "f24": UC_PPC_REG_FPR24, + "f25": UC_PPC_REG_FPR25, + "f26": UC_PPC_REG_FPR26, + "f27": UC_PPC_REG_FPR27, + "f28": UC_PPC_REG_FPR28, + "f29": UC_PPC_REG_FPR29, + "f30": UC_PPC_REG_FPR30, + "f31": UC_PPC_REG_FPR31, +} + +class MSR(IntEnum): + SF = 1 << 63 + TAG = 1 << 62 + ISF = 1 << 61 + HV = 1 << 60 + TS0 = 1 << 34 + TS1 = 1 << 33 + TM = 1 << 32 + CM = 1 << 31 + ICM = 1 << 30 + GS = 1 << 28 + UCLE = 1 << 26 + VR = 1 << 25 + SPE = 1 << 25 + AP = 1 << 23 + VSX = 1 << 23 + SA = 1 << 22 + KEY = 1 << 19 + POW = 1 << 18 + TGPR = 1 << 17 + CE = 1 << 17 + ILE = 1 << 16 + EE = 1 << 15 + PR = 1 << 14 + FP = 1 << 13 + ME = 1 << 12 + FE0 = 1 << 11 + SE = 1 << 10 + DWE = 1 << 10 + UBLE = 1 << 10 + BE = 1 << 9 + DE = 1 << 9 + FE1 = 1 << 8 + AL = 1 << 7 + EP = 1 << 6 + IR = 1 << 5 + DR = 1 << 4 + IS = 1 << 5 + DS = 1 << 4 + PE = 1 << 3 + PX = 1 << 2 + PMM = 1 << 2 + RI = 1 << 1 + LE = 1 << 0 diff --git a/qiling/arch/register.py b/qiling/arch/register.py index 55ec23b4e..368864f6f 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 @@ def __setattr__(self, name: str, value: Any): 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 @@ def read(self, register: Union[str, int]): 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 @@ def write(self, register: Union[str, int], value: int) -> None: 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 @@ def restore(self, context: MutableMapping[str, Any] = {}) -> None: 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,21 +100,15 @@ def arch_pc(self, value: int) -> None: """Set the value of the architectural program counter register. """ - return self.ql.uc.reg_write(self.uc_pc, value) - - @property - def arch_pc_name(self) -> str: - """Get the architectural program counter register name. - """ + return self.uc.reg_write(self.uc_pc, value) - return next(k for k, v in self.register_mapping.items() if v == self.uc_pc) @property def arch_sp(self) -> int: """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 +116,4 @@ def arch_sp(self, value: int) -> None: """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 e2a162eae..80d614c37 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -3,57 +3,69 @@ # 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, + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) + + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **riscv_const.reg_map, + **riscv_const.reg_csr_map, + **riscv_const.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"]) + pc_reg = 'pc' + sp_reg = 'sp' - # get initialized unicorn engine - def get_init_uc(self) -> Uc: - return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - def create_disassembler(self) -> Cs: + @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 soft_interrupt_handler(self, ql, intno): if intno == 2: try: - address, size = ql.reg.pc - 4, 4 + address, size = ql.arch.regs.pc - 4, 4 tmp = ql.mem.read(address, size) - qd = ql.arch.create_disassembler() + qd = ql.arch.disassembler insn = '\n> '.join(f'{insn.mnemonic} {insn.op_str}' for insn in qd.disasm(tmp, address)) except QlErrorNotImplemented: @@ -64,7 +76,7 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def step(self): - self.ql.emu_start(self.get_pc(), 0, count=1) + self.ql.emu_start(self.regs.arch_pc, 0, count=1) self.ql.hw.step() def stop(self): @@ -74,7 +86,7 @@ def run(self, count=-1, end=None): self.runable = True while self.runable and count != 0: - if self.get_pc() == end: + if self.regs.arch_pc == end: break self.step() diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index c7a31d081..295cf1171 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 76149024f..ab9583fb7 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -9,9 +9,10 @@ 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, +from keystone import (Ks, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_X86, KS_ARCH_PPC, + KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_PPC32, KS_MODE_16, KS_MODE_32, KS_MODE_64, KS_MODE_LITTLE_ENDIAN, KS_MODE_BIG_ENDIAN) from qiling import Qiling @@ -24,52 +25,68 @@ def __init__(self, ql: Qiling): 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,16 @@ 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), + QL_ARCH.PPC : (KS_ARCH_PPC, KS_MODE_PPC32 + KS_MODE_BIG_ENDIAN) } if arch in asm_map: diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 34b77cc0b..0a787e8b9 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -3,271 +3,126 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from struct import pack +from functools import cached_property 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 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 - # TODO: generalize this - def get_reg_bit(self, register: int) -> int: - # all regs in reg_map_misc are 16 bits except of eflags - if register == UC_X86_REG_EFLAGS: - return 32 + @cached_property + def msr(self) -> QlMsrManager: + """Model-Specific Registers. + """ - 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), - ) - - return next((rsize for rmap, rsize in regmaps if register in rmap.values()), 0) + return QlMsrManager(self.uc) class QlArchA8086(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) - - reg_maps = ( - reg_map_8, - reg_map_16, - reg_map_misc - ) + type = QL_ARCH.A8086 + bits = 16 - 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: + @cached_property + def 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 + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **x86_const.reg_map_8, + **x86_const.reg_map_16, + **x86_const.reg_map_misc ) - for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + pc_reg = 'ip' + sp_reg = 'sp' - self.ql.reg.register_sp(reg_map_32["esp"]) - self.ql.reg.register_pc(reg_map_32["eip"]) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - 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) + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_X86, CS_MODE_16) - return self._disasm + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_16) - def create_assembler(self) -> Ks: - if not self._asm: - self._asm = Ks(KS_ARCH_X86, KS_MODE_32) - - return self._asm +class QlArchX86(QlArchIntel): + type = QL_ARCH.X86 + bits = 32 -class QlArchX8664(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_X86, UC_MODE_32) - 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 + @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 ) - 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 - + pc_reg = 'eip' + sp_reg = 'esp' - 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]") + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - 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}") + @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) - 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(' 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, + **x86_const.reg_map_xmm, + **x86_const.reg_map_ymm, + **x86_const.reg_map_zmm + ) -def ql_x8664_set_fs(ql: Qiling, addr: int): - ql.reg.msr(FSMSR, addr) + 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) -def ql_x8664_get_fs(ql: Qiling): - return ql.reg.msr(FSMSR) + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_64) diff --git a/qiling/arch/x86_const.py b/qiling/arch/x86_const.py index 6e9df157a..1e60479fb 100644 --- a/qiling/arch/x86_const.py +++ b/qiling/arch/x86_const.py @@ -39,8 +39,9 @@ QL_X86_GDT_ENTRY_SIZE = 0x8 # These msr registers are x86 specific -FSMSR = 0xC0000100 -GSMSR = 0xC0000101 +IA32_FS_BASE_MSR = 0xC0000100 +IA32_GS_BASE_MSR = 0xC0000101 +IA32_APIC_BASE_MSR = 0x1B # WINDOWS SETUP VALUE # Linux also needs these @@ -176,7 +177,7 @@ } reg_map_misc = { - "ef": UC_X86_REG_EFLAGS, + "eflags": UC_X86_REG_EFLAGS, "cs": UC_X86_REG_CS, "ss": UC_X86_REG_SS, "ds": UC_X86_REG_DS, diff --git a/qiling/arch/x86_utils.py b/qiling/arch/x86_utils.py new file mode 100644 index 000000000..de6e24f24 --- /dev/null +++ b/qiling/arch/x86_utils.py @@ -0,0 +1,189 @@ + +from abc import abstractmethod + +from qiling import Qiling +from qiling.arch.x86 import QlArchIntel +from qiling.arch.x86_const import * +from qiling.exception import QlGDTError, QlMemoryMappedError +from qiling.os.memory import QlMemoryManager + +class GDTArray: + entsize = QL_X86_GDT_ENTRY_SIZE + + def __init__(self, mem: QlMemoryManager, base: int, num_entries: int): + self.mem = mem + self.base = base + self.num_entries = num_entries + + def __in_bounds(self, index: int) -> 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_available(base, limit): + raise QlMemoryMappedError('cannot map GDT, memory location is taken') + + ql.mem.map(base, limit, info="[GDT]") + + # setup GDT by writing to GDTR + ql.arch.regs.write(UC_X86_REG_GDTR, (0, base, limit, 0x0)) + + 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 7d69ed49a..5f7216c38 100644 --- a/qiling/cc/__init__.py +++ b/qiling/cc/__init__.py @@ -2,22 +2,22 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from typing import Callable, Tuple +from typing import Callable, Sequence, 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: @@ -26,7 +26,7 @@ def getNumSlots(argbits: int) -> int: raise NotImplementedError - def getRawParam(self, slot: int, argbits: int = None) -> int: + def getRawParam(self, slot: int, argbits: int = 0) -> int: """Read a value of native size from the specified argument slot. Note that argument slots and argument indexes are not the same. Though they often correlate @@ -41,7 +41,7 @@ def getRawParam(self, slot: int, argbits: int = None) -> int: raise NotImplementedError - def setRawParam(self, slot: int, value: int, argbits: int = None) -> None: + def setRawParam(self, slot: int, value: int, argbits: int = 0) -> None: """Replace the value in the specified argument slot. Note that argument slots and argument indexes are not the same. Though they often correlate @@ -107,18 +107,16 @@ class QlCommonBaseCC(QlCC): of the QlCC interface. """ - _argregs = () + _retreg: int + _argregs: Sequence _shadow = 0 _retaddr_on_stack = True - def __init__(self, ql: Qiling, retreg: int): - super().__init__(ql) + def __init__(self, arch: QlArch): + super().__init__(arch) # native address size in bytes - self._asize = self.ql.pointersize - - # return value register - self._retreg = retreg + self._asize = self.arch.pointersize def __access_param(self, index: int, stack_access: Callable, reg_access: Callable) -> Tuple[Callable, int]: """[private] Generic accessor to function call parameters by their index. @@ -150,25 +148,25 @@ def __access_param(self, index: int, stack_access: Callable, reg_access: Callabl else: 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) + def getRawParam(self, index: int, argbits: int = 0) -> int: + read, loc = self.__access_param(index, self.arch.stack_read, self.arch.regs.read) - mask = (0 if argbits is None else (1 << argbits)) - 1 + mask = (argbits and (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) + def setRawParam(self, index: int, value: int, argbits: int = 0) -> None: + write, loc = self.__access_param(index, self.arch.stack_write, self.arch.regs.write) - mask = (0 if argbits is None else (1 << argbits)) - 1 + mask = (argbits and (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 +174,4 @@ def reserve(self, nslots: int) -> None: # 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 3d0aaa254..2457a537b 100644 --- a/qiling/cc/arm.py +++ b/qiling/cc/arm.py @@ -8,8 +8,7 @@ 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.cc import QlCommonBaseCC class QlArmBaseCC(QlCommonBaseCC): """Calling convention base class for ARM-based systems. @@ -22,20 +21,16 @@ def getNumSlots(argbits: int) -> int: 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): + _retreg = UC_ARM64_REG_X0 _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) - class aarch32(QlArmBaseCC): + _retreg = UC_ARM_REG_R0 _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) diff --git a/qiling/cc/intel.py b/qiling/cc/intel.py index 9d90ab54e..9c9aded49 100644 --- a/qiling/cc/intel.py +++ b/qiling/cc/intel.py @@ -3,39 +3,31 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework from unicorn.x86_const import ( - UC_X86_REG_AX, UC_X86_REG_EAX, UC_X86_REG_RAX, UC_X86_REG_RCX, - UC_X86_REG_RDI, UC_X86_REG_RDX, UC_X86_REG_RSI, UC_X86_REG_R8, - UC_X86_REG_R9, UC_X86_REG_R10 + UC_X86_REG_EAX, UC_X86_REG_RAX, UC_X86_REG_RCX, UC_X86_REG_RDI, + UC_X86_REG_RDX, UC_X86_REG_RSI, UC_X86_REG_R8, UC_X86_REG_R9, + UC_X86_REG_R10 ) -from qiling import Qiling -from . import QlCommonBaseCC +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): - retreg = { - 16: UC_X86_REG_AX, - 32: UC_X86_REG_EAX, - 64: UC_X86_REG_RAX - }[ql.archbit] - - super().__init__(ql, 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. """ + _retreg = UC_X86_REG_RAX + @staticmethod def getNumSlots(argbits: int) -> int: return max(argbits, 64) // 64 @@ -44,11 +36,13 @@ class QlIntel32(QlIntelBaseCC): """Calling convention base class for Intel-based 32-bit systems. """ + _retreg = UC_X86_REG_EAX + @staticmethod def getNumSlots(argbits: int) -> int: return max(argbits, 32) // 32 - def getRawParam(self, slot: int, nbits: int = None) -> int: + def getRawParam(self, slot: int, nbits: int = 0) -> int: __super_getparam = super().getRawParam if nbits == 64: @@ -107,6 +101,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 ea69f90d4..c1b3a0897 100644 --- a/qiling/cc/mips.py +++ b/qiling/cc/mips.py @@ -4,21 +4,18 @@ 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.cc import QlCommonBaseCC class mipso32(QlCommonBaseCC): + _retreg = UC_MIPS_REG_V0 _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) - @staticmethod def getNumSlots(argbits: int): return 1 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/ppc.py b/qiling/cc/ppc.py new file mode 100644 index 000000000..9f19e8818 --- /dev/null +++ b/qiling/cc/ppc.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework + +from qiling.cc import QlCommonBaseCC + +from unicorn.ppc_const import ( + UC_PPC_REG_3, UC_PPC_REG_4, UC_PPC_REG_5, + UC_PPC_REG_6, UC_PPC_REG_7, UC_PPC_REG_8, +) + + +class ppc(QlCommonBaseCC): + """Default calling convention for PPC + First 6 arguments are passed in regs, the rest are passed on the stack. + """ + + _retreg = UC_PPC_REG_3 + _argregs = (UC_PPC_REG_3, UC_PPC_REG_4, UC_PPC_REG_5, UC_PPC_REG_6, UC_PPC_REG_7, UC_PPC_REG_8) + (None, ) * 10 + + @staticmethod + def getNumSlots(argbits: int): + return 1 diff --git a/qiling/cc/riscv.py b/qiling/cc/riscv.py index e768f6c4d..60bcf21af 100644 --- a/qiling/cc/riscv.py +++ b/qiling/cc/riscv.py @@ -2,25 +2,21 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.cc import QlCommonBaseCC from unicorn.riscv_const import ( - UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, - UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5, + UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, + UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5 ) - class riscv(QlCommonBaseCC): """Default calling convention for RISCV - First 6 arguments are passed in regs, the rest are passed on the stack. + First 6 arguments are passed in regs, the rest are passed on the stack. """ + _retreg = UC_RISCV_REG_A0 _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) - @staticmethod def getNumSlots(argbits: int): return 1 \ No newline at end of file diff --git a/qiling/const.py b/qiling/const.py index 1b5bf4aa3..d7ac9f8db 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -3,8 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from enum import Enum, IntEnum -from typing import Any, Mapping, Type +from enum import Enum, Flag, IntEnum +from typing import Mapping, Type, TypeVar class QL_ENDIAN(IntEnum): EL = 1 @@ -14,7 +14,6 @@ class QL_ARCH(IntEnum): X86 = 101 X8664 = 102 ARM = 103 - ARM_THUMB = 104 ARM64 = 105 MIPS = 106 A8086 = 107 @@ -22,6 +21,7 @@ class QL_ARCH(IntEnum): CORTEX_M = 109 RISCV = 110 RISCV64 = 111 + PPC = 112 class QL_OS(IntEnum): LINUX = 201 @@ -53,47 +53,37 @@ class QL_INTERCEPT(IntEnum): ENTER = 2 EXIT = 3 -QL_DEBUGGER_ALL = (QL_DEBUGGER.IDAPRO, QL_DEBUGGER.GDB, QL_DEBUGGER.QDB) +class QL_STOP(Flag): + NONE = 0 + STACK_POINTER = (1 << 0) + EXIT_TRAP = (1 << 1) -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_ARCH_INTERPRETER = (QL_ARCH.EVM,) -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,) +QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) +QL_OS_BAREMETAL = (QL_OS.MCU,) QL_HOOK_BLOCK = 0b0001 QL_CALL_BLOCK = 0b0010 -def __reverse_enum(e: Type[Enum]) -> Mapping[str, Any]: - '''Create a reverse mapping for an enum. +T = TypeVar('T', bound=Enum) +def __casefold_enum(e: Type[T]) -> Mapping[str, T]: + '''Create a casefolded mapping of an enum to allow case-insensitive lookup. ''' - return dict((v.name.lower(), v.value) 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) -os_map : Mapping[str, QL_OS] = __reverse_enum(QL_OS) -verbose_map : Mapping[str, QL_VERBOSE] = __reverse_enum(QL_VERBOSE) - -loader_map = { - QL_OS.LINUX : "ELF", - QL_OS.FREEBSD : "ELF", - QL_OS.QNX : "ELF", - QL_OS.MACOS : "MACHO", - QL_OS.WINDOWS : "PE", - QL_OS.UEFI : "PE_UEFI", - QL_OS.DOS : "DOS", - QL_OS.EVM : "EVM", - QL_OS.MCU : "MCU", - QL_OS.BLOB : "BLOB" -} + return dict((k.casefold(), v) for k, v in e._member_map_.items()) + +debugger_map = __casefold_enum(QL_DEBUGGER) +arch_map = __casefold_enum(QL_ARCH) +os_map = __casefold_enum(QL_OS) +verbose_map = __casefold_enum(QL_VERBOSE) arch_os_map = { - QL_ARCH.EVM: QL_OS.EVM, + QL_ARCH.EVM : QL_OS.EVM, + QL_ARCH.CORTEX_M : QL_OS.MCU } + +__all__ = [ + 'QL_ENDIAN', 'QL_ARCH', 'QL_OS', 'QL_VERBOSE', 'QL_DEBUGGER', 'QL_INTERCEPT', 'QL_STOP', + 'QL_ARCH_INTERPRETER', 'QL_OS_POSIX', 'QL_OS_BAREMETAL', 'QL_HOOK_BLOCK', 'QL_CALL_BLOCK' +] diff --git a/qiling/core.py b/qiling/core.py index f4812aec5..1a42d3e7e 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -3,99 +3,74 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from configparser import ConfigParser -import ntpath, os, pickle, platform +import os, pickle +from typing import TYPE_CHECKING, Any, AnyStr, List, Mapping, MutableMapping, Optional, Sequence, Tuple, Union # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports -from typing import Callable, Dict, List, Union -from typing import TYPE_CHECKING - -from unicorn.unicorn import Uc - if TYPE_CHECKING: - from .arch.register import QlRegisterManager + from unicorn.unicorn import Uc + from configparser import ConfigParser + from logging import Logger 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_ARCH_INTERPRETER, QL_OS_BAREMETAL from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType +from .host import QlHost +from .log import * 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, - console=True, + argv: Sequence[str] = None, + rootfs: str = r'.', + 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: bool = True, log_file=None, log_override=None, - log_plain=False, - libcache = False, - multithread = False, + log_plain: bool = False, + multithread: bool = 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: Optional[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. + For each argument or property, please refer to its help. e.g. help(Qiling.multithread) """ ################################## # 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,116 +87,104 @@ def __init__( ############## # argv setup # ############## - if self._argv is None: - self._argv = ["qilingcode"] + if argv is None: + argv = ['qilingcode'] + + elif not os.path.exists(argv[0]): + raise QlErrorFileNotFound(f'Target binary not found: "{argv[0]}"') - elif not os.path.exists(str(self._argv[0])): - raise QlErrorFileNotFound("Target binary not found: %s" % (self._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 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) - - 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 - - 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 not ql_is_valid_arch(self._archtype): - raise QlErrorArch("Invalid ARCH: %s" % (self._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 - - # Once we finish setting up archendian and arcbit, we can init QlCoreStructs. - QlCoreStructs.__init__(self, self._archendian, self._archbit) - - - ####################################### - # Loader and General Purpose OS check # - ####################################### - self._loader = loader_setup(self._ostype, self) - - ##################### - # Profile & Logging # - ##################### - self._profile, debugmsg = profile_setup(self) - - # 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) - - self.log.setLevel(ql_resolve_logger_level(self._verbose)) - - # Now that the logger is configured, we can log profile debug msg: - self.log.debug(debugmsg) + self._host = QlHost() - ############## - # Components # - ############## - if not self.interpreter: - self._mem = component_setup("os", "memory", self) - self._reg = component_setup("arch", "register", self) - + if type(archtype) is str: + archtype = arch_convert(archtype) + + if type(ostype) is str: + ostype = os_convert(ostype) + + # 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 - self._arch = arch_setup(self.archtype, self) - - # Once we finish setting up arch layer, we can init QlCoreHooks. + if ostype is None: + ostype = guessed_ostype + + if endian is None: + endian = guessed_archendian + + # if arch was set but os was not, try to guess it by arch + elif ostype is None: + ostype = arch_os_convert(archtype) + + # arch should have been determined by now; fail if not + if type(archtype) is not QL_ARCH: + raise QlErrorArch(f'Unknown or unsupported architecture: "{archtype}"') + + # os should have been determined by now; fail if not + if type(ostype) is not QL_OS: + raise QlErrorOsType(f'Unknown or unsupported operating system: "{ostype}"') + + # if endianess is still undetermined, set it to little-endian. + # this setting is ignored for architectures with predefined endianess + if endian is None: + endian = QL_ENDIAN.EL + + self._arch = select_arch(archtype, endian, thumb)(self) + + # Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks if not self.interpreter: - self.uc = self.arch.init_uc + QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) 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 + ########## + # Logger # + ########## + self._log_file_fd = setup_logger(self, log_file, console, log_override, log_plain) + + self.filter = filter + self.verbose = verbose - if stdout is not None: - self._os.stdout = stdout + ########### + # Profile # + ########### + self.log.debug(f'Profile: {profile or "default"}') + self._profile = profile_setup(ostype, profile) - if stderr is not None: - self._os.stderr = stderr + ########## + # Loader # + ########## + self._loader = select_loader(ostype, libcache)(self) + + ############## + # Components # + ############## + if not self.interpreter: + self._mem = select_component('os', 'memory')(self) + self._os = select_os(ostype)(self) + + if self.baremetal: + self._hw = select_component('hw', 'hw')(self) # Run the loader self.loader.run() - self._init_stop_guard() + + self._init_stop_guard() ##################### # Qiling Components # @@ -235,14 +198,6 @@ def mem(self) -> "QlMemoryManager": """ 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. @@ -277,7 +232,7 @@ def os(self) -> "QlOs": return self._os @property - def log(self) -> logging.Logger: + def log(self) -> "Logger": """ Returns the logger this Qiling instance uses. You can override this log by passing `log_override=your_log` to Qiling.__init__ @@ -293,24 +248,6 @@ def log(self) -> logging.Logger: # 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. @@ -323,7 +260,7 @@ def multithread(self) -> bool: return self._multithread @property - def profile(self) -> ConfigParser: + def profile(self) -> "ConfigParser": """ Program profile. See qiling/profiles/*.ql for details. Note: Please pass None or the path string to Qiling.__init__. @@ -335,10 +272,9 @@ def profile(self) -> ConfigParser: 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,81 +289,13 @@ def rootfs(self) -> str: 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 - @property - def ostype(self) -> QL_OS: - """ The emulated os 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: - - "macos" : macOS. - - "darwin" : an alias to "macos". - - "freebsd" : FreeBSD - - "windows" : Windows - - "uefi" : UEFI - - "dos" : DOS - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) - """ - 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 +303,7 @@ def code(self) -> bytes: 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 @@ -463,7 +331,7 @@ def interpreter(self) -> bool: Type: bool """ - return self.ostype in QL_OS_INTERPRETER + return self.arch.type in QL_ARCH_INTERPRETER @property def baremetal(self) -> bool: @@ -472,42 +340,22 @@ def baremetal(self) -> bool: Type: bool """ - 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() - """ - return self._platform_os + # os is not initialized for interpreter archs + if self.interpreter: + return False - @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.os.type in QL_OS_BAREMETAL @property - def platform_arch(self): - """ Specify current platform arch 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_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 + return self._host @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,45 +363,28 @@ def internal_exception(self) -> Exception: return self._internal_exception @property - def libcache(self) -> bool: - """ Whether cache dll files. Only take effect in Windows emulation. - - 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. + def verbose(self) -> QL_VERBOSE: + """Set verbosity 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)) - if self.interpreter: - self.arch.utils.setup_output() + + self.log.setLevel(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 @@ -561,7 +392,7 @@ def patch_bin(self) -> list: 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 @@ -580,27 +411,27 @@ def debug_stop(self) -> bool: return self._debug_stop @debug_stop.setter - def debug_stop(self, ds): - self._debug_stop = ds + def debug_stop(self, enabled: bool): + self._debug_stop = enabled @property - def debugger(self) -> 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 @@ -610,172 +441,193 @@ def filter(self) -> str: Example: - Qiling(filter=r'^exit') - ql.filter = r'^open' """ - return self._filter + + lf = self._log_filter + + return '' if lf is None else lf._filter.pattern @filter.setter - def filter(self, ft): - self._filter = ft - if self._log_filter is None: - self._log_filter = RegexFilter(ft) - self.log.addFilter(self._log_filter) + def filter(self, regex: Optional[str]): + if regex is None: + if self._log_filter is not None: + self.log.removeFilter(self._log_filter) + else: - self._log_filter.update_filter(ft) + if self._log_filter is None: + self._log_filter = RegexFilter(regex) + + self.log.addFilter(self._log_filter) + + self._log_filter.update_filter(regex) @property - def uc(self) -> Uc: + def uc(self) -> 'Uc': """ Raw uc instance. - Type: Ucgit + Type: Uc """ - return self._uc - - @uc.setter - def uc(self, u): - self._uc = u + return self.arch.uc @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 - def __enable_bin_patch(self): - - for addr, code in self.patch_bin: - self.mem.write(self.loader.load_address + addr, code) + def do_bin_patch(self): + ba = self.loader.load_address + + for offset, code in self.patch_bin: + self.mem.write(ba + offset, code) + def do_lib_patch(self): + for offset, code, filename in self.patch_lib: + ba = self.mem.get_lib_base(filename) - def enable_lib_patch(self): - for addr, code, filename in self.patch_lib: - try: - self.mem.write(self.mem.get_lib_base(filename) + addr, code) - except: - raise RuntimeError("Fail to patch %s at address 0x%x" % (filename, addr)) + if ba is None: + raise RuntimeError(f'Patch failed: there is no loaded library named "{filename}"') + + self.mem.write(ba + offset, code) 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!') ############### # Qiling APIS # ############### - # Emulate the binary from begin until @end, with timeout in @timeout and - # number of emulated instructions in @count - def run(self, begin=None, end=None, timeout=0, count=0, code = None): + def run(self, begin: Optional[int] = None, end: Optional[int] = None, timeout: int = 0, count: int = 0): + """Start binary emulation. + + Args: + begin : emulation starting address + end : emulation ending address + timeout : limit emulation to a specific amount of time (microseconds); unlimited by default + count : limit emulation to a specific amount of instructions; unlimited by default + """ + # replace the original entry point, exit point, timeout and count self.entry_point = begin self.exit_point = end self.timeout = timeout - self.count = count + self.count = count - # init debugger - if self._debugger != False and self._debugger != None and not self.interpreter: - self._debugger = debugger_setup(self._debugger, self) + # init debugger (if set) + debugger = select_debugger(self._debugger) - if self.interpreter: - return self.arch.run(code) + if debugger: + debugger = debugger(self) - elif self.baremetal: - self.__enable_bin_patch() + # patch binary + self.do_bin_patch() + + if self.baremetal: if self.count <= 0: self.count = -1 - self.arch.run(count=self.count, end=self.exit_point) + + self.arch.run(count=self.count, end=self.exit_point) else: self.write_exit_trap() - # patch binary - self.__enable_bin_patch() # 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) @@ -783,10 +635,10 @@ def restore(self, saved_states=None, snapshot=None): 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"]) @@ -798,24 +650,6 @@ def restore(self, saved_states=None, snapshot=None): 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) @@ -842,23 +676,6 @@ def stack_read(self, offset): 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): @@ -876,8 +693,17 @@ def stop(self): self.uc.emu_stop() # start emulation - def emu_start(self, begin, end, timeout=0, count=0): + def emu_start(self, begin: int, end: int, timeout: int = 0, count: int = 0): + """Start emulation. + + Args: + begin : emulation starting address + end : emulation ending address + timeout : max emulation time (in microseconds); unlimited by default + count : max emulation steps (instructions count); unlimited by default + """ + self.uc.emu_start(begin, end, timeout, count) - if self._internal_exception != None: + if self._internal_exception is not None: raise self._internal_exception diff --git a/qiling/core_hooks.py b/qiling/core_hooks.py index e00af4b03..0a8e4cb83 100644 --- a/qiling/core_hooks.py +++ b/qiling/core_hooks.py @@ -8,7 +8,8 @@ # handling hooks # ############################################## -from typing import Callable, MutableMapping, MutableSequence +from typing import Any, Callable, MutableMapping, MutableSequence, Protocol +from typing import TYPE_CHECKING from unicorn import Uc from unicorn.unicorn_const import * @@ -18,6 +19,73 @@ from .const import QL_HOOK_BLOCK from .exception import QlErrorCoreHook +if TYPE_CHECKING: + from qiling import Qiling + +class MemHookCallback(Protocol): + def __call__(self, __ql: 'Qiling', __access: int, __address: int, __size: int, __value: int, *__context: Any) -> Any: + """Memory access hook callback. + + Args: + __ql : the associated qiling instance + __access : the intercepted memory access type, one of UC_HOOK_MEM_* constants + __addr : the target memory location + __size : size of intercepted memory access + __value : the value to write, for write operations, 0 for others + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + +class TraceHookCalback(Protocol): + def __call__(self, __ql: 'Qiling', __address: int, __size: int, *__context: Any) -> Any: + """Execution hook callback. + + Args: + __ql : the associated qiling instance + __address : address of the instruction to be executed + __size : instruction size + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + +class AddressHookCallback(Protocol): + def __call__(self, __ql: 'Qiling', *__context: Any) -> Any: + """Address hook callback. + + Args: + __ql : the associated qiling instance + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + +class InterruptHookCallback(Protocol): + def __call__(self, __ql: 'Qiling', intno: int, *__context: Any) -> Any: + """Interrupt hook callback. + + Args: + __ql : the associated qiling instance + __intno : the intercepted interrupt number + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + + # Don't assume self is Qiling. class QlCoreHooks: def __init__(self, uc: Uc): @@ -37,6 +105,9 @@ def __init__(self, uc: Uc): # Callback definitions # ######################## def _hook_intr_cb(self, uc: Uc, intno: int, pack_data) -> None: + """Interrupt hooks dispatcher. + """ + ql, hook_type = pack_data handled = False @@ -60,15 +131,18 @@ def _hook_intr_cb(self, uc: Uc, intno: int, pack_data) -> None: def _hook_insn_cb(self, uc: Uc, *args): - ql, hook_type = args[-1] + """Instruction hooks dispatcher. + """ + + *hook_args, (ql, insn_type) = args retval = None - if hook_type in self._insn_hook: - hooks_list = self._insn_hook[hook_type] + if insn_type in self._insn_hook: + hooks_list = self._insn_hook[insn_type] for hook in hooks_list: - if hook.bound_check(ql.reg.arch_pc): - ret = hook.call(ql, *args[:-1]) + if hook.bound_check(ql.arch.regs.arch_pc): + ret = hook.call(ql, *hook_args) if type(ret) is tuple: ret, retval = ret @@ -81,13 +155,16 @@ def _hook_insn_cb(self, uc: Uc, *args): def _hook_trace_cb(self, uc: Uc, addr: int, size: int, pack_data) -> None: + """Code and block hooks dispatcher. + """ + ql, hook_type = pack_data if hook_type in self._hook: hooks_list = self._hook[hook_type] for hook in hooks_list: - if hook.bound_check(ql.reg.arch_pc): + if hook.bound_check(addr, size): ret = hook.call(ql, addr, size) if type(ret) is int and ret & QL_HOOK_BLOCK: @@ -95,6 +172,9 @@ def _hook_trace_cb(self, uc: Uc, addr: int, size: int, pack_data) -> None: def _hook_mem_cb(self, uc: Uc, access: int, addr: int, size: int, value: int, pack_data): + """Memory access hooks dispatcher. + """ + ql, hook_type = pack_data handled = False @@ -116,6 +196,9 @@ def _hook_mem_cb(self, uc: Uc, access: int, addr: int, size: int, value: int, pa def _hook_insn_invalid_cb(self, uc: Uc, pack_data) -> None: + """Invalid instruction hooks dispatcher. + """ + ql, hook_type = pack_data handled = False @@ -134,6 +217,9 @@ def _hook_insn_invalid_cb(self, uc: Uc, pack_data) -> None: def _hook_addr_cb(self, uc: Uc, addr: int, size: int, pack_data): + """Address hooks dispatcher. + """ + ql = pack_data if addr in self._addr_hook: @@ -148,15 +234,15 @@ def _hook_addr_cb(self, uc: Uc, addr: int, size: int, pack_data): ############### # Class Hooks # ############### - def _ql_hook_internal(self, hook_type, callback, user_data=None, *args) -> int: - _callback = (catch_KeyboardInterrupt(self))(callback) - # pack user_data & callback for wrapper _callback - return self._h_uc.hook_add(hook_type, _callback, (self, user_data), 1, 0, *args) + def _ql_hook_internal(self, hook_type: int, callback: Callable, context: Any, *args) -> int: + _callback = catch_KeyboardInterrupt(self, callback) + + return self._h_uc.hook_add(hook_type, _callback, (self, context), 1, 0, *args) def _ql_hook_addr_internal(self, callback: Callable, address: int) -> int: - _callback = (catch_KeyboardInterrupt(self))(callback) - # pack user_data & callback for wrapper _callback + _callback = catch_KeyboardInterrupt(self, callback) + return self._h_uc.hook_add(UC_HOOK_CODE, _callback, self, address, address) @@ -175,7 +261,7 @@ def __handle_insn(t: int) -> None: ins_t = args[0] if ins_t not in self._insn_hook_fuc: - self._insn_hook_fuc[ins_t] = self._ql_hook_internal(t, self._hook_insn_cb, ins_t, *args) + self._insn_hook_fuc[ins_t] = self._ql_hook_internal(t, self._hook_insn_cb, ins_t, ins_t) if ins_t not in self._insn_hook: self._insn_hook[ins_t] = [] @@ -232,60 +318,203 @@ def __handle_invalid_insn(t: int) -> None: handler(t) - def ql_hook(self, hook_type: int, callback: Callable, user_data=None, begin=1, end=0, *args) -> HookRet: + def ql_hook(self, hook_type: int, callback: Callable, user_data: Any = None, begin: int = 1, end: int = 0, *args) -> HookRet: + """Intercept certain emulation events within a specified range. + + Args: + hook_type : event type to intercept; this argument is used as a bitmap and may encode multiple + events to hook with the same calback. see UC_HOOK_* constants for available events + callback : a method to call upon interception; callback signature may vary + depending on the hooked event type + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + hook = Hook(callback, user_data, begin, end) self._ql_hook(hook_type, hook, *args) return HookRet(self, hook_type, hook) - def hook_code(self, callback, user_data=None, begin=1, end=0): - if self.interpreter: - from .arch.evm.hooks import ql_evm_hooks - return ql_evm_hooks(self, 'HOOK_CODE', callback, user_data, begin, end) + def hook_code(self, callback: TraceHookCalback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept assembly instructions before they get executed. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ return self.ql_hook(UC_HOOK_CODE, callback, user_data, begin, end) + # TODO: remove; this is a special case of hook_intno(-1) def hook_intr(self, callback, user_data=None, begin=1, end=0): return self.ql_hook(UC_HOOK_INTR, callback, user_data, begin, end) - def hook_block(self, callback, user_data=None, begin=1, end=0): + def hook_block(self, callback: TraceHookCalback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept landings in new basic blocks in a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_BLOCK, callback, user_data, begin, end) - def hook_mem_unmapped(self, callback, user_data=None, begin=1, end=0): + def hook_mem_unmapped(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal accesses to unmapped memory in a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_UNMAPPED, callback, user_data, begin, end) - def hook_mem_read_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_read_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal reading attempts from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_READ_INVALID, callback, user_data, begin, end) - def hook_mem_write_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_write_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal writing attempts to a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_WRITE_INVALID, callback, user_data, begin, end) - def hook_mem_fetch_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_fetch_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal code fetching attempts from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_FETCH_INVALID, callback, user_data, begin, end) - def hook_mem_valid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_valid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign memory accesses within a specified range. + This is equivalent to hooking memory reads, writes and fetches. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_VALID, callback, user_data, begin, end) - def hook_mem_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept invalid memory accesses within a specified range. + This is equivalent to hooking invalid memory reads, writes and fetches. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_INVALID, callback, user_data, begin, end) - # a convenient API to set callback for a single address - def hook_address(self, callback, address, user_data=None): - hook = HookAddr(callback, address, user_data) + def hook_address(self, callback: AddressHookCallback, address: int, user_data: Any = None) -> HookRet: + """Intercept execution from a certain memory address. - if self.interpreter: - from .arch.evm.hooks import evm_hook_address - return evm_hook_address(self, 'HOOK_ADDR', hook, address) + Args: + callback : a method to call upon interception + address : memory location to watch + user_data : an additional context to pass to callback (default: `None`) + + Returns: + Hook handle + """ + + hook = HookAddr(callback, address, user_data) if address not in self._addr_hook_fuc: self._addr_hook_fuc[address] = self._ql_hook_addr_internal(self._hook_addr_cb, address) @@ -295,80 +524,132 @@ def hook_address(self, callback, address, user_data=None): self._addr_hook[address].append(hook) - return HookRet(self, None, hook) + # note: assuming 0 is not a valid hook type + return HookRet(self, 0, hook) + + def hook_intno(self, callback: InterruptHookCallback, intno: int, user_data: Any = None) -> HookRet: + """Intercept interrupts. - def get_hook_address(self, address): - return self._addr_hook.get(address, []) + Args: + callback : a method to call upon interception + intono : interrupt vector number to intercept, or -1 for any + user_data : an additional context to pass to callback (default: `None`) + Returns: + Hook handle + """ - def hook_intno(self, callback, intno, user_data=None): hook = HookIntr(callback, intno, user_data) self._ql_hook(UC_HOOK_INTR, hook) return HookRet(self, UC_HOOK_INTR, hook) - def hook_mem_read(self, callback, user_data=None, begin=1, end=0): + def hook_mem_read(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign memory reads from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_READ, callback, user_data, begin, end) - def hook_mem_write(self, callback, user_data=None, begin=1, end=0): + def hook_mem_write(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign memory writes to a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_WRITE, callback, user_data, begin, end) - def hook_mem_fetch(self, callback, user_data=None, begin=1, end=0): + def hook_mem_fetch(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign code fetches from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_FETCH, callback, user_data, begin, end) - def hook_insn(self, callback, arg1, user_data=None, begin=1, end=0): - if self.interpreter: - from .arch.evm.hooks import evm_hook_insn - return evm_hook_insn(self, 'HOOK_INSN', callback, arg1, user_data, begin, end) + def hook_insn(self, callback, insn_type: int, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept execution of a certain instruction type within a specified range. - return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, arg1) + Args: + callback : a method to call upon interception; the callback arguments list differs + based on the instruction type + insn_type : instruction type to intercept + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + Notes: + - The set of supported instruction types is very limited and defined by unicorn. + - If `begin` and `end` are not specified, the entire memory space will be watched. - def hook_del(self, *args): - if len(args) != 1 and len(args) != 2: - return + Returns: + Hook handle + """ - if isinstance(args[0], HookRet): - args[0].remove() - return + return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, insn_type) - hook_type, h = args - if self.interpreter: - from .arch.evm.hooks import evm_hook_del - return evm_hook_del(hook_type, h) + def hook_del(self, hret: HookRet) -> None: + """Unregister an existing hook and release its resources. - def __handle_common(t: int) -> None: - if t in self._hook: - if h in self._hook[t]: - del self._hook[t][self._hook[t].index(h)] + Args: + hret : hook handle + """ - if len(self._hook[t]) == 0: - self._h_uc.hook_del(self._hook_fuc[t]) - del self._hook_fuc[t] + h = hret.obj + hook_type = hret.type - def __handle_insn(t: int) -> None: - if t in self._insn_hook: - if h in self._insn_hook[t]: - del self._insn_hook[t][self._insn_hook[t].index(h)] - - if len(self._insn_hook[t]) == 0: - self._h_uc.hook_del(self._insn_hook_fuc[t]) - del self._insn_hook_fuc[t] - - def __handle_addr(t: int) -> None: - if t in self._addr_hook: - if h in self._addr_hook[t]: - del self._addr_hook[t][self._addr_hook[t].index(h)] - - if len(self._addr_hook[t]) == 0: - self._h_uc.hook_del(self._addr_hook_fuc[t]) - del self._addr_hook_fuc[t] + def __remove(hooks_map, handles_map, key: int) -> None: + if key in hooks_map: + hooks_list = hooks_map[key] + + if h in hooks_list: + hooks_list.remove(h) + + if not hooks_list: + uc_handle = handles_map.pop(key) + + self._h_uc.hook_del(uc_handle) + + __handle_common = lambda k: __remove(self._hook, self._hook_fuc, k) + __handle_insn = lambda i: __remove(self._insn_hook, self._insn_hook_fuc, i) + __handle_addr = lambda a: __remove(self._addr_hook, self._addr_hook_fuc, a) type_handlers = ( (UC_HOOK_INTR, __handle_common), diff --git a/qiling/core_hooks_types.py b/qiling/core_hooks_types.py index cd11c931a..896d70a0e 100644 --- a/qiling/core_hooks_types.py +++ b/qiling/core_hooks_types.py @@ -46,11 +46,11 @@ def check(self, intno: int) -> bool: class HookRet: - def __init__(self, ql, t, h): - self._ql = ql - self._t = t - self._h = h + def __init__(self, ql, hook_type: int, hook_obj: Hook): + self.type = hook_type + self.obj = hook_obj + self.__remove = ql.hook_del - def remove(self): - self._ql.hook_del(self._t, self._h) \ No newline at end of file + def remove(self) -> None: + self.__remove(self) diff --git a/qiling/core_struct.py b/qiling/core_struct.py index 47fd0cee4..08c325a07 100644 --- a/qiling/core_struct.py +++ b/qiling/core_struct.py @@ -10,10 +10,13 @@ ############################################## import struct +from typing import Union from .const import QL_ENDIAN from .exception import QlErrorStructConversion +ReadableBuffer = Union[bytes, bytearray, memoryview] + # Don't assume self is Qiling. class QlCoreStructs: def __init__(self, endian: QL_ENDIAN, bit: int): @@ -30,12 +33,11 @@ def __init__(self, endian: QL_ENDIAN, bit: int): self._fmt32s = f'{modifier}i' self._fmt64 = f'{modifier}Q' self._fmt64s = f'{modifier}q' - + handlers = { 64 : (self.pack64, self.pack64s, self.unpack64, self.unpack64s), 32 : (self.pack32, self.pack32s, self.unpack32, self.unpack32s), 16 : (self.pack16, self.pack16s, self.unpack16, self.unpack16s), - 1 : ( None, None, None, None) } if bit not in handlers: @@ -48,50 +50,50 @@ def __init__(self, endian: QL_ENDIAN, bit: int): self.unpack = up self.unpacks = ups - def pack64(self, x): + def pack64(self, x: int, /) -> bytes: return struct.pack(self._fmt64, x) - def pack64s(self, x): + def pack64s(self, x: int, /) -> bytes: return struct.pack(self._fmt64s, x) - def unpack64(self, x): + def unpack64(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt64, x)[0] - def unpack64s(self, x): + def unpack64s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt64s, x)[0] - def pack32(self, x): + def pack32(self, x: int, /) -> bytes: return struct.pack(self._fmt32, x) - def pack32s(self, x): + def pack32s(self, x: int, /) -> bytes: return struct.pack(self._fmt32s, x) - def unpack32(self, x): + def unpack32(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt32, x)[0] - def unpack32s(self, x): + def unpack32s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt32s, x)[0] - def pack16(self, x): + def pack16(self, x: int, /) -> bytes: return struct.pack(self._fmt16, x) - def pack16s(self, x): + def pack16s(self, x: int, /) -> bytes: return struct.pack(self._fmt16s, x) - def unpack16(self, x): + def unpack16(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt16, x)[0] - def unpack16s(self, x): + def unpack16s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt16s, x)[0] - def pack8(self, x): + def pack8(self, x: int, /) -> bytes: return struct.pack(self._fmt8, x) - def pack8s(self, x): + def pack8s(self, x: int, /) -> bytes: return struct.pack(self._fmt8s, x) - def unpack8(self, x): + def unpack8(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt8, x)[0] - def unpack8s(self, x): + def unpack8s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt8s, x)[0] diff --git a/qiling/debugger/disassember.py b/qiling/debugger/disassember.py index d04671897..fea90563a 100644 --- a/qiling/debugger/disassember.py +++ b/qiling/debugger/disassember.py @@ -17,19 +17,20 @@ def __init__(self, ql:Qiling): def disasm_all_lines(self): disasm_result = [] - if self.ql.ostype == QL_OS.LINUX: + if self.ql.os.type == QL_OS.LINUX: disasm_result = self.disasm_elf() return disasm_result 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 e26ab5176..f559a2a83 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -3,47 +3,68 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -# gdbserver --remote-debug 0.0.0.0:9999 /path/to binary -# documentation: according to https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol +# for watching actual protocol messages: +# server: gdbserver --remote-debug 127.0.0.1:9999 /path/to/exec +# client: gdb -q -ex "target remote 127.0.0.1:9999" +# +# also, run this command on the gdb client: +# (gdb) set debug remote 1 +# +# gdb remote protocol: +# https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html + +import os, socket, re +from logging import Logger +from typing import Iterator, Optional, Union -import struct, os, socket -from binascii import unhexlify -from typing import Iterator, Literal +from unicorn import UcError +from unicorn.unicorn_const import ( + UC_ERR_READ_UNMAPPED, UC_ERR_WRITE_UNMAPPED, UC_ERR_FETCH_UNMAPPED, + UC_ERR_READ_PROT, UC_ERR_WRITE_PROT, UC_ERR_FETCH_PROT, + UC_ERR_READ_UNALIGNED, UC_ERR_WRITE_UNALIGNED, UC_ERR_FETCH_UNALIGNED, + UC_ERR_INSN_INVALID +) from qiling import Qiling -from qiling.const import * -from qiling.utils import * +from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS from qiling.debugger import QlDebugger -from qiling.arch.x86_const import reg_map_16 as x86_reg_map_16 -from qiling.arch.x86_const import reg_map_32 as x86_reg_map_32 -from qiling.arch.x86_const import reg_map_64 as x86_reg_map_64 -from qiling.arch.x86_const import reg_map_misc as x86_reg_map_misc -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.loader.elf import AUX - -from .utils import QlGdbUtils - -GDB_SIGNAL_INT = 2 -GDB_SIGNAL_SEGV = 11 -GDB_SIGNAL_GILL = 4 -GDB_SIGNAL_STOP = 17 -GDB_SIGNAL_TRAP = 5 -GDB_SIGNAL_BUS = 10 - - -class QlGdb(QlDebugger, object): - """docstring for Debugsession""" - def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): - super(QlGdb, self).__init__(ql) +from qiling.debugger.gdb.xmlregs import QlGdbFeatures +from qiling.debugger.gdb.utils import QlGdbUtils + +# gdb logging prompt +PROMPT = r'gdb>' + +# default string encoding +ENCODING = 'latin' + +# define a few handy linux signals +SIGINT = 2 +SIGILL = 4 +SIGTRAP = 5 +SIGABRT = 6 +SIGBUS = 7 +SIGKILL = 9 +SIGSEGV = 11 +SIGALRM = 14 +SIGTERM = 15 +SIGCHLD = 16 +SIGCONT = 17 +SIGSTOP = 18 + +# common replies +REPLY_ACK = b'+' +REPLY_EMPTY = b'' +REPLY_OK = b'OK' + +# reply type +Reply = Union[bytes, str] + +class QlGdb(QlDebugger): + """A simple gdbserver implementation. + """ - self.ql = ql - self.last_pkt = None - self.exe_abspath = os.path.abspath(self.ql.argv[0]) - self.rootfs_abspath = os.path.abspath(self.ql.rootfs) - self.gdb = QlGdbUtils() + def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): + super().__init__(ql) if type(port) is str: port = int(port, 0) @@ -51,772 +72,895 @@ def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): self.ip = ip self.port = port - if self.ql.baremetal: - load_address = self.ql.loader.load_address + if ql.baremetal: + load_address = ql.loader.load_address exit_point = load_address + os.path.getsize(ql.path) - elif self.ql.code: - load_address = self.ql.os.entry_point + elif ql.code: + load_address = ql.os.entry_point exit_point = load_address + len(ql.code) else: load_address = ql.loader.load_address exit_point = load_address + os.path.getsize(ql.path) - if self.ql.baremetal: - self.entry_point = self.ql.loader.entry_point - elif self.ql.ostype in (QL_OS.LINUX, QL_OS.FREEBSD) and not self.ql.code: - self.entry_point = self.ql.os.elf_entry + if ql.baremetal: + entry_point = ql.loader.entry_point + elif ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD) and not ql.code: + entry_point = ql.os.elf_entry else: - self.entry_point = self.ql.os.entry_point + entry_point = ql.os.entry_point # Only part of the binary file will be debugged. - if self.ql.entry_point is not None and self.ql.exit_point is not None: - self.entry_point = self.ql.entry_point - exit_point = self.ql.exit_point - - self.gdb.initialize(self.ql, self.entry_point, exit_point=exit_point, mappings=[(hex(load_address))]) - - #Setup register tables, order of tables is important - self.tables = { - QL_ARCH.A8086 : list({**x86_reg_map_16, **x86_reg_map_misc}.keys()), - QL_ARCH.X86 : list({**x86_reg_map_32, **x86_reg_map_misc, **x86_reg_map_st}.keys()), - QL_ARCH.X8664 : list({**x86_reg_map_64, **x86_reg_map_misc, **x86_reg_map_st}.keys()), - QL_ARCH.ARM : list({**arm_reg_map}.keys()), - QL_ARCH.CORTEX_M : list({**arm_reg_map}.keys()), - QL_ARCH.ARM64 : list({**arm64_reg_map}.keys()), - QL_ARCH.MIPS : list({**mips_reg_map}.keys()), - } + if ql.entry_point is not None: + entry_point = ql.entry_point - 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) + if ql.exit_point is not None: + exit_point = ql.exit_point - if nbits == 64: - s = f'{int.from_bytes(self.ql.pack64(addr), byteorder=endian):016x}' + self.gdb = QlGdbUtils(ql, entry_point, exit_point) - elif nbits == 32: - s = f'{int.from_bytes(self.ql.pack32(addr), byteorder=endian):08x}' + self.features = QlGdbFeatures(self.ql.arch.type, self.ql.os.type) + self.regsmap = self.features.regsmap - elif nbits == 16: - s = f'{int.from_bytes(self.ql.pack16(addr), byteorder=endian):04x}' + def run(self): + server = GdbSerialConn(self.ip, self.port, self.ql.log) + killed = False - else: - raise RuntimeError + def __hexstr(value: int, nibbles: int = 0) -> str: + """Encode a value into a hex string. + """ - return s + length = (nibbles or self.ql.arch.bits // 4) // 2 + byteorder = 'little' if self.ql.arch.endian == QL_ENDIAN.EL else 'big' - def bin_to_escstr(self, rawbin): - rawbin_escape = "" + return value.to_bytes(length, byteorder).hex() - def incomplete_hex_check(hexchar): - if len(hexchar) == 1: - hexchar = "0" + hexchar - return hexchar + def __unkown_reg_value(nibbles: int) -> str: + """Encode the hex string for unknown regsiter value. + """ - for a in rawbin: + return 'x' * nibbles - # The binary data representation uses 7d (ASCII ‘}’) as an escape character. - # Any escaped byte is transmitted as the escape character followed by the original character XORed with 0x20. - # For example, the byte 0x7d would be transmitted as the two bytes 0x7d 0x5d. The bytes 0x23 (ASCII ‘#’), 0x24 (ASCII ‘$’), and 0x7d (ASCII ‘}’) - # must always be escaped. Responses sent by the stub must also escape 0x2a (ASCII ‘*’), - # so that it is not interpreted as the start of a run-length encoded sequence (described next). + def __get_reg_value(reg: Optional[int], pos: int, nibbles: int) -> str: + # reg is either None or uc reg invalid + if reg: + value = self.ql.arch.regs.read(reg) + assert type(value) is int - if a in (42,35,36, 125): - a = a ^ 0x20 - a = (str(hex(a)[2:])) - a = incomplete_hex_check(a) - a = str("7d%s" % a) + hexstr = __hexstr(value, nibbles) else: - a = (str(hex(a)[2:])) - a = incomplete_hex_check(a) + hexstr = __unkown_reg_value(nibbles) - rawbin_escape += a + return hexstr - return unhexlify(rawbin_escape) + def __set_reg_value(reg: Optional[int], pos: int, nibbles: int, hexval: str) -> None: + # reg is neither None nor uc reg invalid + if reg and hexval != __unkown_reg_value(nibbles): + assert len(hexval) == nibbles - def setup_server(self): - self.ql.log.info("gdb> Listening on %s:%u" % (self.ip, self.port)) + val = int(hexval, 16) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((self.ip, self.port)) - sock.listen(1) - clientsocket, addr = sock.accept() + if self.ql.arch.endian == QL_ENDIAN.EL: + val = __swap_endianess(val) - self.sock = sock - self.clientsocket = clientsocket - self.netin = clientsocket.makefile('r') - self.netout = clientsocket.makefile('w') + self.ql.arch.regs.write(reg, val) - def close(self): - self.netin.close() - self.netout.close() - self.clientsocket.close() - self.sock.close() + def __swap_endianess(value: int) -> int: + length = (value.bit_length() + 7) // 8 + raw = value.to_bytes(length, 'little') - def run(self): - self.setup_server() - - while self.receive() == 'Good': - pkt = self.last_pkt - self.send_raw('+') - - def handle_qmark(subcmd): - def gdbqmark_converter(arch): - """ - MIPS32_EL : gdbserver response ("$T051d:00e7ff7f;25:40ccfc77;#65") - MIPS32_EB : gdbserver response ("$T051d:7fff6dc0;25:77fc4880;thread:28fa;core:0;"); - ARM64: gdbserver response "$T051d:0*,;1f:80f6f*"ff0* ;20:c02cfdb7f* 0* ;thread:p1f9.1f9;core:0;#56"); - ARM: gdbserver $T050b:0*"00;0d:e0f6ffbe;0f:8079fdb6;#ae" - """ - adapter = { - QL_ARCH.A8086 : [ 0x05, 0x04, 0x08 ], - QL_ARCH.X86 : [ 0x05, 0x04, 0x08 ], - QL_ARCH.X8664 : [ 0x06, 0x07, 0x10 ], - QL_ARCH.MIPS : [ 0x1d, 0x00, 0x25 ], - QL_ARCH.ARM : [ 0x0b, 0x0d, 0x0f ], - QL_ARCH.CORTEX_M : [ 0x0b, 0x0d, 0x0f ], - QL_ARCH.ARM64 : [ 0x1d, 0xf1, 0x20 ] - } - 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) - - 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") - 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) - - if self.gdb.bp_list == [self.entry_point]: - self.send("W00") + return int.from_bytes(raw, 'big') + + + def handle_exclaim(subcmd: str) -> Reply: + return REPLY_OK + + + def handle_qmark(subcmd: str) -> Reply: + """Request status. + + @see: https://sourceware.org/gdb/current/onlinedocs/gdb/Stop-Reply-Packets.html + """ + + from unicorn.x86_const import UC_X86_REG_EBP + from unicorn.x86_const import UC_X86_REG_RBP + from unicorn.arm_const import UC_ARM_REG_R11 + from unicorn.arm64_const import UC_ARM64_REG_X29 + from unicorn.mips_const import UC_MIPS_REG_INVALID + + arch_uc_bp = { + QL_ARCH.X86 : UC_X86_REG_EBP, + QL_ARCH.X8664 : UC_X86_REG_RBP, + QL_ARCH.ARM : UC_ARM_REG_R11, + QL_ARCH.ARM64 : UC_ARM64_REG_X29, + QL_ARCH.MIPS : UC_MIPS_REG_INVALID, # skipped + QL_ARCH.A8086 : UC_X86_REG_EBP, + QL_ARCH.CORTEX_M : UC_ARM_REG_R11 + }[self.ql.arch.type] + + def __get_reg_idx(ucreg: int) -> int: + """Get the index of a uc reg whithin the regsmap array. + + Returns: array index where this reg's info is stored, or -1 if not found + """ + + return next((i for i, (regnum, _, _) in enumerate(self.regsmap) if regnum == ucreg), -1) + + def __get_reg_info(ucreg: int) -> str: + """Retrieve register info and pack it as a pair. + """ + + regnum = __get_reg_idx(ucreg) + hexval = __get_reg_value(*self.regsmap[regnum]) + + return f'{regnum:02x}:{hexval};' + + # mips targets skip this reg info pair + bp_info = '' if self.ql.arch.type == QL_ARCH.MIPS else __get_reg_info(arch_uc_bp) + + # FIXME: a8086 should use 'esp' and 'eip' here instead of 'sp' and 'ip' set by its arch instance + sp_info = __get_reg_info(self.ql.arch.regs.uc_sp) + pc_info = __get_reg_info(self.ql.arch.regs.uc_pc) + + return f'T{SIGTRAP:02x}{bp_info}{sp_info}{pc_info}' + + + def handle_c(subcmd: str) -> Reply: + try: + self.gdb.resume_emu() + except UcError as err: + sigmap = { + UC_ERR_READ_UNMAPPED : SIGSEGV, + UC_ERR_WRITE_UNMAPPED : SIGSEGV, + UC_ERR_FETCH_UNMAPPED : SIGSEGV, + UC_ERR_WRITE_PROT : SIGSEGV, + UC_ERR_READ_PROT : SIGSEGV, + UC_ERR_FETCH_PROT : SIGSEGV, + UC_ERR_READ_UNALIGNED : SIGBUS, + UC_ERR_WRITE_UNALIGNED : SIGBUS, + UC_ERR_FETCH_UNALIGNED : SIGBUS, + UC_ERR_INSN_INVALID : SIGILL + } + + # determine signal from uc error; default to SIGTERM + reply = f'S{sigmap.get(err.errno, SIGTERM):02x}' + + except KeyboardInterrupt: + # emulation was interrupted with ctrl+c + reply = f'S{SIGINT:02x}' + + else: + if self.ql.arch.regs.arch_pc == self.gdb.last_bp: + # emulation stopped because it hit a breakpoint + reply = f'S{SIGTRAP:02x}' else: - self.send(('S%.2x' % GDB_SIGNAL_TRAP)) - - - handle_C = handle_c - - - def handle_g(subcmd): - s = '' - - if self.ql.archtype== QL_ARCH.A8086: - for reg in self.tables[QL_ARCH.A8086][:16]: - r = self.ql.reg.read(reg) - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.archtype== QL_ARCH.X86: - for reg in self.tables[QL_ARCH.X86][:16]: - r = self.ql.reg.read(reg) - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.archtype== 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: - tmp = self.addr_to_str(r) - elif self.ql.reg.bit(reg) == 32: - tmp = self.addr_to_str(r, short = True) - s += tmp - - elif self.ql.archtype == 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) - - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.archtype == QL_ARCH.ARM64: - for reg in self.tables[QL_ARCH.ARM64][:33]: - r = self.ql.reg.read(reg) - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.archtype == 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: - tmp = self.addr_to_str(r, endian ="little") - else: - tmp = self.addr_to_str(r) - s += tmp - - self.send(s) - - - def handle_G(subcmd): - count = 0 - - if self.ql.archtype == 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) - count += 1 - - elif self.ql.archtype == 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) - count += 1 - - - elif self.ql.archtype == 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) - 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) - count += 1 - - elif self.ql.archtype == 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) - count += 1 - - elif self.ql.archtype == 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) - count += 1 - - elif self.ql.archtype == 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) - count += 1 - - self.send('OK') - - - def handle_H(subcmd): - if subcmd.startswith('g'): - self.send('OK') - if subcmd.startswith('c'): - self.send('OK') - - - def handle_m(subcmd): - addr, size = subcmd.split(',') - addr = int(addr, 16) - size = int(size, 16) + # emulation has completed successfully + reply = f'W{self.ql.os.exit_code:02x}' - try: - tmp = '' - for s in range(size): - mem = self.ql.mem.read(addr + s, 1) - mem = "".join( - [str("{:02x}".format(ord(c))) for c in mem.decode('latin1')]) - tmp += mem - self.send(tmp) - - except: - self.send('E14') - - - def handle_M(subcmd): - addr, data = subcmd.split(',') - size, data = data.split(':') - addr = int(addr, 16) - data = bytes.fromhex(data) - try: - self.ql.mem.write(addr, data) - self.send('OK') - except: - self.send('E01') + return reply - def handle_p(subcmd): - reg_index = int(subcmd, 16) - reg_value = None - try: - if self.ql.archtype== QL_ARCH.A8086: - if reg_index <= 9: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.A8086][reg_index-1]) - else: - reg_value = 0 - reg_value = self.addr_to_str(reg_value) + def handle_g(subcmd: str) -> Reply: + # NOTE: in the past the 'g' reply packet for arm included the f0-f7 and fps registers between pc + # and cpsr, which placed cpsr at index (regnum) 25. as the f-registers became obsolete the cpsr + # index decreased. in order to maintain backward compatibility with older gdb versions, the gap + # between pc and cpsr that used to represent the f-registers (96 bits each + 32 bits for fps) is + # filled with unknown reg values. + # + # gdb clients that follow the xml definitions no longer need these placeholders, as registers + # indices are flexible and may be defined arbitrarily though xml. + # + # see: ./xml/arm/arm-fpa.xml - elif self.ql.archtype== QL_ARCH.X86: - if reg_index <= 24: - reg_value = self.ql.reg.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: - if reg_index <= 32: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.X8664][reg_index-1]) - else: - reg_value = 0 - if reg_index <= 17: - reg_value = self.addr_to_str(reg_value) - elif 17 < reg_index: - reg_value = self.addr_to_str(reg_value, short = True) - - elif self.ql.archtype== QL_ARCH.ARM: - if reg_index < 26: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.ARM][reg_index - 1]) - else: - reg_value = 0 - reg_value = self.addr_to_str(reg_value) + return ''.join(__get_reg_value(*entry) for entry in self.regsmap) - elif self.ql.archtype== QL_ARCH.ARM64: - if reg_index <= 32: - reg_value = self.ql.reg.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: - if reg_index <= 37: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) - else: - reg_value = 0 - if self.ql.archendian == QL_ENDIAN.EL: - reg_value = self.addr_to_str(reg_value, endian="little") - else: - reg_value = self.addr_to_str(reg_value) - - if type(reg_value) is not str: - reg_value = self.addr_to_str(reg_value) - - self.send(reg_value) - except: - self.close() - raise - - - 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] - - if self.ql.archtype== QL_ARCH.A8086: - reg_data = int(reg_data, 16) - reg_data = int.from_bytes(struct.pack(' Reply: + data = subcmd + + for reg, pos, nibbles in self.regsmap: + hexval = data[pos : pos + nibbles] + + __set_reg_value(reg, pos, nibbles, hexval) + + return REPLY_OK + + + def handle_H(subcmd: str) -> Reply: + op = subcmd[0] + + if op in ('c', 'g'): + return REPLY_OK + + return REPLY_EMPTY + + + def handle_k(subcmd: str) -> Reply: + global killed + + killed = True + return REPLY_OK - if reg_name == self.ql.reg.arch_pc_name: - self.gdb.current_address = reg_data - self.ql.log.info("gdb> Write to register %s with %x\n" % (self.tables[self.ql.archtype][reg_index], reg_data)) - self.send('OK') + def handle_m(subcmd: str) -> Reply: + """Read target memory. + """ + addr, size = (int(p, 16) for p in subcmd.split(',')) - def handle_Q(subcmd): - if subcmd.startswith( 'StartNoAckMode'): - self.send('OK') + try: + data = self.ql.mem.read(addr, size).hex() + except UcError: + return 'E14' + else: + return data + + + def handle_M(subcmd: str) -> Reply: + """Write target memory. + """ - elif subcmd.startswith( 'DisableRandomization'): - self.send('OK') + addr, data = subcmd.split(',') + size, data = data.split(':') - elif subcmd.startswith( 'ProgramSignals'): - self.send('OK') + addr = int(addr, 16) + data = bytes.fromhex(data) - elif subcmd.startswith( 'NonStop'): - self.send('OK') + assert len(data) == size - elif subcmd.startswith('PassSignals'): - self.send('OK') + try: + self.ql.mem.write(addr, data) + except UcError: + return 'E01' + else: + return REPLY_OK + + + def handle_p(subcmd: str) -> Reply: + """Read register value by index. + """ + + idx = int(subcmd, 16) + + return __get_reg_value(*self.regsmap[idx]) + + + def handle_P(subcmd: str) -> Reply: + """Write register value by index. + """ + + idx, data = subcmd.split('=') + idx = int(idx, 16) + + if idx < len(self.regsmap): + __set_reg_value(*self.regsmap[idx], hexval=data) + + return REPLY_OK + + return 'E00' + + + def handle_Q(subcmd: str) -> Reply: + """General queries. + + @see: https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html + """ + + feature, *data = subcmd.split(':', maxsplit=1) + + supported = ( + 'DisableRandomization', + 'NonStop', + 'PassSignals', + 'ProgramSignals', + 'StartNoAckMode' + ) + + if feature == 'StartNoAckMode': + server.ack_mode = False + server.log.debug('[noack mode enabled]') + + return REPLY_OK if feature in supported else REPLY_EMPTY + + + def handle_D(subcmd: str) -> Reply: + """Detach. + """ + + return REPLY_OK + + + def handle_q(subcmd: str) -> Reply: + query, *data = subcmd.split(':') + + # qSupported command + # + # @see: https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html#qSupported + + if query == 'Supported': + # list of supported features excluding the multithreading-related ones + common = ( + 'BreakpointCommands+', + 'ConditionalBreakpoints+', + 'ConditionalTracepoints+', + 'DisconnectedTracing+', + 'EnableDisableTracepoints+', + 'InstallInTrace+', + 'QAgent+', + 'QCatchSyscalls+', + 'QDisableRandomization+', + 'QEnvironmentHexEncoded+', + 'QEnvironmentReset+', + 'QEnvironmentUnset+', + 'QNonStop+', + 'QPassSignals+', + 'QProgramSignals+', + 'QSetWorkingDir+', + 'QStartNoAckMode+', + 'QStartupWithShell+', + 'QTBuffer:size+', + 'StaticTracepoints+', + 'TraceStateVariables+', + 'TracepointSource+', + # 'augmented-libraries-svr4-read+', + 'exec-events+', + 'fork-events+', + 'hwbreak+', + 'multiprocess+', + 'no-resumed+', + 'qXfer:auxv:read+', + 'qXfer:exec-file:read+', + 'qXfer:features:read+', + # 'qXfer:libraries-svr4:read+', + # 'qXfer:osdata:read+', + 'qXfer:siginfo:read+', + 'qXfer:siginfo:write+', + 'qXfer:statictrace:read+', + 'qXfer:threads:read+', + 'qXfer:traceframe-info:read+', + 'swbreak+', + 'tracenz+', + 'vfork-events+' + ) + + # might or might not need for multi thread + if self.ql.multithread: + features = ( + 'PacketSize=47ff', + 'FastTracepoints+', + 'QThreadEvents+', + 'Qbtrace-conf:bts:size+', + 'Qbtrace-conf:pt:size+', + 'Qbtrace:bts+', + 'Qbtrace:off+', + 'Qbtrace:pt+', + 'qXfer:btrace-conf:read+', + 'qXfer:btrace:read+', + 'vContSupported+' + ) + + else: + features = ( + 'PacketSize=3fff', + 'qXfer:spu:read+', + 'qXfer:spu:write+' + ) - elif subcmd.startswith('qemu'): - self.send('') + return ';'.join(common + features) - def handle_D(subcmd): - self.send('OK') + elif query == 'Xfer': + feature, op, annex, params = data + offset, length = (int(p, 16) for p in params.split(',')) - def handle_q(subcmd): - if subcmd.startswith('Supported:'): - # might or might not need for multi thread - if self.ql.multithread == False: - self.send("PacketSize=3fff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+;augmented-libraries-svr4-read+;qXfer:auxv:read+;qXfer:spu:read+;qXfer:spu:write+;qXfer:siginfo:read+;qXfer:siginfo:write+;qXfer:features:read+;QStartNoAckMode+;qXfer:osdata:read+;multiprocess+;fork-events+;vfork-events+;exec-events+;QNonStop+;QDisableRandomization+;qXfer:threads:read+;ConditionalTracepoints+;TraceStateVariables+;TracepointSource+;DisconnectedTracing+;StaticTracepoints+;InstallInTrace+;qXfer:statictrace:read+;qXfer:traceframe-info:read+;EnableDisableTracepoints+;QTBuffer:size+;tracenz+;ConditionalBreakpoints+;BreakpointCommands+;QAgent+;swbreak+;hwbreak+;qXfer:exec-file:read+;no-resumed+") - else: - self.send("PacketSize=47ff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+;augmented-libraries-svr4-read+;qXfer:auxv:read+;qXfer:siginfo:read+;qXfer:siginfo:write+;qXfer:features:read+;QStartNoAckMode+;qXfer:osdata:read+;multiprocess+;fork-events+;vfork-events+;exec-events+;QNonStop+;QDisableRandomization+;qXfer:threads:read+;ConditionalTracepoints+;TraceStateVariables+;TracepointSource+;DisconnectedTracing+;FastTracepoints+;StaticTracepoints+;InstallInTrace+;qXfer:statictrace:read+;qXfer:traceframe-info:read+;EnableDisableTracepoints+;QTBuffer:size+;tracenz+;ConditionalBreakpoints+;BreakpointCommands+;QAgent+;Qbtrace:bts+;Qbtrace-conf:bts:size+;Qbtrace:pt+;Qbtrace-conf:pt:size+;Qbtrace:off+;qXfer:btrace:read+;qXfer:btrace-conf:read+;swbreak+;hwbreak+;qXfer:exec-file:read+;vContSupported+;QThreadEvents+;no-resumed+") - 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() - xfercmd_file = os.path.join(xfercmd_abspath,"xml",xml_folder, xfercmd_file) + if feature == 'features' and op == 'read': + if annex == r'target.xml': + content = self.features.tostring()[offset:offset + length] - if os.path.exists(xfercmd_file) and self.ql.ostype is not QL_OS.WINDOWS: - with open(xfercmd_file, 'r') as f: - file_contents = f.read() - self.send("l%s" % file_contents) else: - self.ql.log.info("gdb> Platform is not supported by xml or xml file not found: %s\n" % (xfercmd_file)) - self.send("l") - - - elif subcmd.startswith('Xfer:threads:read::0,'): - if self.ql.ostype in QL_OS_NONPID or self.ql.baremetal: - self.send("l") - else: - file_contents = ("\r\n\r\n") - self.send("l" + file_contents) - - elif subcmd.startswith('Xfer:auxv:read::'): - if self.ql.code: - return - - if self.ql.ostype in (QL_OS.LINUX, QL_OS.FREEBSD): - def __read_auxv() -> Iterator[int]: - auxv_entries = ( - AUX.AT_HWCAP, - AUX.AT_PAGESZ, - AUX.AT_CLKTCK, - AUX.AT_PHDR, - AUX.AT_PHENT, - AUX.AT_PHNUM, - AUX.AT_BASE, - AUX.AT_FLAGS, - AUX.AT_ENTRY, - AUX.AT_UID, - AUX.AT_EUID, - AUX.AT_GID, - AUX.AT_EGID, - AUX.AT_SECURE, - AUX.AT_RANDOM, - AUX.AT_HWCAP2, - AUX.AT_EXECFN, - AUX.AT_PLATFORM, - AUX.AT_NULL - ) - - for e in auxv_entries: - yield e.value - yield self.ql.loader.aux_vec[e] - - annex = self.addr_to_str(0)[:-2] - sysinfo_ehdr = self.addr_to_str(0) - - auxvdata_c = unhexlify(''.join([annex, sysinfo_ehdr] + [self.addr_to_str(val) for val in __read_auxv()])) - auxvdata = self.bin_to_escstr(auxvdata_c) + self.ql.log.info(f'{PROMPT} did not expect "{annex}" here') + content = '' + + return f'{"l" if len(content) < length else "m"}{content}' + + elif feature == 'threads' and op == 'read': + if not self.ql.baremetal and hasattr(self.ql.os, 'pid'): + content = '\r\n'.join(( + '', + f'', + '' + )) + else: - auxvdata = b"" + content = '' - self.send(b'l!%s' % auxvdata) + return f'l{content}' - elif subcmd.startswith('Xfer:exec-file:read:'): - self.send("l%s" % str(self.exe_abspath)) + elif feature == 'auxv' and op == 'read': + auxv_data = bytearray() + if hasattr(self.ql.loader, 'auxv'): + nbytes = self.ql.arch.bits // 8 - elif subcmd.startswith('Xfer:libraries-svr4:read:'): - if self.ql.ostype in (QL_OS.LINUX, QL_OS.FREEBSD): - xml_addr_mapping=("") - """ - FIXME: need to find out when do we need this - """ - #for s, e, info in self.ql.map_info: - # addr_mapping += ("" %(info, e, s)) - xml_addr_mapping += ("") - self.send("l%s" % xml_addr_mapping) - else: - self.send("l") + auxv_addr = self.ql.loader.auxv + offset + null_entry = bytes(nbytes * 2) - elif subcmd.startswith("Xfer:btrace-conf:read:"): - self.send("E.Btrace not enabled.") + # keep reading until AUXV.AT_NULL is reached + while not auxv_data.endswith(null_entry): + auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes)) + auxv_addr += nbytes - elif subcmd == "Attached": - self.send("") + auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes)) + auxv_addr += nbytes - elif subcmd.startswith("C"): - self.send("") + return b'l' + auxv_data[:length] - elif subcmd.startswith("L:"): - self.send("M001") + elif feature == 'exec-file' and op == 'read': + return f'l{os.path.abspath(self.ql.path)}' - elif subcmd == "fThreadInfo": - self.send("m0") + elif feature == 'libraries-svr4' and op == 'read': + # TODO: this one requires information of loaded libraries which currently not provided + # by the ELF loader. until we gather that information, we cannot fulfill this request + # + # see: https://sourceware.org/gdb/current/onlinedocs/gdb/Library-List-Format-for-SVR4-Targets.html + return REPLY_EMPTY - elif subcmd == "sThreadInfo": - self.send("l") + # if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): + # tag = 'library-list-svr4' + # xml_lib_entries = (f'' for lbnd, ubnd, _, _, path in self.ql.mem.get_mapinfo() if path) + # + # xml = '\r\n'.join((f'<{tag} version="1.0">', *xml_lib_entries, f'')) + # + # return f'l{xml}' + # else: + # return f'' - elif subcmd == ("TStatus"): - self.send("T0;tnotrun:0;tframes:0;tcreated:0;tfree:50*!;tsize:50*!;circular:0;disconn:0;starttime:0;stoptime:0;username:;notes::") + elif feature == 'btrace-conf' and op == 'read': + return 'E.Btrace not enabled.' - elif subcmd == ("TfV"): - self.send("l") + elif query == 'Attached': + return REPLY_EMPTY - elif subcmd == ("TsV"): - self.send("l") + elif query == 'C': + return REPLY_EMPTY - elif subcmd == ("TfP"): - self.send("l") + elif query == 'L': + return 'M001' - elif subcmd == ("TsP"): - self.send("l") + elif query == 'fThreadInfo': + return 'm0' + elif query == 'sThreadInfo': + return 'l' - elif subcmd.startswith("Symbol"): - self.send("") + elif query == 'TStatus': + tsize = __hexstr(0x500000) - elif subcmd.startswith("Attached"): - self.send("") + fields = ( + 'T0', + 'tnotrun:0', + 'tframes:0', + 'tcreated:0', + f'tfree:{tsize}', + f'tsize:{tsize}', + 'circular:0', + 'disconn:0', + 'starttime:0', + 'stoptime:0', + 'username:', + 'notes::' + ) - elif subcmd == "Offsets": - self.send("Text=0;Data=0;Bss=0") + return ';'.join(fields) + elif query in ('TfV', 'TsV', 'TfP', 'TsP'): + return 'l' - def handle_v(subcmd): + elif query == 'Symbol': + return REPLY_OK - if subcmd == 'MustReplyEmpty': - self.send("") + elif query == 'Offsets': + fields = ('Text=0', 'Data=0', 'Bss=0') - elif subcmd.startswith('File:open'): - if self.ql.ostype == QL_OS.UEFI or self.ql.baremetal: - self.send("F-1") - return + return ';'.join(fields) + + return REPLY_EMPTY - (file_path, flags, mode) = subcmd.split(':')[-1].split(',') - file_path = unhexlify(file_path).decode(encoding='UTF-8') - flags = int(flags, base=16) - mode = int(mode, base=16) - if file_path.startswith(self.rootfs_abspath): - file_abspath = file_path - else: - file_abspath = self.ql.os.path.transform_to_real_path(file_path) - - self.ql.log.debug("gdb> target file: %s" % (file_abspath)) - if os.path.exists(file_abspath) and not (file_path).startswith("/proc"): - fd = os.open(file_abspath, flags, mode) - self.send("F%x" % fd) - else: - self.send("F-1") - return - elif subcmd.startswith('File:pread:'): - (fd, count, offset) = subcmd.split(':')[-1].split(',') + def handle_v(subcmd: str) -> Reply: + if subcmd == 'MustReplyEmpty': + return REPLY_EMPTY - fd = int(fd, base=16) - offset = int(offset, base=16) - count = int(count, base=16) + elif subcmd.startswith('File'): + _, op, data = subcmd.split(':', maxsplit=2) + params = data.split(',') + + if op == 'open': + fd = -1 + + # files can be opened only where there is an os that supports filesystem + if not self.ql.interpreter and hasattr(self.ql.os, 'path'): + path, flags, mode = params + + path = bytes.fromhex(path).decode(encoding='utf-8') + flags = int(flags, 16) + mode = int(mode, 16) + + # try to guess whether this is an emulated path or real one + if path.startswith(os.path.abspath(self.ql.rootfs)): + host_path = path + else: + host_path = self.ql.os.path.virtual_to_host_path(path) + + self.ql.log.debug(f'{PROMPT} target file: {host_path}') + + if os.path.exists(host_path) and not path.startswith(r'/proc'): + fd = os.open(host_path, flags, mode) + + return f'F{fd:x}' + + elif op == 'pread': + fd, count, offset = (int(p, 16) for p in params) data = os.pread(fd, count, offset) - size = len(data) - data = self.bin_to_escstr(data) - if data: - self.send(("F%x;" % size).encode() + (data)) - else: - self.send("F0;") + return f'F{len(data):x};'.encode() + data + + elif op == 'close': + fd, *_ = params + fd = int(fd, 16) - elif subcmd.startswith('File:close'): - fd = subcmd.split(':')[-1] - fd = int(fd, base=16) os.close(fd) - self.send("F0") - - elif subcmd.startswith('Kill'): - self.send('OK') - - elif subcmd.startswith('Cont'): - self.ql.log.debug("gdb> Cont command received: %s" % subcmd) - if subcmd == 'Cont?': - self.send('vCont;c;C;t;s;S;r') - elif subcmd.startswith ("Cont;"): - subcmd = subcmd.split(';') - subcmd = subcmd[1].split(':') - if subcmd[0] in ('c', 'C05'): - handle_c(subcmd) - elif subcmd[0] in ('S', 's', 'S05'): - handle_s(subcmd) - else: - self.send("") + return 'F0' + return REPLY_EMPTY + + elif subcmd.startswith('Kill'): + return handle_k('') + + elif subcmd.startswith('Cont'): + # remove 'Cont' prefix + data = subcmd[len('Cont'):] + + if data == '?': + # note 't' and 'r' are currently not supported + return ';'.join(('vCont', 'c', 'C', 's', 'S')) + + elif data.startswith(';'): + groups = subcmd.split(';')[1:] + + for grp in groups: + cmd, tid = grp.split(':', maxsplit=1) + + if cmd in ('c', f'C{SIGTRAP:02x}'): + return handle_c('') + + elif cmd in ('s', f'S{SIGTRAP:02x}'): + return handle_s('') + + # FIXME: not sure how to handle multiple command + # groups, so handling just the first one + break + + return REPLY_EMPTY - def handle_s(subcmd): - current_address = self.gdb.current_address - if current_address is None: - entry_point = self.gdb.entry_point - if entry_point is not None: - self.gdb.soft_bp = True - self.gdb.resume_emu(entry_point) - else: - self.gdb.soft_bp = True - self.gdb.resume_emu() - self.send('S%.2x' % GDB_SIGNAL_TRAP) - - - def handle_X(subcmd): - self.send('') - - - def handle_Z(subcmd): - data = subcmd - ztype = data[data.find('Z') + 1:data.find(',')] - if ztype == '0': - ztype, address, value = data.split(',') - address = int(address, 16) - try: - self.gdb.bp_insert(address) - self.send('OK') - except: - self.send('E22') - else: - self.send('E22') + def handle_s(subcmd: str) -> Reply: + """Perform a single step. + """ - def handle_z(subcmd): - data = subcmd.split(',') - if len(data) != 3: - self.send('E22') + # BUG: a known unicorn caching issue causes it to emulate more + # steps than requestes. until that issue is fixed, single stepping + # is essentially broken. + # + # @see: https://github.com/unicorn-engine/unicorn/issues/1606 + + self.gdb.resume_emu(steps=1) + + return f'S{SIGTRAP:02x}' + + + def handle_X(subcmd: str) -> Reply: + """Write data to memory. + """ + + params, data = subcmd.split(':', maxsplit=1) + addr, length = (int(p, 16) for p in params.split(',')) + + if length != len(data): + return 'E00' + + try: + if data: + self.ql.mem.write(addr, data.encode(ENCODING)) + except UcError: + return 'E01' + else: + return REPLY_OK + + + def handle_Z(subcmd: str) -> Reply: + """Insert breakpoints or watchpoints. + """ + + params, *conds = subcmd.split(';') + type, addr, kind = (int(p, 16) for p in params.split(',')) + + # type values: + # 0 = sw breakpoint + # 1 = hw breakpoint + # 2 = write watchpoint + # 3 = read watchpoint + # 4 = access watchpoint + + if type == 0: + self.gdb.bp_insert(addr) + return REPLY_OK + + return REPLY_EMPTY + + + def handle_z(subcmd: str) -> Reply: + """Remove breakpoints or watchpoints. + """ + + type, addr, kind = (int(p, 16) for p in subcmd.split(',')) + + if type == 0: try: - type = data[0] - addr = int(data[1], 16) - length = data[2] - self.gdb.bp_remove(addr, type, length) - self.send('OK') - except: - self.send('E22') - - - def handle_exclaim(subcmd): - self.send('OK') - - commands = { - '!': handle_exclaim, - '?': handle_qmark, - 'c': handle_c, - 'C': handle_C, - 'D': handle_D, - 'g': handle_g, - 'G': handle_G, - 'H': handle_H, - 'm': handle_m, - 'M': handle_M, - 'p': handle_p, - 'P': handle_P, - 'q': handle_q, - 'Q': handle_Q, - 's': handle_s, - 'v': handle_v, - 'X': handle_X, - 'Z': handle_Z, - 'z': handle_z - } - - cmd, subcmd = pkt[0], pkt[1:] - if cmd == 'k': + self.gdb.bp_remove(addr) + except ValueError: + return 'E22' + else: + return REPLY_OK + + return REPLY_EMPTY + + + handlers = { + '!': handle_exclaim, + '?': handle_qmark, + 'c': handle_c, + 'C': handle_c, # this is intentional; not a typo + 'D': handle_D, + 'g': handle_g, + 'G': handle_G, + 'H': handle_H, + 'k': handle_k, + 'm': handle_m, + 'M': handle_M, + 'p': handle_p, + 'P': handle_P, + 'q': handle_q, + 'Q': handle_Q, + 's': handle_s, + 'v': handle_v, + 'X': handle_X, + 'Z': handle_Z, + 'z': handle_z + } + + # main server loop + for packet in server.readpackets(): + if server.ack_mode: + server.send(REPLY_ACK, raw=True) + server.log.debug('[sent ack]') + + cmd, subcmd = packet[0], packet[1:] + handler = handlers.get(f'{cmd:c}') + + if handler: + reply = handler(subcmd.decode(ENCODING)) + server.send(reply) + + if killed: + break + else: + self.ql.log.info(f'{PROMPT} command not supported') + server.send(REPLY_EMPTY) + + server.close() + + +class GdbSerialConn: + """Serial connection handler. + """ + + # default recieve buffer size + BUFSIZE = 4096 + + def __init__(self, ipaddr: str, port: int, logger: Logger) -> None: + """Create a new gdb serial connection handler. + + Args: + ipaddr : ip address to bind the socket to + port : port number to listen on + logger : logger instance to use + """ + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((ipaddr, port)) + sock.listen() + + self.log = logger + self.log.info(f'{PROMPT} listening on {ipaddr}:{port:d}') + + client, _ = sock.accept() + + self.sock = sock + self.client = client + + # ack mode should be turend on by default + self.ack_mode = True + + def close(self): + """Close the gdb serial connection handler and release its resources. + """ + + self.client.close() + self.sock.close() + + def readpackets(self) -> Iterator[bytes]: + """Iterate through incoming packets in an active connection until + it is terminated. + """ + + pattern = re.compile(br'^\$(?P[^#]*)#(?P[0-9a-fA-F]{2})') + buffer = bytearray() + + while True: + try: + incoming = self.client.recv(self.BUFSIZE) + except ConnectionError: + break + + # remote connection closed + if not incoming: break - if cmd not in commands: - self.send('') - self.ql.log.info("gdb> Command not supported: %s\n" %(cmd)) + buffer += incoming + + # discard incoming acks + if buffer[0:1] == REPLY_ACK: + del buffer[0] + + packet = pattern.match(buffer) + + # if there is no match, the rest of the packet might be missing + if not packet: continue - self.ql.log.debug("gdb> received: %s%s" % (cmd, subcmd)) - commands[cmd](subcmd) - - self.close() - - - def receive(self): - '''Receive a packet from a GDB client''' - csum = 0 - state = 'Finding SOP' - packet = '' - try: - while True: - c = self.netin.read(1) - if c == '\x03': - return 'Error: CTRL+C' - - if len(c) != 1: - return 'Error: EOF' - - if state == 'Finding SOP': - if c == '$': - state = 'Finding EOP' - elif state == 'Finding EOP': - if c == '#': - if csum != int(self.netin.read(2), 16): - raise Exception('invalid checksum') - self.last_pkt = packet - return 'Good' - else: - packet += c - csum = (csum + ord(c)) & 0xff - else: - raise Exception('should not be here') - except: - self.close() - raise - - def checksum(self, data): - checksum = 0 - for c in data: - if type(c) == str: - checksum += (ord(c)) - else: - checksum += c - return checksum & 0xff - def send(self, msg): - """Send a packet to the GDB client""" - if type(msg) == str: - self.send_raw('$%s#%.2x' % (msg, self.checksum(msg))) + data = packet['data'] + read_csum = int(packet['checksum'], 16) + calc_csum = GdbSerialConn.checksum(data) + + if read_csum != calc_csum: + raise IOError(f'checksum error: expected {calc_csum:02x} but got {read_csum:02x}') + + # follow gdbserver debug output format + self.log.debug(f'getpkt ("{GdbSerialConn.__printable_prefix(data).decode(ENCODING)}");') + + data = GdbSerialConn.rle_decode(data) + data = GdbSerialConn.unescape(data) + + del buffer[:packet.endpos] + yield data + + def send(self, data: Reply, raw: bool = False) -> None: + """Send out a packet. + + Args: + data : data to send out + raw : whether to encapsulate the data with standard header and + checksum or leave it raw + """ + + if type(data) is str: + data = data.encode(ENCODING) + + assert type(data) is bytes + + if raw: + packet = data else: - self.clientsocket.send(b'$%s#%.2x' % (msg, self.checksum(msg))) - self.netout.flush() + data = GdbSerialConn.escape(data) + data = GdbSerialConn.rle_encode(data) + + packet = b'$' + data + b'#' + f'{GdbSerialConn.checksum(data):02x}'.encode() + + # follow gdbserver debug output format + self.log.debug(f'putpkt ("{GdbSerialConn.__printable_prefix(data).decode(ENCODING)}");') + + self.client.sendall(packet) + + @staticmethod + def __printable_prefix(data: bytes) -> bytes: + """Follow the gnu gdbserver debug message format which emits only the + printable prefix of a packet (either incoming or outgoing). Note that + despite of its name, it includes non-printable characters as well. + + Args: + data : packet data to scan + + Returns: a prefix of the specified data buffer + """ + + def __isascii(ch: int) -> bool: + return 0 < ch < 0x80 + + if data.isascii(): + return data + + return data[:next((i for i, ch in enumerate(data) if not __isascii(ch)), len(data))] + + @staticmethod + def escape(data: bytes) -> bytes: + """Escape data according to gdb protocol escaping rules. + """ + + def __repl(m: 're.Match[bytes]') -> bytes: + ch, *_ = m[0] + + return bytes([ord('}'), ch ^ 0x20]) + + return re.sub(br'[*#$}]', __repl, data, flags=re.DOTALL) + + @staticmethod + def unescape(data: bytes) -> bytes: + """Unescape data according to gdb protocol escaping rules. + """ + + def __repl(m: 're.Match[bytes]') -> bytes: + _, ch = m[0] + + return bytes([ch ^ 0x20]) + + return re.sub(br'}.', __repl, data, flags=re.DOTALL) + + @staticmethod + def rle_encode(data: bytes) -> bytes: + """Compact data using run-length encoding. + """ + + def __simple_rep(b: bytes, times: int) -> bytes: + return b * times + + def __runlen_rep(b: bytes, times: int) -> bytes: + return b + b'*' + bytes([times - 1 + 29]) + + def __encode_rep(b: bytes, times: int) -> bytes: + assert times > 0, 'time should be a positive value' + + if 0 < times < 4: + return __simple_rep(b, times) + + elif times == 6+1 or times == 7+1: + return __runlen_rep(b, 6) + __encode_rep(b, times - 6) + + else: + return __runlen_rep(b, times) + + def __repl(m: 're.Match[bytes]') -> bytes: + repetition = m[0] + + ch = repetition[0:1] + times = len(repetition) + + return __encode_rep(ch, times) + + return re.sub(br'(.)\1{3,96}', __repl, data, flags=re.DOTALL) + + @staticmethod + def rle_decode(data: bytes) -> bytes: + """Expand run-length encoded data. + """ + + def __repl(m: 're.Match[bytes]') -> bytes: + ch, _, times = m[0] + + return bytes([ch] * (1 + times - 29)) - self.ql.log.debug("gdb> send: $%s#%.2x" % (msg, self.checksum(msg))) + return re.sub(br'.\*.', __repl, data, flags=re.DOTALL) - def send_raw(self, r): - self.netout.write(r) - self.netout.flush() + @staticmethod + def checksum(data: bytes) -> int: + return sum(data) & 0xff diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index fa4e367af..30bba47e8 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -3,112 +3,76 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn import * -from qiling.const import * - - -class QlGdbUtils(object): - def __init__(self): - 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.breakpoint_count = 0x0 - self.skip_bp_count = 0x0 - self._tmp_hook = None +from typing import Optional + +from qiling import Qiling +from qiling.const import QL_ARCH + +# this code is partially based on uDbg +# @see: https://github.com/iGio90/uDdbg +PROMPT = r'gdb>' - def initialize(self, ql, hook_address, exit_point=None, mappings=None): +class QlGdbUtils: + def __init__(self, ql: Qiling, entry_point: int, exit_point: int): self.ql = ql - if self.ql.baremetal: - self.current_address = self.entry_point - else: - self.current_address = self.entry_point = self.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) - - 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 dbg_hook(self, ql, address, size): - """ - Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg - """ - try: - if self.ql.archtype == QL_ARCH.ARM: - mode = self.ql.arch.check_thumb() - if mode == UC_MODE_THUMB: - address = address + 1 - - self.mapping.append([(hex(address))]) - self.current_address = address - hit_soft_bp = False - - if self.soft_bp == True: - self.soft_bp = False - hit_soft_bp = True - - # Breakpoints are always added without the LSB, even in Thumb, so they should be checked like this as well - if ((address & ~1) in self.bp_list and (address & ~1) != self.last_bp) or self.has_soft_bp == True: - if self.skip_bp_count > 0: - self.skip_bp_count -= 1 - else: - self.breakpoint_count += 1 - self.ql.stop() - self.last_bp = address - ql.log.info("gdb> Breakpoint found, stop at address: 0x%x" % address) - - 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() - except: - raise - - - def bp_insert(self, addr): + self.bp_list = [] + self.last_bp = None + + def __entry_point_hook(ql: Qiling): + ql.hook_del(ep_hret) + ql.hook_code(self.dbg_hook) + + ql.log.info(f'{PROMPT} stopped at entry point: {ql.arch.regs.arch_pc:#x}') + ql.stop() + + # set a one-time hook to be dispatched upon reaching program entry point. + # that hook will be used to set up the breakpoint handling hook + ep_hret = ql.hook_address(__entry_point_hook, entry_point) + + + def dbg_hook(self, ql: Qiling, address: int, size: int): + if ql.arch.type == QL_ARCH.ARM and ql.arch.is_thumb: + address += 1 + + # resuming emulation after hitting a breakpoint will re-enter this hook. + # avoid an endless hooking loop by detecting and skipping this case + if address == self.last_bp: + self.last_bp = None + + elif address in self.bp_list: + self.last_bp = address + + ql.log.info(f'{PROMPT} breakpoint hit, stopped at {address:#x}') + ql.stop() + + # # TODO: not sure what this is about + # if address + size == self.exit_point: + # ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}') + # ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}') + + + 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): self.bp_list.remove(addr) - self.ql.log.info('gdb> Breakpoint removed at: 0x%x' % addr) - - - def resume_emu(self, address=None, skip_bp=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: - mode = self.ql.arch.check_thumb() - if mode == UC_MODE_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.emu_start(self.current_address, self.exit_point) - \ No newline at end of file + self.ql.log.info(f'{PROMPT} breakpoint removed from {addr:#x}') + + + def resume_emu(self, address: Optional[int] = None, steps: int = 0): + if address is None: + address = self.ql.arch.regs.arch_pc + + if self.ql.arch.type == QL_ARCH.ARM and self.ql.arch.is_thumb: + address += 1 + + op = f'stepping {steps} instructions' if steps else 'resuming' + self.ql.log.info(f'{PROMPT} {op} from {address:#x}') + + self.ql.emu_start(address, self.exit_point, count=steps) diff --git a/qiling/debugger/gdb/xml/a8086/target.xml b/qiling/debugger/gdb/xml/a8086/target.xml index 71daae8cd..43bc64d7f 100644 --- a/qiling/debugger/gdb/xml/a8086/target.xml +++ b/qiling/debugger/gdb/xml/a8086/target.xml @@ -1 +1,6 @@ -i8086 + + + + i8086 + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm/arm-core.xml b/qiling/debugger/gdb/xml/arm/arm-core.xml index a2c0a65b2..5032c5039 100644 --- a/qiling/debugger/gdb/xml/arm/arm-core.xml +++ b/qiling/debugger/gdb/xml/arm/arm-core.xml @@ -1,31 +1,31 @@ - + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm/arm-m-profile-with-fpa.xml b/qiling/debugger/gdb/xml/arm/arm-m-profile-with-fpa.xml deleted file mode 100644 index dd30abe87..000000000 --- a/qiling/debugger/gdb/xml/arm/arm-m-profile-with-fpa.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/qiling/debugger/gdb/xml/arm/arm-m-profile.xml b/qiling/debugger/gdb/xml/arm/arm-m-profile.xml index 5319d764e..f0584a206 100644 --- a/qiling/debugger/gdb/xml/arm/arm-m-profile.xml +++ b/qiling/debugger/gdb/xml/arm/arm-m-profile.xml @@ -7,7 +7,7 @@ - + @@ -23,5 +23,6 @@ + diff --git a/qiling/debugger/gdb/xml/arm/target.xml b/qiling/debugger/gdb/xml/arm/target.xml index 579e84b04..89826d474 100644 --- a/qiling/debugger/gdb/xml/arm/target.xml +++ b/qiling/debugger/gdb/xml/arm/target.xml @@ -6,9 +6,9 @@ *!notice and this notice are preserved. --> - - arm - - - - + + arm + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm64/aarch64-core.xml b/qiling/debugger/gdb/xml/arm64/aarch64-core.xml index 5b2706e03..599d92b3e 100644 --- a/qiling/debugger/gdb/xml/arm64/aarch64-core.xml +++ b/qiling/debugger/gdb/xml/arm64/aarch64-core.xml @@ -1,67 +1,67 @@ + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - * - * - * - * - * - * - * - * - * + + + + + + + + + + - * - * + + - * - * - * - * - - + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml b/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml index 0b37e15fa..0cdf73e5e 100644 --- a/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml +++ b/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml @@ -1,86 +1,93 @@ + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> - - - - - - - - - - - - - - * - * - * - - - * - * - * - - - * - * - - - * - * - - - * - * - - - * - * - * - * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm64/target.xml b/qiling/debugger/gdb/xml/arm64/target.xml index d4dbac662..c741efa61 100644 --- a/qiling/debugger/gdb/xml/arm64/target.xml +++ b/qiling/debugger/gdb/xml/arm64/target.xml @@ -1,14 +1,15 @@ + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> - - aarch64 - - + + aarch64 + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/mips/target.xml b/qiling/debugger/gdb/xml/mips/target.xml index a585dffd3..0a6a1b628 100644 --- a/qiling/debugger/gdb/xml/mips/target.xml +++ b/qiling/debugger/gdb/xml/mips/target.xml @@ -6,14 +6,15 @@ *!notice and this notice are preserved. --> - - mips - GNU/Linux - - - + + mips + GNU/Linux - - * - + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/32bit-core.xml b/qiling/debugger/gdb/xml/x86/32bit-core.xml index 0958032a8..702ae3b0b 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-core.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-core.xml @@ -7,59 +7,59 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/32bit-mpx.xml b/qiling/debugger/gdb/xml/x86/32bit-mpx.xml index 7772954d3..1798dbd80 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-mpx.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-mpx.xml @@ -7,39 +7,41 @@ - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml b/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml index 6f3c65a87..71aa7dc12 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml @@ -7,7 +7,5 @@ - - - - + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/32bit-sse.xml b/qiling/debugger/gdb/xml/x86/32bit-sse.xml index c57a2d846..e28536e5d 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-sse.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-sse.xml @@ -7,46 +7,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/target.xml b/qiling/debugger/gdb/xml/x86/target.xml index a664320d9..502703234 100644 --- a/qiling/debugger/gdb/xml/x86/target.xml +++ b/qiling/debugger/gdb/xml/x86/target.xml @@ -1,11 +1,11 @@ -i386 - GNU/Linux - - - - - - - + +i386 + GNU/Linux + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x8664/target.xml b/qiling/debugger/gdb/xml/x8664/target.xml index d58e543ea..d68705f67 100644 --- a/qiling/debugger/gdb/xml/x8664/target.xml +++ b/qiling/debugger/gdb/xml/x8664/target.xml @@ -1 +1,13 @@ -i386:x86-64GNU/Linux + + + + i386:x86-64 + GNU/Linux + + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py new file mode 100644 index 000000000..7385a303f --- /dev/null +++ b/qiling/debugger/gdb/xmlregs.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from typing import Iterator, Mapping, Optional, Sequence, Tuple +from pathlib import PurePath +from xml.etree import ElementTree, ElementInclude + +from qiling.arch.arm_const import reg_map as arm_regs +from qiling.arch.arm_const import reg_vfp as arm_regs_vfp +from qiling.arch.arm64_const import reg_map as arm64_regs +from qiling.arch.arm64_const import reg_map_v as arm64_regs_v +from qiling.arch.mips_const import reg_map as mips_regs_gpr +from qiling.arch.mips_const import reg_map_fpu as mips_regs_fpu +from qiling.arch.x86_const import reg_map_32 as x86_regs_32 +from qiling.arch.x86_const import reg_map_64 as x86_regs_64 +from qiling.arch.x86_const import reg_map_misc as x86_regs_misc +from qiling.arch.x86_const import reg_map_cr as x86_regs_cr +from qiling.arch.x86_const import reg_map_st as x86_regs_st +from qiling.arch.x86_const import reg_map_xmm as x86_regs_xmm +from qiling.arch.x86_const import reg_map_ymm as x86_regs_ymm + +from qiling.const import QL_ARCH, QL_OS + +RegEntry = Tuple[Optional[int], int, int] + +class QlGdbFeatures: + def __init__(self, archtype: QL_ARCH, ostype: QL_OS): + xmltree = QlGdbFeatures.__load_target_xml(archtype, ostype) + regsmap = QlGdbFeatures.__load_regsmap(archtype, xmltree) + + self.xmltree = xmltree + self.regsmap = regsmap + + def tostring(self) -> str: + root = self.xmltree.getroot() + + return ElementTree.tostring(root, encoding='unicode', xml_declaration=True) + + @staticmethod + def __get_xml_path(archtype: QL_ARCH) -> Tuple[str, PurePath]: + import inspect + + p = PurePath(inspect.getfile(QlGdbFeatures)) + basedir = p.parent / 'xml' / archtype.name.lower() + filename = basedir / 'target.xml' + + return str(filename), basedir + + @staticmethod + def __load_target_xml(archtype: QL_ARCH, ostype: QL_OS) -> ElementTree.ElementTree: + filename, base_url = QlGdbFeatures.__get_xml_path(archtype) + + tree = ElementTree.parse(filename) + + # NOTE: this is needed to load xinclude hrefs relative to the main xml file. starting + # from python 3.9 ElementInclude.include has an argument for that called 'base_url'. + # this is a workaround for earlier python versions such as 3.8 + + # + def my_loader(base: PurePath): + def __wrapped(href: str, parse, encoding=None): + abshref = base / href + + return ElementInclude.default_loader(str(abshref), parse, encoding) + + return __wrapped + # + + # inline all xi:include elements + ElementInclude.include(tree.getroot(), loader=my_loader(base_url)) + + # patch xml osabi element with the appropriate abi tag + osabi = tree.find('osabi') + + if osabi is not None: + # NOTE: the 'Windows' abi tag is supported starting from gdb 10. + # earlier gdb versions use 'Cygwin' instead + + abitag = { + QL_OS.LINUX : 'GNU/Linux', + QL_OS.FREEBSD : 'FreeBSD', + QL_OS.MACOS : 'Darwin', + QL_OS.WINDOWS : 'Windows', + QL_OS.UEFI : 'Windows', + QL_OS.DOS : 'Windows', + QL_OS.QNX : 'QNX-Neutrino' + }.get(ostype, 'unknown') + + osabi.text = abitag + + return tree + + @staticmethod + def __walk_xml_regs(xmltree: ElementTree.ElementTree) -> Iterator[Tuple[int, str, int]]: + regnum = -1 + + for reg in xmltree.iter('reg'): + # if regnum is not specified, assume it follows the previous one + regnum = int(reg.get('regnum', regnum + 1)) + + name = reg.attrib['name'] + bitsize = reg.attrib['bitsize'] + + yield regnum, name, int(bitsize) + + @staticmethod + def __load_regsmap(archtype: QL_ARCH, xmltree: ElementTree.ElementTree) -> Sequence[RegEntry]: + """Initialize registers map using available target XML files. + + Args: + archtype: target architecture type + + Returns: a list representing registers data + """ + + # retreive the relevant set of registers; their order of appearance is not + # important as it is determined by the info read from the xml files + ucregs: Mapping[str, int] = { + QL_ARCH.A8086 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st), + QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm), + QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm), + QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp), + QL_ARCH.CORTEX_M : arm_regs, + QL_ARCH.ARM64 : dict(**arm64_regs, **arm64_regs_v), + QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu) + }[archtype] + + regsinfo = sorted(QlGdbFeatures.__walk_xml_regs(xmltree)) + + # pre-allocate regmap and occupy it with null entries + last_regnum = regsinfo[-1][0] + regmap: Sequence[RegEntry] = [(None, 0, 0)] * (last_regnum + 1) + + pos = 0 + + for regnum, name, bitsize in sorted(regsinfo): + # reg value size in nibbles + nibbles = bitsize // 4 + + regmap[regnum] = (ucregs.get(name), pos, nibbles) + + # value position of next reg + pos += nibbles + + return regmap + + +__all__ = ['RegEntry', 'QlGdbFeatures'] diff --git a/qiling/debugger/qdb/arch/__init__.py b/qiling/debugger/qdb/arch/__init__.py new file mode 100644 index 000000000..24794a4cb --- /dev/null +++ b/qiling/debugger/qdb/arch/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from .arch_x86 import ArchX86 +from .arch_mips import ArchMIPS +from .arch_arm import ArchARM, ArchCORTEX_M diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py new file mode 100644 index 000000000..f84bdcd74 --- /dev/null +++ b/qiling/debugger/qdb/arch/arch.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + +from qiling.const import QL_ARCH + +class Arch: + """ + base class for arch + """ + + def __init__(self): + pass + + @property + def arch_insn_size(self): + return 4 + + @property + def archbit(self): + return 4 + + def read_insn(self, address: int): + return self.read_mem(address, self.arch_insn_size) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py new file mode 100644 index 000000000..1e24eef19 --- /dev/null +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from typing import Mapping + +from .arch import Arch + +class ArchARM(Arch): + def __init__(self): + super().__init__() + + @property + def regs(self): + return ( + "r0", "r1", "r2", "r3", + "r4", "r5", "r6", "r7", + "r8", "r9", "r10", "r11", + "r12", "sp", "lr", "pc", + ) + + @property + def regs_need_swapped(self): + return { + "sl": "r10", + "ip": "r12", + "fp": "r11", + } + + @staticmethod + def get_flags(bits: int) -> Mapping[str, bool]: + """ + get flags for ARM + """ + + def get_mode(bits: int) -> int: + """ + get operating mode for ARM + """ + return { + 0b10000: "User", + 0b10001: "FIQ", + 0b10010: "IRQ", + 0b10011: "Supervisor", + 0b10110: "Monitor", + 0b10111: "Abort", + 0b11010: "Hypervisor", + 0b11011: "Undefined", + 0b11111: "System", + }.get(bits & 0x00001f) + + return { + "mode": get_mode(bits), + "thumb": bits & 0x00000020 != 0, + "fiq": bits & 0x00000040 != 0, + "irq": bits & 0x00000080 != 0, + "neg": bits & 0x80000000 != 0, + "zero": bits & 0x40000000 != 0, + "carry": bits & 0x20000000 != 0, + "overflow": bits & 0x10000000 != 0, + } + + @property + def thumb_mode(self) -> bool: + """ + helper function for checking thumb mode + """ + + return self.ql.arch.is_thumb + + + def read_insn(self, address: int) -> bytes: + """ + read instruction depending on current operating mode + """ + + def thumb_read(address: int) -> bytes: + + first_two = self.ql.mem.read_ptr(address, 2) + result = self.ql.pack16(first_two) + + # to judge it's thumb mode or not + if any([ + first_two & 0xf000 == 0xf000, + first_two & 0xf800 == 0xf800, + first_two & 0xe800 == 0xe800, + ]): + + latter_two = self.ql.mem.read_ptr(address+2, 2) + result += self.ql.pack16(latter_two) + + return result + + return super().read_insn(address) if not self.thumb_mode else thumb_read(address) + + + +class ArchCORTEX_M(ArchARM): + def __init__(self): + super().__init__() + self.regs += ("xpsr", "control", "primask", "basepri", "faultmask") diff --git a/qiling/debugger/qdb/arch/arch_mips.py b/qiling/debugger/qdb/arch/arch_mips.py new file mode 100644 index 000000000..d262b0a90 --- /dev/null +++ b/qiling/debugger/qdb/arch/arch_mips.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from .arch import Arch + +class ArchMIPS(Arch): + def __init__(self): + super().__init__() + + @property + def regs(self): + return ( + "gp", "at", "v0", "v1", + "a0", "a1", "a2", "a3", + "t0", "t1", "t2", "t3", + "t4", "t5", "t6", "t7", + "t8", "t9", "sp", "s8", + "s0", "s1", "s2", "s3", + "s4", "s5", "s6", "s7", + "ra", "k0", "k1", "pc", + ) + + @property + def regs_need_swapped(self): + return { + "fp": "s8", + } diff --git a/qiling/debugger/qdb/arch/arch_x86.py b/qiling/debugger/qdb/arch/arch_x86.py new file mode 100644 index 000000000..10617cbd1 --- /dev/null +++ b/qiling/debugger/qdb/arch/arch_x86.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from typing import Mapping + +from .arch import Arch + +class ArchX86(Arch): + def __init__(self): + super().__init__() + + @property + def arch_insn_size(self): + return 15 + + @property + def regs(self): + return ( + "eax", "ebx", "ecx", "edx", + "esp", "ebp", "esi", "edi", + "eip", "ss", "cs", "ds", "es", + "fs", "gs", "eflags", + ) + + def read_insn(self, address: int) -> bytes: + # due to the variadic lengh of x86 instructions ( 1~15 ) + # always assume the maxium size for disassembler to tell + # what is it exactly. + + return self.read_mem(address, self.arch_insn_size) + + @staticmethod + def get_flags(bits: int) -> Mapping[str, bool]: + """ + get flags from ql.reg.eflags + """ + + return { + "CF" : bits & 0x0001 != 0, # CF, carry flag + "PF" : bits & 0x0004 != 0, # PF, parity flag + "AF" : bits & 0x0010 != 0, # AF, adjust flag + "ZF" : bits & 0x0040 != 0, # ZF, zero flag + "SF" : bits & 0x0080 != 0, # SF, sign flag + "OF" : bits & 0x0800 != 0, # OF, overflow flag + } diff --git a/qiling/debugger/qdb/branch_predictor/__init__.py b/qiling/debugger/qdb/branch_predictor/__init__.py new file mode 100644 index 000000000..67c0578fa --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from .branch_predictor_x86 import BranchPredictorX86 +from .branch_predictor_mips import BranchPredictorMIPS +from .branch_predictor_arm import BranchPredictorARM, BranchPredictorCORTEX_M diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py new file mode 100644 index 000000000..4b3e891be --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from ..context import Context +from ..misc import read_int + +class BranchPredictor(Context): + """ + Base class for predictor + """ + + class Prophecy: + """ + container for storing result of the predictor + @going: indicate the certian branch will be taken or not + @where: where will it go if going is true + """ + + def __init__(self): + self.going = False + self.where = None + + def __iter__(self): + return iter((self.going, self.where)) + + def __init__(self, ql): + super().__init__(ql) + + def read_reg(self, reg_name): + """ + read specific register value + """ + + return self.ql.arch.regs.read(reg_name) + + def predict(self): + """ + Try to predict certian branch will be taken or not based on current context + """ + + return NotImplementedError diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py new file mode 100644 index 000000000..97f00964c --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from .branch_predictor import * +from ..arch import ArchARM, ArchCORTEX_M + +class BranchPredictorARM(BranchPredictor, ArchARM): + """ + predictor for ARM + """ + + def __init__(self, ql): + super().__init__(ql) + ArchARM.__init__(self) + + self.INST_SIZE = 4 + self.THUMB_INST_SIZE = 2 + self.CODE_END = "udf" + + def read_reg(self, reg_name): + reg_name = reg_name.replace("ip", "r12").replace("fp", "r11") + return getattr(self.ql.arch.regs, reg_name) + + def regdst_eq_pc(self, op_str): + return op_str.partition(", ")[0] == "pc" + + @staticmethod + def get_cpsr(bits: int) -> (bool, bool, bool, bool): + """ + get flags from ql.reg.cpsr + """ + return ( + bits & 0x10000000 != 0, # V, overflow flag + bits & 0x20000000 != 0, # C, carry flag + bits & 0x40000000 != 0, # Z, zero flag + bits & 0x80000000 != 0, # N, sign flag + ) + + def predict(self): + prophecy = self.Prophecy() + cur_addr = self.cur_addr + line = self.disasm(cur_addr) + + prophecy.where = cur_addr + line.size + + if line.mnemonic == self.CODE_END: # indicates program exited + prophecy.where = True + return prophecy + + jump_table = { + # unconditional branch + "b" : (lambda *_: True), + "bl" : (lambda *_: True), + "bx" : (lambda *_: True), + "blx" : (lambda *_: True), + "b.w" : (lambda *_: True), + + # branch on equal, Z == 1 + "beq" : (lambda V, C, Z, N: Z == 1), + "bxeq" : (lambda V, C, Z, N: Z == 1), + "beq.w": (lambda V, C, Z, N: Z == 1), + + # branch on not equal, Z == 0 + "bne" : (lambda V, C, Z, N: Z == 0), + "bxne" : (lambda V, C, Z, N: Z == 0), + "bne.w": (lambda V, C, Z, N: Z == 0), + + # branch on signed greater than, Z == 0 and N == V + "bgt" : (lambda V, C, Z, N: (Z == 0 and N == V)), + "bgt.w": (lambda V, C, Z, N: (Z == 0 and N == V)), + + # branch on signed less than, N != V + "blt" : (lambda V, C, Z, N: N != V), + + # branch on signed greater than or equal, N == V + "bge" : (lambda V, C, Z, N: N == V), + + # branch on signed less than or queal + "ble" : (lambda V, C, Z, N: Z == 1 or N != V), + + # branch on unsigned higher or same (or carry set), C == 1 + "bhs" : (lambda V, C, Z, N: C == 1), + "bcs" : (lambda V, C, Z, N: C == 1), + + # branch on unsigned lower (or carry clear), C == 0 + "bcc" : (lambda V, C, Z, N: C == 0), + "blo" : (lambda V, C, Z, N: C == 0), + "bxlo" : (lambda V, C, Z, N: C == 0), + "blo.w": (lambda V, C, Z, N: C == 0), + + # branch on negative or minus, N == 1 + "bmi" : (lambda V, C, Z, N: N == 1), + + # branch on positive or plus, N == 0 + "bpl" : (lambda V, C, Z, N: N == 0), + + # branch on signed overflow + "bvs" : (lambda V, C, Z, N: V == 1), + + # branch on no signed overflow + "bvc" : (lambda V, C, Z, N: V == 0), + + # branch on unsigned higher + "bhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), + "bxhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), + "bhi.w": (lambda V, C, Z, N: (Z == 0 and C == 1)), + + # branch on unsigned lower + "bls" : (lambda V, C, Z, N: (C == 0 or Z == 1)), + "bls.w": (lambda V, C, Z, N: (C == 0 or Z == 1)), + } + + cb_table = { + # branch on equal to zero + "cbz" : (lambda r: r == 0), + + # branch on not equal to zero + "cbnz": (lambda r: r != 0), + } + + if line.mnemonic in jump_table: + prophecy.going = jump_table.get(line.mnemonic)(*self.get_cpsr(self.ql.arch.regs.cpsr)) + + elif line.mnemonic in cb_table: + prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0])) + + if prophecy.going: + if "#" in line.op_str: + prophecy.where = read_int(line.op_str.split("#")[-1]) + else: + prophecy.where = self.read_reg(line.op_str) + + if self.regdst_eq_pc(line.op_str): + next_addr = cur_addr + line.size + n2_addr = next_addr + len(self.read_insn(next_addr)) + prophecy.where += len(self.read_insn(n2_addr)) + len(self.read_insn(next_addr)) + + elif line.mnemonic.startswith("it"): + # handle IT block here + + cond_met = { + "eq": lambda V, C, Z, N: (Z == 1), + "ne": lambda V, C, Z, N: (Z == 0), + "ge": lambda V, C, Z, N: (N == V), + "hs": lambda V, C, Z, N: (C == 1), + "lo": lambda V, C, Z, N: (C == 0), + "mi": lambda V, C, Z, N: (N == 1), + "pl": lambda V, C, Z, N: (N == 0), + "ls": lambda V, C, Z, N: (C == 0 or Z == 1), + "le": lambda V, C, Z, N: (Z == 1 or N != V), + "hi": lambda V, C, Z, N: (Z == 0 and C == 1), + }.get(line.op_str)(*self.get_cpsr(self.ql.arch.regs.cpsr)) + + it_block_range = [each_char for each_char in line.mnemonic[1:]] + + next_addr = cur_addr + self.THUMB_INST_SIZE + for each in it_block_range: + _insn = self.read_insn(next_addr) + n2_addr = handle_bnj_arm(ql, next_addr) + + if (cond_met and each == "t") or (not cond_met and each == "e"): + if n2_addr != (next_addr+len(_insn)): # branch detected + break + + next_addr += len(_insn) + + prophecy.where = next_addr + + elif line.mnemonic in ("ldr",): + + if self.regdst_eq_pc(line.op_str): + _, _, rn_offset = line.op_str.partition(", ") + r, _, imm = rn_offset.strip("[]!").partition(", #") + + if "]" in rn_offset.split(", ")[1]: # pre-indexed immediate + prophecy.where = self.unpack32(self.read_mem(read_int(imm) + self.read_reg(r), self.INST_SIZE)) + + else: # post-indexed immediate + # FIXME: weired behavior, immediate here does not apply + prophecy.where = self.unpack32(self.read_mem(self.read_reg(r), self.INST_SIZE)) + + elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): + V, C, Z, N = self.get_cpsr(self.ql.arch.regs.cpsr) + r0, r1, r2, *imm = line.op_str.split(", ") + + # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes + extra = 8 if 'pc' in (r0, r1, r2) else 0 + + if imm: + expr = imm[0].split() + # TODO: should support more bit shifting and rotating operation + if expr[0] == "lsl": # logical shift left + n = read_int(expr[-1].strip("#")) * 2 + + if line.mnemonic == "addls" and (C == 0 or Z == 1): + prophecy.where = extra + self.read_reg(r1) + self.read_reg(r2) * n + + elif line.mnemonic == "add" or (line.mnemonic == "addne" and Z == 0): + prophecy.where = extra + self.read_reg(r1) + (self.read_reg(r2) * n if imm else self.read_reg(r2)) + + elif line.mnemonic in ("tbh", "tbb"): + + cur_addr += self.INST_SIZE + r0, r1, *imm = line.op_str.strip("[]").split(", ") + + if imm: + expr = imm[0].split() + if expr[0] == "lsl": # logical shift left + n = read_int(expr[-1].strip("#")) * 2 + + if line.mnemonic == "tbh": + + r1 = self.read_reg(r1) * n + + elif line.mnemonic == "tbb": + + r1 = self.read_reg(r1) + + to_add = int.from_bytes(self.read_mem(cur_addr+r1, 2 if line.mnemonic == "tbh" else 1), byteorder="little") * n + prophecy.where = cur_addr + to_add + + elif line.mnemonic.startswith("pop") and "pc" in line.op_str: + + prophecy.where = self.ql.stack_read(line.op_str.strip("{}").split(", ").index("pc") * self.INST_SIZE) + if not { # step to next instruction if cond does not meet + "pop" : lambda *_: True, + "pop.w": lambda *_: True, + "popeq": lambda V, C, Z, N: (Z == 1), + "popne": lambda V, C, Z, N: (Z == 0), + "pophi": lambda V, C, Z, N: (C == 1), + "popge": lambda V, C, Z, N: (N == V), + "poplt": lambda V, C, Z, N: (N != V), + }.get(line.mnemonic)(*self.get_cpsr(self.ql.arch.regs.cpsr)): + + prophecy.where = cur_addr + self.INST_SIZE + + elif line.mnemonic == "sub" and self.regdst_eq_pc(line.op_str): + _, r, imm = line.op_str.split(", ") + prophecy.where = self.read_reg(r) - read_int(imm.strip("#")) + + elif line.mnemonic == "mov" and self.regdst_eq_pc(line.op_str): + _, r = line.op_str.split(", ") + prophecy.where = self.read_reg(r) + + if prophecy.where & 1: + prophecy.where -= 1 + + return prophecy + +class BranchPredictorCORTEX_M(BranchPredictorARM): + def __init__(self, ql): + super().__init__(ql) diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py new file mode 100644 index 000000000..0dcc9467f --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from .branch_predictor import * +from ..arch import ArchMIPS + +class BranchPredictorMIPS(BranchPredictor, ArchMIPS): + """ + predictor for MIPS + """ + + def __init__(self, ql): + super().__init__(ql) + ArchMIPS.__init__(self) + self.CODE_END = "break" + self.INST_SIZE = 4 + + @staticmethod + def signed_val(val: int) -> int: + """ + signed value convertion + """ + + def is_negative(i: int) -> int: + """ + check wether negative value or not + """ + + return i & (1 << 31) + + return (val-1 << 32) if is_negative(val) else val + + def read_reg(self, reg_name): + reg_name = reg_name.strip("$").replace("fp", "s8") + return self.signed_val(getattr(self.ql.arch.regs, reg_name)) + + def predict(self): + prophecy = self.Prophecy() + line = self.disasm(self.cur_addr) + + if line.mnemonic == self.CODE_END: # indicates program extied + prophecy.where = True + return prophecy + + prophecy.where = self.cur_addr + self.INST_SIZE + if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): + + # make sure at least delay slot executed + prophecy.where += self.INST_SIZE + + # get registers or memory address from op_str + targets = [ + self.read_reg(each) + if '$' in each else read_int(each) + for each in line.op_str.split(", ") + ] + + prophecy.going = { + "j" : (lambda _: True), # unconditional jump + "jr" : (lambda _: True), # unconditional jump + "jal" : (lambda _: True), # unconditional jump + "jalr" : (lambda _: True), # unconditional jump + "b" : (lambda _: True), # unconditional branch + "bl" : (lambda _: True), # unconditional branch + "bal" : (lambda _: True), # unconditional branch + "beq" : (lambda r0, r1, _: r0 == r1), # branch on equal + "bne" : (lambda r0, r1, _: r0 != r1), # branch on not equal + "blt" : (lambda r0, r1, _: r0 < r1), # branch on r0 less than r1 + "bgt" : (lambda r0, r1, _: r0 > r1), # branch on r0 greater than r1 + "ble" : (lambda r0, r1, _: r0 <= r1), # brach on r0 less than or equal to r1 + "bge" : (lambda r0, r1, _: r0 >= r1), # branch on r0 greater than or equal to r1 + "beqz" : (lambda r, _: r == 0), # branch on equal to zero + "bnez" : (lambda r, _: r != 0), # branch on not equal to zero + "bgtz" : (lambda r, _: r > 0), # branch on greater than zero + "bltz" : (lambda r, _: r < 0), # branch on less than zero + "bltzal" : (lambda r, _: r < 0), # branch on less than zero and link + "blez" : (lambda r, _: r <= 0), # branch on less than or equal to zero + "bgez" : (lambda r, _: r >= 0), # branch on greater than or equal to zero + "bgezal" : (lambda r, _: r >= 0), # branch on greater than or equal to zero and link + }.get(line.mnemonic)(*targets) + + if prophecy.going: + # target address is always the rightmost one + prophecy.where = targets[-1] + + return prophecy diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py new file mode 100644 index 000000000..656c64daa --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +import ast, re + +from .branch_predictor import * +from ..arch import ArchX86 +from ..misc import check_and_eval + +class BranchPredictorX86(BranchPredictor, ArchX86): + """ + predictor for X86 + """ + + class ParseError(Exception): + """ + indicate parser error + """ + pass + + def __init__(self, ql): + super().__init__(ql) + ArchX86.__init__(self) + + def predict(self): + prophecy = self.Prophecy() + line = self.disasm(self.cur_addr) + + jump_table = { + # conditional jump + + "jo" : (lambda C, P, A, Z, S, O: O == 1), + "jno" : (lambda C, P, A, Z, S, O: O == 0), + + "js" : (lambda C, P, A, Z, S, O: S == 1), + "jns" : (lambda C, P, A, Z, S, O: S == 0), + + "je" : (lambda C, P, A, Z, S, O: Z == 1), + "jz" : (lambda C, P, A, Z, S, O: Z == 1), + + "jne" : (lambda C, P, A, Z, S, O: Z == 0), + "jnz" : (lambda C, P, A, Z, S, O: Z == 0), + + "jb" : (lambda C, P, A, Z, S, O: C == 1), + "jc" : (lambda C, P, A, Z, S, O: C == 1), + "jnae" : (lambda C, P, A, Z, S, O: C == 1), + + "jnb" : (lambda C, P, A, Z, S, O: C == 0), + "jnc" : (lambda C, P, A, Z, S, O: C == 0), + "jae" : (lambda C, P, A, Z, S, O: C == 0), + + "jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + "jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + + "ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + "jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + + "jl" : (lambda C, P, A, Z, S, O: S != O), + "jnge" : (lambda C, P, A, Z, S, O: S != O), + + "jge" : (lambda C, P, A, Z, S, O: S == O), + "jnl" : (lambda C, P, A, Z, S, O: S == O), + + "jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + "jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + + "jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + "jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + + "jp" : (lambda C, P, A, Z, S, O: P == 1), + "jpe" : (lambda C, P, A, Z, S, O: P == 1), + + "jnp" : (lambda C, P, A, Z, S, O: P == 0), + "jpo" : (lambda C, P, A, Z, S, O: P == 0), + + # unconditional jump + + "call" : (lambda *_: True), + "jmp" : (lambda *_: True), + + } + + jump_reg_table = { + "jcxz" : (lambda cx: cx == 0), + "jecxz" : (lambda ecx: ecx == 0), + "jrcxz" : (lambda rcx: rcx == 0), + } + + if line.mnemonic in jump_table: + eflags = self.get_flags(self.ql.arch.regs.eflags).values() + prophecy.going = jump_table.get(line.mnemonic)(*eflags) + + elif line.mnemonic in jump_reg_table: + prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.arch.regs.ecx) + + if prophecy.going: + takeaway_list = ["ptr", "dword", "[", "]"] + + if len(line.op_str.split()) > 1: + new_line = line.op_str.replace(":", "+") + for each in takeaway_list: + new_line = new_line.replace(each, " ") + + new_line = " ".join(new_line.split()) + for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + + prophecy.where = check_and_eval(new_line) + + elif line.op_str in self.ql.arch.regs.register_mapping: + prophecy.where = self.ql.arch.regs.read(line.op_str) + + else: + prophecy.where = read_int(line.op_str) + else: + prophecy.where = self.cur_addr + line.size + + return prophecy diff --git a/qiling/debugger/qdb/const.py b/qiling/debugger/qdb/const.py index fd8b358ef..74c72d229 100644 --- a/qiling/debugger/qdb/const.py +++ b/qiling/debugger/qdb/const.py @@ -1,5 +1,9 @@ -# class for colorful prints +from enum import IntEnum + class color: + """ + class for colorful prints + """ CYAN = '\033[96m' PURPLE = '\033[95m' BLUE = '\033[94m' @@ -15,26 +19,6 @@ class color: END = '\033[0m' RESET = '\x1b[39m' - - -FORMAT_LETTER = { - "o", # octal - "x", # hex - "d", # decimal - "u", # unsigned decimal - "t", # binary - "f", # float - "a", # address - "i", # instruction - "c", # char - "s", # string - "z", # hex, zero padded on the left - } - - -SIZE_LETTER = { - "b": 1, # 1-byte, byte - "h": 2, # 2-byte, halfword - "w": 4, # 4-byte, word - "g": 8, # 8-byte, giant - } +class QDB_MSG(IntEnum): + ERROR = 10 + INFO = 20 diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py new file mode 100644 index 000000000..cd9fbc210 --- /dev/null +++ b/qiling/debugger/qdb/context.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from typing import Optional + +from unicorn import UC_ERR_READ_UNMAPPED +import unicorn + +from capstone import CsInsn + +from .misc import read_int + +class Context: + """ + base class for accessing context of running qiling instance + """ + + def __init__(self, ql): + self.ql = ql + self.pointersize = self.ql.arch.pointersize + self.unpack = ql.unpack + self.unpack16 = ql.unpack16 + self.unpack32 = ql.unpack32 + self.unpack64 = ql.unpack64 + + @property + def cur_addr(self): + """ + program counter of qiling instance + """ + + return self.ql.arch.regs.arch_pc + + def read_mem(self, address: int, size: int): + """ + read data from memory of qiling instance + """ + + return self.ql.mem.read(address, size) + + def disasm(self, address: int, detail: bool = False) -> Optional[CsInsn]: + """ + helper function for disassembling + """ + + md = self.ql.arch.disassembler + md.detail = detail + + return next(md.disasm(self.read_insn(address), address), None) + + def try_read(self, address: int, size: int) -> Optional[bytes]: + """ + try to read data from ql.mem + """ + + result = None + err_msg = "" + try: + result = self.read_mem(address, size) + + except unicorn.unicorn.UcError as err: + if err.errno == UC_ERR_READ_UNMAPPED: # Invalid memory read (UC_ERR_READ_UNMAPPED) + err_msg = f"Can not access memory at address 0x{address:08x}" + + except: + pass + + return (result, err_msg) + + def try_read_pointer(self, address: int) -> Optional[bytes]: + """ + try to read pointer size of data from ql.mem + """ + + return self.try_read(address, self.archbit) + + def read_string(self, address: int) -> Optional[str]: + """ + read string from memory of qiling instance + """ + + return self.ql.mem.string(address) + + def try_read_string(self, address: int) -> Optional[str]: + """ + try to read string from memory of qiling instance + """ + + s = None + try: + s = self.read_string(address) + except: + pass + + @staticmethod + def read_int(s: str) -> int: + return read_int(s) + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index cf4a1d087..ed243446b 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -54,10 +54,10 @@ def extract_count(t): 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 extract_count(t): 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 unpack(bs, sz): 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 unpack(bs, sz): 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 @@ -170,10 +165,9 @@ def setup_ctx_manager(ql: Qiling) -> CtxManager: return { QL_ARCH.X86: CtxManager_X86, QL_ARCH.ARM: CtxManager_ARM, - 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,13 +178,13 @@ def __init__(self, ql): 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}" cursor = " " - if self.ql.reg.arch_pc == insn.address: + if self.ql.arch.regs.arch_pc == insn.address: cursor = "►" jump_sign = " " @@ -200,7 +194,7 @@ def print_asm(self, insn: CsInsn, to_jump: Optional[bool] = None, address: int = print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") def dump_regs(self): - return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} + return {reg_name: getattr(self.ql.arch.regs, reg_name) for reg_name in self.regs} def context_reg(self, saved_states): return NotImplementedError @@ -209,12 +203,12 @@ def context_reg(self, saved_states): def context_stack(self): for idx in range(10): - addr = self.ql.reg.arch_sp + idx * self.ql.pointersize - if (val := _try_read(self.ql, addr, self.ql.pointersize)[0]): - print(f"$sp+0x{idx*self.ql.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.ql.unpack(val):08x}", end="") + addr = self.ql.arch.regs.arch_sp + idx * self.ql.arch.pointersize + if (val := _try_read(self.ql, addr, self.ql.arch.pointersize)[0]): + print(f"$sp+0x{idx*self.ql.arch.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.ql.unpack(val):08x}", end="") # try to dereference wether it's a pointer - if (buf := _try_read(self.ql, addr, self.ql.pointersize))[0] is not None: + if (buf := _try_read(self.ql, addr, self.ql.arch.pointersize))[0] is not None: if (addr := self.ql.unpack(buf[0])): @@ -231,11 +225,11 @@ def context_stack(self): 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 = [] - cur_addr = self.ql.reg.arch_pc + cur_addr = self.ql.arch.regs.arch_pc line = disasm(self.ql, cur_addr-0x10) @@ -341,7 +335,7 @@ def context_reg(self, saved_reg_dump): lines += line print(lines.format(*cur_regs.values())) - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=self.get_flags(self.ql.reg.cpsr)), color.END, sep="") + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=self.get_flags(self.ql.arch.regs.cpsr)), color.END, sep="") class CtxManager_MIPS(CtxManager): @@ -395,7 +389,7 @@ def __init__(self, ql): "eax", "ebx", "ecx", "edx", "esp", "ebp", "esi", "edi", "eip", "ss", "cs", "ds", "es", - "fs", "gs", "ef", + "fs", "gs", "eflags", ) @context_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): @@ -422,12 +416,12 @@ def context_reg(self, saved_reg_dump): lines += line 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="") + 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.arch.regs.eflags)), color.END, sep="") - @context_printer("[ DISASM ]") + @context_printer("[ DISASM ]", footer=True) def context_asm(self): past_list = [] - cur_addr = self.ql.reg.arch_pc + cur_addr = self.ql.arch.regs.arch_pc cur_insn = disasm(self.ql, cur_addr) prophecy = self.predictor.predict() @@ -499,9 +493,8 @@ def context_reg(self, saved_reg_dump): lines += line print(lines.format(cur_regs.values())) - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(self.ql.reg.cpsr)), color.END, sep="") + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(self.ql.arch.regs.cpsr)), color.END, sep="") if __name__ == "__main__": pass - diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py new file mode 100644 index 000000000..9090dafe5 --- /dev/null +++ b/qiling/debugger/qdb/memory.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from qiling.const import QL_ARCH + +from .context import Context +from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86 +from .misc import check_and_eval +import re, math + + + +def setup_memory_Manager(ql): + + arch_type = { + QL_ARCH.X86: ArchX86, + QL_ARCH.MIPS: ArchMIPS, + QL_ARCH.ARM: ArchARM, + QL_ARCH.CORTEX_M: ArchCORTEX_M, + }.get(ql.arch.type) + + ret = type( + "MemoryManager", + (MemoryManager, arch_type), + {} + ) + + return ret(ql) + + +class MemoryManager(Context): + """ + memory manager for handing memory access + """ + + def __init__(self, ql): + super().__init__(ql) + + @property + def get_default_fmt(self): + return ('x', 4, 1) + + @property + def get_format_letter(self): + return { + "o", # octal + "x", # hex + "d", # decimal + "u", # unsigned decimal + "t", # binary + "f", # float + "a", # address + "i", # instruction + "c", # char + "s", # string + "z", # hex, zero padded on the left + } + + @property + def get_size_letter(self): + return { + "b": 1, # 1-byte, byte + "h": 2, # 2-byte, halfword + "w": 4, # 4-byte, word + "g": 8, # 8-byte, giant + } + + def extract_count(self, t): + return "".join([s for s in t if s.isdigit()]) + + def get_fmt(self, text): + f, s, c = self.get_default_fmt + if self.extract_count(text): + c = int(self.extract_count(text)) + + for char in text.strip(str(c)): + if char in self.get_size_letter.keys(): + s = self.get_size_letter.get(char) + + elif char in self.get_format_letter: + f = char + + return (f, s, c) + + def fmt_unpack(self, bs: bytes, sz: int) -> int: + return { + 1: lambda x: x[0], + 2: self.unpack16, + 4: self.unpack32, + 8: self.unpack64, + }.get(sz)(bs) + + def handle_i(self, addr, ct=1): + result = [] + + for offset in range(addr, addr+ct*4, 4): + if (line := self.disasm(offset)): + result.append(line) + + return result + + + def parse(self, line: str): + + # test case + # x/wx address + # x/i address + # x $sp + # x $sp +0xc + # x $sp+0xc + # x $sp + 0xc + + if line.startswith("/"): # followed by format letter and size letter + + fmt, *rest = line.strip("/").split() + + fmt = self.get_fmt(fmt) + + else: + args = line.split() + + rest = [args[0]] if len(args) == 1 else args + + fmt = self.get_default_fmt + + if len(rest) == 0: + return + + line = [] + if (regs_dict := getattr(self, "regs_need_swapped", None)): + for each in rest: + for reg in regs_dict: + if each in regs_dict: + line.append(regs_dict[each]) + else: + line.append(each) + else: + line = rest + + # for simple calculation with register and address + + line = " ".join(line) + # substitue register name with real value + for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping): + reg = f"${each_reg}" + if reg in line: + line = re.sub(f"\\{reg}", hex(self.ql.arch.regs.read(each_reg)), line) + + for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping): + reg = f"${each_reg}" + if reg in line: + line = re.sub(f"\\{reg}", hex(self.ql.arch.regs.read(each_reg)), line) + + + ft, sz, ct = fmt + + try: + addr = check_and_eval(line) + except: + return "something went wrong ..." + + if ft == "i": + output = self.handle_i(addr, ct) + for each in output: + print(f"0x{each.address:x}: {each.mnemonic}\t{each.op_str}") + + else: + lines = 1 if ct <= 4 else math.ceil(ct / 4) + + mem_read = [] + for offset in range(ct): + # append data if read successfully, otherwise return error message + if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: + mem_read.append(data[0]) + + else: + return data[1] + + for line in range(lines): + offset = line * sz * 4 + print(f"0x{addr+offset:x}:\t", end="") + + idx = line * self.ql.arch.pointersize + for each in mem_read[idx:idx+self.ql.arch.pointersize]: + data = self.fmt_unpack(each, sz) + prefix = "0x" if ft in ("x", "a") else "" + pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' + ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") + print(f"{prefix}{data:{pad}{ft}}\t", end="") + + print() + + return True diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py new file mode 100644 index 000000000..bfff5aa8e --- /dev/null +++ b/qiling/debugger/qdb/misc.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from typing import Callable, Optional + +import ast + +def check_and_eval(line: str): + """ + This function will valid all type of nodes and evaluate it if nothing went wrong + """ + + class AST_checker(ast.NodeVisitor): + def generic_visit(self, node): + if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): + ast.NodeVisitor.generic_visit(self, node) + else: + raise ParseError("malform or invalid ast node") + + checker = AST_checker() + ast_tree = ast.parse(line) + checker.visit(ast_tree) + + return eval(line) + + +class Breakpoint: + """ + dummy class for breakpoint + """ + def __init__(self, addr: int): + self.addr = addr + self.hitted = False + + +class TempBreakpoint(Breakpoint): + """ + dummy class for temporay breakpoint + """ + def __init__(self, addr: int): + super().__init__(addr) + + +def read_int(s: str) -> int: + """ + parse unsigned integer from string + """ + return int(s, 0) + + +def parse_int(func: Callable) -> Callable: + """ + function dectorator for parsing argument as integer + """ + def wrap(qdb, s: str = "") -> int: + assert type(s) is str + try: + ret = read_int(s) + except: + ret = None + return func(qdb, ret) + return wrap + + + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index d6366254a..d8810ff29 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations from typing import Callable, Optional, Mapping, Tuple, Union import cmd @@ -12,33 +11,43 @@ 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 Breakpoint, TempBreakpoint, read_inst +from .utils import setup_context_render, setup_branch_predictor, SnapshotManager, run_qdb_script +from .memory import setup_memory_Manager +from .misc import parse_int, Breakpoint, TempBreakpoint from .const import color +from .utils import QDB_MSG, qdb_print class QlQdb(cmd.Cmd, QlDebugger): + """ + The built-in debugger of Qiling Framework + """ - def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: + def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False, script: str = "") -> None: + """ + @init_hook: the entry to be paused at + @rr: record/replay debugging + """ self.ql = ql self.prompt = f"{color.BOLD}{color.RED}Qdb> {color.END}" self._saved_reg_dump = None + self._script = script self.bp_list = {} - self.rr = rr - - if self.rr: - self._states_list = [] - self.ctx = setup_ctx_manager(ql) + self.rr = SnapshotManager(ql) if rr else None + self.mm = setup_memory_Manager(ql) self.predictor = setup_branch_predictor(ql) + self.render = setup_context_render(ql, self.predictor) super().__init__() self.dbg_hook(init_hook) - def dbg_hook(self: QlQdb, init_hook: str): + def dbg_hook(self, init_hook: str): + """ + initial hook to prepare everything we need + """ # self.ql.loader.entry_point # ld.so # self.ql.loader.elf_entry # .text of binary @@ -55,7 +64,7 @@ def bp_handler(ql, address, size, bp_list): if bp.hitted: return - print(f"{color.CYAN}[+] hit breakpoint at 0x{self.cur_addr:08x}{color.END}") + qdb_print(QDB_MSG.INFO, f"hit breakpoint at 0x{self.cur_addr:08x}") bp.hitted = True ql.stop() @@ -68,46 +77,35 @@ def bp_handler(ql, address, size, bp_list): 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: - self._init_state = self.ql.save() + self.init_state = self.ql.save() - self.do_context() - self.interactive() + if self._script: + run_qdb_script(self, self._script) + else: + self.do_context() + self.interactive() @property - def cur_addr(self: QlQdb) -> int: + def cur_addr(self) -> int: """ 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: + def cur_addr(self, address: int) -> None: """ setter for current address of qiling instance """ - self.ql.reg.arch_pc = address - - def _save(self: QlQdb, *args) -> None: - """ - internal function for saving state of qiling instance - """ - - self._states_list.append(self.ql.save()) + self.ql.arch.regs.arch_pc = address - def _restore(self: QlQdb, *args) -> None: - """ - internal function for restoring state of qiling instance - """ - - self.ql.restore(self._states_list.pop()) - - def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: + def _run(self, address: int = 0, end: int = 0, count: int = 0) -> None: """ internal function for emulating instruction """ @@ -115,7 +113,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: 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: @@ -123,7 +121,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: if isinstance(bp, TempBreakpoint): self.del_breakpoint(bp) else: - print(f"{color.CYAN}[+] hit breakpoint at 0x{self.cur_addr:08x}{color.END}") + qdb_print(QDB_MSG.INFO, f"hit breakpoint at 0x{self.cur_addr:08x}") break @@ -132,12 +130,35 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: 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) - def parseline(self: QlQdb, line: str) -> Tuple[Optional[str], Optional[str], str]: + def save_reg_dump(func) -> None: + """ + decorator function for saving register dump + """ + + def inner(self, *args, **kwargs): + self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.arch.regs.save().items())) + func(self, *args, **kwargs) + + return inner + + def check_ql_alive(func) -> None: + """ + decorator function for checking ql instance is alive + """ + + def inner(self, *args, **kwargs): + if self.ql is None: + qdb_print(QDB_MSG.ERROR, "The program is not being run.") + else: + func(self, *args, **kwargs) + return inner + + def parseline(self, line: str) -> Tuple[Optional[str], Optional[str], str]: """ Parse the line into a command name and a string containing the arguments. Returns a tuple containing (command, args, line). @@ -159,21 +180,21 @@ def parseline(self: QlQdb, line: str) -> Tuple[Optional[str], Optional[str], str cmd, arg = line[:i], line[i:].strip() return cmd, arg, line - def interactive(self: QlQdb, *args) -> None: + def interactive(self, *args) -> None: """ initial an interactive interface """ return self.cmdloop() - def run(self: QlQdb, *args) -> None: + def run(self, *args) -> None: """ internal command for running debugger """ self._run() - def emptyline(self: QlQdb, *args) -> None: + def emptyline(self, *args) -> None: """ repeat last command """ @@ -181,90 +202,83 @@ def emptyline(self: QlQdb, *args) -> None: if (lastcmd := getattr(self, "do_" + self.lastcmd, None)): return lastcmd() - def do_run(self: QlQdb, *args) -> None: + def do_run(self, *args) -> None: """ launch qiling instance """ self._run() - def do_context(self: QlQdb, *args) -> None: + @SnapshotManager.snapshot + @save_reg_dump + @check_ql_alive + def do_step_in(self, *args) -> Optional[bool]: """ - show context information for current location + execute one instruction at a time, will enter subroutine """ - self.ctx.context_reg(self._saved_reg_dump) - self.ctx.context_stack() - self.ctx.context_asm() - - def do_backward(self: QlQdb, *args) -> None: - """ - step barkward if it's possible, option rr should be enabled and previous instruction must be executed before - """ + prophecy = self.predictor.predict() - if getattr(self, "_states_list", None) is None or len(self._states_list) == 0: - print(f"{color.RED}[!] there is no way back !!!{color.END}") + if prophecy.where is True: + return True + if self.ql.arch == QL_ARCH.CORTEX_M: + self.ql.arch.step() else: - print(f"{color.CYAN}[+] step backward ~{color.END}") - self._restore() - self.do_context() + self._run(count=1) - def update_reg_dump(self: QlQdb) -> None: - """ - internal function for updating registers dump - """ - self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) + self.do_context() - def do_step_in(self: QlQdb, *args) -> Optional[bool]: + @SnapshotManager.snapshot + @save_reg_dump + @check_ql_alive + def do_step_over(self, *args) -> Optional[bool]: """ - execute one instruction at a time, will enter subroutine + execute one instruction at a time, but WON't enter subroutine """ - if self.ql is None: - print(f"{color.RED}[!] The program is not being run.{color.END}") + prophecy = self.predictor.predict() + + if prophecy.going: + cur_insn = self.predictor.disasm(self.cur_addr) + self.set_breakpoint(self.cur_addr + cur_insn.size, is_temp=True) else: - self.update_reg_dump() + self.set_breakpoint(prophecy.where, is_temp=True) - if self.rr: - self._save() + self._run() - prophecy = self.predictor.predict() + @SnapshotManager.snapshot + @parse_int + def do_continue(self, address: Optional[int] = None) -> None: + """ + continue execution from current address if not specified + """ - if prophecy.where is True: - return True + if address is None: + address = self.cur_addr - if self.ql.archtype == QL_ARCH.CORTEX_M: - self.ql.arch.step() - else: - self._run(count=1) + qdb_print(QDB_MSG.INFO, f"continued from 0x{address:08x}") - self.do_context() + self._run(address) - def do_step_over(self: QlQdb, *args) -> Option[bool]: + def do_backward(self, *args) -> None: """ - execute one instruction at a time, but WON't enter subroutine + step barkward if it's possible, option rr should be enabled and previous instruction must be executed before """ - if self.ql is None: - print(f"{color.RED}[!] The program is not being run.{color.END}") - - else: - - prophecy = self.predictor.predict() - self.update_reg_dump() - - if prophecy.going: - cur_insn = disasm(self.ql, self.cur_addr) - self.set_breakpoint(self.cur_addr + cur_insn.size, is_temp=True) + if self.rr: + if len(self.rr.layers) == 0 or not isinstance(self.rr.layers[-1], self.rr.DiffedState): + qdb_print(QDB_MSG.ERROR, "there is no way back !!!") else: - self.set_breakpoint(prophecy.where, is_temp=True) - - self._run() + qdb_print(QDB_MSG.INFO, "step backward ~") + self.rr.restore() + self.do_context() + else: + qdb_print(QDB_MSG.ERROR, f"the option rr yet been set !!!") - def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: + def set_breakpoint(self, address: int, is_temp: bool = False) -> None: """ internal function for placing breakpoint """ @@ -273,25 +287,15 @@ def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: self.bp_list.update({address: bp}) - def del_breakpoint(self: QlQdb, bp: Union[Breakpoint, TempBreakpoint]) -> None: + def del_breakpoint(self, bp: Union[Breakpoint, TempBreakpoint]) -> None: """ internal function for removing breakpoint """ self.bp_list.pop(bp.addr, None) - def do_start(self: QlQdb, *args) -> None: - """ - restore qiling instance context to initial state - """ - - if self.ql.archtype != QL_ARCH.CORTEX_M: - - self.ql.restore(self._init_state) - self.do_context() - @parse_int - def do_breakpoint(self: QlQdb, address: Optional[int] = 0) -> None: + def do_breakpoint(self, address: Optional[int] = None) -> None: """ set breakpoint on specific address """ @@ -301,22 +305,20 @@ def do_breakpoint(self: QlQdb, address: Optional[int] = 0) -> None: self.set_breakpoint(address) - print(f"{color.CYAN}[+] Breakpoint at 0x{address:08x}{color.END}") + qdb_print(QDB_MSG.INFO, f"Breakpoint at 0x{address:08x}") @parse_int - def do_continue(self: QlQdb, address: Optional[int] = 0) -> None: + def do_disassemble(self, address: Optional[int] = None) -> None: """ - continue execution from current address if not specified + disassemble instructions from address specified """ - if address is None: - address = self.cur_addr - - print(f"{color.CYAN}continued from 0x{address:08x}{color.END}") - - self._run(address) + try: + context_asm(self.ql, address) + except: + qdb_print(QDB_MSG.ERROR) - def do_examine(self: QlQdb, line: str) -> None: + def do_examine(self, line: str) -> None: """ Examine memory: x/FMT ADDRESS. format letter: o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left) @@ -324,32 +326,52 @@ def do_examine(self: QlQdb, line: str) -> None: e.g. x/4wx 0x41414141 , print 4 word size begin from address 0x41414141 in hex """ - try: - if type(err_msg := examine_mem(self.ql, line)) is str: - print(f"{color.RED}[!] {err_msg} ...{color.END}") - except: - print(f"{color.RED}[!] something went wrong ...{color.END}") + if type(err_msg := self.mm.parse(line)) is str: + qdb_print(QDB_MSG.ERROR, err_msg) - def do_show(self: QlQdb, *args) -> None: + def do_start(self, *args) -> None: + """ + restore qiling instance context to initial state + """ + + if self.ql.arch != QL_ARCH.CORTEX_M: + + self.ql.restore(self.init_state) + self.do_context() + + def do_context(self, *args) -> None: + """ + display context information for current location + """ + + self.render.context_reg(self._saved_reg_dump) + self.render.context_stack() + self.render.context_asm() + + def do_show(self, *args) -> None: """ show some runtime information """ - self.ql.mem.show_mapinfo() - print(f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") + for info_line in self.ql.mem.get_formatted_mapinfo(): + self.ql.log.info(info_line) - @parse_int - def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: + qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") + if self.rr: + qdb_print(QDB_MSG.INFO, f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") + + def do_script(self, filename: str) -> None: """ - disassemble instructions from address specified + usage: script [filename] + load a script for automate qdb funcitonality, execute qdb command line by line basically """ - try: - context_asm(self.ql, address) - except: - print(f"{color.RED}[!] something went wrong ...{color.END}") + if filename: + run_qdb_script(self, filename) + else: + qdb_print(QDB_MSG.ERROR, "parameter filename must be specified") - def do_shell(self: QlQdb, *command) -> None: + def do_shell(self, *command) -> None: """ run python code """ @@ -357,20 +379,23 @@ def do_shell(self: QlQdb, *command) -> None: try: print(eval(*command)) except: - print("something went wrong ...") + qdb_print(QDB_MSG.ERROR, "something went wrong ...") - def do_quit(self: QlQdb, *args) -> bool: + def do_quit(self, *args) -> bool: """ exit Qdb and stop running qiling instance """ self.ql.stop() + if self._script: + return True exit() - def do_EOF(self: QlQdb, *args) -> None: + def do_EOF(self, *args) -> None: """ handle Ctrl+D """ + if input(f"{color.RED}[!] Are you sure about saying good bye ~ ? [Y/n]{color.END} ").strip() == "Y": self.do_quit() diff --git a/qiling/debugger/qdb/render/__init__.py b/qiling/debugger/qdb/render/__init__.py new file mode 100644 index 000000000..d36dfa9ae --- /dev/null +++ b/qiling/debugger/qdb/render/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from .render_x86 import ContextRenderX86 +from .render_mips import ContextRenderMIPS +from .render_arm import ContextRenderARM, ContextRenderCORTEX_M diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py new file mode 100644 index 000000000..b8d7eb08f --- /dev/null +++ b/qiling/debugger/qdb/render/render.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from capstone import CsInsn +from typing import Mapping +import os, copy + +from ..context import Context +from ..const import color + + + +""" + + Context Render for rendering UI + +""" + +COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) + +class Render: + """ + base class for rendering related functions + """ + + def divider_printer(field_name, ruler="─"): + """ + decorator function for printing divider and field name + """ + + def decorator(context_dumper): + def wrapper(*args, **kwargs): + try: + width, _ = os.get_terminal_size() + except OSError: + width = 130 + + bar = (width - len(field_name)) // 2 - 1 + print(ruler * bar, field_name, ruler * bar) + context_dumper(*args, **kwargs) + if "DISASM" in field_name: + print(ruler * width) + + return wrapper + return decorator + + def __init__(self): + self.regs_a_row = 4 + self.stack_num = 10 + self.color = color + + def reg_diff(self, cur_regs, saved_reg_dump): + """ + helper function for highlighting register changed during execution + """ + + if saved_reg_dump: + reg_dump = copy.deepcopy(saved_reg_dump) + if getattr(self, "regs_need_swapped", None): + reg_dump = self.swap_reg_name(reg_dump) + + return [k for k in cur_regs if cur_regs[k] != reg_dump[k]] + + def render_regs_dump(self, regs, diff_reg=None): + """ + helper function for redering registers dump + """ + + lines = "" + for idx, r in enumerate(regs, 1): + line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // self.regs_a_row], r, color.END) + + if diff_reg and r in diff_reg: + line = f"{color.UNDERLINE}{color.BOLD}{line}" + + if idx % self.regs_a_row == 0 and idx != 32: + line += "\n" + + lines += line + + print(lines.format(*regs.values())) + + def render_stack_dump(self, arch_sp: int) -> None: + """ + helper function for redering stack dump + """ + + for idx in range(self.stack_num): + addr = arch_sp + idx * self.pointersize + if (val := self.try_read_pointer(addr)[0]): + print(f"$sp+0x{idx*self.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="") + + # try to dereference wether it's a pointer + if (buf := self.try_read_pointer(addr))[0] is not None: + + if (addr := self.unpack(buf[0])): + + # try to dereference again + if (buf := self.try_read_pointer(addr))[0] is not None: + s = self.try_read_string(addr) + + if s and s.isprintable(): + print(f" ◂— {self.read_string(addr)}", end="") + else: + print(f" ◂— 0x{self.unpack(buf[0]):08x}", end="") + print() + + def render_assembly(self, lines) -> None: + """ + helper function for rendering assembly + """ + + # assembly before current location + if (backward := lines.get("backward", None)): + for line in backward: + self.print_asm(line) + + # assembly for current location + if (cur_insn := lines.get("current", None)): + prophecy = self.predictor.predict() + self.print_asm(cur_insn, to_jump=prophecy.going) + + # assembly after current location + if (forward := lines.get("forward", None)): + for line in forward: + self.print_asm(line) + + def swap_reg_name(self, cur_regs: Mapping[str, int], extra_dict=None) -> Mapping[str, int]: + """ + swap register name with more readable register name + """ + + target_items = extra_dict.items() if extra_dict else self.regs_need_swapped.items() + + for old_reg, new_reg in target_items: + cur_regs.update({old_reg: cur_regs.pop(new_reg)}) + + return cur_regs + + def print_asm(self, insn: CsInsn, to_jump: bool = False) -> None: + """ + helper function for printing assembly instructions, indicates where we are and the branch prediction + provided by BranchPredictor + """ + + opcode = "".join(f"{b:02x}" for b in insn.bytes) + trace_line = f"0x{insn.address:08x} │ {opcode:15s} {insn.mnemonic:10} {insn.op_str:35s}" + + cursor = "►" if self.cur_addr == insn.address else " " + + jump_sign = f"{color.RED}✓{color.END}" if to_jump else " " + + print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") + + +class ContextRender(Context, Render): + """ + base class for context render + """ + + def __init__(self, ql, predictor): + super().__init__(ql) + Render.__init__(self) + self.predictor = predictor + + def dump_regs(self) -> Mapping[str, int]: + """ + dump all registers + """ + + return {reg_name: self.ql.arch.regs.read(reg_name) for reg_name in self.regs} + + @Render.divider_printer("[ STACK ]") + def context_stack(self) -> None: + """ + display context stack dump + """ + + self.render_stack_dump(self.ql.arch.regs.arch_sp) + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_states: Mapping["str", int]) -> None: + """ + display context registers dump + """ + + return NotImplementedError + + @Render.divider_printer("[ DISASM ]") + def context_asm(self) -> None: + """ + read context assembly and render with render_assembly + """ + + lines = {} + past_list = [] + from_addr = self.cur_addr - 0x10 + to_addr = self.cur_addr + 0x10 + + cur_addr = from_addr + while cur_addr != to_addr: + insn = self.disasm(cur_addr) + cur_addr += self.arch_insn_size + if not insn: + continue + past_list.append(insn) + + bk_list = [] + fd_list = [] + cur_insn = None + for each in past_list: + if each.address < self.cur_addr: + bk_list.append(each) + + elif each.address > self.cur_addr: + fd_list.append(each) + + elif each.address == self.cur_addr: + cur_insn = each + + lines.update({ + "backward": bk_list, + "forward": fd_list, + "current": cur_insn, + }) + + self.render_assembly(lines) diff --git a/qiling/debugger/qdb/render/render_arm.py b/qiling/debugger/qdb/render/render_arm.py new file mode 100644 index 000000000..4e10ac27d --- /dev/null +++ b/qiling/debugger/qdb/render/render_arm.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from .render import * +from ..arch import ArchARM, ArchCORTEX_M + +class ContextRenderARM(ContextRender, ArchARM): + """ + context render for ARM + """ + + def __init__(self, ql, predictor): + super().__init__(ql, predictor) + ArchARM.__init__(self) + + @staticmethod + def print_mode_info(bits): + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=ArchARM.get_flags(bits)), color.END, sep="") + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + """ + redering context registers + """ + + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + self.print_mode_info(self.ql.arch.regs.cpsr) + + + +class ContextRenderCORTEX_M(ContextRenderARM, ArchCORTEX_M): + """ + context render for cortex_m + """ + + def __init__(self, ql, predictor): + super().__init__(ql, predictor) + ArchCORTEX_M.__init__(self) + self.regs_a_row = 3 + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + + # for re-order + extra_dict = { + "xpsr": "xpsr", + "control": "control", + "primask": "primask", + "faultmask": "faultmask", + "basepri": "basepri", + } + + cur_regs = self.swap_reg_name(cur_regs, extra_dict=extra_dict) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + self.print_mode_info(self.ql.arch.regs.cpsr) diff --git a/qiling/debugger/qdb/render/render_mips.py b/qiling/debugger/qdb/render/render_mips.py new file mode 100644 index 000000000..ff67891d8 --- /dev/null +++ b/qiling/debugger/qdb/render/render_mips.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from .render import * +from ..arch import ArchMIPS + +class ContextRenderMIPS(ContextRender, ArchMIPS): + """ + context render for MIPS + """ + + def __init__(self, ql, predictor): + super().__init__(ql, predictor) + ArchMIPS.__init__(self) + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + """ + redering context registers + """ + + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) diff --git a/qiling/debugger/qdb/render/render_x86.py b/qiling/debugger/qdb/render/render_x86.py new file mode 100644 index 000000000..5c43d0e55 --- /dev/null +++ b/qiling/debugger/qdb/render/render_x86.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from .render import * +from ..arch import ArchX86 + +class ContextRenderX86(ContextRender, ArchX86): + """ + context render for X86 + """ + + def __init__(self, ql, predictor): + super().__init__(ql, predictor) + ArchX86.__init__(self) + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + cur_regs = self.dump_regs() + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.arch.regs.eflags)), color.END, sep="") + + @Render.divider_printer("[ DISASM ]") + def context_asm(self): + lines = {} + past_list = [] + + cur_addr = self.cur_addr + while len(past_list) < 10: + line = self.disasm(cur_addr) + past_list.append(line) + cur_addr += line.size + + fd_list = [] + cur_insn = None + for each in past_list: + if each.address > self.cur_addr: + fd_list.append(each) + + elif each.address == self.cur_addr: + cur_insn = each + + """ + only forward and current instruction will be printed, + because we don't have a solid method to disasm backward instructions, + since it's x86 instruction length is variadic + """ + + lines.update({ + "current": cur_insn, + "forward": fd_list, + }) + + self.render_assembly(lines) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 9178a2621..fdbde9d85 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -4,559 +4,259 @@ # from __future__ import annotations -from typing import Callable, Optional, Mapping -from qiling.const import * +from typing import Callable, Optional, Mapping, Tuple -from collections import namedtuple -import ast, re +from capstone import CsInsn -# parse unsigned integer from string -def read_int(s: str) -> int: - return int(s, 0) +from qiling import Qiling +from qiling.const import QL_ARCH +from .context import Context +from .misc import read_int -# function dectorator for parsing argument as integer -def parse_int(func: Callable) -> Callable: - def wrap(qdb, s: str = "") -> int: - assert type(s) is str - try: - ret = read_int(s) - except: - ret = None - return func(qdb, ret) - return wrap +from .render import ( + ContextRenderX86, + ContextRenderARM, + ContextRenderCORTEX_M, + ContextRenderMIPS + ) +from .branch_predictor import ( + BranchPredictorX86, + BranchPredictorARM, + BranchPredictorCORTEX_M, + BranchPredictorMIPS, + ) -# check wether negative value or not -def is_negative(i: int) -> int: - return i & (1 << 31) +from .const import color, QDB_MSG + -# signed value convertion -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): - return ( - bits & 0x10000000 != 0, # V, overflow flag - bits & 0x20000000 != 0, # C, carry flag - bits & 0x40000000 != 0, # Z, zero flag - bits & 0x80000000 != 0, # N, sign flag - ) - - -def get_x86_eflags(bits: int) -> Dict[str, bool]: - return { - "CF" : bits & 0x0001 != 0, # CF, carry flag - "PF" : bits & 0x0004 != 0, # PF, parity flag - "AF" : bits & 0x0010 != 0, # AF, adjust flag - "ZF" : bits & 0x0040 != 0, # ZF, zero flag - "SF" : bits & 0x0080 != 0, # SF, sign flag - "OF" : bits & 0x0800 != 0, # OF, overflow flag - } - - -def is_thumb(bits: int) -> bool: - return bits & 0x00000020 != 0 - - -def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: +def qdb_print(msgtype: QDB_MSG, msg: str) -> None: """ - helper function for disassembling + color printing """ - md = ql.disassembler - md.detail = detail - try: - ret = next(md.disasm(read_inst(ql, address), address)) - - except StopIteration: - ret = None - - return ret - + def print_error(msg): + return f"{color.RED}[!] {msg}{color.END}" -def read_inst(ql: Qiling, addr: int) -> int: - result = ql.mem.read(addr, 4) + def print_info(msg): + return f"{color.CYAN}[+] {msg}{color.END}" - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): - if is_thumb(ql.reg.cpsr): + color_coated = { + QDB_MSG.ERROR: print_error, + QDB_MSG.INFO : print_info, + }.get(msgtype)(msg) - first_two = ql.unpack16(ql.mem.read(addr, 2)) - result = ql.pack16(first_two) + print(color_coated) - # to judge whether it's thumb mode or not - if any([ - first_two & 0xf000 == 0xf000, - first_two & 0xf800 == 0xf800, - first_two & 0xe800 == 0xe800, - ]): - - 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): - # due to the variadic lengh of x86 instructions ( 1~15 ) - # always assume the maxium size for disassembler to tell - # what is it exactly. - result = ql.mem.read(addr, 15) - - return result -""" - Try to predict certian branch will be taken or not based on current context """ -def setup_branch_predictor(ql: Qiling) -> BranchPredictor: - return { - QL_ARCH.X86: BranchPredictor_X86, - QL_ARCH.ARM: BranchPredictor_ARM, - QL_ARCH.ARM_THUMB: BranchPredictor_ARM, - QL_ARCH.CORTEX_M: BranchPredictor_CORTEX_M, - QL_ARCH.MIPS: BranchPredictor_MIPS, - }.get(ql.archtype)(ql) - -class Prophecy(object): - def __init__(self): - self.going = False - self.where = None - - def __iter__(self): - return iter((self.going, self.where)) - -class BranchPredictor(object): - def __init__(self, ql): - self.ql = ql - - def read_reg(self, reg_name): - return getattr(self.ql.reg, reg_name) - - def predict(self): - return NotImplementedError - -class BranchPredictor_ARM(BranchPredictor): - def __init__(self, ql): - super().__init__(ql) + helper functions for setting proper branch predictor and context render depending on different arch - self.INST_SIZE = 4 - self.THUMB_INST_SIZE = 2 - self.CODE_END = "udf" - - def read_reg(self, reg_name): - reg_name = reg_name.replace("ip", "r12").replace("fp", "r11") - return getattr(self.ql.reg, reg_name) - - def regdst_eq_pc(self, op_str): - return op_str.partition(", ")[0] == "pc" - - def predict(self): - prophecy = Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - prophecy.where = cur_addr + line.size - - if line.mnemonic == self.CODE_END: # indicates program exited - return True - - jump_table = { - # unconditional branch - "b" : (lambda *_: True), - "bl" : (lambda *_: True), - "bx" : (lambda *_: True), - "blx" : (lambda *_: True), - "b.w" : (lambda *_: True), - - # branch on equal, Z == 1 - "beq" : (lambda V, C, Z, N: Z == 1), - "bxeq" : (lambda V, C, Z, N: Z == 1), - "beq.w": (lambda V, C, Z, N: Z == 1), - - # branch on not equal, Z == 0 - "bne" : (lambda V, C, Z, N: Z == 0), - "bxne" : (lambda V, C, Z, N: Z == 0), - "bne.w": (lambda V, C, Z, N: Z == 0), - - # branch on signed greater than, Z == 0 and N == V - "bgt" : (lambda V, C, Z, N: (Z == 0 and N == V)), - "bgt.w": (lambda V, C, Z, N: (Z == 0 and N == V)), - - # branch on signed less than, N != V - "blt" : (lambda V, C, Z, N: N != V), - - # branch on signed greater than or equal, N == V - "bge" : (lambda V, C, Z, N: N == V), - - # branch on signed less than or queal - "ble" : (lambda V, C, Z, N: Z == 1 or N != V), - - # branch on unsigned higher or same (or carry set), C == 1 - "bhs" : (lambda V, C, Z, N: C == 1), - "bcs" : (lambda V, C, Z, N: C == 1), - - # branch on unsigned lower (or carry clear), C == 0 - "bcc" : (lambda V, C, Z, N: C == 0), - "blo" : (lambda V, C, Z, N: C == 0), - "bxlo" : (lambda V, C, Z, N: C == 0), - "blo.w": (lambda V, C, Z, N: C == 0), - - # branch on negative or minus, N == 1 - "bmi" : (lambda V, C, Z, N: N == 1), - - # branch on positive or plus, N == 0 - "bpl" : (lambda V, C, Z, N: N == 0), - - # branch on signed overflow - "bvs" : (lambda V, C, Z, N: V == 1), - - # branch on no signed overflow - "bvc" : (lambda V, C, Z, N: V == 0), - - # branch on unsigned higher - "bhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), - "bxhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), - "bhi.w": (lambda V, C, Z, N: (Z == 0 and C == 1)), - - # branch on unsigned lower - "bls" : (lambda V, C, Z, N: (C == 0 or Z == 1)), - "bls.w": (lambda V, C, Z, N: (C == 0 or Z == 1)), - } - - cb_table = { - # branch on equal to zero - "cbz" : (lambda r: r == 0), +""" - # branch on not equal to zero - "cbnz": (lambda r: r != 0), - } +def setup_branch_predictor(ql): + """ + setup BranchPredictor correspondingly + """ - if line.mnemonic in jump_table: - prophecy.going = jump_table.get(line.mnemonic)(*get_cpsr(self.ql.reg.cpsr)) + return { + QL_ARCH.X86: BranchPredictorX86, + QL_ARCH.ARM: BranchPredictorARM, + QL_ARCH.CORTEX_M: BranchPredictorCORTEX_M, + QL_ARCH.MIPS: BranchPredictorMIPS, + }.get(ql.arch.type)(ql) - elif line.mnemonic in cb_table: - prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0])) +def setup_context_render(ql, predictor): + """ + setup context render correspondingly + """ - if prophecy.going: - if "#" in line.op_str: - prophecy.where = read_int(line.op_str.split("#")[-1]) + return { + QL_ARCH.X86: ContextRenderX86, + QL_ARCH.ARM: ContextRenderARM, + QL_ARCH.CORTEX_M: ContextRenderCORTEX_M, + QL_ARCH.MIPS: ContextRenderMIPS, + }.get(ql.arch.type)(ql, predictor) + +def run_qdb_script(qdb, filename: str) -> None: + with open(filename) as fd: + for line in iter(fd.readline, ""): + + # skip commented and empty line + if line.startswith("#") or line == "\n": + continue + + cmd, arg, _ = qdb.parseline(line) + func = getattr(qdb, f"do_{cmd}") + if arg: + func(arg) else: - prophecy.where = self.read_reg(line.op_str) - - if self.regdst_eq_pc(line.op_str): - next_addr = cur_addr + line.size - n2_addr = next_addr + len(read_inst(next_addr)) - prophecy.where += len(read_inst(n2_addr)) + len(read_inst(next_addr)) - - elif line.mnemonic.startswith("it"): - # handle IT block here - - cond_met = { - "eq": lambda V, C, Z, N: (Z == 1), - "ne": lambda V, C, Z, N: (Z == 0), - "ge": lambda V, C, Z, N: (N == V), - "hs": lambda V, C, Z, N: (C == 1), - "lo": lambda V, C, Z, N: (C == 0), - "mi": lambda V, C, Z, N: (N == 1), - "pl": lambda V, C, Z, N: (N == 0), - "ls": lambda V, C, Z, N: (C == 0 or Z == 1), - "le": lambda V, C, Z, N: (Z == 1 or N != V), - "hi": lambda V, C, Z, N: (Z == 0 and C == 1), - }.get(line.op_str)(*get_cpsr(ql.reg.cpsr)) - - it_block_range = [each_char for each_char in line.mnemonic[1:]] - - next_addr = cur_addr + self.THUMB_INST_SIZE - for each in it_block_range: - _inst = read_inst(next_addr) - n2_addr = handle_bnj_arm(ql, next_addr) - - if (cond_met and each == "t") or (not cond_met and each == "e"): - if n2_addr != (next_addr+len(_inst)): # branch detected - break - - next_addr += len(_inst) - - prophecy.where = next_addr - - elif line.mnemonic in ("ldr",): - - if self.regdst_eq_pc(line.op_str): - _, _, rn_offset = line.op_str.partition(", ") - r, _, imm = rn_offset.strip("[]!").partition(", #") - - if "]" in rn_offset.split(", ")[1]: # pre-indexed immediate - prophecy.where = ql.unpack32(ql.mem.read(read_int(imm) + self.read_reg(r), self.INST_SIZE)) - - else: # post-indexed immediate - # FIXME: weired behavior, immediate here does not apply - prophecy.where = ql.unpack32(ql.mem.read(self.read_reg(r), self.INST_SIZE)) - - elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): - V, C, Z, N = get_cpsr(ql.reg.cpsr) - r0, r1, r2, *imm = line.op_str.split(", ") - - # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes - extra = 8 if 'pc' in (r0, r1, r2) else 0 - - if imm: - expr = imm[0].split() - # TODO: should support more bit shifting and rotating operation - if expr[0] == "lsl": # logical shift left - n = read_int(expr[-1].strip("#")) * 2 - - if line.mnemonic == "addls" and (C == 0 or Z == 1): - prophecy.where = extra + self.read_reg(r1) + self.read_reg(r2) * n - - elif line.mnemonic == "add" or (line.mnemonic == "addne" and Z == 0): - prophecy.where = extra + self.read_reg(r1) + (self.read_reg(r2) * n if imm else self.read_reg(r2)) + func() - elif line.mnemonic in ("tbh", "tbb"): - cur_addr += self.INST_SIZE - r0, r1, *imm = line.op_str.strip("[]").split(", ") - - if imm: - expr = imm[0].split() - if expr[0] == "lsl": # logical shift left - n = read_int(expr[-1].strip("#")) * 2 +""" - if line.mnemonic == "tbh": + For supporting Qdb features like: + 1. record/replay debugging + 2. memory access in gdb-style - r1 = self.read_reg(r1) * n +""" - elif line.mnemonic == "tbb": +class SnapshotManager: + """ + for functioning differential snapshot + """ - r1 = self.read_reg(r1) + class State: + """ + internal container for storing raw state from qiling + """ - to_add = int.from_bytes(ql.mem.read(cur_addr+r1, 2 if line.mnemonic == "tbh" else 1), byteorder="little") * n - prophecy.where = cur_addr + to_add + def __init__(self, saved_state): + self.reg, self.ram = SnapshotManager.transform(saved_state) - elif line.mnemonic.startswith("pop") and "pc" in line.op_str: + class DiffedState: + """ + internal container for storing diffed state + """ - prophecy.where = ql.stack_read(line.op_str.strip("{}").split(", ").index("pc") * self.INST_SIZE) - if not { # step to next instruction if cond does not meet - "pop" : lambda *_: True, - "pop.w": lambda *_: True, - "popeq": lambda V, C, Z, N: (Z == 1), - "popne": lambda V, C, Z, N: (Z == 0), - "pophi": lambda V, C, Z, N: (C == 1), - "popge": lambda V, C, Z, N: (N == V), - "poplt": lambda V, C, Z, N: (N != V), - }.get(line.mnemonic)(*get_cpsr(ql.reg.cpsr)): + def __init__(self, diffed_st): + self.reg, self.ram = diffed_st - prophecy.where = cur_addr + self.INST_SIZE + @staticmethod + def transform(st): + """ + transform saved context into binary set + """ - elif line.mnemonic == "sub" and self.regdst_eq_pc(line.op_str): - _, r, imm = line.op_str.split(", ") - prophecy.where = self.read_reg(r) - read_int(imm.strip("#")) + reg = st["reg"] if "reg" in st else st[0] - elif line.mnemonic == "mov" and self.regdst_eq_pc(line.op_str): - _, r = line.op_str.split(", ") - prophecy.where = self.read_reg(r) + if "mem" not in st: + return (reg, st[1]) - if prophecy.where & 1: - prophecy.where -= 1 + ram = [] + for mem_seg in st["mem"]["ram"]: + lbound, ubound, perms, label, raw_bytes = mem_seg + rb_set = {(idx, val) for idx, val in enumerate(raw_bytes)} + ram.append((lbound, ubound, perms, label, rb_set)) - return prophecy + return (reg, ram) -class BranchPredictor_MIPS(BranchPredictor): - def __init__(self, ql): - super().__init__(ql) - self.CODE_END = "break" - self.INST_SIZE = 4 - - def read_reg(self, reg_name): - reg_name = reg_name.strip("$").replace("fp", "s8") - return signed_val(getattr(self.ql.reg, reg_name)) - - def predict(self): - prophecy = Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - - if line.mnemonic == self.CODE_END: # indicates program extied - return True - - prophecy.where = cur_addr + self.INST_SIZE - if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): - - # make sure at least delay slot executed - prophecy.where += self.INST_SIZE - - # get registers or memory address from op_str - targets = [ - self.read_reg(each) - if '$' in each else read_int(each) - for each in line.op_str.split(", ") - ] - - prophecy.going = { - "j" : (lambda _: True), # unconditional jump - "jr" : (lambda _: True), # unconditional jump - "jal" : (lambda _: True), # unconditional jump - "jalr" : (lambda _: True), # unconditional jump - "b" : (lambda _: True), # unconditional branch - "bl" : (lambda _: True), # unconditional branch - "bal" : (lambda _: True), # unconditional branch - "beq" : (lambda r0, r1, _: r0 == r1), # branch on equal - "bne" : (lambda r0, r1, _: r0 != r1), # branch on not equal - "blt" : (lambda r0, r1, _: r0 < r1), # branch on r0 less than r1 - "bgt" : (lambda r0, r1, _: r0 > r1), # branch on r0 greater than r1 - "ble" : (lambda r0, r1, _: r0 <= r1), # brach on r0 less than or equal to r1 - "bge" : (lambda r0, r1, _: r0 >= r1), # branch on r0 greater than or equal to r1 - "beqz" : (lambda r, _: r == 0), # branch on equal to zero - "bnez" : (lambda r, _: r != 0), # branch on not equal to zero - "bgtz" : (lambda r, _: r > 0), # branch on greater than zero - "bltz" : (lambda r, _: r < 0), # branch on less than zero - "bltzal" : (lambda r, _: r < 0), # branch on less than zero and link - "blez" : (lambda r, _: r <= 0), # branch on less than or equal to zero - "bgez" : (lambda r, _: r >= 0), # branch on greater than or equal to zero - "bgezal" : (lambda r, _: r >= 0), # branch on greater than or equal to zero and link - }.get(line.mnemonic)(*targets) - - if prophecy.going: - # target address is always the rightmost one - prophecy.where = targets[-1] - - return prophecy - -class BranchPredictor_X86(BranchPredictor): def __init__(self, ql): - super().__init__(ql) - - def predict(self): - prophecy = Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - - jump_table = { - # conditional jump - - "jo" : (lambda C, P, A, Z, S, O: O == 1), - "jno" : (lambda C, P, A, Z, S, O: O == 0), - - "js" : (lambda C, P, A, Z, S, O: S == 1), - "jns" : (lambda C, P, A, Z, S, O: S == 0), - - "je" : (lambda C, P, A, Z, S, O: Z == 1), - "jz" : (lambda C, P, A, Z, S, O: Z == 1), + self.ql = ql + self.layers = [] - "jne" : (lambda C, P, A, Z, S, O: Z == 0), - "jnz" : (lambda C, P, A, Z, S, O: Z == 0), + def _save(self) -> State: + """ + acquire current State by wrapping saved context from ql.save() + """ - "jb" : (lambda C, P, A, Z, S, O: C == 1), - "jc" : (lambda C, P, A, Z, S, O: C == 1), - "jnae" : (lambda C, P, A, Z, S, O: C == 1), + return self.State(self.ql.save()) - "jnb" : (lambda C, P, A, Z, S, O: C == 0), - "jnc" : (lambda C, P, A, Z, S, O: C == 0), - "jae" : (lambda C, P, A, Z, S, O: C == 0), + def diff_reg(self, prev_reg, cur_reg): + """ + diff two register values + """ - "jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), - "jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + diffed = filter(lambda t: t[0] != t[1], zip(prev_reg.items(), cur_reg.items())) + return {prev[0]: prev[1] for prev, _ in diffed} - "ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), - "jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + def diff_ram(self, prev_ram, cur_ram): + """ + diff two ram data if needed + """ - "jl" : (lambda C, P, A, Z, S, O: S != O), - "jnge" : (lambda C, P, A, Z, S, O: S != O), + if any((cur_ram is None, prev_ram is None, prev_ram == cur_ram)): + return - "jge" : (lambda C, P, A, Z, S, O: S == O), - "jnl" : (lambda C, P, A, Z, S, O: S == O), + ram = [] + paired = zip(prev_ram, cur_ram) + for each in paired: + # lbound, ubound, perm, label, data + *prev_others, prev_rb_set = each[0] + *cur_others, cur_rb_set = each[1] - "jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), - "jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + if prev_others == cur_others and cur_rb_set != prev_rb_set: + diff_set = prev_rb_set - cur_rb_set + else: + continue - "jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), - "jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + ram.append((*cur_others, diff_set)) - "jp" : (lambda C, P, A, Z, S, O: P == 1), - "jpe" : (lambda C, P, A, Z, S, O: P == 1), + return ram - "jnp" : (lambda C, P, A, Z, S, O: P == 0), - "jpo" : (lambda C, P, A, Z, S, O: P == 0), + def diff(self, before_st, after_st): + """ + diff between previous and current state + """ - # unconditional jump + # prev_st = self.layers.pop() + diffed_reg = self.diff_reg(before_st.reg, after_st.reg) + diffed_ram = self.diff_ram(before_st.ram, after_st.ram) + # diffed_reg = self.diff_reg(prev_st.reg, cur_st.reg) + # diffed_ram = self.diff_ram(prev_st.ram, cur_st.ram) + return self.DiffedState((diffed_reg, diffed_ram)) - "call" : (lambda *_: True), - "jmp" : (lambda *_: True), + def snapshot(func): + """ + decorator function for saving differential context on certian qdb command + """ - } + def magic(self, *args, **kwargs): + if self.rr: + # save State before execution + p_st = self.rr._save() - jump_reg_table = { - "jcxz" : (lambda cx: cx == 0), - "jecxz" : (lambda ecx: ecx == 0), - "jrcxz" : (lambda rcx: rcx == 0), - } + # certian execution to be snapshot + func(self, *args, **kwargs) - if line.mnemonic in jump_table: - eflags = get_x86_eflags(self.ql.reg.ef).values() - prophecy.going = jump_table.get(line.mnemonic)(*eflags) + # save State after execution + q_st = self.rr._save() - elif line.mnemonic in jump_reg_table: - prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.reg.ecx) + # merge two saved States into a DiffedState + st = self.rr.diff(p_st, q_st) + self.rr.layers.append(st) + else: + func(self, *args, **kwargs) - if prophecy.going: - takeaway_list = ["ptr", "dword", "[", "]"] - class AST_checker(ast.NodeVisitor): - def generic_visit(self, node): - if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): - ast.NodeVisitor.generic_visit(self, node) - else: - raise ParseError("malform or invalid ast node") + return magic - if len(line.op_str.split()) > 1: - new_line = line.op_str.replace(":", "+") - for each in takeaway_list: - new_line = new_line.replace(each, " ") + def restore(self): + """ + helper function for restoring running state from an existing incremental snapshot + """ - new_line = " ".join(new_line.split()) - for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): - if each_reg in new_line: - new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) - - for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping.keys()): - if each_reg in new_line: - new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + prev_st = self.layers.pop() + cur_st = self._save() - checker = AST_checker() - ast_tree = ast.parse(new_line) + for reg_name, reg_value in prev_st.reg.items(): + cur_st.reg[reg_name] = reg_value - checker.visit(ast_tree) + to_be_restored = {"reg": cur_st.reg} - prophecy.where = eval(new_line) + if getattr(prev_st, "ram", None) and prev_st.ram != cur_st.ram: - elif line.op_str in self.ql.reg.register_mapping: - prophecy.where = getattr(self.ql.reg, line.op_str) + ram = [] + # lbound, ubound, perm, label, data + for each in prev_st.ram: + *prev_others, prev_rb_set = each + for *cur_others, cur_rb_set in cur_st.ram: + if prev_others == cur_others: + cur_rb_dict = dict(cur_rb_set) + for idx, val in prev_rb_set: + cur_rb_dict[idx] = val - else: - prophecy.where = read_int(line.op_str) - else: - prophecy.where = cur_addr + line.size + bs = bytes(dict(sorted(cur_rb_dict.items())).values()) + ram.append((*cur_others, bs)) - return prophecy + to_be_restored.update({"mem": {"ram": ram, "mmio": {}}}) -class BranchPredictor_CORTEX_M(BranchPredictor_ARM): - def __init__(self, ql): - super().__init__(ql) + self.ql.restore(to_be_restored) -class Breakpoint(object): - """ - dummy class for breakpoint - """ - def __init__(self, addr): - self.addr = addr - self.hitted = False -class TempBreakpoint(Breakpoint): - """ - dummy class for temporay breakpoint - """ - def __init__(self, addr): - super().__init__(addr) - -class ParseError(Exception): - pass if __name__ == "__main__": diff --git a/qiling/extensions/afl/afl.py b/qiling/extensions/afl/afl.py index 8693145f6..b2f0448d1 100644 --- a/qiling/extensions/afl/afl.py +++ b/qiling/extensions/afl/afl.py @@ -7,7 +7,7 @@ def ql_afl_fuzz(ql: Qiling, input_file: str, place_input_callback: Callable[["Qiling", bytes, int], bool], exits: List[int], - validate_crash_callback: Callable[["Qiling", bytes, int], bool] = None, + validate_crash_callback: Callable[["Qiling", int, bytes, int], bool] = None, always_validate: bool = False, persistent_iters: int = 1): """ Fuzz a range of code with afl++. @@ -28,13 +28,18 @@ def ql_afl_fuzz(ql: Qiling, def _ql_afl_place_input_wrapper(uc, input_bytes, iters, data): (ql, cb, _) = data + if cb: + return cb(ql, input_bytes, iters) + else: + return False - return cb(ql, input_bytes, iters) - - def _ql_afl_validate_wrapper(uc, input_bytes, iters, data): + def _ql_afl_validate_wrapper(uc, result, input_bytes, iters, data): (ql, _, cb) = data - return cb(ql, input_bytes, iters) + if cb: + return cb(ql, result, input_bytes, iters) + else: + return False data = (ql, place_input_callback, validate_crash_callback) try: diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index ad6f3709d..aec9e0897 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -51,7 +51,6 @@ 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 * @@ -334,7 +333,7 @@ def get_filetype(): @staticmethod def get_ql_arch_string(): info = IDA.get_info_structure() - proc = info.get_procName().lower() + proc = info.procname.lower() result = None if proc == "metapc": result = "x86" @@ -488,10 +487,6 @@ def finish_populating_widget_popup(self, widget, popup): return True def SetReg(self, addr, ql:Qiling): - arch = ql.archtype - if arch == "": - return - #clear self.ClearLines() @@ -509,7 +504,7 @@ def SetReg(self, addr, ql:Qiling): 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 @@ def SetStack(self, ql:Qiling): 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' @@ -814,7 +805,7 @@ def __init__(self, handler, action): self.action_type = action def activate(self, ctx): - if ctx.form_type == BWN_DISASM: + if ctx.widget_type == BWN_DISASM: self.action_handler.ql_handle_menu_action(self.action_type) return 1 @@ -832,18 +823,7 @@ def get_reg_map(ql:Qiling): 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,34 +878,31 @@ 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: + if self.ql.os.type == QL_OS.LINUX: f = open(self.ql.path, 'rb') elffile = ELFFile(f) elf_header = elffile.header 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 @@ def run(self, begin=None, end=None): 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 @@ def ql_continue(self): 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 @@ def ql_continue(self): 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 @@ def ql_run_selection(self): 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 @@ def ql_run_to_here(self): 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 @@ def ql_run_to_here(self): 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 @@ def ql_step(self): 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 @@ def ql_load(self): 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 @@ def ql_show_reg_view(self): 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 @@ def _force_execution_with_microcode(self, ql, ida_addr): 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 @@ def _force_execution_by_parsing_assembly(self, ql, ida_addr): 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 @@ def _force_execution_by_parsing_assembly(self, ql, ida_addr): 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 @@ def _guide_hook(self, ql, addr, size): 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 @@ def _thumb_detect(self, ida_addr): 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 @@ def _search_path(self): 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 @@ def _search_path(self): # 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 @@ def ql_step_hook(self, ql, addr, size): 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 91b022a9f..1189e698c 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.os = list(os_map.keys())[list(os_map.values()).index(ql.ostype)] + self.arch = ql.arch.type.name + self.os = ql.os.type.name self.env = ql.env self.strings = set() - for string in ql.os.utils.appeared_strings: + for string in ql.os.stats.strings: strings = string.split(" ") self.strings |= set(strings) self.profile = {} @@ -31,7 +31,7 @@ def __init__(self, ql): class WindowsReport(Report): def __init__(self, ql): super().__init__(ql) - self.dlls = ql.loader.dlls + self.teb_address = ql.loader.TEB.base self.peb_address = ql.loader.PEB.base self.ldr_address = ql.loader.LDR.base @@ -54,7 +54,7 @@ def __init__(self, ql): def generate_report(ql, pretty_print=False) -> dict: - if ql.ostype == QL_OS.WINDOWS: + if ql.os.type == QL_OS.WINDOWS: report = WindowsReport(ql) else: report = Report(ql) diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py index d2d7692d0..878c6a4b7 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 000000000..89ffd2f77 --- /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.os_convert(system) + + @cached_property + def arch(self) -> Optional[QL_ARCH]: + """Hosting platform architecture type. + """ + + machine = platform.machine() + + return utils.arch_convert(machine) diff --git a/qiling/hw/hw.py b/qiling/hw/hw.py index 040f4d732..701822b84 100644 --- a/qiling/hw/hw.py +++ b/qiling/hw/hw.py @@ -31,7 +31,7 @@ def create(self, label: str, struct: "QlPeripheral"=None, base: int=None) -> "Ql try: - entity = ql_get_module_function('qiling.hw', struct)(self.ql, label, **kwargs) + entity = ql_get_module_function('.hw', struct)(self.ql, label, **kwargs) setattr(self, label, entity) self.entity[label] = entity self.region[label] = [(lbound + base, rbound + base) for (lbound, rbound) in entity.region] diff --git a/qiling/hw/peripheral.py b/qiling/hw/peripheral.py index b437f229d..55d36a23c 100644 --- a/qiling/hw/peripheral.py +++ b/qiling/hw/peripheral.py @@ -18,7 +18,7 @@ def decorator(func): def read(self, offset: int, size: int) -> int: retval = func(self, offset, size) if self.verbose: - self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.reg.pc)}] [R] {self.find_field(offset, size):{width}s} = {hex(retval)}') + self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.arch.regs.pc)}] [R] {self.find_field(offset, size):{width}s} = {hex(retval)}') return retval @@ -28,7 +28,7 @@ def write(self, offset: int, size: int, value: int): 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 787ba6a39..382dbb33c 100644 --- a/qiling/loader/blob.py +++ b/qiling/loader/blob.py @@ -23,6 +23,6 @@ def run(self): 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 69e8826aa..5e3db8bbd 100644 --- a/qiling/loader/dos.py +++ b/qiling/loader/dos.py @@ -87,16 +87,16 @@ def run(self): 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 80ab28f01..ada002294 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -29,7 +29,7 @@ # auxiliary vector types # see: https://man7.org/linux/man-pages/man3/getauxval.3.html -class AUX(IntEnum): +class AUXV(IntEnum): AT_NULL = 0 AT_IGNORE = 1 AT_EXECFD = 2 @@ -68,15 +68,20 @@ def __init__(self, ql: Qiling): def run(self): if self.ql.code: 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 + + shellcode_base = self.ql.os.entry_point + 0x200000 - 0x1000 + self.ql.mem.write(shellcode_base, self.ql.code) + + self.ql.arch.regs.arch_sp = shellcode_base + self.ql.os.entry_point = shellcode_base + self.load_address = shellcode_base + return section = { 32 : 'OS32', 64 : 'OS64' - }[self.ql.archbit] + }[self.ql.arch.bits] self.profile = self.ql.os.profile[section] @@ -115,13 +120,13 @@ def run(self): 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 + if self.ql.os.type == QL_OS.FREEBSD: + # 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: @@ -141,120 +146,87 @@ def seg_perm_to_uc_prot(perm: int) -> int: return prot - @staticmethod - def align(value: int, alignment: int) -> int: - """Align a value down to the specified alignment boundary. If `value` is already - aligned, the same value is returned. Commonly used to determine the base address - of the enclosing page. - - Args: - value: numberic value to align - alignment: alignment boundary; must be a power of 2 - - Returns: - Value aligned down to boundary - """ - - return value & ~(alignment - 1) - - @staticmethod - def align_up(value: int, alignment: int) -> int: - """Align a value up to the specified alignment boundary. If `value` is already - aligned, the same value is returned. Commonly used to determine the end address - of the enlosing page. + def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[str, str] = {}): - Args: - value: numberic value to align - alignment: alignment boundary; must be a power of 2 + def load_elf_segments(elffile: ELFFile, load_address: int, info: str): + # get list of loadable segments; these segments will be loaded to memory + load_segments = sorted(elffile.iter_segments(type='PT_LOAD'), key=lambda s: s['p_vaddr']) - Returns: - Value aligned up to boundary - """ + # determine the memory regions that need to be mapped in order to load the segments. + # note that region boundaries are aligned to page, which means they may be larger than + # the segment they contain. to reduce mapping clutter, adjacent regions with the same + # perms are consolidated into one contigous memory region + load_regions: Sequence[Tuple[int, int, int]] = [] - return (value + alignment - 1) & ~(alignment - 1) + # iterate over loadable segments + for seg in load_segments: + lbound = self.ql.mem.align(load_address + seg['p_vaddr']) + ubound = self.ql.mem.align_up(load_address + seg['p_vaddr'] + seg['p_memsz']) + perms = QlLoaderELF.seg_perm_to_uc_prot(seg['p_flags']) - def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[str, str] = {}): - pagesize = 0x1000 + if load_regions: + prev_lbound, prev_ubound, prev_perms = load_regions[-1] - # get list of loadable segments; these segments will be loaded to memory - seg_pt_load = tuple(seg for seg in elffile.iter_segments() if seg['p_type'] == 'PT_LOAD') + # new region starts where the previous one ended + if lbound == prev_ubound: + # same perms? extend previous memory region + if perms == prev_perms: + load_regions[-1] = (prev_lbound, ubound, prev_perms) - # determine the memory regions that need to be mapped in order to load the segments. - # note that region boundaries are aligned to page, which means they may be larger than - # the segment they contain. to reduce mapping clutter, adjacent regions with the same - # perms are consolidated into one contigous memory region - load_regions: Sequence[Tuple[int, int, int]] = [] + # different perms? start a new one + else: + load_regions.append((lbound, ubound, perms)) - # iterate over loadable segments by vaddr - for seg in sorted(seg_pt_load, key=lambda s: s['p_vaddr']): - lbound = QlLoaderELF.align(load_address + seg['p_vaddr'], pagesize) - ubound = QlLoaderELF.align_up(load_address + seg['p_vaddr'] + seg['p_memsz'], pagesize) - perms = QlLoaderELF.seg_perm_to_uc_prot(seg['p_flags']) + # start a new memory region + elif lbound > prev_ubound: + load_regions.append((lbound, ubound, perms)) - if load_regions: - prev_lbound, prev_ubound, prev_perms = load_regions[-1] + # overlapping segments? something probably went wrong + elif lbound < prev_ubound: + # EDL ELF files use 0x400 bytes pages, which might make some segments look as if they + # start at the same segment as their predecessor. though that is fixable, unicorn + # supports only 0x1000 bytes pages; this becomes problematic when using mem.protect + # + # this workaround unifies such "overlapping" segments, which may apply more permissive + # protection flags to that memory region. + if self.ql.arch.type == QL_ARCH.ARM64: + load_regions[-1] = (prev_lbound, ubound, prev_perms | perms) + continue + + raise RuntimeError + + else: + load_regions.append((lbound, ubound, perms)) - # new region starts where the previous one ended - if lbound == prev_ubound: - # same perms? extend previous memory region - if perms == prev_perms: - load_regions[-1] = (prev_lbound, ubound, prev_perms) + # map the memory regions + for lbound, ubound, perms in load_regions: + try: + self.ql.mem.map(lbound, ubound - lbound, perms, os.path.basename(info)) + except QlMemoryMappedError: + self.ql.log.exception(f'Failed to map {lbound:#x}-{ubound:#x}') + else: + self.ql.log.debug(f'Mapped {lbound:#x}-{ubound:#x}') - # different perms? start a new one - else: - load_regions.append((lbound, ubound, perms)) + # load loadable segments contents to memory + for seg in load_segments: + self.ql.mem.write(load_address + seg['p_vaddr'], seg.data()) - # start a new memory region - elif lbound > prev_ubound: - load_regions.append((lbound, ubound, perms)) + return load_regions[0][0], load_regions[-1][1] - # overlapping segments? something probably went wrong - elif lbound < prev_ubound: - # EDL ELF files use 0x400 bytes pages, which might make some segments look as if they - # start at the same segment as their predecessor. though that is fixable, unicorn - # supports only 0x1000 bytes pages; this becomes problematic when using mem.protect - # - # this workaround unifies such "overlapping" segments, which may apply more permissive - # protection flags to that memory region. - if self.ql.archtype == QL_ARCH.ARM64: - load_regions[-1] = (prev_lbound, ubound, prev_perms | perms) - continue - - raise RuntimeError - - else: - load_regions.append((lbound, ubound, perms)) - - # map the memory regions - for lbound, ubound, perms in load_regions: - try: - self.ql.mem.map(lbound, ubound - lbound, perms, info=self.path) - except QlMemoryMappedError: - self.ql.log.exception(f'Failed to map {lbound:#x}-{ubound:#x}') - else: - self.ql.log.debug(f'Mapped {lbound:#x}-{ubound:#x}') - - # load loadable segments contents to memory - for seg in seg_pt_load: - self.ql.mem.write(load_address + seg['p_vaddr'], seg.data()) - - entry_point = load_address + elffile['e_entry'] - - # the memory space on which the program spans - mem_start = min(seg['p_vaddr'] for seg in seg_pt_load) - mem_end = max(seg['p_vaddr'] + seg['p_memsz'] for seg in seg_pt_load) - - mem_start = QlLoaderELF.align(mem_start, pagesize) - mem_end = QlLoaderELF.align_up(mem_end, pagesize) + mem_start, mem_end = load_elf_segments(elffile, load_address, self.path) + self.elf_entry = entry_point = load_address + elffile['e_entry'] self.ql.log.debug(f'mem_start : {mem_start:#x}') self.ql.log.debug(f'mem_end : {mem_end:#x}') + # by convention the loaded binary is first on the list + self.images.append(Image(mem_start, mem_end, os.path.abspath(self.path))) + # note: 0x2000 is the size of [hook_mem] - self.brk_address = load_address + mem_end + 0x2000 + self.brk_address = mem_end + 0x2000 # determine interpreter path - interp_seg = next((seg for seg in elffile.iter_segments() if type(seg) is InterpSegment), None) + interp_seg = next(elffile.iter_segments(type='PT_INTERP'), None) interp_path = str(interp_seg.get_interp_name()) if interp_seg else '' interp_address = 0 @@ -266,27 +238,19 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg with open(interp_local_path, 'rb') as infile: interp = ELFFile(infile) + min_vaddr = min(seg['p_vaddr'] for seg in interp.iter_segments(type='PT_LOAD')) # determine interpreter base address - interp_address = int(self.profile.get('interp_address'), 0) + # some old interpreters may not be PIE: p_vaddr of the first LOAD segment is not zero + # we should load interpreter at the address p_vaddr specified in such situation + interp_address = int(self.profile.get('interp_address'), 0) if min_vaddr == 0 else 0 self.ql.log.debug(f'Interpreter addr: {interp_address:#x}') - interp_seg_pt_load = tuple(seg for seg in interp.iter_segments() if seg['p_type'] == 'PT_LOAD') - - # determine memory size needed for interpreter - interp_mem_size = max((seg['p_vaddr'] + seg['p_memsz']) for seg in interp_seg_pt_load) - interp_mem_size = QlLoaderELF.align_up(interp_mem_size, pagesize) - self.ql.log.debug(f'Interpreter size: {interp_mem_size:#x}') - - # map memory for interpreter - self.ql.mem.map(interp_address, interp_mem_size, info=os.path.abspath(interp_local_path)) + # load interpreter segments data to memory + interp_start, interp_end = load_elf_segments(interp, interp_address, interp_local_path) - # load interpterter segments data to memory - for seg in interp_seg_pt_load: - addr = interp_address + seg['p_vaddr'] - data = seg.data() - - self.ql.mem.write(addr, data) + # add interpreter to the loaded images list + self.images.append(Image(interp_start, interp_end, os.path.abspath(interp_local_path))) # determine entry point entry_point = interp_address + interp['e_entry'] @@ -296,7 +260,6 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg self.ql.log.debug(f'mmap_address is : {mmap_address:#x}') # set info to be used by gdb - self.interp_address = interp_address self.mmap_address = mmap_address # set elf table @@ -309,7 +272,7 @@ def __push_str(top: int, s: str) -> int: """ data = (s if isinstance(s, bytes) else s.encode("utf-8")) + b'\x00' - top = QlLoaderELF.align(top - len(data), self.ql.pointersize) + top = self.ql.mem.align(top - len(data), self.ql.arch.pointersize) self.ql.mem.write(top, data) return top @@ -338,82 +301,79 @@ def __push_str(top: int, s: str) -> int: new_stack = execfn = __push_str(new_stack, argv[0]) # store aux vector data for gdb use - elf_phdr = load_address + elffile['e_phoff'] + elf_phdr = elffile['e_phoff'] + mem_start elf_phent = elffile['e_phentsize'] 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 elf_hwcap = 0xd7b81f # setup aux vector - aux_entries = ( - (AUX.AT_PHDR, elf_phdr + mem_start), - (AUX.AT_PHENT, elf_phent), - (AUX.AT_PHNUM, elf_phnum), - (AUX.AT_PAGESZ, pagesize), - (AUX.AT_BASE, interp_address), - (AUX.AT_FLAGS, 0), - (AUX.AT_ENTRY, elf_entry), - (AUX.AT_UID, self.ql.os.uid), - (AUX.AT_EUID, self.ql.os.euid), - (AUX.AT_GID, self.ql.os.gid), - (AUX.AT_EGID, self.ql.os.egid), - (AUX.AT_HWCAP, elf_hwcap), - (AUX.AT_CLKTCK, 100), - (AUX.AT_RANDOM, randstraddr), - (AUX.AT_HWCAP2, 0), - (AUX.AT_EXECFN, execfn), - (AUX.AT_PLATFORM, cpustraddr), - (AUX.AT_SECURE, 0), - (AUX.AT_NULL, 0) + auxv_entries = ( + (AUXV.AT_HWCAP, elf_hwcap), + (AUXV.AT_PAGESZ, self.ql.mem.pagesize), + (AUXV.AT_CLKTCK, 100), + (AUXV.AT_PHDR, elf_phdr), + (AUXV.AT_PHENT, elf_phent), + (AUXV.AT_PHNUM, elf_phnum), + (AUXV.AT_BASE, interp_address), + (AUXV.AT_FLAGS, 0), + (AUXV.AT_ENTRY, self.elf_entry), + (AUXV.AT_UID, self.ql.os.uid), + (AUXV.AT_EUID, self.ql.os.euid), + (AUXV.AT_GID, self.ql.os.gid), + (AUXV.AT_EGID, self.ql.os.egid), + (AUXV.AT_SECURE, 0), + (AUXV.AT_RANDOM, randstraddr), + (AUXV.AT_HWCAP2, 0), + (AUXV.AT_EXECFN, execfn), + (AUXV.AT_PLATFORM, cpustraddr), + (AUXV.AT_NULL, 0) ) - # add all aux entries - for key, val in aux_entries: - elf_table.extend(self.ql.pack(key) + self.ql.pack(val)) + bytes_before_auxv = len(elf_table) + + # add all auxv entries + for key, val in auxv_entries: + elf_table.extend(self.ql.pack(key)) + elf_table.extend(self.ql.pack(val)) - new_stack = QlLoaderELF.align(new_stack - len(elf_table), 0x10) + new_stack = self.ql.mem.align(new_stack - len(elf_table), 0x10) self.ql.mem.write(new_stack, bytes(elf_table)) - # if enabled, gdb would need to retrieve aux vector data. - # note that gdb needs the AT_PHDR entry to hold the original elf_phdr value - self.aux_vec = dict(aux_entries) - self.aux_vec[AUX.AT_PHDR] = elf_phdr + self.auxv = new_stack + bytes_before_auxv - self.elf_entry = elf_entry 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 self.ql.os.elf_entry = self.elf_entry - self.ql.os.function_hook = FunctionHook(self.ql, elf_phdr + mem_start, elf_phnum, elf_phent, load_address, load_address + mem_end) + self.ql.os.function_hook = FunctionHook(self.ql, elf_phdr, elf_phnum, elf_phent, load_address, mem_end) # If there is a loader, we ignore exit 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.os.type == QL_OS.LINUX: _vsyscall_addr = int(self.profile.get('vsyscall_address'), 0) _vsyscall_size = int(self.profile.get('vsyscall_size'), 0) - if not self.ql.mem.is_mapped(_vsyscall_addr, _vsyscall_size): + if self.ql.mem.is_available(_vsyscall_addr, _vsyscall_size): # initialize with int3 instructions then insert syscall entry # each syscall should be 1KiB away self.ql.mem.map(_vsyscall_addr, _vsyscall_size, info="[vsyscall]") 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 +466,19 @@ def __get_symbol(name: str) -> Optional[Symbol]: # 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 = self.ql.mem.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 +499,49 @@ def __get_symbol(name: str) -> Optional[Symbol]: 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 +590,7 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N 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 = self.ql.mem.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 +609,20 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N 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/loader.py b/qiling/loader/loader.py index 2afe1a138..6be0ccf1d 100644 --- a/qiling/loader/loader.py +++ b/qiling/loader/loader.py @@ -3,11 +3,15 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Any, Mapping, MutableSequence, NamedTuple +import os +from typing import Any, Mapping, MutableSequence, NamedTuple, Optional from qiling import Qiling -Image = NamedTuple('Image', (('base', int), ('end', int), ('path', str))) +class Image(NamedTuple): + base: int + end: int + path: str class QlLoader: def __init__(self, ql: Qiling): @@ -17,6 +21,28 @@ def __init__(self, ql: Qiling): self.images: MutableSequence[Image] = [] self.skip_exit_check = False + def find_containing_image(self, address: int) -> Optional[Image]: + """Retrieve the image object that contains the specified address. + + Returns: image containing the specified address, or `None` if not found + """ + + return next((image for image in self.images if image.base <= address < image.end), None) + + def get_image_by_name(self, name: str, *, casefold: bool = False) -> Optional[Image]: + """Retrieve an image by its basename. + + Args: + name : image base name + casefold : whether name matching should be case-insensitive (default is case-sensitive) + + Returns: image object whose basename match to the one given, or `None` if not found + """ + + cf = str.casefold if casefold else lambda s: s + + return next((image for image in self.images if cf(os.path.basename(image.path)) == cf(name)), None) + def save(self) -> Mapping[str, Any]: saved_state = { 'images': [tuple(img) for img in self.images] diff --git a/qiling/loader/macho.py b/qiling/loader/macho.py index 813324abd..5c0e5c21a 100644 --- a/qiling/loader/macho.py +++ b/qiling/loader/macho.py @@ -27,7 +27,7 @@ # 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 @@ def run(self): 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 @@ def run(self): 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 @@ def loadMacho(self, depth=0, isdyld=False): 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/const.py b/qiling/loader/macho_parser/const.py index edbd3dedb..0726f374d 100644 --- a/qiling/loader/macho_parser/const.py +++ b/qiling/loader/macho_parser/const.py @@ -45,6 +45,7 @@ LC_LOAD_DYLINKER = 0x0000000E LC_MAIN = 0x80000028 LC_LOAD_DYLIB = 0x0000000C +LC_LOAD_WEAK_DYLIB = 0x80000018 LC_ENCRYPTION_INFO_64 = 0x0000002C LC_BUILD_VERSION = 0x00000032 LC_DYLD_EXPORTS_TRIE = 0x80000033 diff --git a/qiling/loader/macho_parser/loadcommand.py b/qiling/loader/macho_parser/loadcommand.py index e13832bdd..1eb18d362 100644 --- a/qiling/loader/macho_parser/loadcommand.py +++ b/qiling/loader/macho_parser/loadcommand.py @@ -39,6 +39,7 @@ def get_complete(self): LC_LOAD_DYLINKER : LoadDylinker, LC_MAIN : LoadMain, LC_LOAD_DYLIB : LoadDyLib, + LC_LOAD_WEAK_DYLIB : LoadWeakDyLib, LC_ENCRYPTION_INFO_64 : LoadEncryptionInfo64, LC_DYLD_EXPORTS_TRIE : LoadDyldExportTrie, LC_DYLD_CHAINED_FIXUPS : LoadDyldChainedFixups, @@ -255,6 +256,11 @@ def get_complete(self): pass +class LoadWeakDyLib(LoadDyLib): + def __init__(self, data): + super().__init__(data) + + class LoadUnixThread(LoadCommand): def __init__(self, data): diff --git a/qiling/loader/macho_parser/parser.py b/qiling/loader/macho_parser/parser.py index a8b0c29b5..2b38914cb 100644 --- a/qiling/loader/macho_parser/parser.py +++ b/qiling/loader/macho_parser/parser.py @@ -20,7 +20,7 @@ def __init__(self, ql, path, arch= None): 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 4b28447ad..2daf8a21a 100644 --- a/qiling/loader/mcu.py +++ b/qiling/loader/mcu.py @@ -9,7 +9,6 @@ from qiling.const import * from qiling.core import Qiling -from qiling.utils import component_setup from .loader import QlLoader @@ -62,8 +61,6 @@ def __init__(self, ql:Qiling): self.load_address = 0 self.filetype = self.guess_filetype() - self.ql._hw = component_setup("hw", "hw", self.ql) - if self.filetype == 'elf': with open(self.ql.path, 'rb') as infile: self.elf = ELFFile(io.BytesIO(infile.read())) @@ -88,9 +85,8 @@ def guess_filetype(self): def reset(self): if self.filetype == 'elf': - for segment in self.elf.iter_segments(): - if segment['p_type'] == 'PT_LOAD': - self.ql.mem.write(segment['p_paddr'], segment.data()) + for segment in self.elf.iter_segments(type='PT_LOAD'): + self.ql.mem.write(segment['p_paddr'], segment.data()) # TODO: load symbol table @@ -104,7 +100,7 @@ def reset(self): 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 726fc10a1..1b05e6d13 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -3,95 +3,136 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os, pefile, pickle, secrets, traceback -from typing import Any, MutableMapping, Optional, Mapping, Sequence +import os, pefile, pickle, secrets, ntpath +from typing import Any, Dict, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union + +from unicorn import UcError +from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8 from qiling import Qiling -from qiling.arch.x86_const import * -from qiling.const import * +from qiling.arch.x86_const import FS_SEGMENT_ADDR, GS_SEGMENT_ADDR +from qiling.const import QL_ARCH +from qiling.exception import QlErrorArch from qiling.os.const import POINTER -from qiling.os.memory import QlMemoryHeap +from qiling.os.windows.api import HINSTANCE, DWORD, LPVOID from qiling.os.windows.fncc import CDECL -from qiling.os.windows.utils import * +from qiling.os.windows.utils import has_lib_ext from qiling.os.windows.structs import * from .loader import QlLoader, Image -class QlPeCacheEntry: - def __init__(self, ba: int, data: bytearray, cmdlines: Sequence, import_symbols: Mapping, import_table: Mapping): - self. ba = ba - self.data = data - self.cmdlines = cmdlines - self.import_symbols = import_symbols - self.import_table = import_table +class QlPeCacheEntry(NamedTuple): + ba: int + data: bytearray + cmdlines: Sequence + import_symbols: MutableMapping[int, dict] + import_table: MutableMapping[Union[str, int], int] + -# A default simple cache implementation class QlPeCache: - def create_filename(self, path: str) -> str: + @staticmethod + def cache_filename(path: str) -> str: + dirname, basename = os.path.split(path) + + # canonicalize basename while preserving the path + path = os.path.join(dirname, basename.casefold()) + return f'{path}.cache2' def restore(self, path: str) -> Optional[QlPeCacheEntry]: - fcache = self.create_filename(path) + fcache = QlPeCache.cache_filename(path) - # pickle file cannot be outdated + # check whether cache file exists and it is not older than the cached file itself if os.path.exists(fcache) and os.stat(fcache).st_mtime > os.stat(path).st_mtime: with open(fcache, "rb") as fcache_file: return QlPeCacheEntry(*pickle.load(fcache_file)) return None - def save(self, path: str, entry: QlPeCacheEntry): - fcache = self.create_filename(path) + def save(self, path: str, entry: QlPeCacheEntry) -> None: + fcache = QlPeCache.cache_filename(path) - data = (entry.ba, entry.data, entry.cmdlines, entry.import_symbols, entry.import_table) # cache this dll file with open(fcache, "wb") as fcache_file: - pickle.dump(data, fcache_file) + pickle.dump(entry, fcache_file) + -class Process(): +class Process: # let linter recognize mixin members + cmdline: bytes + pe_image_address: int + stack_address: int + stack_size: int + dlls: MutableMapping[str, int] - import_address_table: MutableMapping[str, Any] - import_symbols: MutableMapping[int, Any] - export_symbols: MutableMapping[int, Any] + import_address_table: MutableMapping[str, Mapping] + import_symbols: MutableMapping[int, Dict[str, Any]] + export_symbols: MutableMapping[int, Dict[str, Any]] libcache: Optional[QlPeCache] def __init__(self, ql: Qiling): self.ql = ql - def align(self, size: int, unit: int) -> int: - return (size // unit + (1 if size % unit else 0)) * unit + def __get_path_elements(self, name: str) -> Tuple[str, str]: + """Translate DLL virtual path into host path. - def load_dll(self, name: bytes, driver: bool = False) -> int: - dll_name = name.decode() + Args: + name: dll virtual path; either absolute or relative - self.ql.dlls = os.path.join("Windows", "System32") + Returns: dll path on the host and dll basename in a canonicalized form + """ - if dll_name.upper().startswith('C:\\'): - path = self.ql.os.path.transform_to_real_path(dll_name) - dll_name = path_leaf(dll_name) - else: - dll_name = dll_name.lower() + dirname, basename = ntpath.split(name) + + if not has_lib_ext(basename): + basename = f'{basename}.dll' + + # if only filename was specified assume it is located at the + # system32 folder to prevent potential dll hijacking + if not dirname: + dirname = self.ql.os.winsys + + # reconstruct the dll virtual path + vpath = ntpath.join(dirname, basename) - if not is_file_library(dll_name): - dll_name = dll_name + ".dll" + return self.ql.os.path.virtual_to_host_path(vpath), basename.casefold() - path = os.path.join(self.ql.rootfs, self.ql.dlls, dll_name) + def load_dll(self, name: str, is_driver: bool = False) -> int: + dll_path, dll_name = self.__get_path_elements(name) - if not os.path.exists(path): - self.ql.log.error("Cannot find dll in %s" % path) + if dll_name.startswith('api-ms-win-'): + # Usually we should not reach this point and instead imports from such DLLs should be redirected earlier + self.ql.log.debug(f'Refusing to load virtual DLL {dll_name}') return 0 - # If the dll is already loaded - if dll_name in self.dlls: - return self.dlls[dll_name] + # see if this dll was already loaded + image = self.get_image_by_name(dll_name, casefold=True) - self.ql.log.info(f'Loading {path} ...') + if image is not None: + return image.base + + if not os.path.exists(dll_path): + # posix hosts may not find the requested filename if it was saved under a different case. + # For example, 'KernelBase.dll' may not be found because it is stored as 'kernelbase.dll'. + # + # try to locate the requested file while ignoring the case of its path elements. + dll_casefold_path = self.ql.os.path.host_casefold_path(dll_path) + + if dll_casefold_path is None: + self.ql.log.error(f'Could not find DLL file: {dll_path}') + return 0 + + dll_path = dll_casefold_path + + self.ql.log.info(f'Loading {dll_name} ...') + + import_symbols = {} + import_table = {} cached = None loaded = False if self.libcache: - cached = self.libcache.restore(path) + cached = self.libcache.restore(dll_path) if cached: data = cached.data @@ -109,72 +150,72 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: for entry in cached.cmdlines: self.set_cmdline(entry['name'], entry['address'], data) - self.ql.log.info(f'Loaded {path} from cache') + self.ql.log.info(f'Loaded {dll_name} from cache') loaded = True # either file was not cached, or could not be loaded to the same location in memory if not cached or not loaded: - dll = pefile.PE(path, fast_load=True) + dll = pefile.PE(dll_path, fast_load=True) dll.parse_data_directories() warnings = dll.get_warnings() if warnings: - self.ql.log.warning(f'Warnings while loading {path}:') + self.ql.log.debug(f'Warnings while loading {dll_name}:') for warning in warnings: - self.ql.log.warning(f' - {warning}') - - data = bytearray(dll.get_memory_mapped_image()) + self.ql.log.debug(f' - {warning}') image_base = dll.OPTIONAL_HEADER.ImageBase or self.dll_last_address - image_size = self.ql.mem.align_up(len(data)) + image_size = self.ql.mem.align_up(dll.OPTIONAL_HEADER.SizeOfImage) + relocate = False self.ql.log.debug(f'DLL preferred base address: {image_base:#x}') if (image_base + image_size) > self.ql.mem.max_mem_addr: image_base = self.dll_last_address self.ql.log.debug(f'DLL preferred base address exceeds memory upper bound, loading to: {image_base:#x}') + relocate = True if not self.ql.mem.is_available(image_base, image_size): image_base = self.ql.mem.find_free_space(image_size, minaddr=image_base, align=0x10000) self.ql.log.debug(f'DLL preferred base address is taken, loading to: {image_base:#x}') + relocate = True + + if relocate: + with ShowProgress(0.1337): + dll.relocate_image(image_base) + + data = bytearray(dll.get_memory_mapped_image()) + assert image_size >= len(data) cmdlines = [] - import_symbols = {} - import_table = {} - - dll_symbols = getattr(getattr(dll, 'DIRECTORY_ENTRY_EXPORT', None), 'symbols', []) - for entry in dll_symbols: - import_symbols[image_base + entry.address] = { - "name": entry.name, - "ordinal": entry.ordinal, - "dll": dll_name.split('.')[0] + + for sym in dll.DIRECTORY_ENTRY_EXPORT.symbols: + ea = image_base + sym.address + + import_symbols[ea] = { + 'name' : sym.name, + 'ordinal' : sym.ordinal, + 'dll' : dll_name.split('.')[0] } - if entry.name: - import_table[entry.name] = image_base + entry.address + if sym.name: + import_table[sym.name] = ea - import_table[entry.ordinal] = image_base + entry.address - cmdline_entry = self.set_cmdline(entry.name, entry.address, data) + import_table[sym.ordinal] = ea + cmdline_entry = self.set_cmdline(sym.name, sym.address, data) if cmdline_entry: cmdlines.append(cmdline_entry) if self.libcache: cached = QlPeCacheEntry(image_base, data, cmdlines, import_symbols, import_table) - self.libcache.save(path, cached) - self.ql.log.info("Cached %s" % path) + self.libcache.save(dll_path, cached) + self.ql.log.info(f'Cached {dll_name}') # Add dll to IAT - try: - self.import_address_table[dll_name] = import_table - except Exception as ex: - self.ql.log.exception(f'Unable to add {dll_name} to IAT') - - try: - self.import_symbols.update(import_symbols) - except Exception as ex: - self.ql.log.exception(f'Unable to add {dll_name} import symbols') + self.import_address_table[dll_name] = import_table + self.import_symbols.update(import_symbols) dll_base = image_base dll_len = image_size @@ -183,508 +224,671 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: self.ql.mem.map(dll_base, dll_len, info=dll_name) self.ql.mem.write(dll_base, bytes(data)) - self.dlls[dll_name] = dll_base - if dll_base == self.dll_last_address: - self.dll_last_address += dll_len + self.dll_last_address = self.ql.mem.align_up(self.dll_last_address + dll_len, 0x10000) + + # add DLL to coverage images + self.images.append(Image(dll_base, dll_base + dll_len, dll_path)) # if this is NOT a driver, add dll to ldr data - if not driver: + if not is_driver: self.add_ldr_data_table_entry(dll_name) - # add DLL to coverage images - self.images.append(Image(dll_base, dll_base + dll_len, path)) - - self.ql.log.info(f'Done with loading {path}') + if not cached or not loaded: + # parse directory entry import + self.ql.log.debug(f'Init imports for {dll_name}') + self.init_imports(dll, is_driver) + + # calling DllMain is essential for dlls to initialize properly. however + # DllMain of system libraries may fail due to incomplete or inaccurate + # mock implementation. due to unicorn limitations, recovering from such + # errors may be possible only if the function was not invoked from within + # a hook. + # + # in case of a dll loaded from a hooked API call, failures would not be + # recoverable and we have to give up its DllMain. + if not self.ql.os.PE_RUN: + self.call_dll_entrypoint(dll, dll_base, dll_len, dll_name) + + self.ql.log.info(f'Done loading {dll_name}') return dll_base + def call_dll_entrypoint(self, dll: pefile.PE, dll_base: int, dll_len: int, dll_name: str): + entry_address = dll.OPTIONAL_HEADER.AddressOfEntryPoint + + if dll.get_section_by_rva(entry_address) is None: + return + + if dll_name in ('kernelbase.dll', 'kernel32.dll'): + self.ql.log.debug(f'Ignoring {dll_name} entry point') + return + + # DllMain functions often call many APIs that may crash the program if they + # are not implemented correctly (if at all). here we blacklist the problematic + # DLLs whose DllMain functions are known to be crashing. + # + # the blacklist may be revisited from time to time to see if any of the file + # can be safely unlisted. + blacklist = { + 32 : ('gdi32.dll',), + 64 : ('gdi32.dll',) + }[self.ql.arch.bits] + + if dll_name in blacklist: + self.ql.log.debug(f'Ignoring {dll_name} entry point (blacklisted)') + return + + entry_point = dll_base + entry_address + exit_point = dll_base + dll_len - 16 + + args = ( + (HINSTANCE, dll_base), # hinstDLL = base address of DLL + (DWORD, 1), # fdwReason = DLL_PROCESS_ATTACH + (LPVOID, 0) # lpReserved = 0 + ) + + self.ql.log.info(f'Calling {dll_name} DllMain at {entry_point:#x}') - def _alloc_cmdline(self, wide): - addr = self.ql.os.heap.alloc(len(self.cmdline) * (2 if wide else 1)) - packed_addr = self.ql.pack(addr) - return addr, packed_addr - - def set_cmdline(self, name, address, memory): - cmdline_entry = None - 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 - 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 - 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: - teb_addr = self.structure_last_addr + regs_state = self.ql.arch.regs.save() + + fcall = self.ql.os.fcall_select(CDECL) + fcall.call_native(entry_point, args, exit_point) + + # Execute the call to the entry point + try: + self.ql.emu_start(entry_point, exit_point) + except UcError: + self.ql.log.error(f'Error encountered while running {dll_name} DllMain, bailing') + + self.ql.arch.regs.restore(regs_state) else: - gs = self.structure_last_addr - self.structure_last_addr += 0x30 - teb_addr = self.structure_last_addr + fcall.cc.unwind(len(args)) + + self.ql.log.info(f'Returned from {dll_name} DllMain') - self.ql.log.info("TEB addr is 0x%x" %teb_addr) + def set_cmdline(self, name: bytes, address: int, memory: bytearray): + cmdln = { + b'_acmdln' : 1, + b'_wcmdln' : 2 + } - teb_size = len(TEB(self.ql).bytes()) - teb_data = TEB( - self.ql, - base=teb_addr, - peb_address=teb_addr + teb_size, - stack_base=self.stack_address + self.stack_size, - stack_limit=self.stack_size, - Self=teb_addr) + clen = cmdln.get(name, None) - self.ql.mem.write(teb_addr, teb_data.bytes()) + if clen is None: + return None - self.structure_last_addr += teb_size - if self.ql.archtype == QL_ARCH.X8664: - # TEB - self.ql.mem.write(gs + 0x30, self.ql.pack64(teb_addr)) - # PEB - self.ql.mem.write(gs + 0x60, self.ql.pack64(teb_addr + teb_size)) + addr = self.ql.os.heap.alloc(len(self.cmdline) * clen) + memory[address:address + self.ql.arch.pointersize] = self.ql.pack(addr) + data = self.cmdline - self.TEB = self.ql.TEB = teb_data + if clen == 2: + data = data.decode('ascii').encode('UTF-16LE') + + self.ql.mem.write(addr, data) + + return {"name": name, "address": address} + + def init_teb(self): + teb_obj = make_teb(self.ql.arch.bits) + + teb_addr = self.structure_last_addr + peb_addr = self.ql.mem.align_up(teb_addr + ctypes.sizeof(teb_obj), 0x10) + + teb_obj.StackBase = self.stack_address + self.stack_size + teb_obj.StackLimit = self.stack_address + teb_obj.TebAddress = teb_addr + teb_obj.PebAddress = peb_addr + + self.ql.log.info(f'TEB is at {teb_addr:#x}') + self.ql.mem.write(teb_addr, bytes(teb_obj)) + + self.structure_last_addr = peb_addr + self.TEB = teb_obj def init_peb(self): - peb_addr = self.structure_last_addr + peb_obj = make_peb(self.ql.arch.bits) - self.ql.log.info("PEB addr is 0x%x" % peb_addr) + peb_addr = self.structure_last_addr + ldr_addr = self.ql.mem.align_up(peb_addr + ctypes.sizeof(peb_obj), 0x10) # we must set an heap, will try to retrieve this value. Is ok to be all \x00 - process_heap = self.ql.os.heap.alloc(0x100) - process_parameters = self.ql.os.heap.alloc(0x100) - peb_data = PEB( - self.ql, - base=peb_addr, - process_heap=process_heap, - process_parameters=process_parameters, - number_processors=self.ql.os.profile.getint("HARDWARE", "number_processors")) - peb_data.LdrAddress = peb_addr + peb_data.size - peb_data.write(peb_addr) - self.structure_last_addr += peb_data.size - self.PEB = self.ql.PEB = peb_data + peb_obj.LdrAddress = ldr_addr + peb_obj.ProcessParameters = self.ql.os.heap.alloc(0x100) + peb_obj.ProcessHeap = self.ql.os.heap.alloc(0x100) + peb_obj.NumberOfProcessors = self.ql.os.profile.getint('HARDWARE', 'number_processors') + + self.ql.log.info(f'PEB is at {peb_addr:#x}') + self.ql.mem.write(peb_addr, bytes(peb_obj)) + + self.structure_last_addr = ldr_addr + self.PEB = peb_obj def init_ldr_data(self): + ldr_obj = make_ldr_data(self.ql.arch.bits) + ldr_cls = ldr_obj.__class__ + ldr_addr = self.structure_last_addr - ldr_size = len(LdrData(self.ql).bytes()) - ldr_data = LdrData( - self.ql, - base=ldr_addr, - in_load_order_module_list={ - 'Flink': ldr_addr + 2 * self.ql.pointersize, - 'Blink': ldr_addr + 2 * self.ql.pointersize - }, - in_memory_order_module_list={ - 'Flink': ldr_addr + 4 * self.ql.pointersize, - 'Blink': ldr_addr + 4 * self.ql.pointersize - }, - in_initialization_order_module_list={ - 'Flink': ldr_addr + 6 * self.ql.pointersize, - 'Blink': ldr_addr + 6 * self.ql.pointersize - } - ) - self.ql.mem.write(ldr_addr, ldr_data.bytes()) - self.structure_last_addr += ldr_size - self.LDR = self.ql.LDR = ldr_data - - def add_ldr_data_table_entry(self, dll_name): - dll_base = self.dlls[dll_name] - path = "C:\\Windows\\System32\\" + dll_name - ldr_table_entry_size = len(LdrDataTableEntry(self.ql).bytes()) - base = self.ql.os.heap.alloc(ldr_table_entry_size) - ldr_table_entry = LdrDataTableEntry(self.ql, - base=base, - in_load_order_links={'Flink': 0, 'Blink': 0}, - in_memory_order_links={'Flink': 0, 'Blink': 0}, - in_initialization_order_links={'Flink': 0, 'Blink': 0}, - dll_base=dll_base, - entry_point=0, - full_dll_name=path, - base_dll_name=dll_name) + nobj_addr = self.ql.mem.align_up(ldr_addr + ctypes.sizeof(ldr_obj), 0x10) + + ldr_obj.InLoadOrderModuleList.Flink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset + ldr_obj.InLoadOrderModuleList.Blink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset + + ldr_obj.InMemoryOrderModuleList.Flink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset + ldr_obj.InMemoryOrderModuleList.Blink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset + + ldr_obj.InInitializationOrderModuleList.Flink = ldr_addr + ldr_cls.InInitializationOrderModuleList.offset + ldr_obj.InInitializationOrderModuleList.Blink = ldr_addr + ldr_cls.InInitializationOrderModuleList.offset + + self.ql.log.info(f'LDR is at {ldr_addr:#x}') + self.ql.mem.write(ldr_addr, bytes(ldr_obj)) + + self.structure_last_addr = nobj_addr + self.LDR = ldr_obj + + def add_ldr_data_table_entry(self, dll_name: str): + entry_obj = make_ldr_data_table_entry(self.ql.arch.bits) + entry_cls = entry_obj.__class__ + + entry_size = ctypes.sizeof(entry_obj) + entry_addr = self.ql.os.heap.alloc(entry_size) + + def populate_unistr(obj, s: str) -> None: + encoded = s.encode('utf-16le') + ucslen = len(encoded) + ucsbuf = self.ql.os.heap.alloc(ucslen + 2) + + self.ql.mem.write(ucsbuf, encoded + b'\x00\x00') + + obj.Length = ucslen + obj.MaximumLength = ucslen + 2 + obj.Buffer = ucsbuf + + image = self.get_image_by_name(dll_name, casefold=True) + assert image, 'image should have been added to loader.images first' + + entry_obj.DllBase = image.base + populate_unistr(entry_obj.FullDllName, ntpath.join(self.ql.os.winsys, dll_name)) + populate_unistr(entry_obj.BaseDllName, dll_name) # Flink - if len(self.ldr_list) == 0: - flink = self.LDR - ldr_table_entry.InLoadOrderLinks['Flink'] = flink.InLoadOrderModuleList['Flink'] - ldr_table_entry.InMemoryOrderLinks['Flink'] = flink.InMemoryOrderModuleList['Flink'] - ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderModuleList['Flink'] + if self.ldr_list: + flink_base, flink = self.ldr_list[-1] - 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 + entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderLinks.Flink + entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderLinks.Flink + entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderLinks.Flink + + flink.InLoadOrderLinks.Flink = entry_addr + entry_cls.InLoadOrderLinks.offset + flink.InMemoryOrderLinks.Flink = entry_addr + entry_cls.InMemoryOrderLinks.offset + flink.InInitializationOrderLinks.Flink = entry_addr + entry_cls.InInitializationOrderLinks.offset else: - flink = self.ldr_list[-1] - ldr_table_entry.InLoadOrderLinks['Flink'] = flink.InLoadOrderLinks['Flink'] - ldr_table_entry.InMemoryOrderLinks['Flink'] = flink.InMemoryOrderLinks['Flink'] - ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderLinks['Flink'] + flink_base, flink = (self.PEB.LdrAddress, self.LDR) + + entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderModuleList.Flink + entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderModuleList.Flink + entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderModuleList.Flink - 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.InLoadOrderModuleList.Flink = entry_addr + entry_cls.InLoadOrderLinks.offset + flink.InMemoryOrderModuleList.Flink = entry_addr + entry_cls.InMemoryOrderLinks.offset + flink.InInitializationOrderModuleList.Flink = entry_addr + entry_cls.InInitializationOrderLinks.offset # Blink + blink_base = self.PEB.LdrAddress blink = self.LDR - ldr_table_entry.InLoadOrderLinks['Blink'] = blink.InLoadOrderModuleList['Blink'] - ldr_table_entry.InMemoryOrderLinks['Blink'] = blink.InMemoryOrderModuleList['Blink'] - 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 + entry_obj.InLoadOrderLinks.Blink = blink.InLoadOrderModuleList.Blink + entry_obj.InMemoryOrderLinks.Blink = blink.InMemoryOrderModuleList.Blink + entry_obj.InInitializationOrderLinks.Blink = blink.InInitializationOrderModuleList.Blink - self.ql.mem.write(flink.base, flink.bytes()) - self.ql.mem.write(blink.base, blink.bytes()) - self.ql.mem.write(ldr_table_entry.base, ldr_table_entry.bytes()) + blink.InLoadOrderModuleList.Blink = entry_addr + entry_cls.InLoadOrderLinks.offset + blink.InMemoryOrderModuleList.Blink = entry_addr + entry_cls.InMemoryOrderLinks.offset + blink.InInitializationOrderModuleList.Blink = entry_addr + entry_cls.InInitializationOrderLinks.offset - self.ldr_list.append(ldr_table_entry) + self.ql.mem.write(flink_base, bytes(flink)) + self.ql.mem.write(blink_base, bytes(blink)) + self.ql.mem.write(entry_addr, bytes(entry_obj)) - def init_exports(self): - if self.ql.code: + self.ldr_list.append((entry_addr, entry_obj)) + + @staticmethod + def directory_exists(pe: pefile.PE, entry: str) -> bool: + ent = pefile.DIRECTORY_ENTRY[entry] + + return pe.OPTIONAL_HEADER.DATA_DIRECTORY[ent].VirtualAddress != 0 + + def init_imports(self, pe: pefile.PE, is_driver: bool): + if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_IMPORT'): return - if self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT']].VirtualAddress != 0: - # Do a full load if IMAGE_DIRECTORY_ENTRY_EXPORT is present so we can load the exports - self.pe.full_load() - else: + + pe.full_load() + + for entry in pe.DIRECTORY_ENTRY_IMPORT: + dll_name = entry.dll.decode().casefold() + self.ql.log.debug(f'Requesting imports from {dll_name}') + + orig_dll_name = dll_name + redirected = False + + if dll_name.startswith('api-ms-win-'): + # DLLs starting with this prefix contain no actual code. Instead, the windows loader loads the actual + # code from one of the main windows dlls. + # see https://github.com/lucasg/Dependencies for correct replacement dlls + # + # The correct way to find the dll that replaces all symbols from this dll involves using the hashmap + # inside of apisetschema.dll (see https://lucasg.github.io/2017/10/15/Api-set-resolution/ ). + # + # Currently, we use a simpler, more hacky approach, that seems to work in a lot of cases: we just scan + # through some key dlls and hope that we find the requested symbols there. some symbols may appear on + # more than one dll though; in that case we proceed to the next symbol to see which key dll includes it. + # + # Note: You might be tempted to load the actual dll (dll_name), because they also contain a reference to + # the replacement dll. However, chances are, that these dlls do not exist in the rootfs and maybe they + # don't even exist on windows. Therefore this approach is a bad idea. + + # DLLs that seem to contain most of the requested symbols + key_dlls = ( + 'ntdll.dll', + 'kernelbase.dll', + 'ucrtbase.dll' + ) + + imports = iter(entry.imports) + failed = False + fallback = None + + while not redirected and not failed: + # find all possible redirection options by scanning key dlls for the current imported symbol + imp = next(imports, None) + redirection_options = [fallback] if imp is None else [filename for filename in key_dlls if filename in self.import_address_table and imp.name in self.import_address_table[filename]] + + # no redirection options: failed to redirect dll + if not redirection_options: + failed = True + + # exactly one redirection options: use it + elif len(redirection_options) == 1: + key_dll = redirection_options[0] + redirected = True + + # more than one redirection options: remember one of them and proceed to next symbol + else: + fallback = redirection_options[-1] + + if not redirected: + self.ql.log.warning(f'Failed to resolve {dll_name}') + continue + + self.ql.log.debug(f'Redirecting {dll_name} to {key_dll}') + dll_name = key_dll + + unbound_imports = [imp for imp in entry.imports if not imp.bound] + + if unbound_imports: + # Only load dll if encountered unbound symbol + if not redirected: + dll_base = self.load_dll(entry.dll.decode(), is_driver) + + if not dll_base: + continue + + for imp in unbound_imports: + iat = self.import_address_table[dll_name] + + if imp.name: + if imp.name not in iat: + self.ql.log.debug(f'Error in loading function {imp.name.decode()} ({orig_dll_name}){", probably misdirected" if redirected else ""}') + continue + + addr = iat[imp.name] + else: + addr = iat[imp.ordinal] + + self.ql.mem.write_ptr(imp.address, addr) + + def init_exports(self, pe: pefile.PE): + if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_EXPORT'): return - try: - # parse directory entry export - dll_name = os.path.basename(self.path) - self.import_address_table[dll_name] = {} - for entry in self.pe.DIRECTORY_ENTRY_EXPORT.symbols: - self.export_symbols[self.pe_image_address + entry.address] = {'name': entry.name, 'ordinal': entry.ordinal} - self.import_address_table[dll_name][entry.name] = self.pe_image_address + entry.address - self.import_address_table[dll_name][entry.ordinal] = self.pe_image_address + entry.address - except: - self.ql.log.info('Failed to load exports for %s:\n%s' % (self.ql.argv, traceback.format_exc())) + # Do a full load if IMAGE_DIRECTORY_ENTRY_EXPORT is present so we can load the exports + pe.full_load() + + iat = {} + + # parse directory entry export + for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols: + ea = self.pe_image_address + entry.address + + self.export_symbols[ea] = { + 'name' : entry.name, + 'ordinal' : entry.ordinal + } + + if entry.name: + iat[entry.name] = ea + + iat[entry.ordinal] = ea + + dll_name = os.path.basename(self.path) + self.import_address_table[dll_name.casefold()] = iat def init_driver_object(self): - # PDRIVER_OBJECT DriverObject - driver_object_addr = self.structure_last_addr - self.ql.log.info("Driver object addr is 0x%x" %driver_object_addr) + drv_addr = self.structure_last_addr - if self.ql.archtype == QL_ARCH.X86: - self.driver_object = DRIVER_OBJECT32(self.ql, driver_object_addr) - elif self.ql.archtype == QL_ARCH.X8664: - self.driver_object = DRIVER_OBJECT64(self.ql, driver_object_addr) + # PDRIVER_OBJECT DriverObject + drv_obj = make_driver_object(self.ql, drv_addr, self.ql.arch.bits) + nobj_addr = self.ql.mem.align_up(drv_addr + ctypes.sizeof(drv_obj), 0x10) - driver_object_size = ctypes.sizeof(self.driver_object) - self.ql.mem.write(driver_object_addr, bytes(self.driver_object)) - self.structure_last_addr += driver_object_size - self.driver_object_address = driver_object_addr + self.ql.log.info(f'DriverObject is at {drv_addr:#x}') + self.ql.mem.write(drv_addr, bytes(drv_obj)) + self.structure_last_addr = nobj_addr + self.driver_object_address = drv_addr + self.driver_object = drv_obj def init_registry_path(self): + regpath_addr = self.structure_last_addr + # PUNICODE_STRING RegistryPath - regitry_path_addr = self.structure_last_addr - self.ql.log.info("Registry path addr is 0x%x" %regitry_path_addr) + regpath_obj = make_unicode_string(self.ql.arch.bits, + Length=0, + MaximumLength=0, + Buffer=regpath_addr + ) - if self.ql.archtype == QL_ARCH.X86: - regitry_path_data = UNICODE_STRING32(0, 0, regitry_path_addr) - elif self.ql.archtype == QL_ARCH.X8664: - regitry_path_data = UNICODE_STRING64(0, 0, regitry_path_addr) + nobj_addr = self.ql.mem.align_up(regpath_addr + ctypes.sizeof(regpath_obj), 0x10) - regitry_path_size = ctypes.sizeof(regitry_path_data) - self.ql.mem.write(regitry_path_addr, bytes(regitry_path_data)) - self.structure_last_addr += regitry_path_size - self.regitry_path_address = regitry_path_addr + self.ql.log.info(f'RegistryPath is at {regpath_addr:#x}') + self.ql.mem.write(regpath_addr, bytes(regpath_obj)) + self.structure_last_addr = nobj_addr + self.regitry_path_address = regpath_addr def init_eprocess(self): - addr = self.structure_last_addr - self.ql.log.info("EPROCESS is is 0x%x" %addr) - + eproc_obj = make_eprocess(self.ql.arch.bits) - if self.ql.archtype == QL_ARCH.X86: - self.eprocess_object = EPROCESS32(self.ql, addr) - elif self.ql.archtype == QL_ARCH.X8664: - self.eprocess_object = EPROCESS64(self.ql, addr) + eproc_addr = self.structure_last_addr + nobj_addr = self.ql.mem.align_up(eproc_addr + ctypes.sizeof(eproc_obj), 0x10) - size = ctypes.sizeof(self.eprocess_object) - self.ql.mem.write(addr, bytes(self.driver_object)) - self.structure_last_addr += size - self.ql.eprocess_address = addr + self.ql.mem.write(eproc_addr, bytes(eproc_obj)) + self.structure_last_addr = nobj_addr + self.eprocess_address = eproc_addr def init_ki_user_shared_data(self): - ''' - https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/kuser_shared_data/index.htm + addr = self.ql.os.profile.getint(f'OS{self.ql.arch.bits}', 'KI_USER_SHARED_DATA') - struct information: - https://doxygen.reactos.org/d8/dae/modules_2rostests_2winetests_2ntdll_2time_8c_source.html - ''' - if self.ql.archtype == QL_ARCH.X86: - KI_USER_SHARED_DATA = 0xFFDF0000 - elif self.ql.archtype == QL_ARCH.X8664: - KI_USER_SHARED_DATA = 0xFFFFF78000000000 + user_shared_data_obj = KUSER_SHARED_DATA() + user_shared_data_size = ctypes.sizeof(KUSER_SHARED_DATA) - self.ql.log.info("KI_USER_SHARED_DATA is 0x%x" %KI_USER_SHARED_DATA) + self.ql.mem.map(addr, self.ql.mem.align_up(user_shared_data_size)) + self.ql.mem.write(addr, bytes(user_shared_data_obj)) - shared_user_data = KUSER_SHARED_DATA() + def init_security_cookie(self, pe: pefile.PE, image_base: int): + if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG'): + return + + cookie_rva = pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SecurityCookie - pe.OPTIONAL_HEADER.ImageBase - shared_user_data_len = self.align(ctypes.sizeof(KUSER_SHARED_DATA), 0x1000) - self.ql.mem.map(KI_USER_SHARED_DATA, shared_user_data_len) - self.ql.mem.write(KI_USER_SHARED_DATA, bytes(shared_user_data)) + # get a random cookie value but keep the two most significant bytes zeroes + # + # rol rcx, 10h ; rcx = cookie + # test cx, 0FFFFh + cookie = secrets.randbits(self.ql.arch.bits - 16) + self.ql.mem.write_ptr(cookie_rva + image_base, cookie) 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 + self.ql = ql + self.path = self.ql.path + self.libcache = QlPeCache() if libcache else None + + def run(self): + self.init_dlls = ( + 'ntdll.dll', + 'kernel32.dll', + 'user32.dll' + ) + + self.sys_dlls = ( + 'ntdll.dll', + 'kernel32.dll', + 'ucrtbase.dll' + ) - if ql.libcache is True: - self.libcache = QlPeCache() + if self.ql.code: + pe = None + self.is_driver = False else: - self.libcache = ql.libcache or None + pe = pefile.PE(self.path, fast_load=True) + self.is_driver = pe.is_driver() + + ossection = f'OS{self.ql.arch.bits}' + + self.stack_address = self.ql.os.profile.getint(ossection, 'stack_address') + self.stack_size = self.ql.os.profile.getint(ossection, 'stack_size') + self.image_address = self.ql.os.profile.getint(ossection, 'image_address') + self.dll_address = self.ql.os.profile.getint(ossection, 'dll_address') + self.entry_point = self.ql.os.profile.getint(ossection, 'entry_point') + + self.structure_last_addr = { + 32 : FS_SEGMENT_ADDR, + 64 : GS_SEGMENT_ADDR + }[self.ql.arch.bits] - def run(self): - self.init_dlls = [b"ntdll.dll", b"kernel32.dll", b"user32.dll"] - self.sys_dlls = [b"ntdll.dll", b"kernel32.dll"] - self.pe_entry_point = 0 - self.sizeOfStackReserve = 0 - - if not self.ql.code: - self.pe = pefile.PE(self.path, fast_load=True) - self.is_driver = self.pe.is_driver() - if self.is_driver == True: - self.init_dlls.append(b"ntoskrnl.exe") - self.sys_dlls.append(b"ntoskrnl.exe") - - if self.ql.archtype == 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) - self.dll_address = int(self.ql.os.profile.get("OS32", "dll_address"), 16) - self.entry_point = int(self.ql.os.profile.get("OS32", "entry_point"), 16) - 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: - 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) - self.dll_address = int(self.ql.os.profile.get("OS64", "dll_address"), 16) - self.entry_point = int(self.ql.os.profile.get("OS64", "entry_point"), 16) - self.ql.os.heap_base_address = int(self.ql.os.profile.get("OS64", "heap_address"), 16) - self.ql.os.heap_base_size = int(self.ql.os.profile.get("OS64", "heap_size"), 16) - self.structure_last_addr = GS_SEGMENT_ADDR - - self.dlls = {} self.import_symbols = {} self.export_symbols = {} self.import_address_table = {} self.ldr_list = [] self.pe_image_address = 0 - self.pe_image_address_size = 0 + self.pe_image_size = 0 self.dll_size = 0 self.dll_last_address = self.dll_address - # compatible with ql.__enable_bin_patch() + + # not used, but here to remain compatible with ql.do_bin_patch self.load_address = 0 - self.ql.os.heap = QlMemoryHeap(self.ql, self.ql.os.heap_base_address, self.ql.os.heap_base_address + self.ql.os.heap_base_size) - self.ql.os.setupComponents() - self.ql.os.entry_point = self.entry_point - cmdline = (str(self.ql.os.userprofile)) + "Desktop\\" + self.ql.targetname - self.filepath = bytes(cmdline + "\x00", "utf-8") - for arg in self.argv[1:]: - if ' ' in arg: - cmdline += f' "{arg}"' - else: - cmdline += f' {arg}' - cmdline += "\x00" - self.cmdline = bytes(cmdline, "utf-8") - self.load() + cmdline = ntpath.join(self.ql.os.userprofile, 'Desktop', self.ql.targetname) + cmdargs = ' '.join(f'"{arg}"' if ' ' in arg else arg for arg in self.argv[1:]) + + self.filepath = bytes(f'{cmdline}\x00', "utf-8") + self.cmdline = bytes(f'{cmdline} {cmdargs}\x00', "utf-8") - def init_thread_information_block(self): - super().init_tib() - super().init_peb() - super().init_ldr_data() - super().init_exports() + self.load(pe) - def load(self): + def load(self, pe: Optional[pefile.PE]): # set stack pointer self.ql.log.info("Initiate stack address at 0x%x " % self.stack_address) self.ql.mem.map(self.stack_address, self.stack_size, info="[stack]") - if self.path and not self.ql.code: - # for simplicity, no image base relocation - self.pe_image_address = self.pe.OPTIONAL_HEADER.ImageBase - self.pe_image_address_size = self.ql.mem.align_up(self.pe.OPTIONAL_HEADER.SizeOfImage) + if pe is not None: + image_name = os.path.basename(self.path) + image_base = pe.OPTIONAL_HEADER.ImageBase + image_size = self.ql.mem.align_up(pe.OPTIONAL_HEADER.SizeOfImage) - if self.pe_image_address + self.pe_image_address_size > self.ql.os.heap_base_address: - # pe reloc - self.pe_image_address = self.image_address - self.pe.relocate_image(self.image_address) + # if default base address is taken, use the one specified in profile + if not self.ql.mem.is_available(image_base, image_size): + image_base = self.image_address + pe.relocate_image(image_base) - self.entry_point = self.pe_entry_point = self.pe_image_address + self.pe.OPTIONAL_HEADER.AddressOfEntryPoint - self.sizeOfStackReserve = self.pe.OPTIONAL_HEADER.SizeOfStackReserve - self.ql.log.info("Loading %s to 0x%x" % (self.path, self.pe_image_address)) - self.ql.log.info("PE entry point at 0x%x" % self.entry_point) - self.images.append(Image(self.pe_image_address, self.pe_image_address + self.pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, self.path)) + self.entry_point = image_base + pe.OPTIONAL_HEADER.AddressOfEntryPoint + self.pe_image_address = image_base + self.pe_image_size = image_size - # Stack should not init at the very bottom. Will cause errors with Dlls - sp = self.stack_address + self.stack_size - 0x1000 + self.ql.log.info(f'Loading {self.path} to {image_base:#x}') + self.ql.log.info(f'PE entry point at {self.entry_point:#x}') - if self.ql.archtype == QL_ARCH.X86: - self.ql.reg.esp = sp - self.ql.reg.ebp = sp + self.ql.mem.map(image_base, image_size, info=f'[{image_name}]') + self.images.append(Image(image_base, image_base + pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, os.path.abspath(self.path))) - if self.pe.is_dll(): - self.ql.log.debug('Setting up DllMain args') - load_addr_bytes = self.pe_image_address.to_bytes(length=4, byteorder='little') + if self.is_driver: + self.init_driver_object() + self.init_registry_path() + self.init_eprocess() + self.init_ki_user_shared_data() - self.ql.log.debug('Writing 0x%08X (IMAGE_BASE) to [ESP+4](0x%08X)' % (self.pe_image_address, sp + 0x4)) - self.ql.mem.write(sp + 0x4, load_addr_bytes) + # set IRQ Level in CR8 to PASSIVE_LEVEL + self.ql.arch.regs.write(UC_X86_REG_CR8, 0) - 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')) + # setup CR4, enabling: DE, PSE, PAE, MCE, PGE, OSFXSR and OSXMMEXCPT. + # some drivers may check this at initialized + self.ql.arch.regs.write(UC_X86_REG_CR4, 0b0000011011111000) - elif self.ql.archtype == QL_ARCH.X8664: - self.ql.reg.rsp = sp - self.ql.reg.rbp = sp + else: + # initialize thread information block + self.init_teb() + self.init_peb() + self.init_ldr_data() + self.init_exports(pe) - if self.pe.is_dll(): - self.ql.log.debug('Setting up DllMain args') + # add image to ldr table + self.add_ldr_data_table_entry(image_name) - self.ql.log.debug('Setting RCX (arg1) to %16X (IMAGE_BASE)' % (self.pe_image_address)) - self.ql.reg.rcx = self.pe_image_address + pe.parse_data_directories() + + # done manipulating pe file; write its contents into memory + self.ql.mem.write(image_base, bytes(pe.get_memory_mapped_image())) - self.ql.log.debug('Setting RDX (arg2) to 1 (DLL_PROCESS_ATTACH)') - self.ql.reg.rdx = 1 - else: - raise QlErrorArch("Unknown ql.arch") - - # if this is NOT a driver, init tib/peb/ldr - if not self.is_driver: # userland program - self.init_thread_information_block() - else: # Windows kernel driver - super().init_driver_object() - super().init_registry_path() - super().init_eprocess() - super().init_ki_user_shared_data() - - # setup IRQ Level in CR8 to PASSIVE_LEVEL (0) - self.ql.reg.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.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: - # 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: - # 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')) - - self.ql.log.debug('Setting RCX (arg1) to %16X (PDRIVER_OBJECT)' % (self.ql.loader.driver_object_address)) - self.ql.log.debug('Setting RDX (arg2) to %16X (PUNICODE_STRING)' % (self.ql.loader.regitry_path_address)) - - # setup args for DriverEntry() - self.ql.os.fcall = self.ql.os.fcall_select(CDECL) - self.ql.os.fcall.writeParams(((POINTER, self.ql.loader.driver_object_address), (POINTER, self.ql.loader.regitry_path_address))) - - # mmap PE file into memory - self.ql.mem.map(self.pe_image_address, self.ql.mem.align_up(self.pe_image_address_size), info="[PE]") - self.pe.parse_data_directories() - data = bytearray(self.pe.get_memory_mapped_image()) - self.ql.mem.write(self.pe_image_address, bytes(data)) - if self.is_driver: - # 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) - while SecurityCookie_value == default_security_cookie_value: - SecurityCookie_value = secrets.token_bytes(self.ql.pointersize) - # rol rcx, 10h (rcx: cookie) - # test cx, 0FFFFh - SecurityCookie_value_array = bytearray(SecurityCookie_value) - # Sanity question: We are always little endian, right? - SecurityCookie_value_array[-2:] = b'\x00\x00' - SecurityCookie_value = bytes(SecurityCookie_value_array) - self.ql.mem.write(self.pe_image_address+SecurityCookie_rva, SecurityCookie_value) - - # Add main PE to ldr_data_table - mod_name = os.path.basename(self.path) - self.dlls[mod_name] = self.pe_image_address - # only userland code need LDR table - if not self.is_driver: - super().add_ldr_data_table_entry(mod_name) + # security cookie can be written only after image has been loaded to memory + self.init_security_cookie(pe, image_base) + + # Stack should not init at the very bottom. Will cause errors with Dlls + top_of_stack = self.stack_address + self.stack_size - 0x1000 + + if self.ql.arch.type == QL_ARCH.X86: + bp_reg = 'ebp' + sp_reg = 'esp' + elif self.ql.arch.type == QL_ARCH.X8664: + bp_reg = 'rbp' + sp_reg = 'rsp' + else: + raise QlErrorArch(f'unexpected arch type: {self.ql.arch.type}') + + # we are about to load some dlls and call their DllMain functions. + # the stack should be set first + self.ql.arch.regs.write(bp_reg, top_of_stack) + self.ql.arch.regs.write(sp_reg, top_of_stack) # load system dlls - sys_dlls = self.sys_dlls - for each in sys_dlls: + for each in self.sys_dlls: super().load_dll(each, self.is_driver) + # parse directory entry import - if self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']].VirtualAddress != 0: - for entry in self.pe.DIRECTORY_ENTRY_IMPORT: - dll_name = str(entry.dll.lower(), 'utf-8', 'ignore') - super().load_dll(entry.dll, self.is_driver) - for imp in entry.imports: - # fix IAT - # ql.log.info(imp.name) - # ql.log.info(self.import_address_table[imp.name]) - if imp.name: - try: - addr = self.import_address_table[dll_name][imp.name] - except KeyError: - self.ql.log.debug("Error in loading function %s" % imp.name.decode()) - continue - else: - addr = self.import_address_table[dll_name][imp.ordinal] - - if self.ql.archtype == QL_ARCH.X86: - address = self.ql.pack32(addr) - else: - address = self.ql.pack64(addr) - self.ql.mem.write(imp.address, address) - - self.ql.log.debug("Done with loading %s" % self.path) - self.ql.os.entry_point = self.entry_point - self.ql.os.pid = 101 - - elif self.ql.code: + self.ql.log.debug(f'Init imports for {self.path}') + super().init_imports(pe, self.is_driver) + + self.ql.log.debug(f'Done loading {self.path}') + + if pe.is_driver(): + args = ( + (POINTER, self.driver_object_address), + (POINTER, self.regitry_path_address) + ) + + self.ql.log.debug('Setting up call frame for DriverEntry:') + self.ql.log.debug(f' PDRIVER_OBJECT DriverObject : {args[0][1]:#010x}') + self.ql.log.debug(f' PUNICODE_STRING RegistryPath : {args[1][1]:#010x}') + + # We know that a driver will return, so if the user did not configure stop + # options, write a sentinel return value + ret = None if self.ql.stop_options else self.ql.stack_write(0, 0xdeadc0de) + + # set up call frame for DriverEntry + self.ql.os.fcall.call_native(self.entry_point, args, ret) + + elif pe.is_dll(): + args = ( + (POINTER, image_base), + (DWORD, 1), # DLL_PROCESS_ATTACH + (POINTER, 0) + ) + + self.ql.log.debug('Setting up call frame for DllMain:') + self.ql.log.debug(f' HINSTANCE hinstDLL : {args[0][1]:#010x}') + self.ql.log.debug(f' DWORD fdwReason : {args[1][1]:#010x}') + self.ql.log.debug(f' LPVOID lpReserved : {args[2][1]:#010x}') + + # set up call frame for DllMain + self.ql.os.fcall.call_native(self.entry_point, args, None) + + elif pe is None: 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 - - # load shellcode in - self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode_base]") - # rewrite entrypoint for windows shellcode - self.ql.os.entry_point = self.entry_point - self.ql.os.pid = 101 + self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode]") + + self.init_teb() + self.init_peb() + self.init_ldr_data() + + # write shellcode to memory self.ql.mem.write(self.entry_point, self.ql.code) - - self.init_thread_information_block() + + top_of_stack = self.stack_address + self.stack_size + + if self.ql.arch.type == QL_ARCH.X86: + bp_reg = 'ebp' + sp_reg = 'esp' + elif self.ql.arch.type == QL_ARCH.X8664: + bp_reg = 'rbp' + sp_reg = 'rsp' + else: + raise QlErrorArch(f'unexpected arch type: {self.ql.arch.type}') + + self.ql.arch.regs.write(bp_reg, top_of_stack) + self.ql.arch.regs.write(sp_reg, top_of_stack) + # load dlls for each in self.init_dlls: - super().load_dll(each) + super().load_dll(each, self.is_driver) # 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 + +class ShowProgress: + """Display a progress animation while performing a time + consuming task. + + Example: + >>> with ShowProgress(0.1): + ... do_some_time_consuming_task() + """ + + def __init__(self, interval: float) -> None: + import sys + from threading import Thread, Event + + # animation frames; any sequence of chars or strings may be used, as long + # as they are of the same length. e.g. ['> ', '>> ', ' >> ', ' >>', ' >', ' '] + frames = r'/-\|' + stream = sys.stderr + + def show_animation(): + i = 0 + + while not self.stopped.wait(interval): + # TODO: find a proper way to use the logger for that + print(f'[{frames[i % len(frames)]}]', end='\r', flush=True, file=stream) + i += 1 + + def show_nothing(): + pass + + # avoid flooding log files with animation frames + action = show_animation if stream.isatty() else show_nothing + + self.stopped = Event() + self.thread = Thread(target=action) + + def __enter__(self): + self.thread.start() + + return self + + def __exit__(self, *args) -> None: + self.stopped.set() diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 7bcaec500..a20bf9444 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -119,7 +119,7 @@ def map_and_load(self, path: str, context: UefiContext, exec_now: bool=False): self.install_loaded_image_protocol(image_base, image_size) - # this would be used later be os.find_containing_image + # this would be used later be loader.find_containing_image self.images.append(Image(image_base, image_base + image_size, path)) # update next memory slot to allow sequencial loading. its availability @@ -192,8 +192,8 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context: 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 @@ def run(self): 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/log.py b/qiling/log.py new file mode 100644 index 000000000..d085266ed --- /dev/null +++ b/qiling/log.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import copy +import logging +import os +import re +import weakref + +from typing import Optional, TextIO + +from qiling.const import QL_VERBOSE + +QL_INSTANCE_ID = 114514 + +FMT_STR = '%(levelname)s\t%(message)s' + +class COLOR: + WHITE = '\033[37m' + CRIMSON = '\033[31m' + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + MAGENTA = '\033[95m' + CYAN = '\033[96m' + ENDC = '\033[0m' + +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 = weakref.proxy(ql) + + def get_level_tag(self, level: str) -> str: + return self.__level_tag[level] + + def get_thread_tag(self, thread: str) -> str: + return thread + + def format(self, record: logging.LogRecord): + # In case we have multiple formatters, we have to keep a copy of the record. + record = copy.copy(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: + tid = f'' + else: + 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 QlColoredFormatter(QlBaseFormatter): + __level_color = { + 'WARNING' : COLOR.YELLOW, + 'INFO' : COLOR.BLUE, + 'DEBUG' : COLOR.MAGENTA, + 'CRITICAL' : COLOR.CRIMSON, + 'ERROR' : COLOR.RED + } + + def get_level_tag(self, level: str) -> str: + s = super().get_level_tag(level) + + return f'{self.__level_color[level]}{s}{COLOR.ENDC}' + + def get_thread_tag(self, tid: str) -> str: + s = super().get_thread_tag(tid) + + return f'{COLOR.GREEN}{s}{COLOR.ENDC}' + +class RegexFilter(logging.Filter): + def update_filter(self, regexp: str): + self._filter = re.compile(regexp) + + def filter(self, record: logging.LogRecord): + msg = record.getMessage() + + return self._filter.match(msg) is not None + +def resolve_logger_level(verbose: QL_VERBOSE) -> int: + return { + QL_VERBOSE.DISABLED : logging.CRITICAL, + QL_VERBOSE.OFF : logging.WARNING, + QL_VERBOSE.DEFAULT : logging.INFO, + QL_VERBOSE.DEBUG : logging.DEBUG, + QL_VERBOSE.DISASM : logging.DEBUG, + QL_VERBOSE.DUMP : logging.DEBUG + }[verbose] + +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()) + +def setup_logger(ql, log_file: Optional[str], console: bool, log_override: Optional[logging.Logger], log_plain: bool): + global QL_INSTANCE_ID + + # If there is an override for our logger, then use it. + if log_override is not None: + log = log_override + else: + # We should leave the root logger untouched. + log = logging.getLogger(f'qiling{QL_INSTANCE_ID}') + QL_INSTANCE_ID += 1 + + # Disable propagation to avoid duplicate output. + log.propagate = False + # Clear all handlers and filters. + log.handlers = [] + log.filters = [] + + # Do we have console output? + if console: + handler = logging.StreamHandler() + + if log_plain or not __is_color_terminal(handler.stream): + formatter = QlBaseFormatter(ql, FMT_STR) + else: + formatter = QlColoredFormatter(ql, FMT_STR) + + handler.setFormatter(formatter) + log.addHandler(handler) + else: + 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 = QlBaseFormatter(ql, FMT_STR) + handler.setFormatter(formatter) + log.addHandler(handler) + + log.setLevel(logging.INFO) + + return log + +__all__ = ['RegexFilter', 'setup_logger', 'resolve_logger_level'] diff --git a/qiling/os/blob/__init__.py b/qiling/os/blob/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index 22562a34c..02e6f94d3 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -4,8 +4,8 @@ # from qiling import Qiling -from qiling.cc import QlCC, intel, arm, mips -from qiling.const import QL_ARCH +from qiling.cc import QlCC, intel, arm, mips, riscv, ppc +from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.os import QlOs @@ -17,18 +17,24 @@ class QlOsBlob(QlOs): resolve_fcall_params(), heap or add_fs_mapper() are based on os. To keep the consistence of api usage, QlOsBlob is introduced and placed at its loader temporarily. """ + + type = QL_OS.BLOB + def __init__(self, ql: Qiling): super(QlOsBlob, self).__init__(ql) self.ql = ql cc: QlCC = { - QL_ARCH.X86 : intel.cdecl, - QL_ARCH.X8664 : intel.amd64, - QL_ARCH.ARM : arm.aarch32, - QL_ARCH.ARM64 : arm.aarch64, - QL_ARCH.MIPS : mips.mipso32 - }[ql.archtype](ql) + QL_ARCH.X86 : intel.cdecl, + QL_ARCH.X8664 : intel.amd64, + QL_ARCH.ARM : arm.aarch32, + QL_ARCH.ARM64 : arm.aarch64, + QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.RISCV : riscv.riscv, + QL_ARCH.RISCV64 : riscv.riscv, + QL_ARCH.PPC : ppc.ppc, + }[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 170768ef4..fb7f55c17 100644 --- a/qiling/os/dos/dos.py +++ b/qiling/os/dos/dos.py @@ -10,7 +10,7 @@ from unicorn import UcError from qiling import Qiling -from qiling.const import QL_INTERCEPT +from qiling.const import QL_OS, QL_INTERCEPT from qiling.os.os import QlOs from .interrupts import handlers @@ -29,6 +29,8 @@ class Flags(IntEnum): IOPL = (3 << 12) # io privilege class QlOsDos(QlOs): + type = QL_OS.DOS + def __init__(self, ql: Qiling): super(QlOsDos, self).__init__(ql) @@ -56,10 +58,10 @@ def __del__(self): 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.eflags = self.ql.arch.regs.eflags & (~fl) | (fl * val) def test_flags(self, fl): - return self.ql.reg.ef & fl == fl + return self.ql.arch.regs.eflags & fl == fl def set_cf(self): self.set_flag_value(Flags.CF, 0b1) @@ -76,7 +78,7 @@ def clear_zf(self): 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 837ddad6a..9b887f0b2 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 dc99e6ebe..1714111ab 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 e237c5f74..87110d28f 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 842784122..215d20c2f 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 fe31a3fc3..4513e0200 100644 --- a/qiling/os/dos/interrupts/int19.py +++ b/qiling/os/dos/interrupts/int19.py @@ -7,7 +7,7 @@ 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 ed301e031..4f7edc1f9 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 59480d51d..14bc51fdb 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 153bcce26..a87094941 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 0d51dd3f9..fe4d44740 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 f8aa1960e..c9bd95d2c 100644 --- a/qiling/os/fcall.py +++ b/qiling/os/fcall.py @@ -32,8 +32,8 @@ def __init__(self, ql: Qiling, cc: QlCC, accessors: Mapping[int, Accessor] = {}) self.cc = cc def __make_accessor(nbits: int) -> Accessor: - reader = lambda si: cc.getRawParam(si, nbits or None) - writer = lambda si, val: cc.setRawParam(si, val, nbits or None) + reader = lambda si: cc.getRawParam(si, nbits) + writer = lambda si, val: cc.setRawParam(si, val, nbits) nslots = cc.getNumSlots(nbits) return (reader, writer, nslots) @@ -131,7 +131,7 @@ def call(self, func: CallHook, proto: Mapping[str, Any], params: Mapping[str, An """ 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 @@ def call_native(self, addr: int, args: Sequence[Tuple[Any, int]], ret: Optional[ 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 e865416dc..e0218d4da 100644 --- a/qiling/os/freebsd/freebsd.py +++ b/qiling/os/freebsd/freebsd.py @@ -4,12 +4,15 @@ # 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.const import QL_OS from qiling.os.posix.posix import QlOsPosix class QlOsFreebsd(QlOsPosix): + type = QL_OS.FREEBSD + def __init__(self, ql): super(QlOsFreebsd, self).__init__(ql) @@ -18,13 +21,16 @@ def __init__(self, ql): 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): + def hook_syscall(self, ql): return self.load_syscall() @@ -41,7 +47,7 @@ def run(self): else: if self.ql.loader.elf_entry != self.ql.loader.entry_point: self.ql.emu_start(self.ql.loader.entry_point, self.ql.loader.elf_entry, self.ql.timeout) - self.ql.enable_lib_patch() + self.ql.do_lib_patch() self.ql.emu_start(self.ql.loader.elf_entry, self.exit_point, self.ql.timeout, self.ql.count) diff --git a/qiling/os/freebsd/map_syscall.py b/qiling/os/freebsd/map_syscall.py index d9b67f6df..a9265f7da 100644 --- a/qiling/os/freebsd/map_syscall.py +++ b/qiling/os/freebsd/map_syscall.py @@ -6,9 +6,15 @@ 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: - return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' +def get_syscall_mapper(archtype: QL_ARCH): + syscall_table = { + QL_ARCH.X8664 : x8664_syscall_table + }[archtype] + + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + + return __mapper x8664_syscall_table = { 0: 'syscall', diff --git a/qiling/os/freebsd/syscall.py b/qiling/os/freebsd/syscall.py index 46d9624f0..77691936a 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 608a02e2f..aa2e6cf64 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -68,78 +68,90 @@ def add_hook(self, cb, intercept, userdata): 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 + + # PPC + elif self.ql.arch.type== QL_ARCH.PPC: + return self.ql.arch.regs.lr # 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 + + # PPC + elif self.ql.arch.type== QL_ARCH.PPC: 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 + + # PPC + elif self.ql.arch.type== QL_ARCH.PPC: + self.ql.arch.regs.lr = 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 +180,14 @@ def call_enter(self): 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 - - # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: - self.ql.reg.arch_pc = self.ret_pc - - # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: - self.ql.reg.arch_pc = self.ret_pc - - # X86 - elif self.ql.archtype== QL_ARCH.X86: - self.ql.reg.arch_pc = self.ret_pc - - # X8664 - elif self.ql.archtype== QL_ARCH.X8664: - self.ql.reg.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 @@ -211,7 +200,7 @@ def call_exit(self): else: onexit_cb(self.ql, onexit_userdata) - self.ret() + self.ql.arch.regs.arch_pc = self.ret_pc class HookFuncRel(HookFunc): @@ -241,10 +230,10 @@ def enable(self): 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 +244,21 @@ def __init__(self, ql, fucname, got, gotidx, load_base): 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 +480,7 @@ def __init__(self, ql, symtab, endian = 0): 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 +525,19 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 +545,10 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 +556,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 +564,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 +572,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 +580,14 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 +595,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 @@ -614,6 +603,14 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): ins = b'\x00\x01' self.add_function_hook = self.add_function_hook_relocation + # PowerPC + elif self.ql.arch.type== QL_ARCH.PPC: + self.GLOB_DAT = 21 + self.JMP_SLOT = 22 + # nop + ins = b'\x60\x00\x00\x00' + self.add_function_hook = self.add_function_hook_relocation + self._parse() if self.rel != None: self.show_relocation(self.rel) @@ -626,7 +623,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): 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 +697,9 @@ def parse_program_header64(self): 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 +765,9 @@ def parse_dynamic32(self): 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 +783,19 @@ def _parse(self): 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 +856,31 @@ def _parse(self): 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 +899,7 @@ def show_dynsym_name(self, s, e): 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 +929,9 @@ def _hook_function(self, fn, r, cb, pos, userdata): 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/futex.py b/qiling/os/linux/futex.py index 1f17b9b23..ef78db4f3 100644 --- a/qiling/os/linux/futex.py +++ b/qiling/os/linux/futex.py @@ -10,24 +10,25 @@ from queue import Queue class QlLinuxFutexManagement: - + FUTEX_BITSET_MATCH_ANY = 0xffffffff - + def __init__(self): self._wait_list = {} - + @property def wait_list(self): return self._wait_list - + def futex_wait(self, ql, uaddr, t, val, bitset=FUTEX_BITSET_MATCH_ANY): + EAGAIN = 11 def _sched_wait_event(cur_thread): ql.log.debug(f"Wait for notifications.") event.wait() uaddr_value = ql.unpack32(ql.mem.read(uaddr, 4)) if uaddr_value != val: ql.log.debug(f"uaddr: {hex(uaddr_value)} != {hex(val)}") - return -1 + return -EAGAIN ql.emu_stop() if uaddr not in self.wait_list.keys(): self.wait_list[uaddr] = Queue() @@ -35,7 +36,7 @@ def _sched_wait_event(cur_thread): self.wait_list[uaddr].put((bitset, t, event)) t.sched_cb = _sched_wait_event return 0 - + def get_futex_wake_list(self, ql, addr, number, bitset=FUTEX_BITSET_MATCH_ANY): wakes = [] if addr not in self.wait_list or number == 0: diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 00d1de00c..ed95a7ecd 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -3,37 +3,40 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -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.cc import QlCC, intel, arm, mips, riscv -from qiling.const import QL_ARCH, QL_INTERCEPT +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, ppc +from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * -from qiling.os.posix.const import NR_OPEN from qiling.os.posix.posix import QlOsPosix from . import futex from . import thread class QlOsLinux(QlOsPosix): + type = QL_OS.LINUX + def __init__(self, ql: Qiling): super(QlOsLinux, self).__init__(ql) self.ql = ql cc: QlCC = { - QL_ARCH.X86 : intel.cdecl, - QL_ARCH.X8664 : intel.amd64, - QL_ARCH.ARM : arm.aarch32, - QL_ARCH.ARM64 : arm.aarch64, - QL_ARCH.MIPS : mips.mipso32, - QL_ARCH.RISCV : riscv.riscv, - QL_ARCH.RISCV64: riscv.riscv, - }[ql.archtype](ql) + QL_ARCH.X86 : intel.cdecl, + QL_ARCH.X8664 : intel.amd64, + QL_ARCH.ARM : arm.aarch32, + QL_ARCH.ARM64 : arm.aarch64, + QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.RISCV : riscv.riscv, + QL_ARCH.RISCV64 : riscv.riscv, + QL_ARCH.PPC : ppc.ppc, + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) @@ -44,70 +47,81 @@ def __init__(self, ql: Qiling): 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_linux_traps(self.ql, { + 'memory_barrier': 0xffff0fa0, + 'cmpxchg': 0xffff0fc0, + 'get_tls': 0xffff0fe0 + }) # 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.arch.type == QL_ARCH.RISCV64: 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.PPC: 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() - def add_function_hook(self, fn: str, cb: Callable, intercept: QL_INTERCEPT): - self.ql.os.function_hook.add_function_hook(fn, cb, intercept) - - def register_function_after_load(self, function): if function not in self.function_after_load_list: self.function_after_load_list.append(function) @@ -138,10 +152,10 @@ def run(self): 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() + self.ql.do_lib_patch() self.run_function_after_load() self.ql.loader.skip_exit_check = False self.ql.write_exit_trap() @@ -149,9 +163,9 @@ def run(self): self.ql.emu_start(self.ql.loader.elf_entry, self.exit_point, self.ql.timeout, self.ql.count) except UcError: - # TODO: this is bad We need a better approach for this - #if self.ql.output != QL_OUTPUT.DEBUG: - # return - self.emu_error() raise + + # display summary + for entry in self.stats.summary(): + self.ql.log.debug(entry) diff --git a/qiling/os/linux/map_syscall.py b/qiling/os/linux/map_syscall.py index a4e4c591a..3d0bcead8 100644 --- a/qiling/os/linux/map_syscall.py +++ b/qiling/os/linux/map_syscall.py @@ -11,18 +11,22 @@ from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF -def map_syscall(ql, syscall_num): +def get_syscall_mapper(archtype: QL_ARCH): syscall_table = { - QL_ARCH.ARM64: arm64_syscall_table, - QL_ARCH.ARM: arm_syscall_table, - QL_ARCH.X8664: x8664_syscall_table, - QL_ARCH.X86: x86_syscall_table, - QL_ARCH.MIPS: mips_syscall_table, - QL_ARCH.RISCV: riscv32_syscall_table, - QL_ARCH.RISCV64: riscv64_syscall_table, - }[ql.archtype] - - return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + QL_ARCH.ARM64 : arm64_syscall_table, + QL_ARCH.ARM : arm_syscall_table, + QL_ARCH.X8664 : x8664_syscall_table, + QL_ARCH.X86 : x86_syscall_table, + QL_ARCH.MIPS : mips_syscall_table, + QL_ARCH.RISCV : riscv32_syscall_table, + QL_ARCH.RISCV64 : riscv64_syscall_table, + QL_ARCH.PPC : ppc_syscall_table + }[archtype] + + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + + return __mapper arm_syscall_table = { 0: "restart_syscall", @@ -428,7 +432,7 @@ def map_syscall(ql, syscall_num): 445: "landlock_add_rule", 446: "landlock_restrict_self", 448: "process_mrelease", - 983402: "cacheflush", + 983042: "cacheflush", 983045: "set_tls", } @@ -2547,3 +2551,421 @@ def map_syscall(ql, syscall_num): 446: "landlock_restrict_self", 448: "process_mrelease", } + +ppc_syscall_table = { + 0: "restart_syscall", + 1: "exit", + 2: "fork", + 3: "read", + 4: "write", + 5: "open", + 6: "close", + 7: "waitpid", + 8: "creat", + 9: "link", + 10: "unlink", + 11: "execve", + 12: "chdir", + 13: "time", + 14: "mknod", + 15: "chmod", + 16: "lchown", + 17: "break", + 18: "oldstat", + 19: "lseek", + 20: "getpid", + 21: "mount", + 22: "umount", + 23: "setuid", + 24: "getuid", + 25: "stime", + 26: "ptrace", + 27: "alarm", + 28: "oldfstat", + 29: "pause", + 30: "utime", + 31: "stty", + 32: "gtty", + 33: "access", + 34: "nice", + 35: "ftime", + 36: "sync", + 37: "kill", + 38: "rename", + 39: "mkdir", + 40: "rmdir", + 41: "dup", + 42: "pipe", + 43: "times", + 44: "prof", + 45: "brk", + 46: "setgid", + 47: "getgid", + 48: "signal", + 49: "geteuid", + 50: "getegid", + 51: "acct", + 52: "umount2", + 53: "lock", + 54: "ioctl", + 55: "fcntl", + 56: "mpx", + 57: "setpgid", + 58: "ulimit", + 59: "oldolduname", + 60: "umask", + 61: "chroot", + 62: "ustat", + 63: "dup2", + 64: "getppid", + 65: "getpgrp", + 66: "setsid", + 67: "sigaction", + 68: "sgetmask", + 69: "ssetmask", + 70: "setreuid", + 71: "setregid", + 72: "sigsuspend", + 73: "sigpending", + 74: "sethostname", + 75: "setrlimit", + 76: "getrlimit", + 77: "getrusage", + 78: "gettimeofday", + 79: "settimeofday", + 80: "getgroups", + 81: "setgroups", + 82: "select", + 83: "symlink", + 84: "oldlstat", + 85: "readlink", + 86: "uselib", + 87: "swapon", + 88: "reboot", + 89: "readdir", + 90: "mmap", + 91: "munmap", + 92: "truncate", + 93: "ftruncate", + 94: "fchmod", + 95: "fchown", + 96: "getpriority", + 97: "setpriority", + 98: "profil", + 99: "statfs", + 100: "fstatfs", + 101: "ioperm", + 102: "socketcall", + 103: "syslog", + 104: "setitimer", + 105: "getitimer", + 106: "stat", + 107: "lstat", + 108: "fstat", + 109: "olduname", + 110: "iopl", + 111: "vhangup", + 112: "idle", + 113: "vm86", + 114: "wait4", + 115: "swapoff", + 116: "sysinfo", + 117: "ipc", + 118: "fsync", + 119: "sigreturn", + 120: "clone", + 121: "setdomainname", + 122: "uname", + 123: "modify_ldt", + 124: "adjtimex", + 125: "mprotect", + 126: "sigprocmask", + 127: "create_module", + 128: "init_module", + 129: "delete_module", + 130: "get_kernel_syms", + 131: "quotactl", + 132: "getpgid", + 133: "fchdir", + 134: "bdflush", + 135: "sysfs", + 136: "personality", + 137: "afs_syscall", + 138: "setfsuid", + 139: "setfsgid", + 140: "_llseek", + 141: "getdents", + 142: "_newselect", + 143: "flock", + 144: "msync", + 145: "readv", + 146: "writev", + 147: "getsid", + 148: "fdatasync", + 149: "_sysctl", + 150: "mlock", + 151: "munlock", + 152: "mlockall", + 153: "munlockall", + 154: "sched_setparam", + 155: "sched_getparam", + 156: "sched_setscheduler", + 157: "sched_getscheduler", + 158: "sched_yield", + 159: "sched_get_priority_max", + 160: "sched_get_priority_min", + 161: "sched_rr_get_interval", + 162: "nanosleep", + 163: "mremap", + 164: "setresuid", + 165: "getresuid", + 166: "query_module", + 167: "poll", + 168: "nfsservctl", + 169: "setresgid", + 170: "getresgid", + 171: "prctl", + 172: "rt_sigreturn", + 173: "rt_sigaction", + 174: "rt_sigprocmask", + 175: "rt_sigpending", + 176: "rt_sigtimedwait", + 177: "rt_sigqueueinfo", + 178: "rt_sigsuspend", + 179: "pread64", + 180: "pwrite64", + 181: "chown", + 182: "getcwd", + 183: "capget", + 184: "capset", + 185: "sigaltstack", + 186: "sendfile", + 187: "getpmsg", + 188: "putpmsg", + 189: "vfork", + 190: "ugetrlimit", + 191: "readahead", + 192: "mmap2", + 193: "truncate64", + 194: "ftruncate64", + 195: "stat64", + 196: "lstat64", + 197: "fstat64", + 198: "pciconfig_read", + 199: "pciconfig_write", + 200: "pciconfig_iobase", + 201: "multiplexer", + 202: "getdents64", + 203: "pivot_root", + 204: "fcntl64", + 205: "madvise", + 206: "mincore", + 207: "gettid", + 208: "tkill", + 209: "setxattr", + 210: "lsetxattr", + 211: "fsetxattr", + 212: "getxattr", + 213: "lgetxattr", + 214: "fgetxattr", + 215: "listxattr", + 216: "llistxattr", + 217: "flistxattr", + 218: "removexattr", + 219: "lremovexattr", + 220: "fremovexattr", + 221: "futex", + 222: "sched_setaffinity", + 223: "sched_getaffinity", + 225: "tuxcall", + 226: "sendfile64", + 227: "io_setup", + 228: "io_destroy", + 229: "io_getevents", + 230: "io_submit", + 231: "io_cancel", + 232: "set_tid_address", + 233: "fadvise64", + 234: "exit_group", + 235: "lookup_dcookie", + 236: "epoll_create", + 237: "epoll_ctl", + 238: "epoll_wait", + 239: "remap_file_pages", + 240: "timer_create", + 241: "timer_settime", + 242: "timer_gettime", + 243: "timer_getoverrun", + 244: "timer_delete", + 245: "clock_settime", + 246: "clock_gettime", + 247: "clock_getres", + 248: "clock_nanosleep", + 249: "swapcontext", + 250: "tgkill", + 251: "utimes", + 252: "statfs64", + 253: "fstatfs64", + 254: "fadvise64_64", + 255: "rtas", + 256: "sys_debug_setcontext", + 258: "migrate_pages", + 259: "mbind", + 260: "get_mempolicy", + 261: "set_mempolicy", + 262: "mq_open", + 263: "mq_unlink", + 264: "mq_timedsend", + 265: "mq_timedreceive", + 266: "mq_notify", + 267: "mq_getsetattr", + 268: "kexec_load", + 269: "add_key", + 270: "request_key", + 271: "keyctl", + 272: "waitid", + 273: "ioprio_set", + 274: "ioprio_get", + 275: "inotify_init", + 276: "inotify_add_watch", + 277: "inotify_rm_watch", + 278: "spu_run", + 279: "spu_create", + 280: "pselect6", + 281: "ppoll", + 282: "unshare", + 283: "splice", + 284: "tee", + 285: "vmsplice", + 286: "openat", + 287: "mkdirat", + 288: "mknodat", + 289: "fchownat", + 290: "futimesat", + 291: "fstatat64", + 292: "unlinkat", + 293: "renameat", + 294: "linkat", + 295: "symlinkat", + 296: "readlinkat", + 297: "fchmodat", + 298: "faccessat", + 299: "get_robust_list", + 300: "set_robust_list", + 301: "move_pages", + 302: "getcpu", + 303: "epoll_pwait", + 304: "utimensat", + 305: "signalfd", + 306: "timerfd_create", + 307: "eventfd", + 308: "sync_file_range2", + 309: "fallocate", + 310: "subpage_prot", + 311: "timerfd_settime", + 312: "timerfd_gettime", + 313: "signalfd4", + 314: "eventfd2", + 315: "epoll_create1", + 316: "dup3", + 317: "pipe2", + 318: "inotify_init1", + 319: "perf_event_open", + 320: "preadv", + 321: "pwritev", + 322: "rt_tgsigqueueinfo", + 323: "fanotify_init", + 324: "fanotify_mark", + 325: "prlimit64", + 326: "socket", + 327: "bind", + 328: "connect", + 329: "listen", + 330: "accept", + 331: "getsockname", + 332: "getpeername", + 333: "socketpair", + 334: "send", + 335: "sendto", + 336: "recv", + 337: "recvfrom", + 338: "shutdown", + 339: "setsockopt", + 340: "getsockopt", + 341: "sendmsg", + 342: "recvmsg", + 343: "recvmmsg", + 344: "accept4", + 345: "name_to_handle_at", + 346: "open_by_handle_at", + 347: "clock_adjtime", + 348: "syncfs", + 349: "sendmmsg", + 350: "setns", + 351: "process_vm_readv", + 352: "process_vm_writev", + 353: "finit_module", + 354: "kcmp", + 355: "sched_setattr", + 356: "sched_getattr", + 357: "renameat2", + 358: "seccomp", + 359: "getrandom", + 360: "memfd_create", + 361: "bpf", + 362: "execveat", + 363: "switch_endian", + 364: "userfaultfd", + 365: "membarrier", + 378: "mlock2", + 379: "copy_file_range", + 380: "preadv2", + 381: "pwritev2", + 382: "kexec_file_load", + 383: "statx", + 384: "pkey_alloc", + 385: "pkey_free", + 386: "pkey_mprotect", + 387: "rseq", + 388: "io_pgetevents", + 393: "semget", + 394: "semctl", + 395: "shmget", + 396: "shmctl", + 397: "shmat", + 398: "shmdt", + 399: "msgget", + 400: "msgsnd", + 401: "msgrcv", + 402: "msgctl", + 403: "clock_gettime64", + 404: "clock_settime64", + 405: "clock_adjtime64", + 406: "clock_getres_time64", + 407: "clock_nanosleep_time64", + 408: "timer_gettime64", + 409: "timer_settime64", + 410: "timerfd_gettime64", + 411: "timerfd_settime64", + 412: "utimensat_time64", + 413: "pselect6_time64", + 414: "ppoll_time64", + 416: "io_pgetevents_time64", + 417: "recvmmsg_time64", + 418: "mq_timedsend_time64", + 419: "mq_timedreceive_time64", + 420: "semtimedop_time64", + 421: "rt_sigtimedwait_time64", + 422: "futex_time64", + 423: "sched_rr_get_interval_time64", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", +} diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index 840c38ce9..c61951c97 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -11,30 +11,32 @@ from math import floor import ctypes -class timespec(ctypes.Structure): - _fields_ = [ - ("tv_sec", ctypes.c_uint64), - ("tv_nsec", ctypes.c_int64) - ] +def __get_timespec_struct(archbits: int): + long = getattr(ctypes, f'c_int{archbits}') + ulong = getattr(ctypes, f'c_uint{archbits}') - _pack_ = 8 + class timespec(ctypes.Structure): + _pack_ = archbits // 8 + _fields_ = ( + ('tv_sec', ulong), + ('tv_nsec', long) + ) -# Temporary dirty fix. -# TODO: Pack ctypes.Structure according to ql.archtype and ql.ostype? -class timespec32(ctypes.Structure): - _fields_ = [ - ("tv_sec", ctypes.c_uint32), - ("tv_nsec", ctypes.c_int32) - ] + return timespec - _pack_ = 4 +def __get_timespec_obj(archbits: int): + now = datetime.now().timestamp() + + tv_sec = floor(now) + tv_nsec = floor((now - floor(now)) * 1e6) + ts_cls = __get_timespec_struct(archbits) + + return ts_cls(tv_sec=tv_sec, tv_nsec=tv_nsec) -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,58 +47,45 @@ 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 +def ql_syscall_set_tls(ql: Qiling, address: int): + if ql.arch.type == QL_ARCH.ARM: + ql.arch.regs.c13_c0_3 = address + ql.mem.write_ptr(ql.arch.arm_get_tls_addr + 16, 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: - tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) - else: - tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) - ql.mem.write(clock_gettime_timespec, bytes(tp)) - - ql.log.debug("clock_gettime(clock_id = %d, timespec = 0x%x)" % (clock_gettime_clock_id, clock_gettime_timespec)) - +def ql_syscall_clock_gettime(ql: Qiling, clock_id: int, tp: int): + ts_obj = __get_timespec_obj(ql.arch.bits) + ql.mem.write(tp, bytes(ts_obj)) + return 0 -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: - tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) - else: - tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) - - if gettimeofday_tv != 0: - ql.mem.write(gettimeofday_tv, bytes(tp)) - if gettimeofday_tz != 0: - ql.mem.write(gettimeofday_tz, b'\x00' * 8) - regreturn = 0 - return regreturn +def ql_syscall_gettimeofday(ql: Qiling, tv: int, tz: int): + if tv: + ts_obj = __get_timespec_obj(ql.arch.bits) + ql.mem.write(tv, bytes(ts_obj)) + + if tz: + ql.mem.write(tz, b'\x00' * 8) + + return 0 diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index b4a7f273d..a5d0d8d24 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -5,15 +5,16 @@ 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 -from qiling.os.path import QlPathManager +from qiling.os.path import QlOsPath LINUX_THREAD_ID = 2000 @@ -25,7 +26,7 @@ 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 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ 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): @@ -139,8 +140,8 @@ def path(self): return self._path @path.setter - def path(self, p): - self._path = QlPathManager(self._ql, p.cwd) + def path(self, p: QlOsPath): + self._path = QlOsPath(self.ql.rootfs, p.cwd, self.ql.os.type) @property def log_file_fd(self): @@ -222,7 +223,7 @@ def _run(self): # 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 @@ def _run(self): 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 @@ def _run(self): 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 @@ def _run(self): 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 @@ def clone(self): 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 @@ def _on_stop(self): 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 set_thread_tls(self, tls_addr): - old_tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) + def __read_tls(self) -> Sequence[bytes]: + return [self.ql.os.gdtm.get_entry(i) for i in (12, 13, 14)] - self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, self.tls) + 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 = 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 @@ def set_thread_tls(self, tls_addr): if index == -1: index = self.ql.os.gdtm.get_free_idx(12) - if index == -1 or index < 12 or index > 14: - raise + 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.ql.os.gdtm.register_gdt_segment(index, base, limit, access) + self.ql.mem.write_ptr(tls_addr, index, 4) 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)) + raise + + self.tls = self.__read_tls() + self.__write_tls(old_tls) - 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.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 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ 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 @@ def set_thread_tls(self, tls_addr): # - 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 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ 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 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ 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 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ 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,16 +577,16 @@ def _clear_queued_msg(self): 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.do_lib_patch() self.ql.os.run_function_after_load() self.ql.loader.skip_exit_check = False self.ql.write_exit_trap() diff --git a/qiling/os/macos/events/macos.py b/qiling/os/macos/events/macos.py index 635f706e3..83145f6c0 100644 --- a/qiling/os/macos/events/macos.py +++ b/qiling/os/macos/events/macos.py @@ -269,10 +269,10 @@ def trigger(self): 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 @@ def sysctlbyname(self, name, namelen, oldp, oldlenp, new, newlen): 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 @@ def syscall(self, sysnum, params): 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 ff32c17c9..b967e0b40 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 15fb449e2..eb7f38ee2 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -4,13 +4,14 @@ # 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.const import QL_ARCH, QL_OS, QL_VERBOSE from qiling.os.fcall import QlFunctionCall from qiling.os.posix.posix import QlOsPosix from qiling.os.macos.events.macos import QlMacOSEvManager @@ -19,11 +20,13 @@ from qiling.os.macos.structs import kmod_info_t, POINTER64 class QlOsMacos(QlOsPosix): + type = QL_OS.MACOS + def __init__(self, ql: Qiling): 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 +57,59 @@ def load_kext(self): 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 +134,8 @@ def load_kext(self): 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,23 +144,26 @@ def load(self): 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): + def hook_syscall(self, ql, intno = None): return self.load_syscall() - def hook_sigtrap(self, intno= None, int = None): + def hook_sigtrap(self, ql, intno): self.ql.log.info("Trap Found") self.emu_error() exit(1) @@ -179,7 +185,7 @@ def run(self): """ 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 e7ab802fe..feb1cd38b 100644 --- a/qiling/os/macos/map_syscall.py +++ b/qiling/os/macos/map_syscall.py @@ -3,25 +3,24 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -# cols = ("arm64", "x8664") - 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 syscall_num >= 0x2000000 and syscall_num <= 0x3000000: - syscall_num = syscall_num - 0x2000000 +def get_syscall_mapper(archtype: QL_ARCH): + syscall_table = { + QL_ARCH.X8664 : x8664_syscall_table, + QL_ARCH.ARM64 : arm64_syscall_table + }[archtype] - print("") - print(syscall_num) - return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' + syscall_fixup = { + QL_ARCH.X8664 : lambda n: (n - 0x2000000) if 0x2000000 <= n <= 0x3000000 else n, + QL_ARCH.ARM64 : lambda n: (n - 0xffffffffffffff00) if n >= 0xffffffffffffff00 else n + }[archtype] - elif ql.archtype == QL_ARCH.ARM64: - if syscall_num >= 0xffffffffffffff00: - syscall_num = syscall_num - 0xffffffffffffff00 + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_fixup(syscall_num)]}' - return f'{SYSCALL_PREF}{arm64_syscall_table[syscall_num]}' + return __mapper arm64_syscall_table = { diff --git a/qiling/os/macos/syscall.py b/qiling/os/macos/syscall.py index 36c1fd79b..96cc099f6 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 cb3c64818..a2f8279a8 100644 --- a/qiling/os/mcu/mcu.py +++ b/qiling/os/mcu/mcu.py @@ -3,13 +3,11 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn import UcError - +from qiling.const import QL_OS from qiling.os.os import QlOs class QlOsMcu(QlOs): - def __init__(self, ql): - super(QlOsMcu, self).__init__(ql) + type = QL_OS.MCU def run(self): pass diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 80f292053..21d461e65 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -4,12 +4,11 @@ # import os, re -from typing import Any, Callable, List, MutableSequence, Optional, Sequence, Tuple +from typing import Any, Callable, Iterator, List, Mapping, MutableSequence, Optional, Pattern, Sequence, Tuple, Union from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL from qiling import Qiling -from qiling.const import * from qiling.exception import * # tuple: range start, range end, permissions mask, range label, is mmio? @@ -35,10 +34,10 @@ def __init__(self, ql: Qiling): 16 : (1 << 20) - 1 # 20bit address line } - if ql.archbit not in bit_stuff: - raise QlErrorStructConversion("Unsupported Qiling archtecture for memory manager") + if ql.arch.bits not in bit_stuff: + raise QlErrorStructConversion("Unsupported Qiling architecture 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 @@ -128,8 +127,8 @@ def del_mapinfo(self, mem_s: int, mem_e: int): self.map_info = tmp_map_info - def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: int = None, mem_info: str = None): - tmp_map_info: MapInfoEntry = None + def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, mem_info: Optional[str] = None): + tmp_map_info: Optional[MapInfoEntry] = None info_idx: int = None for idx, map_info in enumerate(self.map_info): @@ -150,12 +149,12 @@ def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: int = None, mem_info: st if mem_info is not None: self.map_info[info_idx] = (tmp_map_info[0], tmp_map_info[1], tmp_map_info[2], mem_info, tmp_map_info[4]) - def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, Optional[str]]]: + def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, str]]: """Get memory map info. Returns: A sequence of 5-tuples representing the memory map entries. Each tuple contains range start, range end, permissions, range label and path of - containing image (or None if not contained by any image) + containing image (or an empty string if not contained by any image) """ def __perms_mapping(ps: int) -> str: @@ -167,43 +166,66 @@ def __perms_mapping(ps: int) -> str: return ''.join(val if idx & ps else '-' for idx, val in perms_d.items()) - def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, Optional[str]]: + def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, str]: perms_str = __perms_mapping(perms) - if hasattr(self.ql, 'os'): - image = self.ql.os.find_containing_image(lbound) - container = image.path if image and not is_mmio else None + if hasattr(self.ql, 'loader'): + image = self.ql.loader.find_containing_image(lbound) + container = image.path if image and not is_mmio else '' else: - container = None + container = '' return (lbound, ubound, perms_str, label, container) return tuple(__process(*entry) for entry in self.map_info) - def show_mapinfo(self): - """Emit memory map info in a nicely formatted table. + def get_formatted_mapinfo(self) -> Sequence[str]: + """Get memory map info in a nicely formatted table. """ - # emit title row - self.ql.log.info(f'{"Start":8s} {"End":8s} {"Perm":5s} {"Label":12s} {"Image"}') + mapinfo = self.get_mapinfo() - # emit table rows - for lbound, ubound, perms, label, container in self.get_mapinfo(): - self.ql.log.info(f'{lbound:08x} - {ubound:08x} {perms:5s} {label:12s} {container or ""}') + # determine columns sizes based on the longest value for each field + lengths = ((len(f'{ubound:#x}'), len(label)) for _, ubound, _, label, _ in mapinfo) + grouped = tuple(zip(*lengths)) + + len_addr = max(grouped[0]) + len_label = max(grouped[1]) + + # pre-allocate table + table = [''] * (len(mapinfo) + 1) + + # add title row + table[0] = f'{"Start":{len_addr}s} {"End":{len_addr}s} {"Perm":5s} {"Label":{len_label}s} {"Image"}' + + # add table rows + for i, (lbound, ubound, perms, label, container) in enumerate(mapinfo, 1): + table[i] = f'{lbound:0{len_addr}x} - {ubound:0{len_addr}x} {perms:5s} {label:{len_label}s} {container}' + + return table # TODO: relying on the label string is risky; find a more reliable method - def get_lib_base(self, filename: str) -> int: - return next((s for s, _, _, info, _ in self.map_info if os.path.split(info)[1] == filename), -1) + def get_lib_base(self, filename: str) -> Optional[int]: + # regex pattern to capture boxed labels prefixes + p = re.compile(r'^\[.+\]\s*') - def align(self, value: int, alignment: int = None) -> int: - """Align a value down to the specified alignment boundary. + # some info labels may be prefixed by boxed label which breaks the search by basename. + # iterate through all info labels and remove all boxed prefixes, if any + stripped = ((lbound, p.sub('', info)) for lbound, _, _, info, _ in self.map_info) + + return next((lbound for lbound, info in stripped if os.path.basename(info) == filename), None) + + def align(self, value: int, alignment: Optional[int] = None) -> int: + """Align a value down to the specified alignment boundary. If `value` is already + aligned, the same value is returned. Commonly used to determine the base address + of the enclosing page. Args: value: a value to align - alignment: alignment boundary; must be a power of 2. if not specified - value will be aligned to page size + alignment: alignment boundary; must be a power of 2. if not specified value + will be aligned to page size - Returns: aligned value + Returns: value aligned down to boundary """ if alignment is None: @@ -215,15 +237,17 @@ def align(self, value: int, alignment: int = None) -> int: # round down to nearest alignment return value & ~(alignment - 1) - def align_up(self, value: int, alignment: int = None) -> int: - """Align a value up to the specified alignment boundary. + def align_up(self, value: int, alignment: Optional[int] = None) -> int: + """Align a value up to the specified alignment boundary. If `value` is already + aligned, the same value is returned. Commonly used to determine the end address + of the enlosing page. Args: value: value to align - alignment: alignment boundary; must be a power of 2. if not specified - value will be aligned to page size + alignment: alignment boundary; must be a power of 2. if not specified value + will be aligned to page size - Returns: aligned value + Returns: value aligned up to boundary """ if alignment is None: @@ -261,7 +285,7 @@ def restore(self, mem_dict): self.ql.log.debug(f'restoring memory range: {lbound:#08x} {ubound:#08x} {label}') size = ubound - lbound - if not self.is_mapped(lbound, size): + if self.is_available(lbound, size): self.ql.log.debug(f'mapping {lbound:#08x} {ubound:#08x}, mapsize = {size:#x}') self.map(lbound, size, perms, label) @@ -286,18 +310,19 @@ def read(self, addr: int, size: int) -> bytearray: return self.ql.uc.mem_read(addr, size) - def read_ptr(self, addr: int, size: int=None) -> int: + def read_ptr(self, addr: int, size: int = 0) -> int: """Read an integer value from a memory address. + Bytes read will be unpacked using emulated architecture properties. Args: addr: memory address to read - size: pointer size (in bytes): either 1, 2, 4, 8, or None for arch native size + size: pointer size (in bytes): either 1, 2, 4, 8, or 0 for arch native size Returns: integer value stored at the specified memory address """ if not size: - size = self.ql.pointersize + size = self.ql.arch.pointersize __unpack = { 1 : self.ql.unpack8, @@ -306,10 +331,10 @@ def read_ptr(self, addr: int, size: int=None) -> int: 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,18 +346,43 @@ def write(self, addr: int, data: bytes) -> None: self.ql.uc.mem_write(addr, data) - def search(self, needle: bytes, begin: int = None, end: int = None) -> Sequence[int]: + def write_ptr(self, addr: int, value: int, size: int = 0) -> None: + """Write an integer value to a memory address. + Bytes written will be packed using emulated architecture properties. + + Args: + addr: target memory address + value: integer value to write + size: pointer size (in bytes): either 1, 2, 4, 8, or 0 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: Union[bytes, Pattern[bytes]], begin: Optional[int] = None, end: Optional[int] = None) -> Sequence[int]: """Search for a sequence of bytes in memory. Args: - needle: bytes sequence to look for + needle: bytes sequence or regex pattern to look for begin: search starting address (or None to start at lowest avaiable address) end: search ending address (or None to end at highest avaiable address) Returns: addresses of all matches """ - # if starting point not set, search from the first mapped region + # if starting point not set, search from the first mapped region if begin is None: begin = self.map_info[0][0] @@ -346,6 +396,10 @@ def search(self, needle: bytes, begin: int = None, end: int = None) -> Sequence[ ranges = [(max(begin, lbound), min(ubound, end)) for lbound, ubound, _, _, is_mmio in self.map_info if not (end < lbound or ubound < begin or is_mmio)] results = [] + # if needle is a bytes sequence use it verbatim, not as a pattern + if type(needle) is bytes: + needle = re.escape(needle) + for lbound, ubound in ranges: haystack = self.read(lbound, ubound - lbound) local_results = (match.start(0) + lbound for match in re.finditer(needle, haystack)) @@ -368,17 +422,41 @@ def unmap(self, addr: int, size: int) -> None: if (addr, addr + size) in self.mmio_cbs: del self.mmio_cbs[(addr, addr+size)] - def unmap_all(self): + def unmap_all(self) -> None: """Reclaim the entire memory space. """ for begin, end, _ in self.ql.uc.mem_regions(): - if begin and end: - self.unmap(begin, end - begin + 1) + self.unmap(begin, end - begin + 1) + + def __mapped_regions(self) -> Iterator[Tuple[int, int]]: + """Iterate through all mapped memory regions, consolidating adjacent regions + together to a continuous one. Protection bits and labels are ignored. + """ + + if not self.map_info: + return + + iter_memmap = iter(self.map_info) + + p_lbound, p_ubound, _, _, _ = next(iter_memmap) + + # map_info is assumed to contain non-overlapping regions sorted by lbound + for lbound, ubound, _, _, _ in iter_memmap: + if lbound == p_ubound: + p_ubound = ubound + else: + yield (p_lbound, p_ubound) + + p_lbound = lbound + p_ubound = ubound + + yield (p_lbound, p_ubound) + def is_available(self, addr: int, size: int) -> bool: """Query whether the memory range starting at `addr` and is of length of `size` bytes - can be allocated. + is available for allocation. Returns: True if it can be allocated, False otherwise """ @@ -389,37 +467,23 @@ def is_available(self, addr: int, size: int) -> bool: end = addr + size # make sure neither begin nor end are enclosed within a mapped range, or entirely enclosing one - return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound, _, _, _ in self.map_info) + return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound in self.__mapped_regions()) def is_mapped(self, addr: int, size: int) -> bool: """Query whether the memory range starting at `addr` and is of length of `size` bytes - is mapped, either partially or entirely. + is fully mapped. - Returns: True if any part of the specified memory range is taken, False otherwise + Returns: True if the specified memory range is taken fully, False otherwise """ - return not self.is_available(addr, size) - - def is_free(self, address, size): - ''' - The main function of is_free first must fufull is_mapped condition. - then, check for is the mapped range empty, either fill with 0xFF or 0x00 - Returns true if mapped range is empty else return Flase - If not not mapped, map it and return true - ''' - if self.is_mapped(address, size) == True: - address_end = (address + size) - while address < address_end: - mem_read = self.ql.mem.read(address, 0x1) - if (mem_read[0] != 0x00) and (mem_read[0] != 0xFF): - return False - address += 1 - return True - else: - return True - - - def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None) -> int: + assert size > 0, 'expected a positive size value' + + begin = addr + end = addr + size + + return any((lbound <= begin < end <= ubound) for lbound, ubound in self.__mapped_regions()) + + def find_free_space(self, size: int, minaddr: Optional[int] = None, maxaddr: Optional[int] = None, align: Optional[int] = None) -> int: """Locate an unallocated memory that is large enough to contain a range in size of `size` and based at `minaddr`. @@ -464,7 +528,7 @@ def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, a raise QlOutOfMemory('Out Of Memory') - def map_anywhere(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None, perms: int = UC_PROT_ALL, info: str = None) -> int: + def map_anywhere(self, size: int, minaddr: Optional[int] = None, maxaddr: Optional[int] = None, align: Optional[int] = None, perms: int = UC_PROT_ALL, info: Optional[str] = None) -> int: """Map a region anywhere in memory. Args: @@ -499,7 +563,7 @@ def protect(self, addr: int, size: int, perms): self.change_mapinfo(aligned_address, aligned_address + aligned_size, mem_p = perms) - def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: str = None): + def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None): """Map a new memory range. Args: @@ -678,7 +742,7 @@ def clear(self): self.current_alloc = 0 self.current_use = 0 - def _find(self, addr: int, inuse: bool = None) -> Optional[Chunk]: + def _find(self, addr: int, inuse: Optional[bool] = None) -> Optional[Chunk]: """Find a chunk starting at a specified address. Args: diff --git a/qiling/os/os.py b/qiling/os/os.py index b5824b921..93c4c012e 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -4,7 +4,7 @@ # import sys -from typing import Any, Iterable, Optional, Callable, Mapping, Sequence, TextIO, Tuple +from typing import Any, Hashable, Iterable, Optional, Callable, Mapping, Sequence, TextIO, Tuple from unicorn import UcError @@ -15,10 +15,13 @@ from .filestruct import ql_file from .mapper import QlFsMapper +from .stats import QlOsStats from .utils import QlOsUtils -from .path import QlPathManager +from .path import QlOsPath class QlOs: + type: QL_OS + Resolver = Callable[[int], Any] def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): @@ -30,14 +33,19 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): 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 self.type in QL_OS_POSIX + (QL_OS.WINDOWS, QL_OS.DOS): + cwd = self.profile.get("MISC", "current_path") + + self.path = QlOsPath(ql.rootfs, cwd, self.type) + self.fs_mapper = QlFsMapper(self.path) + self.user_defined_api = { QL_INTERCEPT.CALL : {}, QL_INTERCEPT.ENTER: {}, @@ -61,13 +69,13 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): 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) # this shellcode entrypoint does not work for windows # windows shellcode entry point will comes from pe loader - self.entry_point = int(self.profile.get("CODE", "entry_point"), 16) + self.entry_point = self.profile.getint('CODE', 'entry_point') + self.code_ram_size = self.profile.getint('CODE', 'ram_size') # default fcall paramters resolving methods self.resolvers = { @@ -79,8 +87,6 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): # 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,35 +193,30 @@ def call(self, pc: int, func: Callable, proto: Mapping[str, Any], onenter: Optio 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): - if self.ql.ostype == QL_OS.UEFI: - api_name = f'hook_{api_name}' + def set_api(self, target: Hashable, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + """Either hook or replace an OS API with a custom one. - # 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: - self.add_function_hook(api_name, intercept_function, intercept) + Args: + target: target API identifier + handler: 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 + """ - def find_containing_image(self, pc: int): - for image in self.ql.loader.images: - if image.base <= pc < image.end: - return image + self.user_defined_api[intercept][target] = handler # os main method; derivatives must implement one of their own def run(self) -> None: @@ -229,11 +230,11 @@ def stop(self): 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) @@ -246,10 +247,11 @@ def emu_error(self): self.ql.log.error('Disassembly:') self.ql.arch.utils.disassembler(self.ql, pc, 64) - containing_image = self.find_containing_image(pc) + containing_image = self.ql.loader.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() + self.ql.log.error(f'Memory map:') + for info_line in self.ql.mem.get_formatted_mapinfo(): + self.ql.log.error(info_line) diff --git a/qiling/os/path.py b/qiling/os/path.py index b3914bcac..2cab19913 100644 --- a/qiling/os/path.py +++ b/qiling/os/path.py @@ -3,160 +3,330 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os -from typing import Union -from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath +from typing import Optional, Union +from pathlib import Path, PurePosixPath, PureWindowsPath -from qiling import Qiling from qiling.const import QL_OS, QL_OS_POSIX -# OH-MY-WIN32 !!! -# Some codes from cygwin. -# -# Basic guide: -# We should only handle "normal" paths like "C:\Windows\System32" and "bin/a.exe" for users. -# For UNC paths like '\\.\PHYSICALDRIVE0" and "\\Server\Share", they should be implemented -# by users via fs mapping interface. -class QlPathManager: - def __init__(self, ql: Qiling, cwd: str): - self.ql = ql - self._cwd = cwd +AnyPurePath = Union[PurePosixPath, PureWindowsPath] + +class QlOsPath: + """Virtual to host path manipulations helper. + """ + + def __init__(self, rootfs: str, cwd: str, emulos: QL_OS) -> None: + """Initialize a path manipulation object. + + Args: + rootfs : host path to serve as the virtual root directory + cwd : virtual current working directory + emuls : emulated operating system + """ + + nt_path_os = (QL_OS.WINDOWS, QL_OS.DOS) + posix_path_os = QL_OS_POSIX + + # rootfs is a local directory on the host, and expected to exist + self._rootfs_path = Path(rootfs).resolve(strict=True) + + # determine how virtual paths should be handled + if emulos in nt_path_os: + self.PureVirtualPath = PureWindowsPath + + elif emulos in posix_path_os: + self.PureVirtualPath = PurePosixPath + + else: + raise ValueError(f'unexpected os type: {emulos}') + + self.cwd = cwd + + # + self.transform_to_relative_path = self.virtual_abspath + self.transform_to_real_path = self.virtual_to_host_path + self.transform_to_link_path = self.virtual_to_host_path + # + + @staticmethod + def __strip_parent_refs(path: AnyPurePath) -> AnyPurePath: + """Strip leading parent dir references, if any. + """ + + if path.parts: + pardir = r'..' + + while path.parts[0] == pardir: + path = path.relative_to(pardir) + + return path @property def cwd(self) -> str: - return self._cwd + return str(self._cwd_anchor / self._cwd_vpath) @cwd.setter - def cwd(self, c: str) -> None: - if not c.startswith('/'): - self.ql.log.warning(f'Sanity check: path does not start with a forward slash "/"') + def cwd(self, virtpath: str) -> None: + vpath = self.PureVirtualPath(virtpath) - self._cwd = c + if not vpath.is_absolute(): + raise ValueError(f'current working directory must be an absolute path: {virtpath}') - @staticmethod - def normalize(path: Union[Path, PurePath]) -> Union[Path, PurePath]: - # expected types: PosixPath, PurePosixPath, WindowsPath, PureWindowsPath - assert isinstance(path, (Path, PurePath)), f'did not expect {type(path).__name__!r} here' + # extract the virtual path anchor so we can append cwd path to rootfs later on. + # however, we will still need the anchor to provide full virtual paths + cwd_anchor = self.PureVirtualPath(vpath.anchor) + cwd_vpath = vpath.relative_to(cwd_anchor) - normalized_path = type(path)() + cwd_vpath = QlOsPath.__strip_parent_refs(cwd_vpath) + + self._cwd_anchor = cwd_anchor + self._cwd_vpath = cwd_vpath + + def __virtual_abspath(self, virtpath: Union[str, AnyPurePath]) -> AnyPurePath: + """Get the absolute virtual path representation of a virtual path. + This method does not follow symbolic links or parent directory references. + + Args: + virtpath: virtual path to resolve, either relative or absolute - # remove anchor (necessary for Windows UNC paths) and convert to relative path - if path.is_absolute(): - path = path.relative_to(path.anchor) + Returns: An absolute virtual path + """ + + vpath = self.PureVirtualPath(virtpath) + + if vpath.is_absolute(): + return vpath + + # rebase on top the current working directory. in case vpath is an absolute + # path, cwd will be discarded + absvpath = self._cwd_vpath / vpath + + # referencing root's parent directory should circle back to root. + # remove any leading parent dir references absvpath might have + absvpath = QlOsPath.__strip_parent_refs(absvpath) + + return self._cwd_anchor / absvpath + + def __resolved_vsymlink(self, basepath: Path, name: str): + """Attempt to resolve a virtual symbolic link. + + A virtual symbolic link points to a location within the virtual file + system, not to be confused with paths on the host file system. + + For example: + a vsymlink that points to '/var/tmp' should be resolved to + 'my/rootfs/path/var/tmp' on the host + """ + + fullpath = self._rootfs_path / basepath / name + vpath = None + + if fullpath.is_symlink(): + resolved = fullpath.resolve(strict=False) + + try: + # the resolve method turns fullpath into an absolute path on the host, + # but we need it to be an absolute virtual path. to convert the host + # path to virtual we have to make sure it resides within rootfs. + # + # if the host path is indeed under rootfs, we can rebase the virtual + # portion on top of rootfs and return an absolute virtual path. + # + # returning an absolute path will discard the accumulated vpath. + + vpath = self._cwd_anchor / resolved.relative_to(self._rootfs_path) + except ValueError: + # failing to convert the host path into a virtual one means that either + # the symbolic link is already an absolute virtual path, or it is pointing + # to an external directory on the host file system, which means it does + # not reflect a virtual path. + # + # on both cases we may return the path as-is, discarding the accumulated + # vpath. + # + # that, however, may not work in case the host filesystem and the virtual + # one are not of the same type (e.g. a virtual Windows path on top of a + # Linux host): the absolute path will not discard the accumulated vpath + # and will be appended - which is a corner case we do not know how to + # handle efficiently. + + vpath = resolved + + return vpath + + # this will work only if hosting os = virtual os + def __virtual_resolve(self, virtpath: Union[str, AnyPurePath]) -> AnyPurePath: + """Resolve a virtual path, including symbolic links and directory + references it might include. Path must not include circular symbolic + links. + + Args: + virtpath: virtual path to resolve, either relative or absolute + + Returns: An absolute virtual path + """ + + vpath = self.PureVirtualPath(virtpath) - for p in path.parts: - if p == '.': - continue + # if not already, turn vpath into an absolute path + if not vpath.is_absolute(): + vpath = self.__virtual_abspath(vpath) - if p == '..': - normalized_path = normalized_path.parent - continue + # accumulate paths as we progress through the resolution process. + # + # since symlink inspection and resolution can only be done on an + # actual file system, each step in the progress has to be translated + # into its correpsonding host path. that is the reason we keep track + # on the acumulated host path in parallel to the virtual one + # + # note: the reason we do not set acc_hpath to rootfs is to prevent + # parent dir refs from traversing beyond rootfs directory. + + acc_hpath = Path() + acc_vpath = self.PureVirtualPath(vpath.anchor) + + # eliminate virtual path's anchor to allow us accumulate its + # parts on top of rootfs + vpath = vpath.relative_to(vpath.anchor) + + for part in vpath.parts: + if part == '..': + acc_hpath = acc_hpath.parent + acc_vpath = acc_vpath.parent - normalized_path /= p + else: + # if this is a symlink attempt to resolve it + vtemp = self.__resolved_vsymlink(acc_hpath, part) - return normalized_path + # not a symlink; accumulate path part + if vtemp is None: + acc_hpath = acc_hpath / part + acc_vpath = acc_vpath / part - @staticmethod - def convert_win32_to_posix(rootfs: Union[str, Path], cwd: str, path: str) -> Path: - _rootfs = Path(rootfs) - _cwd = PurePosixPath(cwd[1:]) - - # Things are complicated here. - # See https://docs.microsoft.com/zh-cn/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN - if PureWindowsPath(path).is_absolute(): - if (len(path) >= 2 and path.startswith(r'\\')) or \ - (len(path) >= 3 and path[0].isalpha() and path[1:3] == ':\\'): # \\.\PhysicalDrive0 or \\Server\Share\Directory or X:\ - # UNC path should be handled in fs mapping. If not, append it to rootfs directly. - pw = PureWindowsPath(path) - result = _rootfs / QlPathManager.normalize(pw) - else: - # code should never reach here. - result = _rootfs / QlPathManager.normalize(path) - else: - if len(path) >= 3 and path[:3] == r'\\?' or path[:3] == r'\??': # \??\ or \\?\ or \Device\.. - # Similair to \\.\, it should be handled in fs mapping. - pw = PureWindowsPath(path) - result = _rootfs / QlPathManager.normalize(_cwd / pw.relative_to(pw.anchor).as_posix()) - else: - # a normal relative path - result = _rootfs / QlPathManager.normalize(_cwd / PureWindowsPath(path).as_posix()) + else: + # rebase it on top of the accumulated virtual path + new_vpath = acc_vpath / vtemp - return result + # recursively resolve the new virtual path we got + vres = self.__virtual_resolve(new_vpath) - @staticmethod - def convert_posix_to_win32(rootfs: Union[str, Path], cwd: str, path: str) -> Path: - _rootfs = Path(rootfs) - _cwd = PurePosixPath(cwd[1:]) - _path = PurePosixPath(path) + acc_hpath = Path(vres) + acc_vpath = vres - if _path.is_absolute(): - return _rootfs / QlPathManager.normalize(_path) - else: - return _rootfs / QlPathManager.normalize(_cwd / _path) + return acc_vpath - @staticmethod - def convert_for_native_os(rootfs: Union[str, Path], cwd: str, path: str) -> Path: - _rootfs = Path(rootfs) - _cwd = PurePosixPath(cwd[1:]) - _path = Path(path) + def __virtual_to_host_path(self, virtpath: Union[str, AnyPurePath]) -> Path: + """Convert a virtual path to its corresponding path on the host. - if _path.is_absolute(): - return _rootfs / QlPathManager.normalize(_path) - else: - return _rootfs / QlPathManager.normalize(_cwd / _path.as_posix()) + This method partialy normalizes the virtual path and does not resolve + references to parent directories neither virtual symbolic links + """ + + absvpath = self.__virtual_abspath(virtpath) - def convert_path(self, rootfs: Union[str, Path], cwd: str, path: str) -> Path: - emulated_os = self.ql.ostype - hosting_os = self.ql.platform_os + # remove path anchor to allow path to be rebased + vpath = absvpath.relative_to(absvpath.anchor) - # 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): - return QlPathManager.convert_for_native_os(rootfs, cwd, path) + # rebase virtual path on top of rootfs to get the host path + return self._rootfs_path / vpath - elif emulated_os in QL_OS_POSIX and hosting_os == QL_OS.WINDOWS: - return QlPathManager.convert_posix_to_win32(rootfs, cwd, path) + def __is_safe_host_path(self, hostpath: Path, strict: bool = False) -> bool: + """Sanitize the specified host path and make sure it does not traverse out + of the rootfs directory hierarchy. - elif emulated_os == QL_OS.WINDOWS and hosting_os in QL_OS_POSIX: - return QlPathManager.convert_win32_to_posix(rootfs, cwd, path) + Args: + hostpath : a local path to sanitize + strict : whether to raise an error if target path does not exist + + Returns: whether the path is safe to use + """ + + # canonicalization before assertion: resolve any relative path references and + # symbolic links that may exist. + # + # in case strict is set to True and the path does not exist, a FileNotFoundError + # is raised. this error is left for the user to catch and handle + hostpath = hostpath.resolve(strict=strict) + + try: + # to prevent path-traversal issues we have to make sure hostpath ended up + # as a subpath of rootfs. the following method will fail if that is not + # the case + _ = hostpath.relative_to(self._rootfs_path) + except ValueError: + return False else: - return QlPathManager.convert_for_native_os(rootfs, cwd, path) + return True + + def virtual_abspath(self, virtpath: str) -> str: + """Convert a relative virtual path to an absolute virtual path based + on the current working directory. + + Args: + virtpath : relative virtual path - def transform_to_link_path(self, path: str) -> str: - real_path = self.convert_path(self.ql.rootfs, self.cwd, path) + Returns: the absolute virtual path + """ - return str(real_path.absolute()) + absvpath = self.__virtual_abspath(virtpath) + + return str(absvpath) + + def virtual_to_host_path(self, virtpath: str) -> str: + """Convert a virtual path to its corresponding path on the hosting system. + + Args: + virtpath : path on the emulated system. the path may be either absolute + or relative + + Returns: the corresponding path on the hosting system + """ + + absvpath = self.__virtual_resolve(virtpath) + hostpath = self.__virtual_to_host_path(absvpath) + + return str(hostpath) + + def is_safe_host_path(self, hostpath: str) -> bool: + hpath = Path(hostpath) + + return self.__is_safe_host_path(hpath, strict=False) + + @staticmethod + def __host_casefold_path(hostpath: str) -> Optional[str]: + # assuming posix host + p = PurePosixPath(hostpath) + norm = Path(p.anchor) - def transform_to_real_path(self, path: str) -> str: - # TODO: We really need a virtual file system. - real_path = self.convert_path(self.ql.rootfs, self.cwd, path) + for elem in p.relative_to(norm).parts: + folded = elem.casefold() - if os.path.islink(real_path): - link_path = Path(os.readlink(real_path)) + try: + norm = next(entry for entry in norm.iterdir() if entry.name.casefold() == folded) + except StopIteration: + return None - real_path = self.convert_path(os.path.dirname(real_path), "/", link_path) + return str(norm) - if os.path.islink(real_path): - real_path = self.transform_to_real_path(real_path) + def host_casefold_path(self, hostpath: str) -> Optional[str]: + """As opposed to POSIX paths, NT paths are case insensitive and may be specified + in multiple ways. When emulating an NT file system on top of POSIX one, virtual + NT paths and files might not be found because they are specified in a different + case than the one that is actually used on the hosting POSIX system. - # resolve multilevel symbolic link - if not os.path.exists(real_path): - path_dirs = link_path.parts + This method translates a case insensitive path into the actual case sensitive + name that is used on the hosting POSIX file system. - if link_path.is_absolute(): - path_dirs = path_dirs[1:] + Args: + hostpath: a path on the host, case insensitive - for i in range(len(path_dirs) - 1): - path_prefix = os.path.sep.join(path_dirs[:i+1]) - real_path_prefix = self.transform_to_real_path(path_prefix) - path_remain = os.path.sep.join(path_dirs[i+1:]) - real_path = Path(os.path.join(real_path_prefix, path_remain)) + Returns: the corresponding path on the host system, or None if the path does not + exist + """ - if os.path.exists(real_path): - break + # only relevant if the emulated file system is NT-based + if self.PureVirtualPath is PureWindowsPath: + return QlOsPath.__host_casefold_path(hostpath) - return str(real_path.absolute()) + return hostpath - # The `relative path` here refers to the path which is relative to the rootfs. - def transform_to_relative_path(self, path: str) -> str: - return str(Path(self.cwd) / path) diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index ba49a938f..7340a758a 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -515,6 +515,24 @@ 'O_LARGEFILE': None, } +linux_ppc_open_flags = { + 'O_RDONLY': 0x0, + 'O_WRONLY': 0x1, + 'O_RDWR': 0x2, + 'O_NONBLOCK': 0x800, + 'O_APPEND': 0x400, + 'O_ASYNC': 0x2000, + 'O_SYNC': 0x101000, + 'O_NOFOLLOW': 0x8000, + 'O_CREAT': 0x40, + 'O_TRUNC': 0x200, + 'O_EXCL': 0x80, + 'O_NOCTTY': 0x100, + 'O_DIRECTORY': 0x4000, + 'O_BINARY' : None, + 'O_LARGEFILE': 0x10000, +} + freebsd_x86_open_flags = { 'O_RDONLY': 0x0, 'O_WRONLY': 0x1, @@ -582,6 +600,7 @@ FD_CLOEXEC = 1 AT_FDCWD = -100 +AT_EMPTY_PATH = 0x1000 # error code EPERM = 1 @@ -856,4 +875,4 @@ SHM_RDONLY = 8**4 SHM_RND = 2*(8**4) SHM_REMAP= 4*(8**4) -SHM_EXEC = 1*(8**5) \ No newline at end of file +SHM_EXEC = 1*(8**5) diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index 42673b304..99fb31ca5 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -3,11 +3,12 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import MutableSequence -from .const import * +from typing import Mapping, MutableSequence from qiling import Qiling -from qiling.const import * +from qiling.const import QL_ARCH, QL_OS + +from .const import * def _invert_dict(d: Mapping) -> Mapping: return { v:k for k, v in d.items()} @@ -34,54 +35,61 @@ def _constant_mapping(bits: int, d_map: Mapping[str, int], ret: MutableSequence[ def ql_open_flag_mapping(ql: Qiling, flags): - def flag_mapping(flags, mapping_name, mapping_from, mapping_to): + def flag_mapping(flags, mapping_name, mapping_from, mapping_to, host_os, virt_os): ret = 0 for n in mapping_name: if mapping_from[n] is None or mapping_to[n] is None: continue if (flags & mapping_from[n]) == mapping_from[n]: ret = ret | mapping_to[n] + if (host_os == QL_OS.WINDOWS and virt_os != QL_OS.WINDOWS): + ret = ret | mapping_to['O_BINARY'] return ret f = {} t = {} - if ql.platform_os == None: + host_os = ql.host.os + virt_os = ql.os.type + + if host_os is None: return flags - - if ql.ostype == QL_OS.LINUX: - if ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + + if virt_os == QL_OS.LINUX: + 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.arch.type == QL_ARCH.PPC: + f = linux_ppc_open_flags - elif ql.ostype == QL_OS.MACOS: - if ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + elif virt_os == QL_OS.MACOS: + if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): f = macos_x86_open_flags - elif ql.ostype == QL_OS.FREEBSD: + elif virt_os == QL_OS.FREEBSD: f = freebsd_x86_open_flags - elif ql.ostype == QL_OS.WINDOWS: + elif virt_os == QL_OS.WINDOWS: f = windows_x86_open_flags - elif ql.ostype == QL_OS.QNX: + elif virt_os == QL_OS.QNX: f = qnx_arm64_open_flags - if ql.platform_os == QL_OS.LINUX: + if host_os == QL_OS.LINUX: t = linux_x86_open_flags - elif ql.platform_os == QL_OS.MACOS: + elif host_os == QL_OS.MACOS: t = macos_x86_open_flags - elif ql.platform_os == QL_OS.FREEBSD: + elif host_os == QL_OS.FREEBSD: t = freebsd_x86_open_flags - elif ql.platform_os == QL_OS.WINDOWS: + elif host_os == QL_OS.WINDOWS: t = windows_x86_open_flags if f == t: return flags - return flag_mapping(flags, open_flags_name, f, t) + return flag_mapping(flags, open_flags_name, f, t, host_os, virt_os) def mmap_flag_mapping(flags): @@ -130,7 +138,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 +157,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 +173,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 +190,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 +207,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 22330f8b4..6f5041c9e 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -4,27 +4,30 @@ # from inspect import signature, Parameter -from typing import TextIO, Union, Callable +from typing import TextIO, Union, Callable, IO, List, Optional from unicorn.arm64_const import UC_ARM64_REG_X8, UC_ARM64_REG_X16 -from unicorn.arm_const import UC_ARM_REG_R7 +from unicorn.arm_const import ( + UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, + UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R7, UC_ARM_REG_R12 +) from unicorn.mips_const import UC_MIPS_REG_V0 -from unicorn.x86_const import UC_X86_REG_EAX, UC_X86_REG_RAX +from unicorn.x86_const import ( + UC_X86_REG_EAX, UC_X86_REG_EBX, UC_X86_REG_ECX, UC_X86_REG_EDX, + UC_X86_REG_ESI, UC_X86_REG_EDI, UC_X86_REG_EBP, UC_X86_REG_RDI, + UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8, + UC_X86_REG_R9, UC_X86_REG_RAX +) from unicorn.riscv_const import UC_RISCV_REG_A7 +from unicorn.ppc_const import UC_PPC_REG_0 from qiling import Qiling -from qiling.cc import QlCC, intel, arm, mips, riscv +from qiling.cc import QlCC, intel, arm, mips, riscv, ppc 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.utils import QlFileDes, ostype_convert_str, ql_get_module_function, ql_syscall_mapping_function - -from qiling.os.posix.syscall import * -from qiling.os.linux.syscall import * -from qiling.os.macos.syscall import * -from qiling.os.freebsd.syscall import * -from qiling.os.qnx.syscall import * +from qiling.os.posix.const import NR_OPEN, errors +from qiling.utils import ql_get_module, ql_get_module_function SYSCALL_PREF: str = f'ql_syscall_' @@ -49,8 +52,8 @@ def setReturnValue(self, value: int): 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 @@ -58,6 +61,36 @@ class riscv32(riscv.riscv): class riscv64(riscv.riscv): pass +class ppc(ppc.ppc): + pass + + +class QlFileDes: + def __init__(self): + self.__fds: List[Optional[IO]] = [None] * NR_OPEN + + def __len__(self): + return len(self.__fds) + + def __getitem__(self, idx: Union[slice, int]): + return self.__fds[idx] + + def __setitem__(self, idx: int, val: Optional[IO]): + self.__fds[idx] = val + + def __iter__(self): + return iter(self.__fds) + + def __repr__(self): + return repr(self.__fds) + + def save(self): + return self.__fds + + def restore(self, fds): + self.__fds = fds + + class QlOsPosix(QlOs): def __init__(self, ql: Qiling): @@ -80,33 +113,39 @@ def __init__(self, ql: Qiling): } self.__syscall_id_reg = { - QL_ARCH.ARM64: UC_ARM64_REG_X8, - QL_ARCH.ARM : UC_ARM_REG_R7, - QL_ARCH.MIPS : UC_MIPS_REG_V0, - 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] - - # handle a special case - if (self.ql.archtype == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS): + QL_ARCH.ARM64 : UC_ARM64_REG_X8, + QL_ARCH.ARM : UC_ARM_REG_R7, + QL_ARCH.MIPS : UC_MIPS_REG_V0, + 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, + QL_ARCH.PPC : UC_PPC_REG_0 + }[self.ql.arch.type] + + # handle some special cases + if (self.ql.arch.type == QL_ARCH.ARM64) and (self.type == 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): + + elif (self.ql.arch.type == QL_ARCH.ARM) and (self.type == 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 self.__syscall_cc: QlCC = { - QL_ARCH.ARM64: aarch64, - QL_ARCH.ARM : aarch32, - QL_ARCH.MIPS : mipso32, - QL_ARCH.X86 : intel32, - QL_ARCH.X8664: intel64, - QL_ARCH.RISCV: riscv32, - QL_ARCH.RISCV64: riscv64, - }[self.ql.archtype](ql) + QL_ARCH.ARM64 : aarch64, + QL_ARCH.ARM : aarch32, + QL_ARCH.MIPS : mipso32, + QL_ARCH.X86 : intel32, + QL_ARCH.X8664 : intel64, + QL_ARCH.RISCV : riscv32, + QL_ARCH.RISCV64 : riscv64, + QL_ARCH.PPC : ppc + }[self.ql.arch.type](self.ql.arch) - self._fd = QlFileDes([0] * NR_OPEN) + # select syscall mapping function based on emulated OS and architecture + self.syscall_mapper = self.__get_syscall_mapper(self.ql.arch.type) + + 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 @@ -117,6 +156,14 @@ def __init__(self, ql: Qiling): self._shms = {} + def __get_syscall_mapper(self, archtype: QL_ARCH): + qlos_path = f'.os.{self.type.name.lower()}.map_syscall' + qlos_func = 'get_syscall_mapper' + + func = ql_get_module_function(qlos_path, qlos_func) + + return func(archtype) + @QlOs.stdin.setter def stdin(self, stream: TextIO) -> None: self._stdin = stream @@ -141,20 +188,30 @@ def root(self, enabled: bool) -> None: self.euid = 0 if enabled else self.uid self.egid = 0 if enabled else self.gid - @property - def syscall(self): - return self.get_syscall() + 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 + """ - def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT): 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 + def set_api(self, target: str, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + if self.ql.loader.is_driver: + super().set_api(target, handler, intercept) + else: + self.function_hook.add_function_hook(target, handler, intercept) + + @staticmethod def getNameFromErrorCode(ret: int) -> str: """Return the hex representation of a return value and if possible @@ -171,10 +228,8 @@ def getNameFromErrorCode(ret: int) -> str: return f'{ret:#x}{f" ({errors[-ret]})" if -ret in errors else f""}' def load_syscall(self): - # import syscall mapping function - map_syscall = ql_syscall_mapping_function(self.ql.ostype) - syscall_id = self.syscall - syscall_name = map_syscall(self.ql, syscall_id) + syscall_id = self.get_syscall() + syscall_name = self.syscall_mapper(syscall_id) # get syscall on-enter hook (if any) hooks_dict = self.posix_syscall_hooks[QL_INTERCEPT.ENTER] @@ -189,14 +244,14 @@ def load_syscall(self): syscall_hook = hooks_dict.get(syscall_name) or hooks_dict.get(syscall_id) if not syscall_hook: - osname = ostype_convert_str(self.ql.ostype) - os_syscalls = ql_get_module_function(f"qiling.os.{osname.lower()}", "syscall") - posix_syscalls = ql_get_module_function(f"qiling.os.posix", "syscall") + def __get_os_module(osname: str): + return ql_get_module(f'.os.{osname.lower()}.syscall') + + os_syscalls = __get_os_module(self.type.name) + posix_syscalls = __get_os_module('posix') # 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)) - syscall_hook = getattr(os_syscalls, syscall_name, None) or getattr(posix_syscalls, syscall_name, None) + syscall_hook = getattr(os_syscalls, syscall_name, None) or getattr(posix_syscalls, syscall_name, None) if syscall_hook: syscall_name = syscall_hook.__name__ @@ -252,37 +307,38 @@ def load_syscall(self): 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/__init__.py b/qiling/os/posix/syscall/__init__.py index ef20cef56..4e87d9b65 100644 --- a/qiling/os/posix/syscall/__init__.py +++ b/qiling/os/posix/syscall/__init__.py @@ -7,9 +7,11 @@ from .ioctl import * from .mman import * from .net import * +from .personality import * from .poll import * from .prctl import * from .ptrace import * +from .random import * from .resource import * from .sched import * from .select import * @@ -25,4 +27,3 @@ from .unistd import * from .utsname import * from .wait import * -from .random import * diff --git a/qiling/os/posix/syscall/fcntl.py b/qiling/os/posix/syscall/fcntl.py index fe672ce9e..54b00555e 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.os.type != 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 in range(arg, len(ql.os.fd)): + if ql.os.fd[idx] 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,19 +160,24 @@ 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 + if arg not in range(NR_OPEN): + regreturn = -1 + + for idx in range(arg, len(ql.os.fd)): + if ql.os.fd[idx] is None: + ql.os.fd[idx] = f.dup() + regreturn = idx + break else: regreturn = -1 @@ -179,8 +185,8 @@ def ql_syscall_fcntl64(ql: Qiling, fd: int, cmd: int, arg: int): 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 233b229ca..5e3eae0bf 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn import UC_PROT_ALL +import os from qiling import Qiling from qiling.exception import QlMemoryMappedError @@ -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.os.type == QL_OS.QNX: MAP_ANONYMOUS = 0x00080000 fd = ql.unpack32s(ql.pack32s(fd)) @@ -86,7 +86,7 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f mmap_base = addr mmap_size = ql.mem.align_up(mlen - (addr & (pagesize - 1))) - if ql.ostype != QL_OS.QNX: + if ql.os.type != QL_OS.QNX: mmap_base = ql.mem.align(mmap_base) if (flags & MAP_FIXED) and mmap_base != addr: @@ -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=f'[{api_name}] {os.path.basename(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 151262b4c..01dd7c0e8 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/personality.py b/qiling/os/posix/syscall/personality.py new file mode 100644 index 000000000..91a869255 --- /dev/null +++ b/qiling/os/posix/syscall/personality.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import os + + +from qiling import Qiling +from qiling.const import * + +def ql_syscall_personality(ql: Qiling, persona: int): + regreturn = 0 + return regreturn diff --git a/qiling/os/posix/syscall/prctl.py b/qiling/os/posix/syscall/prctl.py index 3652c2bd5..c4811a5c6 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/resource.py b/qiling/os/posix/syscall/resource.py index c244ad558..f04251bcc 100644 --- a/qiling/os/posix/syscall/resource.py +++ b/qiling/os/posix/syscall/resource.py @@ -19,9 +19,16 @@ def setrlimit(self, resource, rlim): from qiling import Qiling def __getrlimit_common(ql: Qiling, res: int, rlim: int) -> int: - rlimit = resource.getrlimit(res) - ql.mem.write(rlim, ql.pack32s(rlimit[0]) + ql.pack32s(rlimit[1])) - + RLIMIT_STACK = 3 + if res == RLIMIT_STACK: + if ql.arch.bits == 64: + stack_size = int(ql.os.profile.get("OS64", "stack_size"), 16) + elif ql.arch.bits == 32: + stack_size = int(ql.os.profile.get("OS32", "stack_size"), 16) + rlimit = (stack_size, -1) + else: + rlimit = resource.getrlimit(res) + ql.mem.write(rlim, ql.pack64s(rlimit[0]) + ql.pack64s(rlimit[1])) return 0 def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int): @@ -30,23 +37,29 @@ def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int): def ql_syscall_getrlimit(ql: Qiling, res: int, rlim: int): return __getrlimit_common(ql, res, rlim) -def ql_syscall_setrlimit(ql: Qiling, setrlimit_resource: int, setrlimit_rlim: int): +def ql_syscall_setrlimit(ql: Qiling, res: int, rlim: int): # maybe we can nop the setrlimit - tmp_rlim = (ql.unpack32s(ql.mem.read(setrlimit_rlim, 4)), ql.unpack32s(ql.mem.read(setrlimit_rlim + 4, 4))) - resource.setrlimit(setrlimit_resource, tmp_rlim) + tmp_rlim = (ql.unpack32s(ql.mem.read(rlim, 4)), ql.unpack32s(ql.mem.read(rlim + 4, 4))) + resource.setrlimit(res, tmp_rlim) return 0 def ql_syscall_prlimit64(ql: Qiling, pid: int, res: int, new_limit: int, old_limit: int): # setrlimit() and getrlimit() if pid == 0 and new_limit == 0: - rlim = resource.getrlimit(res) - ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1])) - - return 0 + try: + rlim = resource.getrlimit(res) + ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1])) + return 0 + except: + return -1 # set other process which pid != 0 return -1 -def ql_syscall_getpriority(ql: Qiling, getpriority_which: int, getpriority_who: int): - return os.getpriority(getpriority_which, getpriority_who) +def ql_syscall_getpriority(ql: Qiling, which: int, who: int): + try: + regreturn = os.getpriority(which, who) + except: + regreturn = -1 + return regreturn diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index dbc493f3a..9648182da 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 6d1aab718..2e61222a0 100644 --- a/qiling/os/posix/syscall/select.py +++ b/qiling/os/posix/syscall/select.py @@ -21,7 +21,7 @@ def parse_fd_set(ql: Qiling, max_fd: int, struct_addr: int) -> Tuple[Sequence[in for i in range(max_fd): if i % 32 == 0: - tmp = ql.unpack32(ql.mem.read(struct_addr + i, 4)) + tmp = ql.mem.read_ptr(struct_addr + i, 4) if tmp & 0x1: fileno = ql.os.fd[i].fileno() @@ -49,11 +49,11 @@ def handle_ready_fds(ptr: int, ready_fds: Sequence, fds_map: Mapping): 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)) - usec = ql.unpack(ql.mem.read(timeout + n * 1, n)) + sec = ql.mem.read_ptr(timeout + n * 0) + usec = ql.mem.read_ptr(timeout + n * 1) timeout_total = sec + float(usec) / 1000000 else: diff --git a/qiling/os/posix/syscall/sendfile.py b/qiling/os/posix/syscall/sendfile.py index 0c8c092e8..6d4b81ca7 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/signal.py b/qiling/os/posix/syscall/signal.py index e8c06cbe3..c0e4583a7 100644 --- a/qiling/os/posix/syscall/signal.py +++ b/qiling/os/posix/syscall/signal.py @@ -7,15 +7,13 @@ def ql_syscall_rt_sigaction(ql: Qiling, signum: int, act: int, oldact: int): if oldact: - if ql.os.sigaction_act[signum] == 0: - data = b'\x00' * 20 - else: - data = b''.join(ql.pack32(key) for key in ql.os.sigaction_act[signum]) + arr = ql.os.sigaction_act[signum] or [0] * 5 + data = b''.join(ql.pack32(key) for key in arr) ql.mem.write(oldact, data) if act: - ql.os.sigaction_act[signum] = [ql.unpack32(ql.mem.read(act + 4 * key, 4)) for key in range(5)] + ql.os.sigaction_act[signum] = [ql.mem.read_ptr(act + 4 * i, 4) for i in range(5)] return 0 diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index b48152dc5..a80c7b998 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -68,46 +68,36 @@ 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) + regreturn = idx - 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 + if idx != -1: + # ql_socket.open should use host platform based socket_type. + try: + emu_socket_value = socket_type + emu_socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.os.type) + socket_type = getattr(socket, emu_socket_type) + ql.log.debug(f'Convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type {emu_socket_type}:{socket_type}') - except Exception: - ql.log.error("Can't convert emu_socket_type {} to host platform based socket_type".format( - emu_socket_value)) - raise + 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 + try: if ql.verbose >= QL_VERBOSE.DEBUG: # set REUSEADDR options under debug mode ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol, (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)) else: ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol) + except OSError as e: # May raise error: Protocol not supported + ql.log.debug(f'{e}: {socket_domain=}, {socket_type=}, {socket_protocol=}') + regreturn = -1 - regreturn = (idx) - - except Exception: - ql.log.exception("") - regreturn = -1 - - socket_type = socket_type_mapping(socket_type, ql.archtype, ql.ostype) - socket_domain = socket_domain_mapping(socket_domain, ql.archtype, ql.ostype) + socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.os.type) + socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.os.type) ql.log.debug("socket(%s, %s, %s) = %d" % (socket_domain, socket_type, socket_protocol, regreturn)) return regreturn @@ -151,7 +141,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 +151,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.os.type) level = getattr(socket, emu_level_name) ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format( emu_level_name, emu_level, emu_level_name, level)) @@ -178,15 +168,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.os.type) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.os.type) 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.os.type) # 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 +202,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 +212,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.os.type) level = getattr(socket, emu_level_name) ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format( emu_level_name, emu_level, emu_level_name, level)) @@ -240,15 +229,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.os.type) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.os.type) 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.os.type) # 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 +266,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 +284,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 +378,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 +409,12 @@ def inet_addr(ip): 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 +427,7 @@ def inet_addr(ip): 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 +436,51 @@ def inet_addr(ip): 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 - return regreturn +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 -def ql_syscall_send(ql: Qiling, send_sockfd, send_buf, send_len, send_flags): - 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): +def ql_syscall_recvmsg(ql: Qiling, sockfd: int, msg_addr: int, flags: int): 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 +523,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): - # For x8664, recvfrom() is called finally when calling recv() in TCP communications +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 - if ql.os.fd[recvfrom_sockfd].socktype == SOCK_STREAM: - return ql_syscall_recv(ql, recvfrom_sockfd, recvfrom_buf, recvfrom_len, recvfrom_flags) + + # For x8664, recvfrom() is called finally when calling recv() in TCP communications + 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.unpack(ql.mem.read(recvfrom_addrlen, ql.pointersize)) - data = data[:addrlen] - ql.mem.write(recvfrom_addr, data) - - ql.mem.write(recvfrom_buf, tmp_buf) - regreturn = len(tmp_buf) - else: - regreturn = -1 + ql.log.debug("recvfrom() addr is %s:%d" % (tmp_addr[0], tmp_addr[1])) + data += struct.pack(">H", tmp_addr[1]) + data += ipaddress.ip_address(tmp_addr[0]).packed + addrlen = ql.mem.read_ptr(addrlen) + data = data[:addrlen] - return regreturn + ql.mem.write(addr, data) + ql.mem.write(buf, tmp_buf) + return len(tmp_buf) + + +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 -def ql_syscall_sendto(ql: Qiling, sendto_sockfd, sendto_buf, sendto_len, sendto_flags, sendto_addr, sendto_addrlen): - # 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 ql.archtype== QL_ARCH.X8664: - data = ql.mem.read(sendto_addr, 8) - else: - data = ql.mem.read(sendto_addr, sendto_addrlen) - - sin_family, = struct.unpack("HI", data[2:8]) - host = ql_bin_to_ip(host) - - 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 + # For x8664, sendto() is called finally when calling send() in TCP communications + if sock.socktype == SOCK_STREAM: + return ql_syscall_send(ql, sockfd, sendto_buf, sendto_len, sendto_flags) + + regreturn = 0 + + try: + ql.log.debug("debug sendto() start") + tmp_buf = ql.mem.read(sendto_buf, sendto_len) + + 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) + + 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()) - return regreturn + 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 2b8d8fc8d..efc809119 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -10,7 +10,7 @@ from qiling import Qiling from qiling.const import QL_OS, QL_ARCH, QL_ENDIAN -from qiling.os.posix.const import NR_OPEN, EBADF, AT_FDCWD +from qiling.os.posix.const import NR_OPEN, EBADF, ENOENT, AT_FDCWD, AT_EMPTY_PATH from qiling.os.posix.stat import Stat, Lstat # Caveat: Never use types like ctypes.c_long whose size differs across platforms. @@ -319,6 +319,57 @@ class LinuxMips64Stat(ctypes.Structure): _pack_ = 8 +class LinuxMips32EBStat(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint32), + ("st_pad1", ctypes.c_int32 * 3), + ("st_ino", ctypes.c_uint32), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint32), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint32), + ("st_pad2", ctypes.c_uint32 * 2), + ("st_size", ctypes.c_uint32), + ("st_pad3", ctypes.c_uint32), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("st_blksize", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint32), + ("st_pad4", ctypes.c_uint32 * 14) + ] + + _pack_ = 4 + +class LinuxMips64EBStat(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint32), + ("st_pad0", ctypes.c_uint32 * 3), + ("st_ino", ctypes.c_uint64), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint32), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint32), + ("st_pad1", ctypes.c_uint32 * 3), + ("st_size", ctypes.c_uint64), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("st_blksize", ctypes.c_uint32), + ("st_pad2", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint64) + ] + + _pack_ = 8 + class LinuxMips32Stat64(ctypes.Structure): _fields_ = [ ("st_dev", ctypes.c_uint32), @@ -768,6 +819,108 @@ class LinuxRISCVStat(ctypes.Structure): _pack_ = 8 +# Srouce: https://elixir.bootlin.com/linux/latest/source/arch/powerpc/include/uapi/asm/stat.h#L30 +# struct stat { +# unsigned long st_dev; +# ino_t st_ino; +# #ifdef __powerpc64__ +# unsigned long st_nlink; +# mode_t st_mode; +# #else +# mode_t st_mode; +# unsigned short st_nlink; +# #endif +# uid_t st_uid; +# gid_t st_gid; +# unsigned long st_rdev; +# long st_size; +# unsigned long st_blksize; +# unsigned long st_blocks; +# unsigned long st_atime; +# unsigned long st_atime_nsec; +# unsigned long st_mtime; +# unsigned long st_mtime_nsec; +# unsigned long st_ctime; +# unsigned long st_ctime_nsec; +# unsigned long __unused4; +# unsigned long __unused5; +# #ifdef __powerpc64__ +# unsigned long __unused6; +# #endif +# }; + +class LinuxPPCStat(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint32), + ("st_ino", ctypes.c_uint32), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint16), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint32), + ("st_size", ctypes.c_uint32), + ("st_blksize", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint32), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("__unused4", ctypes.c_uint32), + ("__unused5", ctypes.c_uint32) + ] + + _pack_ = 8 + +# Srouce: https://elixir.bootlin.com/linux/latest/source/arch/powerpc/include/uapi/asm/stat.h#L60 +# struct stat64 { +# unsigned long long st_dev; /* Device. */ +# unsigned long long st_ino; /* File serial number. */ +# unsigned int st_mode; /* File mode. */ +# unsigned int st_nlink; /* Link count. */ +# unsigned int st_uid; /* User ID of the file's owner. */ +# unsigned int st_gid; /* Group ID of the file's group. */ +# unsigned long long st_rdev; /* Device number, if device. */ +# unsigned short __pad2; +# long long st_size; /* Size of file, in bytes. */ +# int st_blksize; /* Optimal block size for I/O. */ +# long long st_blocks; /* Number 512-byte blocks allocated. */ +# int st_atime; /* Time of last access. */ +# unsigned int st_atime_nsec; +# int st_mtime; /* Time of last modification. */ +# unsigned int st_mtime_nsec; +# int st_ctime; /* Time of last status change. */ +# unsigned int st_ctime_nsec; +# unsigned int __unused4; +# unsigned int __unused5; +# }; + +class LinuxPPCStat64(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint64), + ("st_ino", ctypes.c_uint64), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint32), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint64), + ("__pad2", ctypes.c_uint16), + ("st_size", ctypes.c_uint64), + ("st_blksize", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint64), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("__unused4", ctypes.c_uint32), + ("__unused5", ctypes.c_uint32) + ] + + _pack_ = 8 + # Source: openqnx lib/c/public/sys/stat.h # # struct stat { @@ -929,63 +1082,73 @@ 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.ostype == QL_OS.LINUX: - if ql.archtype == QL_ARCH.X86: + if ql.arch.bits == 64: + ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.os.type} and {ql.arch.type}!") + if ql.os.type == QL_OS.LINUX: + 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: + elif ql.arch.type == QL_ARCH.PPC: + return LinuxPPCStat64() + elif ql.os.type == QL_OS.MACOS: return MacOSStat64() - elif ql.ostype == QL_OS.QNX: + elif ql.os.type == 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.os.type} 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.os.type == QL_OS.FREEBSD: + if ql.arch.type == QL_ARCH.X8664 or ql.arch.bits == 64: return FreeBSDX8664Stat() else: return FreeBSDX86Stat() - elif ql.ostype == QL_OS.MACOS: + elif ql.os.type == QL_OS.MACOS: return MacOSStat() - elif ql.ostype == QL_OS.LINUX: - if ql.archtype == QL_ARCH.X8664: + elif ql.os.type == QL_OS.LINUX: + 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: - return LinuxMips64Stat() + elif ql.arch.type == QL_ARCH.MIPS: + if ql.arch.bits == 64: + if ql.arch.endian == QL_ENDIAN.EL: + return LinuxMips64Stat() + else: + return LinuxMips64EBStat() else: - return LinuxMips32Stat() - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): - if ql.archendian == QL_ENDIAN.EL: + if ql.arch.endian == QL_ENDIAN.EL: + return LinuxMips32Stat() + else: + return LinuxMips32EBStat() + 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: + elif ql.archtype == QL_ARCH.PPC: + return LinuxPPCStat() + elif ql.os.type == QL_OS.QNX: + 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.os.type} for stat! Fallback to Linux x86.") return LinuxX86Stat() def __common_pack_stat_struct(stat, info) -> bytes: @@ -1026,7 +1189,7 @@ def statFamily(ql: Qiling, path: int, ptr: int, name: str, stat_func, struct_fun ql.log.debug(f'{name}("{file_path}", {ptr:#x}) write completed') return regreturn -def transform_path(ql: Qiling, dirfd: int, path: int): +def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0): """ An absolute pathname If pathname begins with a slash, then it is an absolute pathname that identifies the target file. @@ -1045,96 +1208,104 @@ def transform_path(ql: Qiling, dirfd: int, path: int): then the target file is the one referred to by the file descriptor dirfd. """ - dirfd = ql.unpacks(ql.pack(dirfd)) + dirfd = ql.unpack32s(ql.pack32(dirfd & (1<<32)-1)) path = ql.os.utils.read_cstring(path) if path.startswith('/'): - return None, os.path.join(ql.rootfs, path) + return None, os.path.join(ql.rootfs, path.lstrip('/')) if dirfd == AT_FDCWD: return None, ql.os.path.transform_to_real_path(path) + if len(path) == 0 and flags & AT_EMPTY_PATH: + return None, ql.os.fd[dirfd].name + if 0 < dirfd < NR_OPEN: return ql.os.fd[dirfd].fileno(), path - + def ql_syscall_chmod(ql: Qiling, filename: int, mode: int): + file_path = ql.os.utils.read_cstring(filename) + real_path = ql.os.path.transform_to_real_path(file_path) + try: + os.chmod(real_path, mode) + regreturn = 0 + except: + regreturn = -1 ql.log.debug(f'chmod("{ql.os.utils.read_cstring(filename)}", {mode:d}) = 0') - - return 0 + return regreturn 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 + try: + os.fchmod(ql.os.fd[fd].fileno(), mode) + regreturn = 0 + except: + regreturn = -1 + ql.log.debug("fchmod(%d, %d) = %d" % (fd, mode, regreturn)) + return regreturn - return 0 - -def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flag: int): - dirfd, real_path = transform_path(ql, dirfd, path) +def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int): + dirfd, real_path = transform_path(ql, dirfd, path, flags) - if os.path.exists(real_path): + try: buf = pack_stat64_struct(ql, Stat(real_path, dirfd)) ql.mem.write(buf_ptr, buf) regreturn = 0 - else: + except: regreturn = -1 return regreturn -def ql_syscall_newfstatat(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flag: int): - dirfd, real_path = transform_path(ql, dirfd, path) - - if os.path.exists(real_path): +def ql_syscall_newfstatat(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int): + dirfd, real_path = transform_path(ql, dirfd, path, flags) + + try: buf = pack_stat_struct(ql, Stat(real_path, dirfd)) ql.mem.write(buf_ptr, buf) regreturn = 0 - else: + except: regreturn = -1 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 + return 0 - if regreturn == 0: - ql.log.debug("fstat64 write completed") - else: - ql.log.debug("fstat64 read/write fail") - return regreturn +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] -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()) - ql.mem.write(buf_ptr, buf) + if f is None or not hasattr(f, "fstat"): + return -1 - regreturn = 0 - else: - regreturn = -1 + fstat = f.fstat() - if regreturn == 0: - ql.log.debug("fstat write completed") - else: - ql.log.debug("fstat read/write fail") + if fstat != -1: + buf = pack_stat_struct(ql, fstat) + ql.mem.write(buf_ptr, buf) - return regreturn + return 0 # int stat(const char *path, struct stat *buf); @@ -1233,7 +1404,7 @@ def statx_convert_timestamp(tv_sec, tv_nsec): 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) @@ -1245,15 +1416,12 @@ def major(dev): def minor(dev): return (dev & 0xff) | ((dev >> 12) & ~0xff) - fd, real_path = transform_path(ql, dirfd, path) + fd, real_path = transform_path(ql, dirfd, path, flags) try: - if len(real_path) == 0: - st = ql.os.fd[dirfd].fstat() - else: - st = Stat(real_path, fd) + st = Stat(real_path, fd) - if ql.archbit == 32: + if ql.arch.bits == 32: Statx = Statx32 else: Statx = Statx64 @@ -1295,7 +1463,7 @@ def ql_syscall_lstat64(ql: Qiling, path: int, buf_ptr: int): def ql_syscall_mknodat(ql: Qiling, dirfd: int, path: int, mode: int, dev: int): - dirfd, real_path = transform_path(ql, dirfd, path) + dirfd, real_path = transform_path(ql, dirfd, path) try: os.mknod(real_path, mode, dev, dir_fd=dirfd) @@ -1303,6 +1471,7 @@ def ql_syscall_mknodat(ql: Qiling, dirfd: int, path: int, mode: int, dev: int): except: regreturn = -1 + ql.log.debug("mknodat(%d, %s, 0%o, %d) = %d" % (dirfd, real_path, mode, dev, regreturn)) return regreturn @@ -1317,6 +1486,7 @@ def ql_syscall_mkdir(ql: Qiling, pathname: int, mode: int): except: regreturn = -1 + ql.log.debug("mkdir(%s, 0%o) = %d" % (real_path, mode, regreturn)) return regreturn def ql_syscall_rmdir(ql: Qiling, pathname: int): diff --git a/qiling/os/posix/syscall/time.py b/qiling/os/posix/syscall/time.py index ea63f5c6f..ad0004f04 100644 --- a/qiling/os/posix/syscall/time.py +++ b/qiling/os/posix/syscall/time.py @@ -12,11 +12,22 @@ def ql_syscall_time(ql: Qiling): return int(time.time()) -def __sleep_common(ql: Qiling, req: int, rem: int) -> int: - n = ql.pointersize +def __sleep_common(ql: Qiling, req: int, rem: int, force_timespec64: bool = False) -> int: + tv_sec_size = 8 if force_timespec64 else ql.arch.pointersize + tv_nsec_size = ql.arch.pointersize - tv_sec = ql.unpack(ql.mem.read(req, n)) - tv_sec += ql.unpack(ql.mem.read(req + n, n)) / 1000000000 + # struct timespec { + # long tv_sec; + # long tv_nsec; + # }; + # struct timespec64 { + # time64_t tv_sec; + # long tv_nsec; + # }; + + tv_sec = ql.unpack64(ql.mem.read(req, tv_sec_size)) if force_timespec64 else ql.unpack(ql.mem.read(req, tv_sec_size)) + + tv_sec += ql.unpack(ql.mem.read(req + tv_sec_size, tv_nsec_size)) / 1000000000 if ql.os.thread_management: def _sched_sleep(cur_thread): @@ -33,7 +44,7 @@ def _sched_sleep(cur_thread): return 0 def ql_syscall_clock_nanosleep_time64(ql: Qiling, clk_id: int, flags: int, req: int, rem: int): - return __sleep_common(ql, req, rem) + return __sleep_common(ql, req, rem, True) def ql_syscall_nanosleep(ql: Qiling, req: int, rem: int): return __sleep_common(ql, req, rem) diff --git a/qiling/os/posix/syscall/uio.py b/qiling/os/posix/syscall/uio.py index fda88cc84..f07346a04 100644 --- a/qiling/os/posix/syscall/uio.py +++ b/qiling/os/posix/syscall/uio.py @@ -7,7 +7,7 @@ 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 e16500766..fe0c01ca6 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -12,7 +12,7 @@ from multiprocessing import Process from qiling import Qiling -from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE +from qiling.const import QL_ARCH, QL_OS from qiling.os.posix.filestruct import ql_pipe from qiling.os.posix.const import * from qiling.os.posix.stat import Stat @@ -116,6 +116,27 @@ def ql_syscall_capset(ql: Qiling, hdrp: int, datap: int): def ql_syscall_kill(ql: Qiling, pid: int, sig: int): return 0 + +def ql_syscall_fsync(ql: Qiling, fd: int): + try: + os.fsync(ql.os.fd[fd].fileno()) + regreturn = 0 + except: + regreturn = -1 + ql.log.debug("fsync(%d) = %d" % (fd, regreturn)) + return regreturn + + +def ql_syscall_fdatasync(ql: Qiling, fd: int): + try: + os.fdatasync(ql.os.fd[fd].fileno()) + regreturn = 0 + except: + regreturn = -1 + ql.log.debug("fdatasync(%d) = %d" % (fd, regreturn)) + return regreturn + + def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int): access_path = ql.os.utils.read_cstring(filename) real_path = ql.os.path.transform_to_real_path(access_path) @@ -137,33 +158,46 @@ 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] + + if f is None: + return -EBADF - # ql.log.debug("lseek(fd = %d, ofset = 0x%x, origin = 0x%x) = %d" % (lseek_fd, lseek_ofset, lseek_origin, regreturn)) + 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 def ql_syscall__llseek(ql: Qiling, fd: int, offset_high: int, offset_low: int, result: int, whence: int): + if fd not in range(NR_OPEN): + return -EBADF + + f = ql.os.fd[fd] + + if f is None: + return -EBADF + # treat offset as a signed value offset = ql.unpack64s(ql.pack64((offset_high << 32) | offset_low)) origin = whence try: - ret = ql.os.fd[fd].seek(offset, origin) + ret = f.seek(offset, origin) 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)) @@ -176,13 +210,14 @@ def ql_syscall_brk(ql: Qiling, inp: int): # otherwise, just return current brk_address if inp: - new_brk_addr = ((inp + 0xfff) // 0x1000) * 0x1000 + cur_brk_addr = ql.loader.brk_address + new_brk_addr = ql.mem.align_up(inp) - if inp > ql.loader.brk_address: # increase current brk_address if inp is greater - ql.mem.map(ql.loader.brk_address, new_brk_addr - ql.loader.brk_address, info="[brk]") + if inp > cur_brk_addr: # increase current brk_address if inp is greater + ql.mem.map(cur_brk_addr, new_brk_addr - cur_brk_addr, info="[brk]") - elif inp < ql.loader.brk_address: # shrink current bkr_address to inp if its smaller - ql.mem.unmap(new_brk_addr, ql.loader.brk_address - new_brk_addr) + elif inp < cur_brk_addr: # shrink current bkr_address to inp if its smaller + ql.mem.unmap(new_brk_addr, cur_brk_addr - new_brk_addr) ql.loader.brk_address = new_brk_addr @@ -207,58 +242,80 @@ 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 def ql_syscall_write(ql: Qiling, fd: int, buf: int, count: int): + if fd not in range(NR_OPEN): + return -EBADF + + f = ql.os.fd[fd] + + if f is None: + return -EBADF + try: data = ql.mem.read(buf, count) except: @@ -266,12 +323,14 @@ def ql_syscall_write(ql: Qiling, fd: int, buf: int, count: int): else: ql.log.debug(f'write() CONTENT: {bytes(data)}') - if hasattr(ql.os.fd[fd], 'write'): - ql.os.fd[fd].write(data) + if hasattr(f, 'write'): + f.write(data) + + regreturn = count else: ql.log.warning(f'write failed since fd {fd:d} does not have a write method') + regreturn = -1 - regreturn = count return regreturn @@ -365,7 +424,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 +466,7 @@ def __read_str_array(addr: int) -> Iterator[str]: 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 +486,22 @@ def __read_str_array(addr: int) -> Iterator[str]: 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 + + QlCoreHooks.__init__(ql, uc) ql.os.load() ql.loader.run() @@ -441,44 +509,53 @@ def __read_str_array(addr: int) -> Iterator[str]: 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 + + f = ql.os.fd[fd] + + if f is None: + return -EBADF - 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 - return -1 +def ql_syscall_dup3(ql: Qiling, fd: int, newfd: int, flags: int): + if fd not in range(NR_OPEN) or newfd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + 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: - ql.os.thread_management.cur_thread.set_clear_child_tid_addr(tidptr) - regreturn = ql.os.thread_management.cur_thread.id else: regreturn = os.getpid() @@ -489,37 +566,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 - 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.os.fd[idx1] = rd + ql.os.fd[idx2] = wd - ql.log.debug("pipe(%x, [%d, %d]) = %d" % (pipefd, idx1, idx2, regreturn)) + if ql.arch.type == QL_ARCH.MIPS: + ql.arch.regs.v1 = idx2 + regreturn = idx1 + else: + ql.mem.write_ptr(pipefd + 0, idx1, 4) + ql.mem.write_ptr(pipefd + 4, idx2, 4) + regreturn = 0 return regreturn @@ -579,7 +642,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 +736,7 @@ def _type_mapping(ent): 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 8902f23cf..8315b2946 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 7208b8515..48f674cd5 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 774ca17bf..66c586337 100644 --- a/qiling/os/qnx/map_syscall.py +++ b/qiling/os/qnx/map_syscall.py @@ -6,9 +6,15 @@ 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: - return f'{SYSCALL_PREF}{arm_syscall_table[syscall_num]}' +def get_syscall_mapper(archtype: QL_ARCH): + syscall_table = { + QL_ARCH.ARM : arm_syscall_table + }[archtype] + + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + + return __mapper # Source: https://github.com/vocho/openqnx # trunk/services/system/public/sys/kercalls.h diff --git a/qiling/os/qnx/message.py b/qiling/os/qnx/message.py index f04373354..e4621fcf6 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 55bcc10d8..9f79f1d95 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -3,40 +3,41 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Callable import os -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 from qiling.os.qnx.structs import _thread_local_storage -from qiling.cc import QlCC, intel, arm, mips, riscv -from qiling.const import QL_ARCH, QL_INTERCEPT +from qiling.cc import QlCC, intel, arm, mips, riscv, ppc +from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * -from qiling.os.posix.const import NR_OPEN from qiling.os.posix.posix import QlOsPosix class QlOsQnx(QlOsPosix): + type = QL_OS.QNX + def __init__(self, ql: Qiling): super(QlOsQnx, self).__init__(ql) self.ql = ql cc: QlCC = { - QL_ARCH.X86 : intel.cdecl, - QL_ARCH.X8664 : intel.amd64, - QL_ARCH.ARM : arm.aarch32, - QL_ARCH.ARM64 : arm.aarch64, - QL_ARCH.MIPS : mips.mipso32, - QL_ARCH.RISCV : riscv.riscv, - QL_ARCH.RISCV64: riscv.riscv, - }[ql.archtype](ql) + QL_ARCH.X86 : intel.cdecl, + QL_ARCH.X8664 : intel.amd64, + QL_ARCH.ARM : arm.aarch32, + QL_ARCH.ARM64 : arm.aarch64, + QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.RISCV : riscv.riscv, + QL_ARCH.RISCV64 : riscv.riscv, + QL_ARCH.PPC : ppc.ppc, + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) @@ -64,19 +65,19 @@ def load(self): 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() - - - def hook_syscall(self, intno= None, int = None): - return self.load_syscall() + arm_utils.init_linux_traps(self.ql, { + 'memory_barrier': 0xffff0fa0, + 'cmpxchg': 0xffff0fc0, + 'get_tls': 0xffff0fe0 + }) - def add_function_hook(self, fn: str, cb: Callable, intercept: QL_INTERCEPT): - self.ql.os.function_hook.add_function_hook(fn, cb, intercept) + def hook_syscall(self, ql, intno): + return self.load_syscall() def register_function_after_load(self, function): @@ -89,12 +90,6 @@ def run_function_after_load(self): f() - def hook_sigtrap(self, intno= None, int = None): - self.ql.log.info("Trap Found") - self.emu_error() - exit(1) - - def run(self): if self.ql.exit_point is not None: self.exit_point = self.ql.exit_point @@ -114,7 +109,7 @@ def run(self): 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 +121,7 @@ def run(self): 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 +129,7 @@ def run(self): 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 cdf4d9c69..2d22c9685 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') @@ -198,7 +198,7 @@ def _msg_sendv(ql:Qiling, coid, smsg, sparts, rmsg, rparts, *args, **kw): type_ = ql.unpack16(sbody[:2]) msg_name = map_msgtype(ql, type_) - _msg_handler = ql_get_module_function(f"qiling.os.qnx", "message") + _msg_handler = ql_get_module_function(f".os.qnx", "message") if msg_name in dir(_msg_handler): msg_hook = eval(msg_name) diff --git a/qiling/os/stats.py b/qiling/os/stats.py new file mode 100644 index 000000000..0755872fe --- /dev/null +++ b/qiling/os/stats.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import json +from typing import Any, List, MutableMapping, Mapping, Optional, Set + +class QlOsStats: + """Record basic OS statistics, such as API calls and strings. + """ + + def __init__(self): + self.syscalls: MutableMapping[str, List] = {} + self.strings: MutableMapping[str, Set] = {} + + self.position = 0 + + def clear(self): + """Reset collected stats. + """ + + self.syscalls.clear() + self.strings.clear() + + self.position = 0 + + @staticmethod + def _banner(caption: str) -> List[str]: + bar = '-' * 24 + + return ['', caption, bar] + + def summary(self) -> List[str]: + ret = [] + + ret.extend(QlOsStats._banner('syscalls called')) + + for key, values in self.syscalls.items(): + ret.append(f'{key}:') + ret.extend(f' {json.dumps(value):s}' for value in values) + + ret.extend(QlOsStats._banner('strings ocurrences')) + + for key, values in self.strings.items(): + ret.append(f'{key}: {", ".join(str(word) for word in values)}') + + return ret + + def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: + """Record API calls along with their details. + + Args: + address : location of the calling instruction + name : api function name + params : mapping of the parameters name to their effective values + retval : value returned by the api function + retaddr : address to which the api function returned + """ + + if name.startswith('hook_'): + name = name[5:] + + self.syscalls.setdefault(name, []).append({ + 'params' : params, + 'retval' : retval, + 'address' : address, + 'retaddr' : retaddr, + 'position' : self.position + }) + + self.position += 1 + + def log_string(self, s: str) -> None: + """Record strings appearance as they are encountered during emulation. + + Args: + s : string to record + """ + + for token in s.split(' '): + self.strings.setdefault(token, set()).add(self.position) + + +class QlWinStats(QlOsStats): + """OS statistics object for Windows OS. Includes registry access stats. + """ + + def __init__(self): + super().__init__() + + self.registry: MutableMapping[str, List] = {} + + def clear(self): + super().clear() + + self.registry.clear() + + def summary(self) -> List[str]: + ret = super().summary() + + ret.extend(QlOsStats._banner('registry keys accessed')) + + for key, values in self.registry.items(): + ret.append(f'{key}:') + ret.extend(f' {json.dumps(value):s}' for value in values) + + return ret + + def log_reg_access(self, key: str, item: Optional[str], type: Optional[int], value: Any) -> None: + """Record registry access. + + Args: + key : accessed key name + name : sub item name (if provided) + type : sub item type (if provided) + value : value set to item, in case of a registry modification + """ + + self.registry.setdefault(key, []).append({ + 'item' : item, + 'type' : type, + 'value' : value, + 'position' : self.position + }) + + +class QlOsNullStats(QlOsStats): + """Nullified OS statistics object. + """ + + def clear(self): + pass + + def summary(self) -> List[str]: + return [] + + def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: + pass + + def log_string(self, s: str) -> None: + pass + + +class QlWinNullStats(QlOsNullStats): + """Nullified Windows statistics object. + """ + + def log_reg_access(self, key: str, item: Optional[str], type: Optional[int], value: Any) -> None: + pass diff --git a/qiling/os/uefi/__init__.py b/qiling/os/uefi/__init__.py index 5b8faeab6..0e68ec3c5 100644 --- a/qiling/os/uefi/__init__.py +++ b/qiling/os/uefi/__init__.py @@ -1,15 +1,15 @@ import csv from typing import Mapping -from os import path +import inspect +from pathlib import Path def __init_guids_db() -> Mapping[str, str]: """Initialize GUIDs dictionary from a local database. """ - csv_path = path.dirname(path.abspath(__file__)) - csv_path = path.join(csv_path, 'guids.csv') + csv_path = Path(inspect.getfile(inspect.currentframe())).parent / 'guids.csv' - with open(csv_path) as guids_file: + with csv_path.open('r') as guids_file: guids_reader = csv.reader(guids_file) return dict(tuple(entry) for entry in guids_reader) diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index d80ac385a..3532a159c 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 78838a12e..83f999bf3 100644 --- a/qiling/os/uefi/fncc.py +++ b/qiling/os/uefi/fncc.py @@ -11,7 +11,7 @@ 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 cda5e9461..22f92537d 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 fe582b542..a729f5f4c 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 c1cee75df..15e80e485 100644 --- a/qiling/os/uefi/smm.py +++ b/qiling/os/uefi/smm.py @@ -155,7 +155,7 @@ def enter(self) -> None: # 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 @@ def leave(self) -> None: 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 @@ def invoke_swsmi(self, cpu: int, idx: int, entry: int, args: Mapping[str, Any], 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 @@ def __cleanup(ql: Qiling): 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 @@ def __cleanup(ql: Qiling): 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 1ee19fbb1..67163e90b 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -9,6 +9,7 @@ from qiling import Qiling from qiling.cc import QlCC, intel +from qiling.const import QL_INTERCEPT, QL_OS from qiling.os.const import * from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs, QlOsUtils @@ -18,6 +19,8 @@ from qiling.os.uefi.smm import SmmEnv class QlOsUefi(QlOs): + type = QL_OS.UEFI + def __init__(self, ql: Qiling): super().__init__(ql) @@ -33,7 +36,7 @@ def __init__(self, ql: Qiling): cc: QlCC = { 32: intel.cdecl, 64: intel.ms64 - }[ql.archbit](ql) + }[ql.arch.bits](ql.arch) self.fcall = QlFunctionCall(ql, cc) @@ -116,7 +119,7 @@ def emit_context(self): 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 +149,7 @@ def emit_hexdump(self, address: int, data: bytearray, num_cols: int = 16): 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 +163,21 @@ def emit_stack(self, nitems: int = 4): 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) @@ -185,7 +188,7 @@ def emu_error(self): self.emit_hexdump(pc, data) self.emit_disasm(pc, data) - containing_image = self.find_containing_image(pc) + containing_image = self.ql.loader.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:#010x}{pc_info}') @@ -194,7 +197,12 @@ def emu_error(self): self.emit_stack() self.ql.log.error(f'Memory map:') - self.ql.mem.show_mapinfo() + for info_line in self.ql.mem.get_formatted_mapinfo(): + self.ql.log.error(info_line) + + + def set_api(self, target: str, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + super().set_api(f'hook_{target}', handler, intercept) def run(self): # TODO: this is not the right place for this diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 8c66631cb..fe3576749 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 __notify_next(ql: Qiling): 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 a5c3294c3..1228df029 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -7,7 +7,7 @@ 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 MutableMapping, Union, Sequence, MutableSequence, Tuple from uuid import UUID from qiling import Qiling @@ -20,53 +20,6 @@ class QlOsUtils: def __init__(self, ql: Qiling): self.ql = ql - # We can save every syscall called - self.syscalls = {} - self.syscalls_counter = 0 - self.appeared_strings = {} - - def clear_syscalls(self): - """Reset API and string appearance stats. - """ - - self.syscalls = {} - self.syscalls_counter = 0 - self.appeared_strings = {} - - def _call_api(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: - """Record API calls along with their details. - - Args: - address : location of the calling instruction - name : api function name - params : mapping of the parameters name to their effective values - retval : value returned by the api function - retaddr : address to which the api function returned - """ - - if name.startswith('hook_'): - name = name[5:] - - self.syscalls.setdefault(name, []).append({ - 'params': params, - 'retval': retval, - 'address': address, - 'retaddr': retaddr, - 'position': self.syscalls_counter - }) - - self.syscalls_counter += 1 - - def string_appearance(self, s: str) -> None: - """Record strings appearance as they are encountered during emulation. - - Args: - s : string to record - """ - - for token in s.split(' '): - self.appeared_strings.setdefault(token, set()).add(self.syscalls_counter) - @staticmethod def read_string(ql: Qiling, address: int, terminator: bytes) -> str: result = bytearray() @@ -86,14 +39,14 @@ def read_wstring(self, address: int) -> str: # 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 +92,7 @@ def __assign_arg(name: str, value: str) -> str: # 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 +117,7 @@ def __common_printf(self, format: str, args: MutableSequence, wstring: bool): 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/clipboard.py b/qiling/os/windows/clipboard.py index 02c1dc007..f7fecad9d 100644 --- a/qiling/os/windows/clipboard.py +++ b/qiling/os/windows/clipboard.py @@ -4,69 +4,75 @@ # # A Simple Windows Clipboard Simulation -NOT_LOCKED = -1 +from typing import TYPE_CHECKING, Optional +if TYPE_CHECKING: + from qiling.os.windows.windows import QlOsWindows -class Clipboard: +NOT_LOCKED = -1 +ERROR_CLIPBOARD_NOT_OPEN = 0x58a - def __init__(self, os): - #super(Clipboard, self).__init__(ql) +class Clipboard: + def __init__(self, os: 'QlOsWindows'): self.locked_by = NOT_LOCKED self.data = b"Default Clipboard Data" self.os = os + # Valid formats taken from https://doxygen.reactos.org/d8/dd6/base_ # 2applications_2mstsc_2constants_8h_source.html - self.formats = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 129, 130, 131, 142, 512, 767, - 768, 1023] + self.formats = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 129, 130, 131, 142, 512, 767, 768, 1023] - def open(self, h_wnd): - """ - Lock clipboard to hWnd if not already locked. + def open(self, h_wnd: int) -> bool: + """Lock clipboard to hWnd if not already locked. If hWnd is null default to current thead id """ + if h_wnd == 0: - hWnd = self.os.thread_manager.cur_thread.id + h_wnd = self.os.thread_manager.cur_thread.id if self.locked_by != NOT_LOCKED and self.locked_by != h_wnd: - return 0 - else: - self.locked_by = h_wnd - return 1 - - def format_available(self, fmt): - if fmt in self.formats: - return 1 - else: - return 0 + return False + + self.locked_by = h_wnd - def close(self): + return True + + def close(self) -> bool: if self.locked_by == NOT_LOCKED: - self.os.last_error = 0x58A # ERROR_CLIPBOARD_NOT_OPEN + self.os.last_error = ERROR_CLIPBOARD_NOT_OPEN + return False + + self.locked_by = NOT_LOCKED + + return True + + def format_available(self, fmt: int) -> bool: + return fmt in self.formats + + def set_data(self, fmt: int, data: bytes) -> int: + if fmt not in self.formats: return 0 - else: - self.locked_by = NOT_LOCKED - return 1 - def set_data(self, fmt, data): hWnd = self.os.thread_manager.cur_thread.id - + if self.locked_by != hWnd: - self.os.last_error = 0x58A # ERROR_CLIPBOARD_NOT_OPEN + self.os.last_error = ERROR_CLIPBOARD_NOT_OPEN return 0 - else: - if fmt not in self.formats: - return 0 - self.data = data - return 1 - def get_data(self, fmt): + self.data = data + + # BUG: this should be the handle of the clipboard object + return 1 + + def get_data(self, fmt: int) -> Optional[bytes]: if fmt not in self.formats: - return 0 + return None + hWnd = self.os.thread_manager.cur_thread.id - + if self.locked_by != hWnd: - self.os.last_error = 0x58A # ERROR_CLIPBOARD_NOT_OPEN - return 0 - else: - return self.data + self.os.last_error = ERROR_CLIPBOARD_NOT_OPEN + return None + + return self.data diff --git a/qiling/os/windows/const.py b/qiling/os/windows/const.py index 2453319af..13884a299 100644 --- a/qiling/os/windows/const.py +++ b/qiling/os/windows/const.py @@ -612,6 +612,7 @@ # https://docs.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess +# https://www.pinvoke.net/default.aspx/ntdll/PROCESSINFOCLASS.html ProcessBasicInformation = 0 ProcessDebugPort = 7 ProcessExecuteFlags = 0x22 diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index b4d244db2..e98779897 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -13,13 +13,11 @@ def __RegOpenKey(ql: Qiling, address: int, params): hKey = params["hKey"] lpSubKey = params["lpSubKey"] phkResult = params["phkResult"] - ql.log.debug("Key %s %s" % (hKey, lpSubKey)) if hKey not in REG_KEYS: - ql.log.debug("Key %s %s not present" % (hKey, lpSubKey)) return ERROR_FILE_NOT_FOUND - else: - s_hKey = REG_KEYS[hKey] + + s_hKey = REG_KEYS[hKey] key = s_hKey + "\\" + lpSubKey # Keys in the profile are saved as KEY\PARAM = VALUE, so i just want to check that the key is the same @@ -36,51 +34,52 @@ 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): +def __RegQueryValue(ql: Qiling, address: int, params, wstring: bool): ret = ERROR_SUCCESS hKey = params["hKey"] - s_lpValueName = params["lpValueName"] + lpValueName = params["lpValueName"] lpType = params["lpType"] lpData = params["lpData"] lpcbData = params["lpcbData"] s_hKey = ql.os.handle_manager.get(hKey).obj params["hKey"] = s_hKey # read reg_type - reg_type = Registry.RegNone if lpType == 0 else ql.unpack32(ql.mem.read(lpType, 4)) + reg_type = Registry.RegNone if lpType == 0 else ql.mem.read_ptr(lpType, 4) - try: - # Keys in the profile are saved as KEY\PARAM = VALUE, so i just want to check that the key is the same - value = ql.os.profile["REGISTRY"][s_hKey + "\\" + s_lpValueName] - ql.log.debug("Using profile for value of key %s" % (s_hKey + "\\" + s_lpValueName,)) + # try reading the registry key value from profile first. + # if the key is not specified in profile, proceed to registry manager - # TODO i have no fucking idea on how to set a None value, fucking configparser - if value == "None": - return ERROR_FILE_NOT_FOUND + keyname = f'{s_hKey}\\{lpValueName}' + value = ql.os.profile["REGISTRY"].get(keyname) + + if value is None: + reg_type, value = ql.os.registry_manager.read(s_hKey, lpValueName, reg_type) + + else: + ql.log.debug(f'Value for {keyname} was read from profile') reg_type = Registry.RegSZ # set that the registry has been accessed - ql.os.registry_manager.access(s_hKey, s_lpValueName, value, reg_type) - - except KeyError: - # Read the registry - reg_type, value = ql.os.registry_manager.read(s_hKey, s_lpValueName, reg_type) + ql.os.registry_manager.access(s_hKey, lpValueName, reg_type, value) # error key if reg_type is None or value is None: ql.log.debug("Key value not found") return ERROR_FILE_NOT_FOUND - else: - # set lpData - 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)) - if max_size < length: - ret = ERROR_MORE_DATA + + # set lpData + length = ql.os.registry_manager.write_reg_value_into_mem(reg_type, lpData, value, wstring) + + # set lpcbData + max_size = ql.mem.read_ptr(lpcbData, 4) + ql.mem.write_ptr(lpcbData, length, 4) + + if max_size < length: + ret = ERROR_MORE_DATA return ret @@ -91,53 +90,57 @@ def __RegCreateKey(ql: Qiling, address: int, params): lpSubKey = params["lpSubKey"] phkResult = params["phkResult"] - if not (hKey in REG_KEYS): + if hKey not in REG_KEYS: return ERROR_FILE_NOT_FOUND - else: - s_hKey = REG_KEYS[hKey] - params["hKey"] = s_hKey - if not ql.os.registry_manager.exists(s_hKey + "\\" + lpSubKey): - ql.os.registry_manager.create(s_hKey + "\\" + lpSubKey) - ret = ERROR_SUCCESS + s_hKey = REG_KEYS[hKey] + params["hKey"] = s_hKey + + keyname = f'{s_hKey}\\{lpSubKey}' + + if not ql.os.registry_manager.exists(keyname): + ql.os.registry_manager.create(keyname) + ret = ERROR_SUCCESS # new handle if ret == ERROR_SUCCESS: - new_handle = Handle(obj=s_hKey + "\\" + lpSubKey) + new_handle = Handle(obj=keyname) 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 return ret -def __RegSetValue(ql: Qiling, address: int, params): +def __RegSetValue(ql: Qiling, address: int, params, wstring: bool): hKey = params["hKey"] lpSubKey = params["lpSubKey"] dwType = params["dwType"] lpData = params["lpData"] + cbData = params["cbData"] s_hKey = ql.os.handle_manager.get(hKey).obj # this is done so the print_function would print the correct value params["hKey"] = s_hKey - ql.os.registry_manager.write(s_hKey, lpSubKey, dwType, lpData) + # dwType is expected to be REG_SZ and lpData to point to a null-terminated string + ql.os.registry_manager.write(s_hKey, lpSubKey, dwType, lpData, cbData, wstring) return ERROR_SUCCESS -def __RegSetValueEx(ql: Qiling, address: int, params): +def __RegSetValueEx(ql: Qiling, address: int, params, wstring: bool): hKey = params["hKey"] lpValueName = params["lpValueName"] dwType = params["dwType"] lpData = params["lpData"] + cbData = params["cbData"] s_hKey = ql.os.handle_manager.get(hKey).obj params["hKey"] = s_hKey - # BUG: lpData should be handled according to the value in dwType - ql.os.registry_manager.write(s_hKey, lpValueName, dwType, lpData) + ql.os.registry_manager.write(s_hKey, lpValueName, dwType, lpData, cbData, wstring) return ERROR_SUCCESS @@ -240,7 +243,7 @@ def hook_RegOpenKeyA(ql: Qiling, address: int, params): 'lpcbData' : LPDWORD }) def hook_RegQueryValueExA(ql: Qiling, address: int, params): - return __RegQueryValue(ql, address, params) + return __RegQueryValue(ql, address, params, wstring=False) # LSTATUS RegQueryValueExW( # HKEY hKey, @@ -259,7 +262,7 @@ def hook_RegQueryValueExA(ql: Qiling, address: int, params): 'lpcbData' : LPDWORD }) def hook_RegQueryValueExW(ql: Qiling, address: int, params): - return __RegQueryValue(ql, address, params) + return __RegQueryValue(ql, address, params, wstring=True) # LSTATUS RegCloseKey( # HKEY hKey @@ -340,7 +343,7 @@ def hook_RegCreateKeyExW(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueA(ql: Qiling, address: int, params): - return __RegSetValue(ql, address, params) + return __RegSetValue(ql, address, params, wstring=False) @winsdkapi(cc=STDCALL, params={ 'hKey' : HKEY, @@ -350,7 +353,7 @@ def hook_RegSetValueA(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueW(ql: Qiling, address: int, params): - return __RegSetValue(ql, address, params) + return __RegSetValue(ql, address, params, wstring=False) # LSTATUS RegSetValueExA( # HKEY hKey, @@ -369,7 +372,7 @@ def hook_RegSetValueW(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueExA(ql: Qiling, address: int, params): - return __RegSetValueEx(ql, address, params) + return __RegSetValueEx(ql, address, params, wstring=False) # LSTATUS RegSetValueExW( # HKEY hKey, @@ -388,7 +391,7 @@ def hook_RegSetValueExA(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueExW(ql: Qiling, address: int, params): - return __RegSetValueEx(ql, address, params) + return __RegSetValueEx(ql, address, params, wstring=True) # LSTATUS RegDeleteKeyA( # HKEY hKey, @@ -457,8 +460,10 @@ def hook_GetTokenInformation(ql: Qiling, address: int, params): token = ql.os.handle_manager.get(TokenHandle).obj information_value = token.get(TokenInformationClass) - ql.mem.write(ReturnLength, len(information_value).to_bytes(4, byteorder="little")) - return_size = int.from_bytes(ql.mem.read(ReturnLength, 4), byteorder="little") + + ql.mem.write_ptr(ReturnLength, len(information_value), 4) + return_size = ql.mem.read_ptr(ReturnLength, 4) + ql.log.debug("The target is checking for its permissions") if return_size > TokenInformationLength: @@ -494,7 +499,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 @@ -659,20 +664,16 @@ def hook_StartServiceA(ql: Qiling, address: int, params): }) def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params): count = params["nSubAuthorityCount"] - subs = b"" - - for i in range(count): - sub = params[f"nSubAuthority{i}"] - subs += sub.to_bytes(4, "little") + subs = b''.join(ql.pack32(params[f'nSubAuthority{i}']) for i in range(count)) - sid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=count) + sid = Sid(ql, revision=1, subs_count=count, identifier=5, subs=subs) sid_addr = ql.os.heap.alloc(sid.size) sid.write(sid_addr) 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 @@ -685,34 +686,46 @@ def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params): def get_adminsid(ql): global __adminsid - if __adminsid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_ADMINS[0x220] + + if __adminsid is None: + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_ADMINS[0x220] subs = b"\x20\x00\x00\x00\x20\x02\x00\x00" __adminsid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) + return __adminsid def get_userssid(ql): global __userssid - if __userssid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_USERS[0x221] + + if __userssid is None: + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_USERS[0x221] subs = b"\x20\x00\x00\x00\x21\x02\x00\x00" __userssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) + return __userssid def get_guestssid(ql): global __guestssid - if __guestssid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_GUESTS[0x222] + + if __guestssid is None: + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_GUESTS[0x222] subs = b"\x20\x00\x00\x00\x22\x02\x00\x00" __guestssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) + return __guestssid def get_poweruserssid(ql): global __poweruserssid - if __poweruserssid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_POWER_USERS[0x223] + + if __poweruserssid is None: + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_POWER_USERS[0x223] subs = b"\x20\x00\x00\x00\x23\x02\x00\x00" __poweruserssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) + return __poweruserssid @@ -744,7 +757,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/cng.py b/qiling/os/windows/dlls/cng.py deleted file mode 100644 index 96c4c5412..000000000 --- a/qiling/os/windows/dlls/cng.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - - -from qiling.os.windows.const import * -from qiling.os.windows.fncc import * -from qiling.os.const import * -from qiling.os.windows.utils import * -from qiling.os.windows.thread import * -from qiling.os.windows.handle import * -from qiling.exception import * - -dllname = 'cng_dll' - -# typedef struct _OSVERSIONINFOW { -# ULONG dwOSVersionInfoSize; -# ULONG dwMajorVersion; -# ULONG dwMinorVersion; -# ULONG dwBuildNumber; -# ULONG dwPlatformId; -# WCHAR szCSDVersion[128]; -# } -# NTSYSAPI NTSTATUS RtlGetVersion( -# PRTL_OSVERSIONINFOW lpVersionInformation -# ); -@winsdkapi(cc=CDECL, dllname=dllname, replace_params={"lpVersionInformation": POINTER}) -def hook_RtlGetVersion(ql, address, params): - pointer = params["lpVersionInformation"] - size = int.from_bytes(ql.mem.read(pointer, 4), byteorder="little") - os_version_info_asked = { - "dwOSVersionInfoSize": - size, - VER_MAJORVERSION: - int.from_bytes(ql.mem.read(pointer + 4, 4), byteorder="little"), - VER_MINORVERSION: - int.from_bytes(ql.mem.read(pointer + 8, 4), byteorder="little"), - VER_BUILDNUMBER: - int.from_bytes(ql.mem.read(pointer + 12, 4), byteorder="little"), - VER_PLATFORMID: - int.from_bytes(ql.mem.read(pointer + 16, 4), byteorder="little"), - "szCSDVersion": - int.from_bytes(ql.mem.read(pointer + 20, 128), byteorder="little"), - } - ql.mem.write( - pointer + 4, - ql.os.profile.getint("SYSTEM", - "majorVersion").to_bytes(4, byteorder="little")) - ql.mem.write( - pointer + 8, - ql.os.profile.getint("SYSTEM", - "minorVersion").to_bytes(4, byteorder="little")) - - ql.log.debug("The sample is checking the windows Version!") - return STATUS_SUCCESS diff --git a/qiling/os/windows/dlls/crypt32.py b/qiling/os/windows/dlls/crypt32.py index c1fb589fd..7efa91191 100644 --- a/qiling/os/windows/dlls/crypt32.py +++ b/qiling/os/windows/dlls/crypt32.py @@ -19,9 +19,11 @@ def _CryptStringToBinary(ql: Qiling, address: int, params) -> int: string_dst = params["pbBinary"] flag_dst = params["pdwFlags"] - size_dst = int.from_bytes(ql.mem.read(size_dst_pointer, 4), byteorder="little") - if size_dst != 0 and size_dst < size_src: + size_dst = ql.mem.read_ptr(size_dst_pointer, 4) + + if size_dst and size_dst < size_src: raise QlErrorNotImplemented("API not implemented") + if flag_src == CRYPT_STRING_BASE64: # Had a padding error, hope this always works add_pad = 4 - (len(string_src) % 4) @@ -37,11 +39,13 @@ def _CryptStringToBinary(ql: Qiling, address: int, params) -> int: # Only wants the length return len(output) else: - if flag_dst != 0: + if flag_dst: # Is optional - ql.mem.write(flag_dst, flag_src.to_bytes(length=4, byteorder='little')) + ql.mem.write_ptr(flag_dst, flag_src, 4) + # Write size - ql.mem.write(size_dst_pointer, len(output).to_bytes(length=4, byteorder='little')) + ql.mem.write_ptr(size_dst_pointer, len(output), 4) + # Write result ql.mem.write(string_dst, bytes(output, encoding="utf-16le")) return 1 diff --git a/qiling/os/windows/dlls/kernel32/errhandlingapi.py b/qiling/os/windows/dlls/kernel32/errhandlingapi.py index 3926a2730..5dbc32d7a 100644 --- a/qiling/os/windows/dlls/kernel32/errhandlingapi.py +++ b/qiling/os/windows/dlls/kernel32/errhandlingapi.py @@ -97,9 +97,9 @@ def exec_into_0x2d(ql: Qiling, intno: int, start): # 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 exec_standard_into(ql: Qiling, intno: int, user_data): # 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/fibersapi.py b/qiling/os/windows/dlls/kernel32/fibersapi.py index 36e1b27f4..1623fb115 100644 --- a/qiling/os/windows/dlls/kernel32/fibersapi.py +++ b/qiling/os/windows/dlls/kernel32/fibersapi.py @@ -14,7 +14,7 @@ 'dwFlsIndex' : DWORD }) def hook_FlsFree(ql: Qiling, address: int, params): - return ql.os.fiber_manager.free(params['dwFlsIndex']) + return int(ql.os.fiber_manager.free(params['dwFlsIndex'])) # LPVOID FlsGetValue( # DWORD dwFlsIndex @@ -25,7 +25,7 @@ def hook_FlsFree(ql: Qiling, address: int, params): def hook_FlsGetValue(ql: Qiling, address: int, params): return ql.os.fiber_manager.get(params['dwFlsIndex']) -# LPVOID FlsSetValue( +# BOOL FlsSetValue( # DWORD dwFlsIndex # PVOID lpFlsData # ); @@ -34,7 +34,7 @@ def hook_FlsGetValue(ql: Qiling, address: int, params): 'lpFlsData' : PVOID }) def hook_FlsSetValue(ql: Qiling, address: int, params): - return ql.os.fiber_manager.set(params['dwFlsIndex'], params['lpFlsData']) + return int(ql.os.fiber_manager.set(params['dwFlsIndex'], params['lpFlsData'])) # DWORD FlsAlloc( # PFLS_CALLBACK_FUNCTION lpCallback @@ -43,10 +43,6 @@ def hook_FlsSetValue(ql: Qiling, address: int, params): 'lpCallback' : PFLS_CALLBACK_FUNCTION }) def hook_FlsAlloc(ql: Qiling, address: int, params): - # global cb = params['lpCallback'] cb = params['lpCallback'] - if cb: - return ql.os.fiber_manager.alloc(cb) - else: - return ql.os.fiber_manager.alloc() + return ql.os.fiber_manager.alloc(cb or None) diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 514bc7a3f..0ccf37e0e 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import ntpath import os from shutil import copyfile @@ -25,18 +26,13 @@ def hook_GetFileType(ql: Qiling, address: int, params): hFile = params["hFile"] - if hFile in (STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE): - ret = FILE_TYPE_CHAR - else: - obj = ql.os.handle_manager.get(hFile) + handle = ql.os.handle_manager.get(hFile) - if obj is None: - raise QlErrorNotImplemented("API not implemented") - else: - # technically is not always a type_char but.. almost - ret = FILE_TYPE_CHAR + if handle is None: + raise QlErrorNotImplemented("API not implemented") - return ret + # technically is not always a type_char but.. almost + return FILE_TYPE_CHAR # HANDLE FindFirstFileA( # LPCSTR lpFileName, @@ -155,28 +151,16 @@ def hook_ReadFile(ql: Qiling, address: int, params): nNumberOfBytesToRead = params["nNumberOfBytesToRead"] lpNumberOfBytesRead = params["lpNumberOfBytesRead"] - if hFile == STD_INPUT_HANDLE: - if ql.os.automatize_input: - # TODO maybe insert a good random generation input - s = (b"A" * (nNumberOfBytesToRead - 1)) + b"\x00" - else: - ql.log.debug("Insert input") - s = ql.os.stdin.read(nNumberOfBytesToRead) + handle = ql.os.handle_manager.get(hFile) - slen = len(s) - read_len = slen + if handle is None: + ql.os.last_error = ERROR_INVALID_HANDLE + return 0 - if slen > nNumberOfBytesToRead: - s = s[:nNumberOfBytesToRead] - read_len = nNumberOfBytesToRead + data = handle.obj.read(nNumberOfBytesToRead) - ql.mem.write(lpBuffer, s) - ql.mem.write(lpNumberOfBytesRead, ql.pack32(read_len)) - 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(lpBuffer, data) + ql.mem.write_ptr(lpNumberOfBytesRead, len(data), 4) return 1 @@ -200,24 +184,20 @@ def hook_WriteFile(ql: Qiling, address: int, params): nNumberOfBytesToWrite = params["nNumberOfBytesToWrite"] lpNumberOfBytesWritten = params["lpNumberOfBytesWritten"] - 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)) - else: - f = ql.os.handle_manager.get(hFile) + handle = ql.os.handle_manager.get(hFile) - if f is None: - # Invalid handle - ql.os.last_error = ERROR_INVALID_HANDLE - return 0 - else: - f = f.obj + if handle is None: + ql.os.last_error = ERROR_INVALID_HANDLE + return 0 + + fobj = handle.obj + data = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) + + if hFile == STD_OUTPUT_HANDLE: + ql.os.stats.log_string(data.decode()) - buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - nNumberOfBytesWritten = f.write(bytes(buffer)) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesWritten)) + written = fobj.write(bytes(data)) + ql.mem.write_ptr(lpNumberOfBytesWritten, written, 4) return 1 @@ -290,6 +270,27 @@ def hook_CreateFileA(ql: Qiling, address: int, params): def hook_CreateFileW(ql: Qiling, address: int, params): return _CreateFile(ql, address, params) +def _GetTempPath(ql: Qiling, address: int, params, wide: bool): + temp_path = ntpath.join(ql.rootfs, 'Windows', 'Temp') + + if not os.path.exists(temp_path): + os.makedirs(temp_path, 0o755) + + nBufferLength = params['nBufferLength'] + lpBuffer = params['lpBuffer'] + + enc = 'utf-16le' if wide else 'utf-8' + + # temp dir path has to end with a path separator + tmpdir = f'{ntpath.join(ql.os.windir, "Temp")}{ntpath.sep}'.encode(enc) + cstr = tmpdir + '\x00'.encode(enc) + + if nBufferLength >= len(cstr): + ql.mem.write(lpBuffer, cstr) + + # returned length does not include the null-terminator + return len(tmpdir) + # DWORD GetTempPathW( # DWORD nBufferLength, # LPWSTR lpBuffer @@ -299,16 +300,7 @@ def hook_CreateFileW(ql: Qiling, address: int, params): 'lpBuffer' : LPWSTR }) def hook_GetTempPathW(ql: Qiling, address: int, params): - temp_path = os.path.join(ql.rootfs, "Windows", "Temp") - - if not os.path.exists(temp_path): - os.makedirs(temp_path, 0o755) - - dest = params["lpBuffer"] - temp = (ql.os.windir + "Temp" + "\\\x00").encode('utf-16le') - ql.mem.write(dest, temp) - - return len(temp) + return _GetTempPath(ql, address, params, wide=True) # DWORD GetTempPathA( # DWORD nBufferLength, @@ -319,16 +311,7 @@ def hook_GetTempPathW(ql: Qiling, address: int, params): 'lpBuffer' : LPSTR }) def hook_GetTempPathA(ql: Qiling, address: int, params): - temp_path = os.path.join(ql.rootfs, "Windows", "Temp") - - if not os.path.exists(temp_path): - os.makedirs(temp_path, 0o755) - - dest = params["lpBuffer"] - temp = (ql.os.windir + "Temp" + "\\\x00").encode('utf-8') - ql.mem.write(dest, temp) - - return len(temp) + return _GetTempPath(ql, address, params, wide=False) # DWORD GetShortPathNameW( # LPCWSTR lpszLongPath, @@ -402,7 +385,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 +398,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 4caf87ab5..11e7291b7 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 bfc415d42..658107135 100644 --- a/qiling/os/windows/dlls/kernel32/libloaderapi.py +++ b/qiling/os/windows/dlls/kernel32/libloaderapi.py @@ -10,7 +10,7 @@ from qiling.os.windows.api import * from qiling.os.windows.const import * from qiling.os.windows.fncc import * -from qiling.os.windows.utils import * +from qiling.os.windows.utils import has_lib_ext def _GetModuleHandle(ql: Qiling, address: int, params): lpModuleName = params["lpModuleName"] @@ -20,13 +20,15 @@ def _GetModuleHandle(ql: Qiling, address: int, params): else: lpModuleName = lpModuleName.lower() - if not is_file_library(lpModuleName): - lpModuleName += ".dll" + if not has_lib_ext(lpModuleName): + lpModuleName = f'{lpModuleName}.dll' - if lpModuleName in ql.loader.dlls: - ret = ql.loader.dlls[lpModuleName] + image = ql.loader.get_image_by_name(lpModuleName) + + if image: + ret = image.base else: - ql.log.debug("Library %s not imported" % lpModuleName) + ql.log.debug(f'Library "{lpModuleName}" not imported') ret = 0 return ret @@ -63,7 +65,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 @@ -148,33 +150,38 @@ def hook_GetModuleFileNameW(ql: Qiling, address: int, params): 'lpProcName' : POINTER # LPCSTR }) def hook_GetProcAddress(ql: Qiling, address: int, params): - if params["lpProcName"] > MAXUSHORT: + hModule = params['hModule'] + lpProcName = params['lpProcName'] + + if lpProcName > MAXUSHORT: # Look up by name - params["lpProcName"] = ql.os.utils.read_cstring(params["lpProcName"]) + params["lpProcName"] = ql.os.utils.read_cstring(lpProcName) lpProcName = bytes(params["lpProcName"], "ascii") else: # Look up by ordinal lpProcName = params["lpProcName"] # TODO fix for gandcrab - if params["lpProcName"] == "RtlComputeCrc32": + if lpProcName == "RtlComputeCrc32": return 0 # Check if dll is loaded - try: - dll_name = [key for key, value in ql.loader.dlls.items() if value == params['hModule']][0] - except IndexError as ie: - ql.log.info('Failed to import function "%s" with handle 0x%X' % (lpProcName, params['hModule'])) + dll_name = next((os.path.basename(image.path).casefold() for image in ql.loader.images if image.base == hModule), None) + + if dll_name is None: + ql.log.info('Failed to import function "%s" with handle 0x%X' % (lpProcName, hModule)) return 0 # Handle case where module is self - if dll_name == os.path.basename(ql.loader.path): + if dll_name == os.path.basename(ql.loader.path).casefold(): for addr, export in ql.loader.export_symbols.items(): if export['name'] == lpProcName: return addr - if lpProcName in ql.loader.import_address_table[dll_name]: - return ql.loader.import_address_table[dll_name][lpProcName] + iat = ql.loader.import_address_table[dll_name] + + if lpProcName in iat: + return iat[lpProcName] return 0 @@ -185,12 +192,12 @@ def _LoadLibrary(ql: Qiling, address: int, params): # Loading self return ql.loader.pe_image_address - return ql.loader.load_dll(lpLibFileName.encode()) + return ql.loader.load_dll(lpLibFileName) def _LoadLibraryEx(ql: Qiling, address: int, params): lpLibFileName = params["lpLibFileName"] - return ql.loader.load_dll(lpLibFileName.encode()) + return ql.loader.load_dll(lpLibFileName) # HMODULE LoadLibraryA( # LPCSTR lpLibFileName diff --git a/qiling/os/windows/dlls/kernel32/memoryapi.py b/qiling/os/windows/dlls/kernel32/memoryapi.py index b12776769..b7a416dad 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 6825e0e8b..b01aa1822 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 6aadc12a7..537578510 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 47310b058..50ccd5e01 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/sysinfoapi.py b/qiling/os/windows/dlls/kernel32/sysinfoapi.py index 4b7bb2e30..fc2073eb4 100644 --- a/qiling/os/windows/dlls/kernel32/sysinfoapi.py +++ b/qiling/os/windows/dlls/kernel32/sysinfoapi.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os +import ntpath from datetime import datetime from qiling import Qiling @@ -94,7 +94,7 @@ def __GetWindowsDirectory(ql: Qiling, address: int, params, wstring: bool): lpBuffer = params["lpBuffer"] enc = 'utf-16le' if wstring else 'utf-8' - res = os.path.normpath(ql.os.windir) + res = ntpath.normpath(ql.os.windir) ql.mem.write(lpBuffer, f'{res}\x00'.encode(enc)) @@ -104,7 +104,7 @@ def __GetSystemDirectory(ql: Qiling, address: int, params, wstring: bool): lpBuffer = params["lpBuffer"] enc = 'utf-16le' if wstring else 'utf-8' - res = os.path.join(ql.os.windir, 'System32') + res = ql.os.winsys ql.mem.write(lpBuffer, f'{res}\x00'.encode(enc)) @@ -119,14 +119,14 @@ def __GetSystemDirectory(ql: Qiling, address: int, params, wstring: bool): 'uSize' : UINT }) def hook_GetWindowsDirectoryW(ql: Qiling, address: int, params): - return __GetWindowsDirectory(ql, address, params, True) + return __GetWindowsDirectory(ql, address, params, wstring=True) @winsdkapi(cc=STDCALL, params={ 'lpBuffer' : LPSTR, 'uSize' : UINT }) def hook_GetWindowsDirectoryA(ql: Qiling, address: int, params): - return __GetWindowsDirectory(ql, address, params, False) + return __GetWindowsDirectory(ql, address, params, wstring=False) # UINT GetSystemWindowsDirectoryW( # LPWSTR lpBuffer, diff --git a/qiling/os/windows/dlls/kernel32/winbase.py b/qiling/os/windows/dlls/kernel32/winbase.py index 66fa1d622..00f1d9260 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 2c8ac1e5f..ca59d5a84 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 b5097bb5b..84600e9f3 100644 --- a/qiling/os/windows/dlls/kernel32/wow64apiset.py +++ b/qiling/os/windows/dlls/kernel32/wow64apiset.py @@ -19,7 +19,7 @@ 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 0b55bba26..73b84b3c4 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 @@ -412,6 +411,17 @@ def hook_strncmp(ql: Qiling, address: int, params): return result +def __malloc(ql: Qiling, address: int, params): + size = params['size'] + + return ql.os.heap.alloc(size) + +@winsdkapi(cc=CDECL, params={ + 'size' : UINT +}) +def hook__malloc_base(ql: Qiling, address: int, params): + return __malloc(ql, address, params) + # void* malloc(unsigned int size) @winsdkapi(cc=CDECL, params={ 'size' : UINT @@ -421,15 +431,23 @@ def hook_malloc(ql: Qiling, address: int, params): return ql.os.heap.alloc(size) +def __free(ql: Qiling, address: int, params): + address = params['address'] -# void* void* free(void *address) + ql.os.heap.free(address) + +@winsdkapi(cc=CDECL, params={ + 'address': POINTER +}) +def hook__free_base(ql: Qiling, address: int, params): + return __free(ql, address, params) + +# void* free(void *address) @winsdkapi(cc=CDECL, params={ 'address': POINTER }) def hook_free(ql: Qiling, address: int, params): - address = params['address'] - - return ql.os.heap.free(address) + return __free(ql, address, params) # _onexit_t _onexit( # _onexit_t function @@ -440,8 +458,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 @@ -464,6 +482,23 @@ def hook_memset(ql: Qiling, address: int, params): return dest +def __calloc(ql: Qiling, address: int, params): + num = params['num'] + size = params['size'] + + count = num * size + ret = ql.os.heap.alloc(count) + ql.mem.write(ret, bytes([0] * count)) + + return ret + +@winsdkapi(cc=CDECL, params={ + 'num' : SIZE_T, + 'size' : SIZE_T +}) +def hook__calloc_base(ql: Qiling, address: int, params): + return __calloc(ql, address, params) + # void *calloc( # size_t num, # size_t size @@ -473,14 +508,7 @@ def hook_memset(ql: Qiling, address: int, params): 'size' : SIZE_T }) def hook_calloc(ql: Qiling, address: int, params): - num = params['num'] - size = params['size'] - - count = num * size - ret = ql.os.heap.alloc(count) - ql.mem.write(ret, bytes([0] * count)) - - return ret + return __calloc(ql, address, params) # void * memmove( # void *dest, @@ -532,7 +560,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 @@ -545,7 +573,7 @@ def hook__time64(ql: Qiling, address: int, params): time_wasted = int(time.time()) - if dst != 0: - ql.mem.write(dst, time_wasted.to_bytes(8, "little")) + if dst: + ql.mem.write_ptr(dst, time_wasted, 8) return time_wasted diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index f506bcf36..5f424f777 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import os + from qiling import Qiling from qiling.os.windows.api import * from qiling.os.windows.fncc import * @@ -11,7 +13,6 @@ from qiling.exception import * from qiling.os.const import * from qiling.os.windows.const import * -from qiling.os.windows.utils import * from qiling.os.windows.handle import * from qiling.os.windows import structs @@ -33,7 +34,7 @@ def hook_memcpy(ql: Qiling, address: int, params): try: data = bytes(ql.mem.read(src, count)) ql.mem.write(dest, data) - except Exception as e: + except Exception: ql.log.exception("") return dest @@ -55,7 +56,8 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): elif flag == ProcessBasicInformation: pbi = structs.ProcessBasicInformation(ql, exitStatus=0, - pebBaseAddress=ql.os.heap_base_address, affinityMask=0, + pebBaseAddress=ql.loader.TEB.PebAddress, + affinityMask=0, basePriority=0, uniqueId=ql.os.profile.getint("KERNEL", "pid"), parentPid=ql.os.profile.getint("KERNEL", "parent_pid") @@ -63,7 +65,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 = ql.pack(addr) else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") @@ -71,8 +73,8 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): ql.log.debug("The target is checking the debugger via QueryInformationProcess ") ql.mem.write(dst, value) - if pt_res != 0: - ql.mem.write(pt_res, 0x8.to_bytes(1, byteorder="little")) + if pt_res: + ql.mem.write_ptr(pt_res, 8, 1) return STATUS_SUCCESS @@ -125,7 +127,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, @@ -145,11 +147,11 @@ def _QuerySystemInformation(ql: Qiling, address: int, params): if (bufferLength==sbi.size): sbi.write(dst) - if pt_res != 0: - ql.mem.write(pt_res, sbi.size.to_bytes(1, byteorder="little")) + if pt_res: + ql.mem.write_ptr(pt_res, sbi.size, 1) else: - if pt_res != 0: - ql.mem.write(pt_res, sbi.size.to_bytes(1, byteorder="little")) + if pt_res: + ql.mem.write_ptr(pt_res, sbi.size, 1) return STATUS_INFO_LENGTH_MISMATCH else: @@ -251,11 +253,11 @@ def hook_ZwQueryObject(ql: Qiling, address: int, params): else: raise QlErrorNotImplemented("API not implemented") - if dest != 0 and params["Handle"] != 0: + if dest and params["Handle"]: res.write(dest) - if size_dest != 0: - ql.mem.write(size_dest, res.size.to_bytes(4, "little")) + if size_dest: + ql.mem.write_ptr(size_dest, res.size, 4) return STATUS_SUCCESS @@ -301,26 +303,27 @@ def _SetInformationProcess(ql: Qiling, address: int, params): elif flag == ProcessBreakOnTermination: ql.log.debug("The target may be attempting modify a the 'critical' flag of the process") - elif flag == ProcessExecuteFlags: + elif flag == ProcessExecuteFlags: ql.log.debug("The target may be attempting to modify DEP for the process") - if dst != 0: - ql.mem.write(dst, 0x0.to_bytes(1, byteorder="little")) + if dst: + ql.mem.write_ptr(dst, 0, 1) elif flag == ProcessBasicInformation: pbi = structs.ProcessBasicInformation( ql, exitStatus=0, - pebBaseAddress=ql.os.heap_base_address, affinityMask=0, + pebBaseAddress=ql.loader.TEB.PebAddress, + affinityMask=0, basePriority=0, uniqueId=ql.os.profile.getint("KERNEL", "pid"), - parentPid=ql.os.profile.geting("KERNEL", "parent_pid") + parentPid=ql.os.profile.getint("KERNEL", "parent_pid") ) ql.log.debug("The target may be attempting to modify the PEB debug flag") addr = ql.os.heap.alloc(pbi.size) pbi.write(addr) - value = addr.to_bytes(ql.pointersize, "little") + value = ql.pack(addr) else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") @@ -357,17 +360,17 @@ def hook_LdrGetProcedureAddress(ql: Qiling, address: int, params): FunctionAddress = params['FunctionAddress'] # Check if dll is loaded - dll_name = next((key for key, value in ql.loader.dlls.items() if value == ModuleHandle), None) + dll_name = next((os.path.basename(path).casefold() for base, _, path in ql.loader.images if base == ModuleHandle), None) if dll_name is None: ql.log.debug(f'Could not find specified handle {ModuleHandle} in loaded DLL') return 0 identifier = bytes(FunctionName, 'ascii') if FunctionName else Ordinal + iat = ql.loader.import_address_table[dll_name] - 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) + if identifier in iat: + ql.mem.write_ptr(FunctionAddress, iat[identifier]) return 0 return 0xFFFFFFFF diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 82eecbea2..c54d274e8 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -11,6 +11,7 @@ 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}') @@ -161,14 +162,7 @@ def __IoCreateDevice(ql: Qiling, address: int, params): DeviceCharacteristics = params['DeviceCharacteristics'] DeviceObject = params['DeviceObject'] - objcls = { - QL_ARCH.X86 : DEVICE_OBJECT32, - QL_ARCH.X8664 : DEVICE_OBJECT64 - }[ql.archtype] - - addr = ql.os.heap.alloc(ctypes.sizeof(objcls)) - - device_object = objcls() + device_object = make_device_object(ql.arch.bits) device_object.Type = 3 # FILE_DEVICE_CD_ROM_FILE_SYSTEM ? device_object.DeviceExtension = ql.os.heap.alloc(DeviceExtensionSize) device_object.Size = ctypes.sizeof(device_object) + DeviceExtensionSize @@ -185,8 +179,10 @@ 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)) + addr = ql.os.heap.alloc(ctypes.sizeof(device_object)) + + ql.mem.write(addr, bytes(device_object)) + ql.mem.write_ptr(DeviceObject, addr) # update DriverObject.DeviceObject ql.loader.driver_object.DeviceObject = addr @@ -475,9 +471,11 @@ def hook_MmGetSystemRoutineAddress(ql: Qiling, address: int, params): index = hook_only_routine_address.index(SystemRoutineName) # found! for dll_name in ('ntoskrnl.exe', 'ntkrnlpa.exe', 'hal.dll'): - if dll_name in ql.loader.dlls: + image = ql.loader.get_image_by_name(dll_name) + + if image: # create fake address - new_function_address = ql.loader.dlls[dll_name] + index + 1 + new_function_address = image.base + index + 1 # update import address table ql.loader.import_symbols[new_function_address] = { 'name': SystemRoutineName, @@ -640,10 +638,7 @@ def hook_KeLeaveCriticalRegion(ql: Qiling, address: int, params): def hook_MmMapLockedPagesSpecifyCache(ql: Qiling, address: int, params): MemoryDescriptorList = params['MemoryDescriptorList'] - mdl_class: ctypes.Structure = { - QL_ARCH.X8664 : MDL64, - QL_ARCH.X86 : MDL32 - }[ql.archtype] + mdl_class = make_mdl(ql.arch.bits).__class__ mdl_buffer = ql.mem.read(MemoryDescriptorList, ctypes.sizeof(mdl_class)) mdl = mdl_class.from_buffer(mdl_buffer) @@ -756,30 +751,28 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): # if SystemInformationLength = 0, we return the total size in ReturnLength NumberOfModules = 1 - if ql.archbit == 64: - # only 1 module for ntoskrnl.exe - # FIXME: let users customize this? - size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION64) * NumberOfModules - else: - size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION32) * NumberOfModules + rpmi_class = make_rtl_process_module_info(ql.arch.bits).__class__ + + # only 1 module for ntoskrnl.exe + # FIXME: let users customize this? + size = 4 + ctypes.sizeof(rpmi_class) * 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: - module = RTL_PROCESS_MODULE_INFORMATION64() - else: - module = RTL_PROCESS_MODULE_INFORMATION32() - + module = make_rtl_process_module_info(ql.arch.bits) module.Section = 0 module.MappedBase = 0 - if ql.loader.is_driver == True: - module.ImageBase = ql.loader.dlls.get("ntoskrnl.exe") + if ql.loader.is_driver: + image = ql.loader.get_image_by_name("ntoskrnl.exe") + assert image, 'image is a driver, but ntoskrnl.exe was not loaded' + + module.ImageBase = image.base module.ImageSize = 0xab000 module.Flags = 0x8804000 @@ -873,7 +866,7 @@ def hook_IoAcquireCancelSpinLock(ql: Qiling, address: int, params): # PEPROCESS PsGetCurrentProcess(); @winsdkapi(cc=STDCALL, params={}) def hook_PsGetCurrentProcess(ql: Qiling, address: int, params): - return ql.eprocess_address + return ql.loader.eprocess_address # HANDLE PsGetCurrentProcessId(); @winsdkapi(cc=STDCALL, params={}) @@ -898,7 +891,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 +908,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 +1065,10 @@ def hook_PsLookupProcessByProcessId(ql: Qiling, address: int, params): ProcessId = params["ProcessId"] Process = params["Process"] - if ql.archbit == 64: - obj = EPROCESS64 - else: - obj = EPROCESS32 + eprocess_obj = make_eprocess(ql.arch.bits) + addr = ql.os.heap.alloc(ctypes.sizeof(eprocess_obj)) - 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 +1149,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 +1261,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 69e1ff48e..f19cb479d 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/dlls/shell32.py b/qiling/os/windows/dlls/shell32.py index abeae6eba..18bcc56c1 100644 --- a/qiling/os/windows/dlls/shell32.py +++ b/qiling/os/windows/dlls/shell32.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import ntpath import os from typing import Sequence @@ -146,7 +147,7 @@ def hook_SHGetSpecialFolderPathW(ql: Qiling, address: int, params): dst = params["pszPath"] if directory_id == CSIDL_COMMON_APPDATA: - path = str(ql.os.userprofile + "AppData\\") + path = ntpath.join(ql.os.userprofile, "AppData\\") # We always create the directory appdata_dir = path.split("C:\\")[1].replace("\\", "/") ql.log.debug("dir path: %s" % path) @@ -160,9 +161,10 @@ def hook_SHGetSpecialFolderPathW(ql: Qiling, address: int, params): if not os.path.exists(path_emulated): try: os.makedirs(path_emulated, 0o755) - ql.log.debug("os.makedirs completed") except OSError: ql.log.debug("os.makedirs failed") + else: + ql.log.debug("os.makedirs completed") else: raise QlErrorNotImplemented("API not implemented") diff --git a/qiling/os/windows/dlls/user32.py b/qiling/os/windows/dlls/user32.py index 61c238097..a761ded42 100644 --- a/qiling/os/windows/dlls/user32.py +++ b/qiling/os/windows/dlls/user32.py @@ -140,12 +140,12 @@ def hook_GetDesktopWindow(ql: Qiling, address: int, params): 'hWndNewOwner' : HWND }) def hook_OpenClipboard(ql: Qiling, address: int, params): - return ql.os.clipboard.open(params['hWndNewOwner']) + return int(ql.os.clipboard.open(params['hWndNewOwner'])) # BOOL CloseClipboard(); @winsdkapi(cc=STDCALL, params={}) def hook_CloseClipboard(ql: Qiling, address: int, params): - return ql.os.clipboard.close() + return int(ql.os.clipboard.close()) # HANDLE SetClipboardData( # UINT uFormat, @@ -190,7 +190,7 @@ def hook_GetClipboardData(ql: Qiling, address: int, params): 'format' : UINT }) def hook_IsClipboardFormatAvailable(ql: Qiling, address: int, params): - return ql.os.clipboard.format_available(params['uFormat']) + return int(ql.os.clipboard.format_available(params['uFormat'])) # UINT MapVirtualKeyW( # UINT uCode, @@ -866,8 +866,8 @@ def hook_GetWindowThreadProcessId(ql: Qiling, address: int, params): dst = params["lpdwProcessId"] - if dst != 0: - ql.mem.write(dst, pid.to_bytes(4, "little")) + if dst: + ql.mem.write_ptr(dst, pid, 4) return pid diff --git a/qiling/os/windows/fiber.py b/qiling/os/windows/fiber.py index 51715c421..098620763 100644 --- a/qiling/os/windows/fiber.py +++ b/qiling/os/windows/fiber.py @@ -2,11 +2,16 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework +from typing import MutableMapping, Optional + from qiling import Qiling +from qiling.const import QL_ARCH +from qiling.os.windows.api import PVOID from qiling.os.windows.const import ERROR_INVALID_PARAMETER +from qiling.os.windows.fncc import CDECL class Fiber: - def __init__(self, idx, cb=None): + def __init__(self, idx: int, cb: Optional[int] = None): self.idx = idx self.data = 0 self.cb = cb @@ -14,61 +19,96 @@ def __init__(self, idx, cb=None): class FiberManager: def __init__(self, ql: Qiling): - self.fibers = {} + self.fibers: MutableMapping[int, Fiber] = {} self.idx = 0 self.ql = ql - def alloc(self, cb=None): - rtn = self.idx - self.fibers[self.idx] = Fiber(self.idx, cb=cb) + def alloc(self, cb: Optional[int] = None) -> int: + idx = self.idx self.idx += 1 - return rtn - - def free(self, idx): - if idx in self.fibers: - fiber = self.fibers[idx] - - if fiber.cb: - 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? - - # 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') - 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) - 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) - # 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 - """ - - else: - del self.fibers[idx] - return 1 - - self.last_error = ERROR_INVALID_PARAMETER - return 0 - - def set(self, idx, data): - if idx in self.fibers: - self.fibers[idx].data = data - return 1 - - self.last_error = ERROR_INVALID_PARAMETER - return 0 - - def get(self, idx): - if idx in self.fibers: - return self.fibers[idx].data - - self.last_error = ERROR_INVALID_PARAMETER - return 0 + + self.fibers[idx] = Fiber(idx, cb) + + return idx + + def free(self, idx: int) -> bool: + if idx not in self.fibers: + self.last_error = ERROR_INVALID_PARAMETER + return False + + fiber = self.fibers[idx] + + if fiber.cb is not None: + self.ql.log.debug(f'Skipping callback function of fiber {fiber.idx} at {fiber.cb:#010x}') + + # TODO: should figure out how to emulate the fiber callback and still return to complete + # the free api hook. + # + # details: normally the emulation flow is diverted by setting the architectural pc reg to + # the desired address. however that would only take effect when all hooks for the current + # address are done. here we want to call a native function and regain control once it is + # done to complete the 'free' api that was started. + # + # one way to do that it to use 'ql.emu_start' and emulate the callback from its entry point + # till it reaches its return address. that would indeed let us regain control and resume the + # 'free' api hook we started here, but doing that will cause uc to abandon the current + # emulation session -- effectively ending it. once the hooks for the current address are done, + # the program will go idle. + # + # if we choose to emulate till 'ql.os.exit_point' instead, the program will continue but the + # hook we are in will not resume and we will never "return" from it. using 'uc.context_save' + # and 'uc.context_restore' to maintain the current emulation properties does not seem to help + # here. + # + # we skip the fiber callback for now. + + # + # self.ql.log.debug(f'Invoking callback function of fiber {fiber.idx} at {fiber.cb:#010x}') + # self.__invoke_callback(fiber) + # self.ql.log.debug(f'Callback function of fiber {fiber.idx} returned gracefully') + # + + del self.fibers[idx] + + return True + + # TODO: this one is unused for now; see above + def __invoke_callback(self, fiber: Fiber): + assert fiber.cb is not None + + # we are in an api hook. extract the return address of the free + # api to know where the callback should be returning to + retaddr = self.ql.stack_read(0) + + # one PVOID arg, set to fiber data + args = ((PVOID, fiber.data),) + + # set up call frame for callback + fcall = self.ql.os.fcall_select(CDECL) + fcall.call_native(fiber.cb, args, retaddr) + + # callback has to be invoked before returning from the free api + self.ql.emu_start(fiber.cb, retaddr) + + # unwind call frame + fcall.cc.unwind(len(args)) + + # ms64 cc needs also to unwind the reserved shadow slots on the stack + if self.ql.arch.type == QL_ARCH.X8664: + self.ql.arch.regs.arch_sp += (4 * self.ql.arch.pointersize) + + def set(self, idx: int, data: int) -> bool: + if idx not in self.fibers: + self.last_error = ERROR_INVALID_PARAMETER + return False + + self.fibers[idx].data = data + + return True + + def get(self, idx: int) -> int: + if idx not in self.fibers: + self.last_error = ERROR_INVALID_PARAMETER + return 0 + + return self.fibers[idx].data diff --git a/qiling/os/windows/handle.py b/qiling/os/windows/handle.py index 2a018ba13..b5776c961 100644 --- a/qiling/os/windows/handle.py +++ b/qiling/os/windows/handle.py @@ -4,68 +4,66 @@ # # A Simple Windows Handle Simulation +from typing import Any, MutableMapping, Optional + +from qiling.os.windows.const import STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE class Handle: ID = 0xa0000000 - def __init__(self, id=None, obj=None, - name=None, permissions=None): + def __init__(self, id: Optional[int] = None, obj: Any = None, name: Optional[str] = None, permissions: Optional[int] = None): if id is None: - self.id = Handle.ID + id = Handle.ID Handle.ID += 1 - else: - self.id = id + + self.id = id self.obj = obj self.name = name self.permissions = permissions - - # rewrite "=" - def __eq__(self, other): + + # overload "==" + def __eq__(self, other: 'Handle'): return self.id == other.id class HandleManager: # IO - STD_INPUT_HANDLE = Handle(id=0xfffffff6) - STD_OUTPUT_HANDLE = Handle(id=0xfffffff5) - STD_ERROR_HANDLE = Handle(id=0xfffffff4) + STDIN = Handle(id=STD_INPUT_HANDLE) + STDOUT = Handle(id=STD_OUTPUT_HANDLE) + STDERR = Handle(id=STD_ERROR_HANDLE) # Register - HKEY_CLASSES_ROOT = Handle(id=0x80000000) - HKEY_CURRENT_CONFIG = Handle(id=0x80000005) - HKEY_CURRENT_USER = Handle(id=0x80000001) + HKEY_CLASSES_ROOT = Handle(id=0x80000000) + HKEY_CURRENT_CONFIG = Handle(id=0x80000005) + HKEY_CURRENT_USER = Handle(id=0x80000001) HKEY_CURRENT_USER_LOCAL_SETTINGS = Handle(id=0x80000007) - HKEY_LOCAL_MACHINE = Handle(id=0x80000002) - HKEY_PERFORMANCE_DATA = Handle(id=0x80000004) + HKEY_LOCAL_MACHINE = Handle(id=0x80000002) + HKEY_PERFORMANCE_DATA = Handle(id=0x80000004) HKEY_PERFORMANCE_NLSTEXT = Handle(id=0x80000060) - HKEY_PERFORMANCE_TEXT = Handle(id=0x80000050) - HKEY_USERS = Handle(id=0x80000003) + HKEY_PERFORMANCE_TEXT = Handle(id=0x80000050) + HKEY_USERS = Handle(id=0x80000003) def __init__(self): - self.handles = {} - self.append(HandleManager.STD_INPUT_HANDLE) - self.append(HandleManager.STD_OUTPUT_HANDLE) - self.append(HandleManager.STD_ERROR_HANDLE) + self.handles: MutableMapping[int, Handle] = {} - def append(self, handle): + self.append(HandleManager.STDIN) + self.append(HandleManager.STDOUT) + self.append(HandleManager.STDERR) + + def append(self, handle: Handle) -> None: self.handles[handle.id] = handle - def get(self, id): + def get(self, id: int) -> Optional[Handle]: return self.handles.get(id, None) - def delete(self, id): - key = self.handles.get(id, None) + def delete(self, id: int) -> None: + key = self.get(id) + if key is not None: del self.handles[id] - def search(self, name): - for handle in self.handles.values(): - if handle.name == name: - return handle - return None - - def search_by_obj(self, obj): - for handle in self.handles.values(): - if handle.obj == obj: - return handle - return None + def search(self, name: str) -> Optional[Handle]: + return next((handle for handle in self.handles.values() if handle.name == name), None) + + def search_by_obj(self, obj: Any) -> Optional[Handle]: + return next((handle for handle in self.handles.values() if handle.obj == obj), None) diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index 5a69b81db..70dbff3cc 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -3,13 +3,13 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import json, os, sys - +import json, os from Registry import Registry +from typing import Any, MutableMapping, Optional, Tuple, Union -from qiling.os.windows.const import * +from qiling import Qiling +from qiling.os.windows.const import REG_TYPES from qiling.exception import * -from qiling.const import * # Registry Manager reads data from two places @@ -22,192 +22,265 @@ # Registry Manager will only write registry changes to config.json # and will not modify the hive file. +class RegConf: + def __init__(self, fname: str): + try: + with open(fname, 'r') as infile: + data = infile.read() + except IOError: + config = {} + else: + config = json.loads(data or '{}') + + self.conf: MutableMapping[str, dict[str, dict]] = config -class RegistryManager: - def __init__(self, ql, hive=None): - self.ql = ql - self.log_registry_dir = self.ql.rootfs - - if self.log_registry_dir == None: - self.log_registry_dir = "qlog" + def exists(self, key: str) -> bool: + return key in self.conf + + def create(self, key: str) -> None: + if not self.exists(key): + self.conf[key] = {} + + def delete(self, key: str, subkey: str) -> None: + if self.exists(key): + del self.conf[key][subkey] + + def read(self, key: str, subkey: str, reg_type: int) -> Tuple: + if key in self.conf: + subkeys = self.conf[key] + + if subkey in subkeys: + subkey_item = subkeys[subkey] + + item_type = subkey_item['type'] + item_value = subkey_item['value'] + + if item_type not in REG_TYPES: + raise QlErrorNotImplemented(f'Windows Registry Type {item_type} not implemented') + + return REG_TYPES[item_type], item_value + + return None, None + + def write(self, key: str, subkey: str, reg_type: int, data: Union[str, bytes, int]) -> None: + if not self.exists(key): + self.create(key) + + self.conf[key][subkey] = { + 'type' : REG_TYPES[reg_type], + 'value' : data + } + + def save(self, fname: str): + if self.conf: + with open(fname, 'wb') as ofile: + data = json.dumps(self.conf) + + ofile.write(data.encode('utf-8')) + + +class RegHive: + def __init__(self, hname: str): + def __make_reg(kname: str) -> Registry.Registry: + return Registry.Registry(os.path.join(hname, kname)) + + # hkey local system + self.hklm = { + 'SECURITY' : __make_reg('SECURITY'), + 'SAM' : __make_reg('SAM'), + 'SOFTWARE' : __make_reg('SOFTWARE'), + 'SYSTEM' : __make_reg('SYSTEM'), + 'HARDWARE' : __make_reg('HARDWARE') + } + + # hkey current user + self.hkcu = __make_reg('NTUSER.DAT') - self.registry_diff = self.ql.targetname + "_diff.json" - self.regdiff = os.path.join(self.log_registry_dir, "registry", self.registry_diff) + def __split_reg_path(self, key: str) -> Tuple[Optional[Registry.Registry], Optional[str]]: + regsep = '\\' + keys = key.split(regsep) + + if keys[0] == 'HKEY_LOCAL_MACHINE': + reg = self.hklm[keys[1].upper()] + sub = regsep.join(keys[2:]) + + elif keys[0] == 'HKEY_CURRENT_USER': + reg = self.hkcu + sub = regsep.join(keys[1:]) - # hive dir - if hive: - self.hive = hive else: - self.hive = os.path.join(ql.rootfs, "Windows", "registry") - ql.log.debug("Windows Registry PATH: %s" % self.hive) - if not os.path.exists(self.hive) and not self.ql.code: - raise QlErrorFileNotFound(f"Error: Registry files not found in '{self.hive}'!") + reg = None + sub = None - if not os.path.exists(self.regdiff): - self.registry_config = {} - try: - os.makedirs(os.path.join(self.log_registry_dir, "registry"), 0o755) - except Exception: - pass + return reg, sub + + def exists(self, key: str) -> bool: + reg, sub = self.__split_reg_path(key) + + if reg is None: + return False + + try: + reg.open(sub) + except: + return False else: - # read config - # use registry config first - self.f_config = open(self.regdiff, "rb") - data = self.f_config.read() - if data == b"": - self.registry_config = {} - self.f_config.close() - else: - try: - self.registry_config = json.loads(data) - except json.decoder.JSONDecodeError: - raise QlErrorJsonDecode("Windows Registry JSON decode error") - finally: - self.f_config.close() + return True + + def create(self, key: str) -> None: + pass + + def delete(self, key: str, subkey: str) -> None: + pass + + def read(self, key: str, subkey: str, reg_type: int) -> Tuple: + reg, sub = self.__split_reg_path(key) + + if reg is None: + raise QlErrorNotImplemented(f'registry root key not implemented') + + v_value = None + v_type = None + + try: + data = reg.open(sub) + except Registry.RegistryKeyNotFoundException: + pass + else: + value = next((v for v in data.values() if v.name() == subkey and reg_type in (Registry.RegNone, v.value_type())), None) + + if value: + v_value = value.value() + v_type = value.value_type() + + return (v_type, v_value) + + def write(self, key: str, subkey: str, reg_type: int, data: Union[str, bytes, int]) -> None: + pass + + +class RegistryManager: + def __init__(self, ql: Qiling, hivedir: str): + self.ql = ql + self.regdiff = os.path.join(ql.rootfs, 'registry', f'{ql.targetname}_diff.json') + + # if conf file does not exist, create its directory to enable saving later on + if not os.path.exists(self.regdiff): + os.makedirs(os.path.dirname(self.regdiff), 0o755, exist_ok=True) + + if not ql.code: + if not os.path.exists(hivedir): + raise QlErrorFileNotFound(f'Windows registry directory not found: "{hivedir}"!') + + ql.log.debug(f'Loading Windows registry hive from {hivedir}') - # hkey local system - self.hklm = {} try: - self.hklm['SECURITY'] = Registry.Registry(os.path.join(self.hive, 'SECURITY')) - self.hklm['SAM'] = Registry.Registry(os.path.join(self.hive, 'SAM')) - self.hklm['SOFTWARE'] = Registry.Registry(os.path.join(self.hive, 'SOFTWARE')) - self.hklm['SYSTEM'] = Registry.Registry(os.path.join(self.hive, 'SYSTEM')) - self.hklm['HARDWARE'] = Registry.Registry(os.path.join(self.hive, 'HARDWARE')) - # hkey current user - self.hkcu = Registry.Registry(os.path.join(self.hive, 'NTUSER.DAT')) + self.reghive = RegHive(hivedir) except FileNotFoundError: if not ql.code: - QlErrorFileNotFound("WARNING: Registry files not found!") + QlErrorFileNotFound("Windows registry hive not found") + except Exception: if not ql.code: - QlErrorFileNotFound("WARNING: Registry files format error") - self.accessed = {} + QlErrorFileNotFound("Windows registry hive format error") - def exists(self, key): - if key in self.regdiff: - return True - keys = key.split("\\") - self.access(key) try: - if keys[0] == "HKEY_LOCAL_MACHINE": - reg = self.hklm[keys[1]] - sub = "\\".join(keys[2:]) - data = reg.open(sub) - elif keys[0] == "HKEY_CURRENT_USER": - reg = self.hkcu - sub = "\\".join(keys[1:]) - data = reg.open(sub) - else: - raise QlErrorNotImplemented("Windows Registry %s not implemented" % (keys[0])) - except Exception: - return False + self.regconf = RegConf(self.regdiff) + except json.decoder.JSONDecodeError: + raise QlErrorJsonDecode("Windows registry JSON decode error") - return True + def exists(self, key: str) -> bool: + self.access(key) - def read(self, key, subkey, reg_type): - # of the key, the subkey is the value checked + return self.regconf.exists(key) or self.reghive.exists(key) - # read reg conf first - if key in self.regdiff and subkey in self.regdiff[key]: - if self.regdiff[key][subkey].type in REG_TYPES: - return REG_TYPES[self.regdiff[key][subkey].type], self.regdiff[key][subkey].value - else: - raise QlErrorNotImplemented( - "Windows Registry Type %s not implemented" % self.regdiff[key][subkey].type) + def read(self, key: str, subkey: str, reg_type: int) -> Tuple: + result = self.regconf.read(key, subkey, reg_type) - # read hive - reg = None - data = None - keys = key.split('\\') - try: - if keys[0] == "HKEY_LOCAL_MACHINE": - reg = self.hklm[keys[1]] - sub = "\\".join(keys[2:]) - data = reg.open(sub) - elif keys[0] == "HKEY_CURRENT_USER": - reg = self.hkcu - sub = "\\".join(keys[1:]) - data = reg.open(sub) - else: - raise QlErrorNotImplemented("Windows Registry %s not implemented" % (keys[0])) - - for value in data.values(): - if value.name() == subkey and (reg_type == Registry.RegNone or - value.value_type() == reg_type): - - self.access(key, value_name=subkey, value=value.value(), type=value.value_type()) - return value.value_type(), value.value() + if result == (None, None): + result = self.reghive.read(key, subkey, reg_type) - except Registry.RegistryKeyNotFoundException: - pass + self.access(key, subkey, *result) - self.access(key, value_name=subkey, value=None, type=None) + return result - return None, None + def access(self, key: str, name: Optional[str] = None, type: Optional[int] = None, value: Any = None): + self.ql.os.stats.log_reg_access(key, name, type, value) + + def create(self, key: str) -> None: + self.regconf.create(key) + self.reghive.create(key) + + def delete(self, key: str, subkey: str) -> None: + self.regconf.delete(key, subkey) + self.reghive.delete(key, subkey) + + def __reg_mem_read(self, data_type: int, data_addr: int, data_size: int, wide: bool) -> Optional[Union[str, bytes, int]]: + if data_type in (Registry.RegSZ, Registry.RegExpandSZ): + os_utils = self.ql.os.utils + read_string = os_utils.read_wstring if wide else os_utils.read_cstring + + data = read_string(data_addr) + + elif data_type == Registry.RegDWord: + data = self.ql.mem.read_ptr(data_addr, 4) + + elif data_type == Registry.RegQWord: + data = self.ql.mem.read_ptr(data_addr, 8) + + elif data_type == Registry.RegBin: + data = bytes(self.ql.mem.read(data_addr, data_size)) - def access(self, key, value_name=None, value=None, type=None): - if value_name is None: - if key not in self.accessed: - self.accessed[key] = [] else: - self.accessed[key].append({ - "value_name": value_name, - "value": value, - "type": type, - "position": self.ql.os.utils.syscalls_counter - }) - # we don't have to increase the counter since we are technically inside a hook - - def create(self, key): - self.registry_config[key] = dict() - - def write(self, key, subkey, reg_type, data): - if key not in self.registry_config: - self.create(key) - # write registry changes to config.json - self.registry_config[key][subkey] = { - "type": REG_TYPES[reg_type], - "value": data - } + data = None - def delete(self, key, subkey): - del self.registry_config[key][subkey] + return data - @staticmethod - def _encode_binary_value(data): - # bytes(hex(data), 'ascii') - # TODO - pass + def __reg_mem_write(self, data_type: int, data_addr: int, data_val: Union[str, bytes, int], wide: bool) -> Optional[int]: + if data_type in (Registry.RegSZ, Registry.RegExpandSZ): + assert type(data_val) is str + + enc = 'utf-16le' if wide else 'utf-8' + data = f'{data_val}\x00'.encode(enc) + + elif data_type == Registry.RegDWord: + assert type(data_val) is int + + data = self.ql.pack32(data_val) + + elif data_type == Registry.RegQWord: + assert type(data_val) is int + + data = self.ql.pack64(data_val) + + elif data_type == Registry.RegBin: + assert type(data_val) is bytes + + data = data_val - def write_reg_value_into_mem(self, reg_value, reg_type, address): - length = 0 - # string - if reg_type == Registry.RegSZ or reg_type == Registry.RegExpandSZ: - self.ql.mem.write(address, bytes(reg_value, "utf-16le") + b"\x00") - length = len(reg_value) - elif reg_type == Registry.RegBin: - # you can set REG_BINARY like '\x00\x01\x02' in config.json - if type(reg_value) == str: - self.ql.mem.write(address, bytes(reg_value)) - length = len(reg_value) - else: - raise QlErrorNotImplemented("Windows Registry Type not implemented") - elif reg_type == Registry.RegDWord: - data = self.ql.pack32(reg_value) - self.ql.mem.write(address, data) - length = len(data) - elif reg_type == Registry.RegQWord: - data = self.ql.pack64(reg_value) - self.ql.mem.write(address, data) - length = len(data) else: - raise QlErrorNotImplemented( - "Windows Registry Type write to memory %s not implemented" % (REG_TYPES[reg_type])) + return None + + self.ql.mem.write(data_addr, data) + + return len(data) + + def write(self, key: str, subkey: str, reg_type: int, data_addr: int, data_size: int, wide: bool) -> None: + data = self.__reg_mem_read(reg_type, data_addr, data_size, wide) + + if data is None: + raise QlErrorNotImplemented(f'registry type {REG_TYPES[reg_type]} not implemented') + + self.regconf.write(key, subkey, reg_type, data) + self.reghive.write(key, subkey, reg_type, data) + + def write_reg_value_into_mem(self, data_type: int, data_addr: int, data_val: Union[str, bytes, int], wide: bool) -> int: + length = self.__reg_mem_write(data_type, data_addr, data_val, wide) + + if length is None: + raise QlErrorNotImplemented(f'registry type {REG_TYPES[data_type]} not implemented') return length def save(self): - # write registry config to config file - if self.registry_config and len(self.registry_config) != 0: - with open(self.regdiff, "wb") as f: - f.write(bytes(json.dumps(self.registry_config), "utf-8")) + self.regconf.save(self.regdiff) diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index 9f05a6647..84412a571 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -3,486 +3,266 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import ctypes, struct +import ctypes from enum import IntEnum -from unicorn.x86_const import * +from qiling import Qiling +from qiling.os.windows.handle import Handle +from qiling.exception import QlErrorNotImplemented +from .wdk_const import IRP_MJ_MAXIMUM_FUNCTION, PROCESSOR_FEATURE_MAX +def __make_struct(archbits: int): + """Provide a ctypes Structure base class based on the underlying + architecture properties. + """ -from qiling.const import * -from qiling.os.windows.handle import * -from qiling.exception import * -from .wdk_const import * + class Struct(ctypes.LittleEndianStructure): + _pack_ = archbits // 8 + return Struct -class POINTER32(ctypes.Structure): - _fields_ = [('value', ctypes.c_uint32)] +def __select_native_type(archbits: int): + """Select a ctypes integer type with the underlying architecture + native size. + """ -class POINTER64(ctypes.Structure): - _fields_ = [('value', ctypes.c_uint64)] + __type = { + 32 : ctypes.c_uint32, + 64 : ctypes.c_uint64 + } + return __type[archbits] -class TEB: - def __init__(self, - ql, - base=0, - exception_list=0, - stack_base=0, - stack_limit=0, - sub_system_tib=0, - fiber_data=0, - arbitrary_user_pointer=0, - Self=0, - environment_pointer=0, - client_id_unique_process=0, - client_id_unique_thread=0, - rpc_handle=0, - tls_storage=0, - peb_address=0, - last_error_value=0, - last_status_value=0, - count_owned_locks=0, - hard_error_mode=0): - self.ql = ql - self.base = base - self.ExceptionList = exception_list - self.StackBase = stack_base - self.StackLimit = stack_limit - self.SubSystemTib = sub_system_tib - self.FiberData = fiber_data - self.ArbitraryUserPointer = arbitrary_user_pointer - self.Self = Self - self.EnvironmentPointer = environment_pointer - self.ClientIdUniqueProcess = client_id_unique_process - self.ClientIdUniqueThread = client_id_unique_thread - self.RpcHandle = rpc_handle - self.Tls_Storage = tls_storage - self.PEB_Address = peb_address - self.LastErrorValue = last_error_value - self.LastStatusValue = last_status_value - self.Count_Owned_Locks = count_owned_locks - self.HardErrorMode = hard_error_mode - - def bytes(self): - s = b'' - s += self.ql.pack(self.ExceptionList) # 0x00 - s += self.ql.pack(self.StackBase) # 0x04 - s += self.ql.pack(self.StackLimit) # 0x08 - s += self.ql.pack(self.SubSystemTib) # 0x0c - s += self.ql.pack(self.FiberData) # 0x10 - s += self.ql.pack(self.ArbitraryUserPointer) # 0x14 - s += self.ql.pack(self.Self) # 0x18 - s += self.ql.pack(self.EnvironmentPointer) # 0x1c - s += self.ql.pack(self.ClientIdUniqueProcess) # 0x20 - s += self.ql.pack(self.ClientIdUniqueThread) # 0x24 - s += self.ql.pack(self.RpcHandle) # 0x28 - s += self.ql.pack(self.Tls_Storage) # 0x2c - s += self.ql.pack(self.PEB_Address) # 0x30 - s += self.ql.pack(self.LastErrorValue) # 0x34 - s += self.ql.pack(self.LastStatusValue) # 0x38 - s += self.ql.pack(self.Count_Owned_Locks) # 0x3c - s += self.ql.pack(self.HardErrorMode) # 0x40 - return s - - -# https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/index.htm - - -class PEB: - def __init__(self, - ql, - base=0, - flag=0, - mutant=0, - image_base_address=0, - ldr_address=0, - process_parameters=0, - sub_system_data=0, - process_heap=0, - fast_peb_lock=0, - alt_thunk_s_list_ptr=0, - ifeo_key=0, - number_processors=0): - self.ql = ql - self.base = base - self.flag = flag - self.ImageBaseAddress = image_base_address - self.Mutant = mutant - self.LdrAddress = ldr_address - self.ProcessParameters = process_parameters - self.SubSystemData = sub_system_data - self.ProcessHeap = process_heap - self.FastPebLock = fast_peb_lock - self.AtlThunkSListPtr = alt_thunk_s_list_ptr - self.IFEOKey = ifeo_key - self.numberOfProcessors = number_processors - if self.ql.archtype == 32: - self.size = 0x0468 - else: - self.size = 0x07B0 - def write(self, addr): - s = b'' - s += self.ql.pack(self.flag) # 0x0 / 0x0 - s += self.ql.pack(self.Mutant) # 0x4 / 0x8 - s += self.ql.pack(self.ImageBaseAddress) # 0x8 / 0x10 - s += self.ql.pack(self.LdrAddress) # 0xc / 0x18 - s += self.ql.pack(self.ProcessParameters) # 0x10 / 0x20 - s += self.ql.pack(self.SubSystemData) # 0x14 / 0x28 - s += self.ql.pack(self.ProcessHeap) # 0x18 / 0x30 - s += self.ql.pack(self.FastPebLock) # 0x1c / 0x38 - s += self.ql.pack(self.AtlThunkSListPtr) # 0x20 / 0x40 - s += self.ql.pack(self.IFEOKey) # 0x24 / 0x48 - self.ql.mem.write(addr, s) - # FIXME: understand how each attribute of the PEB works before adding it - self.ql.mem.write(addr + 0x64, self.ql.pack(self.numberOfProcessors)) - - -class LDR_DATA: - def __init__(self, - ql, - base=0, - Length=0, - Initialized=0, - SsHandle=0, - InLoadOrderModuleList={ - 'Flink': 0, - 'Blink': 0 - }, - InMemoryOrderModuleList={ - 'Flink': 0, - 'Blink': 0 - }, - InInitializationOrderModuleList={ - 'Flink': 0, - 'Blink': 0 - }, - EntryInProgress=0, - ShutdownInProgress=0, - ShutdownThreadId=0): - self.ql = ql - self.base = base - self.Length = Length - self.Initialized = Initialized - self.SsHandle = SsHandle - self.InLoadOrderModuleList = InLoadOrderModuleList - self.InMemoryOrderModuleList = InMemoryOrderModuleList - self.InInitializationOrderModuleList = InInitializationOrderModuleList - self.EntryInProgress = EntryInProgress - self.ShutdownInProgress = ShutdownInProgress - self.selfShutdownThreadId = ShutdownThreadId - - def bytes(self): - s = b'' - s += self.ql.pack32(self.Length) # 0x0 - s += self.ql.pack32(self.Initialized) # 0x4 - s += self.ql.pack(self.SsHandle) # 0x8 - s += self.ql.pack(self.InLoadOrderModuleList['Flink']) # 0x0c - s += self.ql.pack(self.InLoadOrderModuleList['Blink']) - s += self.ql.pack(self.InMemoryOrderModuleList['Flink']) # 0x14 - s += self.ql.pack(self.InMemoryOrderModuleList['Blink']) - s += self.ql.pack( - self.InInitializationOrderModuleList['Flink']) # 0x1C - s += self.ql.pack(self.InInitializationOrderModuleList['Blink']) - s += self.ql.pack(self.EntryInProgress) - s += self.ql.pack(self.ShutdownInProgress) - s += self.ql.pack(self.selfShutdownThreadId) - - return s - - -class LDR_DATA_TABLE_ENTRY: - def __init__(self, - ql, - base=0, - InLoadOrderLinks={ - 'Flink': 0, - 'Blink': 0 - }, - InMemoryOrderLinks={ - 'Flink': 0, - 'Blink': 0 - }, - InInitializationOrderLinks={ - 'Flink': 0, - 'Blink': 0 - }, - DllBase=0, - EntryPoint=0, - SizeOfImage=0, - FullDllName='', - BaseDllName='', - Flags=0, - LoadCount=0, - TlsIndex=0, - HashLinks=0, - SectionPointer=0, - CheckSum=0, - TimeDateStamp=0, - LoadedImports=0, - EntryPointActivationContext=0, - PatchInformation=0, - ForwarderLinks=0, - ServiceTagLinks=0, - StaticLinks=0, - ContextInformation=0, - OriginalBase=0, - LoadTime=0): - self.ql = ql - self.base = base - self.InLoadOrderLinks = InLoadOrderLinks - self.InMemoryOrderLinks = InMemoryOrderLinks - self.InInitializationOrderLinks = InInitializationOrderLinks - self.DllBase = DllBase - self.EntryPoint = EntryPoint - self.SizeOfImage = SizeOfImage - - FullDllName = FullDllName.encode("utf-16le") - self.FullDllName = {} - self.FullDllName['Length'] = len(FullDllName) - self.FullDllName['MaximumLength'] = len(FullDllName) + 2 - self.FullDllName['BufferPtr'] = ql.heap.alloc( - self.FullDllName['MaximumLength']) - ql.mem.write(self.FullDllName['BufferPtr'], FullDllName + b"\x00\x00") - - BaseDllName = BaseDllName.encode("utf-16le") - self.BaseDllName = {} - self.BaseDllName['Length'] = len(BaseDllName) - self.BaseDllName['MaximumLength'] = len(BaseDllName) + 2 - self.BaseDllName['BufferPtr'] = ql.heap.alloc( - self.BaseDllName['MaximumLength']) - ql.mem.write(self.BaseDllName['BufferPtr'], BaseDllName + b"\x00\x00") - - self.Flags = Flags - self.LoadCount = LoadCount - self.TlsIndex = TlsIndex - self.HashLinks = HashLinks - self.SectionPointer = SectionPointer - self.CheckSum = CheckSum - self.TimeDateStamp = TimeDateStamp - self.LoadedImports = LoadedImports - self.EntryPointActivationContext = EntryPointActivationContext - self.PatchInformation = PatchInformation - self.ForwarderLinks = ForwarderLinks - self.ServiceTagLinks = ServiceTagLinks - self.StaticLinks = StaticLinks - self.ContextInformation = ContextInformation - self.OriginalBase = OriginalBase - self.LoadTime = LoadTime - - def attrs(self): - return ", ".join("{}={}".format(k, getattr(self, k)) - for k in self.__dict__.keys()) - - def print(self): - return "[{}:{}]".format(self.__class__.__name__, self.attrs()) - - def bytes(self): - s = b'' - s += self.ql.pack(self.InLoadOrderLinks['Flink']) # 0x0 - s += self.ql.pack(self.InLoadOrderLinks['Blink']) - s += self.ql.pack(self.InMemoryOrderLinks['Flink']) # 0x8 - s += self.ql.pack(self.InMemoryOrderLinks['Blink']) - s += self.ql.pack(self.InInitializationOrderLinks['Flink']) # 0x10 - s += self.ql.pack(self.InInitializationOrderLinks['Blink']) - s += self.ql.pack(self.DllBase) # 0x18 - s += self.ql.pack(self.EntryPoint) # 0x1c - 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.arch == 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.arch == QL_ARCH.X8664: - s += self.ql.pack32(0) - - s += self.ql.pack(self.BaseDllName['BufferPtr']) - s += self.ql.pack(self.Flags) - s += self.ql.pack(self.LoadCount) - s += self.ql.pack(self.TlsIndex) - s += self.ql.pack(self.HashLinks) - s += self.ql.pack(self.SectionPointer) - s += self.ql.pack(self.CheckSum) - s += self.ql.pack(self.TimeDateStamp) - s += self.ql.pack(self.LoadedImports) - s += self.ql.pack(self.EntryPointActivationContext) - s += self.ql.pack(self.PatchInformation) - s += self.ql.pack(self.ForwarderLinks) - s += self.ql.pack(self.ServiceTagLinks) - s += self.ql.pack(self.StaticLinks) - s += self.ql.pack(self.ContextInformation) - s += self.ql.pack(self.OriginalBase) - s += self.ql.pack(self.LoadTime) - - return s - - -''' -https://docs.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string - -typedef struct _UNICODE_STRING { - USHORT Length; - USHORT MaximumLength; - PWSTR Buffer; -} UNICODE_STRING, *PUNICODE_STRING; -''' - - -class UNICODE_STRING64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Length', ctypes.c_uint16), ('MaximumLength', ctypes.c_int16), - ('Buffer', ctypes.c_uint64)) - - -class UNICODE_STRING32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('Length', ctypes.c_uint16), ('MaximumLength', ctypes.c_int16), - ('Buffer', ctypes.c_uint32)) - - -''' -https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_driver_object - -typedef struct _DRIVER_OBJECT { - CSHORT Type; - CSHORT Size; - PDEVICE_OBJECT DeviceObject; - ULONG Flags; - PVOID DriverStart; - ULONG DriverSize; - PVOID DriverSection; - PDRIVER_EXTENSION DriverExtension; - UNICODE_STRING DriverName; - PUNICODE_STRING HardwareDatabase; - PFAST_IO_DISPATCH FastIoDispatch; - PDRIVER_INITIALIZE DriverInit; - PDRIVER_STARTIO DriverStartIo; - PDRIVER_UNLOAD DriverUnload; - PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; -} DRIVER_OBJECT, *PDRIVER_OBJECT; -''' - - -class DRIVER_OBJECT64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ("_Type", ctypes.c_uint16), - ("_Size", ctypes.c_uint16), - ("_DeviceObject", POINTER64), - ("_Flags", ctypes.c_uint32), - ("_DriverStart", POINTER64), - ("_DriverSize", ctypes.c_uint32), - ("_DriverSection", POINTER64), - ("_DriverExtension", POINTER64), - ("_DriverName", UNICODE_STRING64), - ("_HardwareDatabase", POINTER64), - ("_FastIoDispatch", POINTER64), - ("_DriverInit", POINTER64), - ("_DriverStartIo", POINTER64), - ("_DriverUnload", POINTER64), - ("_MajorFunction", ctypes.c_uint64 * (IRP_MJ_MAXIMUM_FUNCTION + 1))) - - def __init__(self, ql, base): - self.ql = ql - self.base = base - - # get MajorFunction - @property - def MajorFunction(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._MajorFunction - - @property - def DeviceObject(self): - # TODO: improve this code to avoid reading the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DeviceObject.value - - @DeviceObject.setter - def DeviceObject(self, value): - # TODO: improve this code to avoid reading/writing the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - obj._DeviceObject.value = value - # update back to memory. - self.ql.mem.write(self.base, bytes(obj)) - - @property - def DriverUnload(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DriverUnload.value - - -class DRIVER_OBJECT32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ("_Type", ctypes.c_uint16), - ("_Size", ctypes.c_uint16), - ("_DeviceObject", POINTER32), - ("_Flags", ctypes.c_uint32), - ("_DriverStart", POINTER32), - ("_DriverSize", ctypes.c_uint32), - ("_DriverSection", POINTER32), - ("_DriverExtension", POINTER32), - ("_DriverName", UNICODE_STRING32), - ("_HardwareDatabase", POINTER32), - ("_FastIoDispatch", POINTER32), - ("_DriverInit", POINTER32), - ("_DriverStartIo", POINTER32), - ("_DriverUnload", POINTER32), - ("_MajorFunction", ctypes.c_uint32 * (IRP_MJ_MAXIMUM_FUNCTION + 1))) - - def __init__(self, ql, base): - self.ql = ql - self.base = base - - # get MajorFunction - @property - def MajorFunction(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._MajorFunction - - @property - def DeviceObject(self): - # TODO: improve this code to avoid reading the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DeviceObject.value - - @DeviceObject.setter - def DeviceObject(self, value): - # TODO: improve this code to avoid reading/writing the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - obj._DeviceObject.value = value - # update back to memory. - self.ql.mem.write(self.base, bytes(obj)) - - @property - def DriverUnload(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DriverUnload.value +def __select_pointer_type(archbits: int): + """Provide a pointer base class based on the underlying + architecture properties. + """ + + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + class Pointer(Struct): + _fields_ = ( + ('value', native_type), + ) + + return Pointer + + +def make_teb(archbits: int, *args, **kwargs): + """Initialize a TEB structure. + + Additional arguments may be used to initialize specific TEB fields. + """ + + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + class TEB(Struct): + _fields_ = ( + ('CurrentSEH', native_type), + ('StackBase', native_type), + ('StackLimit', native_type), + ('SubSystemTib', native_type), + ('FiberData', native_type), + ('ArbitraryDataSlot', native_type), + ('TebAddress', native_type), + ('EnvironmentPointer', native_type), + ('ProcessID', native_type), + ('ThreadID', native_type), + ('RpcHandle', native_type), + ('TlsAddress', native_type), + ('PebAddress', native_type), + ('LastError', ctypes.c_int32), + ('CriticalSectionsCount', ctypes.c_int32), + ('CsrClientThreadAddress', native_type), + ('Win32ThreadInfo', native_type), + ('Win32ClientInfo', ctypes.c_byte * 124), + ('ReservedWow64', native_type), + ('CurrentLocale', ctypes.c_int32), + ('FpSwStatusReg', ctypes.c_int32), + ('ReservedOS', ctypes.c_byte * 216) + ) + + return TEB(*args, **kwargs) + + +def make_peb(archbits: int, *args, **kwargs): + """Initialize a PEB structure. + + Additional arguments may be used to initialize specific PEB fields. + """ + + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + # https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/index.htm + class PEB(Struct): + _fields_ = ( + ('InheritedAddressSpace', ctypes.c_int8), + ('ReadImageFileExecOptions', ctypes.c_int8), + ('BeingDebugged', ctypes.c_int8), + ('BitField', ctypes.c_int8), + ('Mutant', native_type), + ('ImageBaseAddress', native_type), + ('LdrAddress', native_type), + ('ProcessParameters', native_type), + ('SubSystemData', native_type), + ('ProcessHeap', native_type), + ('FastPebLock', native_type), + ('AtlThunkSListPtr', native_type), + ('IFEOKey', native_type), + ('CrossProcessFlags', ctypes.c_int32), + ('KernelCallbackTable', native_type), + ('SystemReserved', ctypes.c_int32), + ('AtlThunkSListPtr32', ctypes.c_int32), + ('ApiSetMap', native_type), + ('TlsExpansionCounter', ctypes.c_int32), + ('TlsBitmap', native_type), + ('TlsBitmapBits', ctypes.c_int32 * 2), + ('ReadOnlySharedMemoryBase', native_type), + ('SharedData', native_type), + ('ReadOnlyStaticServerData', native_type), + ('AnsiCodePageData', native_type), + ('OemCodePageData', native_type), + ('UnicodeCaseTableData', native_type), + ('NumberOfProcessors', ctypes.c_int32), + ('NtGlobalFlag', ctypes.c_int32), + ('CriticalSectionTimeout', native_type) + # ... more + ) + + obj_size = { + 32: 0x047c, + 64: 0x07c8 + }[archbits] + + obj = PEB(*args, **kwargs) + ctypes.resize(obj, obj_size) + + return obj + + +# https://docs.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string +# +# typedef struct _UNICODE_STRING { +# USHORT Length; +# USHORT MaximumLength; +# PWSTR Buffer; +# } UNICODE_STRING, *PUNICODE_STRING; + +def make_unicode_string(archbits: int, *args, **kwargs): + """Initialize a Unicode String structure. + + Additional arguments may be used to initialize specific structure fields. + """ + + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + class UNICODE_STRING(Struct): + _fields_ = ( + ('Length', ctypes.c_uint16), + ('MaximumLength', ctypes.c_uint16), + ('Buffer', native_type) + ) + + return UNICODE_STRING(*args, **kwargs) + +# https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_driver_object +# +# typedef struct _DRIVER_OBJECT { +# CSHORT Type; +# CSHORT Size; +# PDEVICE_OBJECT DeviceObject; +# ULONG Flags; +# PVOID DriverStart; +# ULONG DriverSize; +# PVOID DriverSection; +# PDRIVER_EXTENSION DriverExtension; +# UNICODE_STRING DriverName; +# PUNICODE_STRING HardwareDatabase; +# PFAST_IO_DISPATCH FastIoDispatch; +# PDRIVER_INITIALIZE DriverInit; +# PDRIVER_STARTIO DriverStartIo; +# PDRIVER_UNLOAD DriverUnload; +# PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; +# } DRIVER_OBJECT, *PDRIVER_OBJECT; + +def make_driver_object(ql: Qiling, base: int, archbits: int): + native_type = __select_native_type(archbits) + pointer_type = __select_pointer_type(archbits) + Struct = __make_struct(archbits) + + ucstrtype = make_unicode_string(archbits).__class__ + + class DRIVER_OBJECT(Struct): + _fields_ = ( + ('_Type', ctypes.c_uint16), + ('_Size', ctypes.c_uint16), + ('_DeviceObject', pointer_type), + ('_Flags', ctypes.c_uint32), + ('_DriverStart', pointer_type), + ('_DriverSize', ctypes.c_uint32), + ('_DriverSection', pointer_type), + ('_DriverExtension', pointer_type), + ('_DriverName', ucstrtype), + ('_HardwareDatabase', pointer_type), + ('_FastIoDispatch', pointer_type), + ('_DriverInit', pointer_type), + ('_DriverStartIo', pointer_type), + ('_DriverUnload', pointer_type), + ('_MajorFunction', native_type * (IRP_MJ_MAXIMUM_FUNCTION + 1)) + ) + + def __read_obj(self) -> 'DRIVER_OBJECT': + data = ql.mem.read(base, ctypes.sizeof(self)) + + return self.__class__.from_buffer(data) + + def __write_obj(self) -> None: + ql.mem.write(base, bytes(self)) + + # get MajorFunction + @property + def MajorFunction(self): + obj = self.__read_obj() + + return getattr(obj, '_MajorFunction') + @property + def DeviceObject(self): + obj = self.__read_obj() + return getattr(obj, '_DeviceObject').value + + @DeviceObject.setter + def DeviceObject(self, value): + obj = self.__read_obj() + getattr(obj, '_DeviceObject').value = value + + obj.__write_obj() + + @property + def DriverUnload(self): + obj = self.__read_obj() + + return getattr(obj, '_DriverUnload').value + + return DRIVER_OBJECT() class KSYSTEM_TIME(ctypes.Structure): - _fields_ = (('LowPart', ctypes.c_uint32), ('High1Time', ctypes.c_int32), - ('High2Time', ctypes.c_int32)) + _fields_ = ( + ('LowPart', ctypes.c_uint32), + ('High1Time', ctypes.c_int32), + ('High2Time', ctypes.c_int32) + ) -class LARGE_INTEGER_DUMMYSTRUCTNAME(ctypes.Structure): +class LARGE_INTEGER_DUMMYSTRUCTNAME(ctypes.LittleEndianStructure): _fields_ = ( ('LowPart', ctypes.c_uint32), ('HighPart', ctypes.c_int32), @@ -495,8 +275,12 @@ class LARGE_INTEGER(ctypes.Union): ('QuadPart', ctypes.c_int64), ) +# https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/kuser_shared_data/index.htm +# +# struct information: +# https://doxygen.reactos.org/d8/dae/modules_2rostests_2winetests_2ntdll_2time_8c_source.html -class KUSER_SHARED_DATA(ctypes.Structure): +class KUSER_SHARED_DATA(ctypes.LittleEndianStructure): _fields_ = ( ('TickCountLowDeprecated', ctypes.c_uint32), ('TickCountMultiplier', ctypes.c_uint32), @@ -539,216 +323,123 @@ class KUSER_SHARED_DATA(ctypes.Structure): ('TestRetInstruction', ctypes.c_uint8), ('_padding0', ctypes.c_uint8 * 0x2F8)) +def make_list_entry(archbits: int): + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) -class LIST_ENTRY32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Flink', ctypes.c_uint32), - ('Blink', ctypes.c_uint32), - ) - - -class KDEVICE_QUEUE_ENTRY32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('DeviceListEntry', LIST_ENTRY32), - ('SortKey', ctypes.c_uint32), - ('Inserted', ctypes.c_uint8) + class LIST_ENTRY(Struct): + _fields_ = ( + ('Flink', native_type), + ('Blink', native_type) ) + return LIST_ENTRY -class WAIT_ENTRY32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('DmaWaitEntry', LIST_ENTRY32), - ('NumberOfChannels', ctypes.c_uint32), - ('DmaContext', ctypes.c_uint32) - ) - - -class WAIT_QUEUE_UNION32(ctypes.Union): - _pack_ = 4 - _fields_ = ("WaitQueueEntry", KDEVICE_QUEUE_ENTRY32), ("Dma", WAIT_ENTRY32) - +def make_device_object(archbits: int): + native_type = __select_native_type(archbits) + pointer_type = __select_pointer_type(archbits) + Struct = __make_struct(archbits) -class WAIT_CONTEXT_BLOCK32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('WaitQueue', WAIT_QUEUE_UNION32), - ('DeviceRoutine', POINTER32), - ('DeviceContext', POINTER32), - ('NumberOfMapRegisters', ctypes.c_uint32), - ('DeviceObject', POINTER32), - ('CurrentIrp', POINTER32), - ('BufferChainingDpc', POINTER32)) + LIST_ENTRY = make_list_entry(archbits) + class KDEVICE_QUEUE_ENTRY(Struct): + _fields_ = ( + ('DeviceListEntry', LIST_ENTRY), + ('SortKey', ctypes.c_uint32), + ('Inserted', ctypes.c_uint8) + ) -class KDEVICE_QUEUE32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('Type', ctypes.c_int16), ('Size', ctypes.c_int16), - ('DeviceListHead', LIST_ENTRY32), ('Lock', ctypes.c_uint32), - ('Busy', ctypes.c_uint8)) - - -class SINGLE_LIST_ENTRY32(ctypes.Structure): - _fields_ = [(('Next', ctypes.c_uint32))] - - -# https://github.com/ntdiff/headers/blob/master/Win10_1507_TS1/x64/System32/hal.dll/Standalone/_KDPC.h -class KDPC32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Type', ctypes.c_uint8), - ('Importance', ctypes.c_uint8), - ('Number', ctypes.c_uint16), - ('DpcListEntry', LIST_ENTRY32), - ('DeferredRoutine', POINTER32), - ('DeferredContext', POINTER32), - ('SystemArgument1', POINTER32), - ('SystemArgument2', POINTER32), - ('DpcData', POINTER32), - ) - - -class DISPATCHER_HEADER32(ctypes.Structure): - _fields_ = ( - ('Lock', ctypes.c_int32), - ('SignalState', ctypes.c_int32), - ('WaitListHead', LIST_ENTRY32), - ) - - -# https://docs.microsoft.com/vi-vn/windows-hardware/drivers/ddi/wdm/ns-wdm-_device_object -class DEVICE_OBJECT32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Type', ctypes.c_int16), - ('Size', ctypes.c_uint16), - ('ReferenceCount', ctypes.c_int32), - ('DriverObject', POINTER32), - ('NextDevice', POINTER32), - ('AttachedDevice', POINTER32), - ('CurrentIrp', POINTER32), - ('Timer', POINTER32), - ('Flags', ctypes.c_uint32), - ('Characteristics', ctypes.c_uint32), - ('Vpb', POINTER32), - ('DeviceExtension', ctypes.c_uint32), - ('DeviceType', ctypes.c_uint32), - ('StackSize', ctypes.c_int16), - ('Queue', WAIT_CONTEXT_BLOCK32), - ('AlignmentRequirement', ctypes.c_uint32), - ('DeviceQueue', KDEVICE_QUEUE32), - ('Dpc', KDPC32), - ('ActiveThreadCount', ctypes.c_uint32), - ('SecurityDescriptor', POINTER32), - ('DeviceLock', DISPATCHER_HEADER32), - ('SectorSize', ctypes.c_uint16), - ('Spare1', ctypes.c_uint16), - ('DeviceObjectExtension', POINTER32), - ('Reserved', POINTER32), - ) - - -## 64bit structures -class LIST_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Flink', ctypes.c_uint64), - ('Blink', ctypes.c_uint64), - ) - - -class KDEVICE_QUEUE_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('DeviceListEntry', LIST_ENTRY64), - ('SortKey', ctypes.c_uint32), ('Inserted', ctypes.c_uint8)) - - -class WAIT_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('DmaWaitEntry', LIST_ENTRY64), - ('NumberOfChannels', ctypes.c_uint32), ('DmaContext', - ctypes.c_uint32)) - - -class WAIT_QUEUE_UNION64(ctypes.Union): - _fields_ = ("WaitQueueEntry", KDEVICE_QUEUE_ENTRY64), ("Dma", WAIT_ENTRY64) - - -class WAIT_CONTEXT_BLOCK64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('WaitQueue', WAIT_QUEUE_UNION64), - ('DeviceRoutine', POINTER64), ('DeviceContext', POINTER64), - ('NumberOfMapRegisters', ctypes.c_uint32), ('DeviceObject', - POINTER64), - ('CurrentIrp', POINTER64), ('BufferChainingDpc', POINTER64)) - - -class KDEVICE_QUEUE64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Type', ctypes.c_int16), ('Size', ctypes.c_int16), - ('DeviceListHead', LIST_ENTRY64), ('Lock', ctypes.c_uint32), - ('Busy', ctypes.c_uint8)) - + class WAIT_ENTRY(Struct): + _fields_ = ( + ('DmaWaitEntry', LIST_ENTRY), + ('NumberOfChannels', ctypes.c_uint32), + ('DmaContext', ctypes.c_uint32) + ) -class SINGLE_LIST_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = [(('Next', ctypes.c_uint64))] + class WAIT_QUEUE_UNION(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ("WaitQueueEntry", KDEVICE_QUEUE_ENTRY), + ("Dma", WAIT_ENTRY) + ) + class WAIT_CONTEXT_BLOCK(Struct): + _fields_ = ( + ('WaitQueue', WAIT_QUEUE_UNION), + ('DeviceRoutine', pointer_type), + ('DeviceContext', pointer_type), + ('NumberOfMapRegisters', ctypes.c_uint32), + ('DeviceObject', pointer_type), + ('CurrentIrp', pointer_type), + ('BufferChainingDpc', pointer_type) + ) -class KDPC64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_uint8), - ('Importance', ctypes.c_uint8), - ('Number', ctypes.c_uint16), - ('DpcListEntry', LIST_ENTRY64), - ('DeferredRoutine', POINTER64), - ('DeferredContext', POINTER64), - ('SystemArgument1', POINTER64), - ('SystemArgument2', POINTER64), - ('DpcData', POINTER64), - ) + class KDEVICE_QUEUE(Struct): + _fields_ = ( + ('Type', ctypes.c_int16), + ('Size', ctypes.c_int16), + ('DeviceListHead', LIST_ENTRY), + ('Lock', ctypes.c_uint32), + ('Busy', ctypes.c_uint8) + ) + # class SINGLE_LIST_ENTRY(Struct): + # _fields_ = ( + # ('Next', native_type), + # ) + + # https://github.com/ntdiff/headers/blob/master/Win10_1507_TS1/x64/System32/hal.dll/Standalone/_KDPC.h + class KDPC(Struct): + _fields_ = ( + ('Type', ctypes.c_uint8), + ('Importance', ctypes.c_uint8), + ('Number', ctypes.c_uint16), + ('DpcListEntry', LIST_ENTRY), + ('DeferredRoutine', pointer_type), + ('DeferredContext', pointer_type), + ('SystemArgument1', pointer_type), + ('SystemArgument2', pointer_type), + ('DpcData', pointer_type) + ) -class DISPATCHER_HEADER64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Lock', ctypes.c_int32), - ('SignalState', ctypes.c_int32), - ('WaitListHead', LIST_ENTRY64), - ) + class DISPATCHER_HEADER(Struct): + _fields_ = ( + ('Lock', ctypes.c_int32), + ('SignalState', ctypes.c_int32), + ('WaitListHead', LIST_ENTRY) + ) + # https://docs.microsoft.com/vi-vn/windows-hardware/drivers/ddi/wdm/ns-wdm-_device_object + class DEVICE_OBJECT(Struct): + _fields_ = ( + ('Type', ctypes.c_int16), + ('Size', ctypes.c_uint16), + ('ReferenceCount', ctypes.c_int32), + ('DriverObject', pointer_type), + ('NextDevice', pointer_type), + ('AttachedDevice', pointer_type), + ('CurrentIrp', pointer_type), + ('Timer', pointer_type), + ('Flags', ctypes.c_uint32), + ('Characteristics', ctypes.c_uint32), + ('Vpb', pointer_type), + ('DeviceExtension', native_type), + ('DeviceType', ctypes.c_uint32), + ('StackSize', ctypes.c_int16), + ('Queue', WAIT_CONTEXT_BLOCK), + ('AlignmentRequirement', ctypes.c_uint32), + ('DeviceQueue', KDEVICE_QUEUE), + ('Dpc', KDPC), + ('ActiveThreadCount', ctypes.c_uint32), + ('SecurityDescriptor', pointer_type), + ('DeviceLock', DISPATCHER_HEADER), + ('SectorSize', ctypes.c_uint16), + ('Spare1', ctypes.c_uint16), + ('DeviceObjectExtension', pointer_type), + ('Reserved', pointer_type) + ) -class DEVICE_OBJECT64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_int16), - ('Size', ctypes.c_uint16), - ('ReferenceCount', ctypes.c_int32), - ('DriverObject', POINTER64), - ('NextDevice', POINTER64), - ('AttachedDevice', POINTER64), - ('CurrentIrp', POINTER64), - ('Timer', POINTER64), - ('Flags', ctypes.c_uint32), - ('Characteristics', ctypes.c_uint32), - ('Vpb', POINTER64), - ('DeviceExtension', ctypes.c_uint64), - ('DeviceType', ctypes.c_uint32), - ('StackSize', ctypes.c_int16), - ('Queue', WAIT_CONTEXT_BLOCK64), - ('AlignmentRequirement', ctypes.c_uint32), - ('DeviceQueue', KDEVICE_QUEUE64), - ('Dpc', KDPC64), - ('ActiveThreadCount', ctypes.c_uint32), - ('SecurityDescriptor', POINTER64), - ('DeviceLock', DISPATCHER_HEADER64), - ('SectorSize', ctypes.c_uint16), - ('Spare1', ctypes.c_uint16), - ('DeviceObjectExtension', POINTER64), - ('Reserved', POINTER64), - ) + return DEVICE_OBJECT() # struct IO_STATUS_BLOCK { @@ -759,234 +450,102 @@ class DEVICE_OBJECT64(ctypes.Structure): # ULONG_PTR Information; # }; +def make_irp(archbits: int): + pointer_type = __select_pointer_type(archbits) + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) -class IO_STATUS_BLOCK_DUMMY64(ctypes.Union): - _pack_ = 8 - _fields_ = ( - ('Status', ctypes.c_int32), - ('Pointer', POINTER64), - ) - - -class IO_STATUS_BLOCK64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Status', IO_STATUS_BLOCK_DUMMY64), ('Information', - POINTER64)) - + LIST_ENTRY = make_list_entry(archbits) -class IO_STATUS_BLOCK_DUMMY32(ctypes.Union): - _fields_ = ( - ('Status', ctypes.c_int32), - ('Pointer', POINTER32), - ) - - -class IO_STATUS_BLOCK32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('Status', IO_STATUS_BLOCK_DUMMY32), ('Information', - POINTER32)) - - -# struct IO_STACK_LOCATION { -# UCHAR MajorFunction; -# UCHAR MinorFunction; -# UCHAR Flags; -# UCHAR Control; -# union { -# struct { -# char _padding1[4]; -# ULONG OutputBufferLength; -# char _padding2[4]; -# ULONG POINTER_ALIGNMENT InputBufferLength; -# char _padding3[4]; -# ULONG POINTER_ALIGNMENT FsControlCode; -# char _padding4[4]; -# PVOID Type3InputBuffer; -# } FileSystemControl; -# struct { -# char _padding5[4]; -# ULONG OutputBufferLength; -# ULONG POINTER_ALIGNMENT InputBufferLength; // 10 -# char _padding7[4]; -# ULONG POINTER_ALIGNMENT IoControlCode; // 18 -# char _padding8[4]; -# PVOID Type3InputBuffer; // 20 -# } DeviceIoControl; -# } Parameters; -# }; -class IO_STACK_LOCATION_FILESYSTEMCONTROL64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('OutputBufferLength', ctypes.c_uint32), ('_padding1', - ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), ('_padding2', - ctypes.c_uint32), - ('FsControlCode', ctypes.c_uint32), ('Type3InputBuffer', - POINTER64)) - - -class IO_STACK_LOCATION_FILESYSTEMCONTROL32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('OutputBufferLength', ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), - ('FsControlCode', ctypes.c_uint32), - ('Type3InputBuffer', POINTER32)) - - -class IO_STACK_LOCATION_DEVICEIOCONTROL64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('OutputBufferLength', ctypes.c_uint32), - ('_padding1', ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), - ('_padding2', ctypes.c_uint32), - ('IoControlCode', ctypes.c_uint32), - ('Type3InputBuffer', POINTER64)) - - -class IO_STACK_LOCATION_DEVICEIOCONTROL32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('OutputBufferLength', ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), - ('IoControlCode', ctypes.c_uint32), - ('Type3InputBuffer', POINTER32) + class IO_STATUS_BLOCK_DUMMY(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ('Status', ctypes.c_int32), + ('Pointer', pointer_type) ) -class IO_STACK_LOCATION_WRITE64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Length', ctypes.c_uint32), - ('_padding1', ctypes.c_uint32), - ('Key', ctypes.c_uint32), - ('Flags', ctypes.c_uint32), - ('ByteOffset', LARGE_INTEGER) - ) - -class IO_STACK_LOCATION_WRITE32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Length', ctypes.c_uint32), - ('Key', ctypes.c_uint32), - ('Flags', ctypes.c_uint32), - ('ByteOffset', LARGE_INTEGER) - ) - -class IO_STACK_LOCATION_PARAM64(ctypes.Union): - _pack_ = 8 - _fields_ = (('FileSystemControl', IO_STACK_LOCATION_FILESYSTEMCONTROL64), - ('DeviceIoControl', IO_STACK_LOCATION_DEVICEIOCONTROL64), - ('Write', IO_STACK_LOCATION_WRITE64)) - - -class IO_STACK_LOCATION_PARAM32(ctypes.Union): - _pack_ = 4 - _fields_ = (('FileSystemControl', IO_STACK_LOCATION_FILESYSTEMCONTROL32), - ('DeviceIoControl', IO_STACK_LOCATION_DEVICEIOCONTROL32), - ('Write', IO_STACK_LOCATION_WRITE32)) - - -class IO_STACK_LOCATION64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('MajorFunction', ctypes.c_byte), - ('MinorFunction', ctypes.c_byte), - ('Flags', ctypes.c_byte), - ('Control', ctypes.c_byte), - ('_padding1', ctypes.c_byte * 0x4), - ('Parameters', IO_STACK_LOCATION_PARAM64), - ('DeviceObject', POINTER64), - ('FileObject', POINTER64), - ('CompletionRoutine', POINTER64), - ('Context', POINTER64), - ) - - -class IO_STACK_LOCATION32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('MajorFunction', ctypes.c_byte), - ('MinorFunction', ctypes.c_byte), - ('Flags', ctypes.c_byte), - ('Control', ctypes.c_byte), - ('Parameters', IO_STACK_LOCATION_PARAM32), - ('DeviceObject', POINTER32), - ('FileObject', POINTER32), - ('CompletionRoutine', POINTER32), - ('Context', POINTER32), - ) + class IO_STATUS_BLOCK(Struct): + _fields_ = ( + ('Status', IO_STATUS_BLOCK_DUMMY), + ('Information', pointer_type) + ) + class IO_STACK_LOCATION_FILESYSTEMCONTROL(Struct): + _fields_ = ( + ('OutputBufferLength', native_type), # c_uint32 padded to native size + ('InputBufferLength', native_type), # c_uint32 padded to native size + ('FsControlCode', ctypes.c_uint32), + ('Type3InputBuffer', pointer_type) + ) -# union { -# struct _IRP *MasterIrp; -# __volatile LONG IrpCount; -# PVOID SystemBuffer; -# } AssociatedIrp; + class IO_STACK_LOCATION_DEVICEIOCONTROL(Struct): + _fields_ = ( + ('OutputBufferLength',native_type), # c_uint32 padded to native size + ('InputBufferLength', native_type), # c_uint32 padded to native size + ('IoControlCode', ctypes.c_uint32), + ('Type3InputBuffer', pointer_type) + ) + class IO_STACK_LOCATION_WRITE(Struct): + _fields_ = ( + ('Length', native_type), # c_uint32 padded to native size + ('Key', ctypes.c_uint32), + ('Flags', ctypes.c_uint32), + ('ByteOffset', LARGE_INTEGER) + ) -class AssociatedIrp64(ctypes.Union): - _fields_ = ( - ('MasterIrp', POINTER64), # ('MasterIrp', ctypes.POINTER(IRP64)), - ('IrpCount', ctypes.c_uint32), - ('SystemBuffer', POINTER64)) + class IO_STACK_LOCATION_PARAM(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ('FileSystemControl', IO_STACK_LOCATION_FILESYSTEMCONTROL), + ('DeviceIoControl', IO_STACK_LOCATION_DEVICEIOCONTROL), + ('Write', IO_STACK_LOCATION_WRITE) + ) + class IO_STACK_LOCATION(Struct): + _fields_ = ( + ('MajorFunction', ctypes.c_byte), + ('MinorFunction', ctypes.c_byte), + ('Flags', ctypes.c_byte), + ('Control', ctypes.c_byte), + ('Parameters', IO_STACK_LOCATION_PARAM), + ('DeviceObject', pointer_type), + ('FileObject', pointer_type), + ('CompletionRoutine', pointer_type), + ('Context', pointer_type), + ) -class AssociatedIrp32(ctypes.Union): - _fields_ = ( - ('MasterIrp', POINTER32), # ('MasterIrp', ctypes.POINTER(IRP32)), - ('IrpCount', ctypes.c_uint32), - ('SystemBuffer', POINTER32)) - - -# struct _IRP { -# char _padding1[0x30]; -# IO_STATUS_BLOCK IoStatus; // distance is 0x30?? -# char _padding2[0x70 - 0x30 - sizeof(io_status_block)]; -# PVOID UserBuffer; // distance is 0x70 from _IRP -# char _padding3[0xB8 - 0x70 - sizeof(PVOID)]; -# IO_STACK_LOCATION *irpstack; -# }; -class IRP64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_uint16), - ('Size', ctypes.c_uint16), - ('MdlAddress', POINTER64), - ('Flags', ctypes.c_uint32), - ('AssociatedIrp', AssociatedIrp64), - ('ThreadListEntry', LIST_ENTRY64), - ('IoStatus', IO_STATUS_BLOCK64), - ('_padding1', ctypes.c_char * 0x8), - ('UserIosb', POINTER64), - ('UserEvent', POINTER64), - ('Overlay', ctypes.c_char * 0x10), - ('CancelRoutine', POINTER64), - ('UserBuffer', POINTER64), - ('_padding1', ctypes.c_char * 0x40), - ('irpstack', ctypes.POINTER(IO_STACK_LOCATION64)), - ('_padding2', ctypes.c_char * 0x10), - ) + class AssociatedIrp(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ('MasterIrp', pointer_type), + ('IrpCount', ctypes.c_uint32), + ('SystemBuffer', pointer_type) + ) + sz_factor = archbits // 32 + + class IRP(Struct): + _fields_ = ( + ('Type', ctypes.c_uint16), + ('Size', ctypes.c_uint16), + ('MdlAddress', pointer_type), + ('Flags', ctypes.c_uint32), + ('AssociatedIrp', AssociatedIrp), + ('ThreadListEntry', LIST_ENTRY), + ('IoStatus', IO_STATUS_BLOCK), + ('_padding1', ctypes.c_char * 8), + ('UserIosb', pointer_type), + ('UserEvent', pointer_type), + ('Overlay', ctypes.c_char * (8 * sz_factor)), + ('CancelRoutine', pointer_type), + ('UserBuffer', pointer_type), + ('_padding1', ctypes.c_char * (32 * sz_factor)), + ('irpstack', ctypes.POINTER(IO_STACK_LOCATION)), + ('_padding2', ctypes.c_char * (8 * sz_factor)) + ) -class IRP32(ctypes.Structure): - _fields_ = ( - ('Type', ctypes.c_uint16), - ('Size', ctypes.c_uint16), - ('MdlAddress', POINTER32), - ('Flags', ctypes.c_uint32), - ('AssociatedIrp', AssociatedIrp32), - ('ThreadListEntry', LIST_ENTRY32), - ('IoStatus', IO_STATUS_BLOCK32), - ('_padding1', ctypes.c_char * 0x8), - ('UserIosb', POINTER32), # 0x28 - ('UserEvent', POINTER32), - ('Overlay', ctypes.c_char * 8), - ('CancelRoutine', POINTER32), - ('UserBuffer', POINTER32), - ('_padding1', ctypes.c_char * 0x20), - ('irpstack', ctypes.POINTER(IO_STACK_LOCATION32)), - ('_padding2', ctypes.c_char * 8), - ) + return IRP() # typedef struct _MDL { @@ -1001,24 +560,26 @@ class IRP32(ctypes.Structure): # } MDL, *PMDL; -class MDL64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Next', POINTER64), ('Size', ctypes.c_uint16), - ('MdlFlags', ctypes.c_uint16), ('Process', POINTER64), - ('MappedSystemVa', POINTER64), ('StartVa', POINTER64), - ('ByteCount', ctypes.c_uint32), ('ByteOffset', - ctypes.c_uint32)) - - -class MDL32(ctypes.Structure): - _fields_ = (('Next', POINTER32), ('Size', ctypes.c_uint16), - ('MdlFlags', ctypes.c_uint16), ('Process', POINTER32), - ('MappedSystemVa', POINTER32), ('StartVa', POINTER32), - ('ByteCount', ctypes.c_uint32), ('ByteOffset', - ctypes.c_uint32)) +def make_mdl(archbits: int): + pointer_type = __select_pointer_type(archbits) + Struct = __make_struct(archbits) + + class MDL(Struct): + _fields_ = ( + ('Next', pointer_type), + ('Size', ctypes.c_uint16), + ('MdlFlags', ctypes.c_uint16), + ('Process', pointer_type), + ('MappedSystemVa', pointer_type), + ('StartVa', pointer_type), + ('ByteCount', ctypes.c_uint32), + ('ByteOffset', ctypes.c_uint32) + ) -#TODO: Repeated and might not be needed + return MDL() +# NOTE: the following classes are currently not needed +# # class DISPATCHER_HEADER64(ctypes.Structure): # _fields_ = ( # ('Lock', ctypes.c_int32), @@ -1029,8 +590,8 @@ class MDL32(ctypes.Structure): # ('SignalState', ctypes.c_int32), # ('WaitListHead', LIST_ENTRY64), # ) - - +# +# # class DISPATCHER_HEADER32(ctypes.Structure): # _fields_ = ( # ('Lock', ctypes.c_int32), @@ -1041,198 +602,198 @@ class MDL32(ctypes.Structure): # ('ThreadControlFlags', ctypes.c_uint8), # ('TimerMiscFlags', ctypes.c_uint8), # ) - - -class KAPC_STATE64(ctypes.Structure): - _fields_ = ( - ('ApcListHead', LIST_ENTRY64 * 2), - ('Process', POINTER64), - ('KernelApcInProgress', ctypes.c_uint8), - ('KernelApcPending', ctypes.c_uint8), - ('UserApcPending', ctypes.c_uint8), - ) - - -class KAPC_STATE32(ctypes.Structure): - _fields_ = ( - ('ApcListHead', LIST_ENTRY32 * 2), - ('Process', POINTER32), - ('KernelApcInProgress', ctypes.c_uint8), - ('KernelApcPending', ctypes.c_uint8), - ('UserApcPending', ctypes.c_uint8), - ) - - -class KTIMER64(ctypes.Structure): - _fields_ = ( - ('Header', DISPATCHER_HEADER64), - ('DueTime', LARGE_INTEGER), - ('TimerListEntry', LIST_ENTRY64), - ('Dpc', POINTER64), - ('Period', ctypes.c_uint32), - ) - - -class KTIMER32(ctypes.Structure): - _fields_ = ( - ('Header', DISPATCHER_HEADER32), - ('DueTime', LARGE_INTEGER), - ('TimerListEntry', LIST_ENTRY32), - ('Dpc', POINTER32), - ('Period', ctypes.c_uint32), - ) - - -class KWAIT_BLOCK64(ctypes.Structure): - _fields_ = ( - ('WaitListEntry', LIST_ENTRY64), - ('Thread', POINTER64), - ('Object', POINTER64), - ('NextWaitBlock', POINTER64), - ('WaitKey', ctypes.c_uint16), - ('WaitType', ctypes.c_uint8), - ('BlockState', ctypes.c_uint8), - ) - - -class KWAIT_BLOCK32(ctypes.Structure): - _fields_ = ( - ('WaitListEntry', LIST_ENTRY32), - ('Thread', POINTER32), - ('Object', POINTER32), - ('NextWaitBlock', POINTER32), - ('WaitKey', ctypes.c_uint16), - ('WaitType', ctypes.c_uint8), - ('BlockState', ctypes.c_uint8), - ) - - -class GROUP_AFFINITY64(ctypes.Structure): - _fields_ = (('Mask', ctypes.c_uint64), ('Group', ctypes.c_uint16), - ('Reserved', ctypes.c_uint16 * 3)) - - -class GROUP_AFFINITY32(ctypes.Structure): - _fields_ = (('Mask', ctypes.c_uint32), ('Group', ctypes.c_uint16), - ('Reserved', ctypes.c_uint16 * 3)) - - -class KAPC64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_uint8), - ('SpareByte0', ctypes.c_uint8), - ('Size', ctypes.c_uint8), - ('SpareByte1', ctypes.c_uint8), - ('SpareLong0', ctypes.c_uint32), - ('Thread', POINTER64), - ('ApcListEntry', LIST_ENTRY64), - ('KernelRoutine', POINTER64), - ('RundownRoutine', POINTER64), - ('NormalRoutine', POINTER64), - ('NormalContext', POINTER64), - ('SystemArgument1', POINTER64), - ('SystemArgument2', POINTER64), - ('ApcStateIndex', ctypes.c_uint8), - ('ApcMode', ctypes.c_uint8), - ('Inserted', ctypes.c_uint8), - ) - - -class KAPC32(ctypes.Structure): - _fields_ = () - - -class KSEMAPHORE64(ctypes.Structure): - _pack_ = 8 - _fields_ = (("Header", DISPATCHER_HEADER64), ("Limit", ctypes.c_int32)) - - -class COUNTER_READING64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ("Type", ctypes.c_uint32), - ("Index", ctypes.c_uint32), - ("Start", ctypes.c_uint64), - ("Total", ctypes.c_uint64), - ) - - -class KTHREAD_COUNTERS64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ("WaitReasonBitMap", ctypes.c_int64), - ("UserData", POINTER64), - ("Flags", ctypes.c_uint32), - ("ContextSwitches", ctypes.c_uint32), - ("CycleTimeBias", ctypes.c_uint64), - ("HardwareCounters", ctypes.c_uint64), - ("HwCounter", COUNTER_READING64 * 16), - ) - - -class KTHREAD64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Header', DISPATCHER_HEADER64), - ('CycleTime', ctypes.c_uint64), - ('QuantumTarget', ctypes.c_uint64), - ('InitialStack', POINTER64), - ('StackLimit', POINTER64), - ('KernelStack', POINTER64), - ('ThreadLock', ctypes.c_uint64), - ('WaitRegister', ctypes.c_uint8), # _KWAIT_STATUS_REGISTER - ('Running', ctypes.c_uint8), - ('Alerted', ctypes.c_uint8 * 2), - ('MiscFlags', ctypes.c_uint32), - ('ApcState', KAPC_STATE64), - ('DeferredProcessor', ctypes.c_uint32), - ('ApcQueueLock', ctypes.c_uint64), - ('WaitStatus', ctypes.c_int64), - ('WaitBlockList', POINTER64), - ('WaitListEntry', LIST_ENTRY64), - ('Queue', POINTER64), - ('Teb', POINTER64), - ('Timer', KTIMER64), - ('ThreadFlags', ctypes.c_int32), - ('Spare0', ctypes.c_uint32), - ('WaitBlock', KWAIT_BLOCK64 * 4), - ('QueueListEntry', LIST_ENTRY64), - ('TrapFrame', POINTER64), - ('FirstArgument', POINTER64), - ('CallbackStack', POINTER64), - ('ApcStateIndex', ctypes.c_uint8), - ('BasePriority', ctypes.c_char), - ('PriorityDecrement', ctypes.c_char), - ('Preempted', ctypes.c_uint8), - ('AdjustReason', ctypes.c_uint8), - ('AdjustIncrement', ctypes.c_char), - ('PreviousMode', ctypes.c_char), - ('Saturation', ctypes.c_char), - ('SystemCallNumber', ctypes.c_uint32), - ('FreezeCount', ctypes.c_uint32), - ('UserAffinity', GROUP_AFFINITY64), - ('Process', POINTER64), - ('Affinity', GROUP_AFFINITY64), - ('IdealProcessor', ctypes.c_uint32), - ('UserIdealProcessor', ctypes.c_uint32), - ('ApcStatePointer', POINTER64 * 2), - ('SavedApcState', KAPC_STATE64), - ('Win32Thread', POINTER64), - ('StackBase', POINTER64), - ('SuspendApc', KAPC64), - ('SuspendSemaphore', KSEMAPHORE64), - ('ThreadListEntry', LIST_ENTRY64), - ('MutantListHead', LIST_ENTRY64), - ('SListFaultAddress', POINTER64), - ('ReadOperationCount', ctypes.c_int64), - ('WriteOperationCount', ctypes.c_int64), - ('OtherOperationCount', ctypes.c_int64), - ('ReadTransferCount', ctypes.c_int64), - ('WriteTransferCount', ctypes.c_int64), - ('OtherTransferCount', ctypes.c_int64), - ('ThreadCounters', POINTER64), - ('XStateSave', POINTER64)) +# +# +# class KAPC_STATE64(ctypes.Structure): +# _fields_ = ( +# ('ApcListHead', LIST_ENTRY64 * 2), +# ('Process', POINTER64), +# ('KernelApcInProgress', ctypes.c_uint8), +# ('KernelApcPending', ctypes.c_uint8), +# ('UserApcPending', ctypes.c_uint8), +# ) +# +# +# class KAPC_STATE32(ctypes.Structure): +# _fields_ = ( +# ('ApcListHead', LIST_ENTRY32 * 2), +# ('Process', POINTER32), +# ('KernelApcInProgress', ctypes.c_uint8), +# ('KernelApcPending', ctypes.c_uint8), +# ('UserApcPending', ctypes.c_uint8), +# ) +# +# +# class KTIMER64(ctypes.Structure): +# _fields_ = ( +# ('Header', DISPATCHER_HEADER64), +# ('DueTime', LARGE_INTEGER), +# ('TimerListEntry', LIST_ENTRY64), +# ('Dpc', POINTER64), +# ('Period', ctypes.c_uint32), +# ) +# +# +# class KTIMER32(ctypes.Structure): +# _fields_ = ( +# ('Header', DISPATCHER_HEADER32), +# ('DueTime', LARGE_INTEGER), +# ('TimerListEntry', LIST_ENTRY32), +# ('Dpc', POINTER32), +# ('Period', ctypes.c_uint32), +# ) +# +# +# class KWAIT_BLOCK64(ctypes.Structure): +# _fields_ = ( +# ('WaitListEntry', LIST_ENTRY64), +# ('Thread', POINTER64), +# ('Object', POINTER64), +# ('NextWaitBlock', POINTER64), +# ('WaitKey', ctypes.c_uint16), +# ('WaitType', ctypes.c_uint8), +# ('BlockState', ctypes.c_uint8), +# ) +# +# +# class KWAIT_BLOCK32(ctypes.Structure): +# _fields_ = ( +# ('WaitListEntry', LIST_ENTRY32), +# ('Thread', POINTER32), +# ('Object', POINTER32), +# ('NextWaitBlock', POINTER32), +# ('WaitKey', ctypes.c_uint16), +# ('WaitType', ctypes.c_uint8), +# ('BlockState', ctypes.c_uint8), +# ) +# +# +# class GROUP_AFFINITY64(ctypes.Structure): +# _fields_ = (('Mask', ctypes.c_uint64), ('Group', ctypes.c_uint16), +# ('Reserved', ctypes.c_uint16 * 3)) +# +# +# class GROUP_AFFINITY32(ctypes.Structure): +# _fields_ = (('Mask', ctypes.c_uint32), ('Group', ctypes.c_uint16), +# ('Reserved', ctypes.c_uint16 * 3)) +# +# +# class KAPC64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ('Type', ctypes.c_uint8), +# ('SpareByte0', ctypes.c_uint8), +# ('Size', ctypes.c_uint8), +# ('SpareByte1', ctypes.c_uint8), +# ('SpareLong0', ctypes.c_uint32), +# ('Thread', POINTER64), +# ('ApcListEntry', LIST_ENTRY64), +# ('KernelRoutine', POINTER64), +# ('RundownRoutine', POINTER64), +# ('NormalRoutine', POINTER64), +# ('NormalContext', POINTER64), +# ('SystemArgument1', POINTER64), +# ('SystemArgument2', POINTER64), +# ('ApcStateIndex', ctypes.c_uint8), +# ('ApcMode', ctypes.c_uint8), +# ('Inserted', ctypes.c_uint8), +# ) +# +# +# class KAPC32(ctypes.Structure): +# _fields_ = () +# +# +# class KSEMAPHORE64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = (("Header", DISPATCHER_HEADER64), ("Limit", ctypes.c_int32)) +# +# +# class COUNTER_READING64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ("Type", ctypes.c_uint32), +# ("Index", ctypes.c_uint32), +# ("Start", ctypes.c_uint64), +# ("Total", ctypes.c_uint64), +# ) +# +# +# class KTHREAD_COUNTERS64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ("WaitReasonBitMap", ctypes.c_int64), +# ("UserData", POINTER64), +# ("Flags", ctypes.c_uint32), +# ("ContextSwitches", ctypes.c_uint32), +# ("CycleTimeBias", ctypes.c_uint64), +# ("HardwareCounters", ctypes.c_uint64), +# ("HwCounter", COUNTER_READING64 * 16), +# ) +# +# +# class KTHREAD64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ('Header', DISPATCHER_HEADER64), +# ('CycleTime', ctypes.c_uint64), +# ('QuantumTarget', ctypes.c_uint64), +# ('InitialStack', POINTER64), +# ('StackLimit', POINTER64), +# ('KernelStack', POINTER64), +# ('ThreadLock', ctypes.c_uint64), +# ('WaitRegister', ctypes.c_uint8), # _KWAIT_STATUS_REGISTER +# ('Running', ctypes.c_uint8), +# ('Alerted', ctypes.c_uint8 * 2), +# ('MiscFlags', ctypes.c_uint32), +# ('ApcState', KAPC_STATE64), +# ('DeferredProcessor', ctypes.c_uint32), +# ('ApcQueueLock', ctypes.c_uint64), +# ('WaitStatus', ctypes.c_int64), +# ('WaitBlockList', POINTER64), +# ('WaitListEntry', LIST_ENTRY64), +# ('Queue', POINTER64), +# ('Teb', POINTER64), +# ('Timer', KTIMER64), +# ('ThreadFlags', ctypes.c_int32), +# ('Spare0', ctypes.c_uint32), +# ('WaitBlock', KWAIT_BLOCK64 * 4), +# ('QueueListEntry', LIST_ENTRY64), +# ('TrapFrame', POINTER64), +# ('FirstArgument', POINTER64), +# ('CallbackStack', POINTER64), +# ('ApcStateIndex', ctypes.c_uint8), +# ('BasePriority', ctypes.c_char), +# ('PriorityDecrement', ctypes.c_char), +# ('Preempted', ctypes.c_uint8), +# ('AdjustReason', ctypes.c_uint8), +# ('AdjustIncrement', ctypes.c_char), +# ('PreviousMode', ctypes.c_char), +# ('Saturation', ctypes.c_char), +# ('SystemCallNumber', ctypes.c_uint32), +# ('FreezeCount', ctypes.c_uint32), +# ('UserAffinity', GROUP_AFFINITY64), +# ('Process', POINTER64), +# ('Affinity', GROUP_AFFINITY64), +# ('IdealProcessor', ctypes.c_uint32), +# ('UserIdealProcessor', ctypes.c_uint32), +# ('ApcStatePointer', POINTER64 * 2), +# ('SavedApcState', KAPC_STATE64), +# ('Win32Thread', POINTER64), +# ('StackBase', POINTER64), +# ('SuspendApc', KAPC64), +# ('SuspendSemaphore', KSEMAPHORE64), +# ('ThreadListEntry', LIST_ENTRY64), +# ('MutantListHead', LIST_ENTRY64), +# ('SListFaultAddress', POINTER64), +# ('ReadOperationCount', ctypes.c_int64), +# ('WriteOperationCount', ctypes.c_int64), +# ('OtherOperationCount', ctypes.c_int64), +# ('ReadTransferCount', ctypes.c_int64), +# ('WriteTransferCount', ctypes.c_int64), +# ('OtherTransferCount', ctypes.c_int64), +# ('ThreadCounters', POINTER64), +# ('XStateSave', POINTER64)) # struct _RTL_PROCESS_MODULE_INFORMATION { @@ -1247,36 +808,25 @@ class KTHREAD64(ctypes.Structure): # USHORT OffsetToFileName; # UCHAR FullPathName[256]; # } RTL_PROCESS_MODULE_INFORMATION, -class RTL_PROCESS_MODULE_INFORMATION64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Section', ctypes.c_uint64), - ('MappedBase', ctypes.c_uint64), - ('ImageBase', ctypes.c_uint64), - ('ImageSize', ctypes.c_uint32), - ('Flags', ctypes.c_uint32), - ('LoadOrderIndex', ctypes.c_uint16), - ('InitOrderIndex', ctypes.c_uint16), - ('LoadCount', ctypes.c_uint16), - ('OffsetToFileName', ctypes.c_uint16), - ('FullPathName', ctypes.c_char * 256) - ) - - -class RTL_PROCESS_MODULE_INFORMATION32(ctypes.Structure): - _fields_ = ( - ('Section', ctypes.c_uint32), - ('MappedBase', ctypes.c_uint32), - ('ImageBase', ctypes.c_uint32), - ('ImageSize', ctypes.c_uint32), - ('Flags', ctypes.c_uint32), - ('LoadOrderIndex', ctypes.c_uint16), - ('InitOrderIndex', ctypes.c_uint16), - ('LoadCount', ctypes.c_uint16), - ('OffsetToFileName', ctypes.c_uint16), - ('FullPathName', ctypes.c_char * 256) - ) +def make_rtl_process_module_info(archbits: int): + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + class RTL_PROCESS_MODULE_INFORMATION(Struct): + _fields_ = ( + ('Section', native_type), + ('MappedBase', native_type), + ('ImageBase', native_type), + ('ImageSize', ctypes.c_uint32), + ('Flags', ctypes.c_uint32), + ('LoadOrderIndex', ctypes.c_uint16), + ('InitOrderIndex', ctypes.c_uint16), + ('LoadCount', ctypes.c_uint16), + ('OffsetToFileName', ctypes.c_uint16), + ('FullPathName', ctypes.c_char * 256) + ) + return RTL_PROCESS_MODULE_INFORMATION() # struct _EPROCESS { # struct _KPROCESS Pcb; //0x0 @@ -1427,205 +977,99 @@ class RTL_PROCESS_MODULE_INFORMATION32(ctypes.Structure): # ULONG SmallestTimerResolution; //0x4c0 # struct _PO_DIAG_STACK_RECORD* TimerResolutionStackRecord; //0x4c8 # }; -class EPROCESS64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('dummy', ctypes.c_char * 0x4d0), ) - def __init__(self, ql, base): - self.ql = ql - self.base = base +def make_eprocess(archbits: int): + Struct = __make_struct(archbits) + class EPROCESS(Struct): + _fields_ = ( + ('dummy', ctypes.c_uint8), + ) -class EPROCESS32(ctypes.Structure): - _fields_ = (('dummy', ctypes.c_char * 0x2c0), ) + obj_size = { + 32: 0x2c0, + 64: 0x4d0 + }[archbits] - def __init__(self, ql, base): - self.ql = ql - self.base = base - - -# FIXME: duplicate class -class LdrData: - def __init__(self, - ql, - base=0, - length=0, - initialized=0, - ss_handle=0, - in_load_order_module_list={ - 'Flink': 0, - 'Blink': 0 - }, - in_memory_order_module_list={ - 'Flink': 0, - 'Blink': 0 - }, - in_initialization_order_module_list={ - 'Flink': 0, - 'Blink': 0 - }, - entry_in_progress=0, - shutdown_in_progress=0, - shutdown_thread_id=0): - self.ql = ql - self.base = base - self.Length = length - self.Initialized = initialized - self.SsHandle = ss_handle - self.InLoadOrderModuleList = in_load_order_module_list - self.InMemoryOrderModuleList = in_memory_order_module_list - self.InInitializationOrderModuleList = in_initialization_order_module_list - self.EntryInProgress = entry_in_progress - self.ShutdownInProgress = shutdown_in_progress - self.selfShutdownThreadId = shutdown_thread_id - - def bytes(self): - s = b'' - s += self.ql.pack32(self.Length) # 0x0 - s += self.ql.pack32(self.Initialized) # 0x4 - s += self.ql.pack(self.SsHandle) # 0x8 - s += self.ql.pack(self.InLoadOrderModuleList['Flink']) # 0x0c - s += self.ql.pack(self.InLoadOrderModuleList['Blink']) - s += self.ql.pack(self.InMemoryOrderModuleList['Flink']) # 0x14 - s += self.ql.pack(self.InMemoryOrderModuleList['Blink']) - s += self.ql.pack( - self.InInitializationOrderModuleList['Flink']) # 0x1C - s += self.ql.pack(self.InInitializationOrderModuleList['Blink']) - s += self.ql.pack(self.EntryInProgress) - s += self.ql.pack(self.ShutdownInProgress) - s += self.ql.pack(self.selfShutdownThreadId) - return s - - -class LdrDataTableEntry: - def __init__(self, - ql, - base=0, - in_load_order_links={ - 'Flink': 0, - 'Blink': 0 - }, - in_memory_order_links={ - 'Flink': 0, - 'Blink': 0 - }, - in_initialization_order_links={ - 'Flink': 0, - 'Blink': 0 - }, - dll_base=0, - entry_point=0, - size_of_image=0, - full_dll_name='', - base_dll_name='', - flags=0, - load_count=0, - tls_index=0, - hash_links=0, - section_pointer=0, - check_sum=0, - time_date_stamp=0, - loaded_imports=0, - entry_point_activation_context=0, - patch_information=0, - forwarder_links=0, - service_tag_links=0, - static_links=0, - context_information=0, - original_base=0, - load_time=0): - self.ql = ql - self.base = base - self.InLoadOrderLinks = in_load_order_links - self.InMemoryOrderLinks = in_memory_order_links - self.InInitializationOrderLinks = in_initialization_order_links - self.DllBase = dll_base - self.EntryPoint = entry_point - self.SizeOfImage = size_of_image - - full_dll_name = full_dll_name.encode("utf-16le") - self.FullDllName = { - 'Length': len(full_dll_name), - 'MaximumLength': len(full_dll_name) + 2 - } - self.FullDllName['BufferPtr'] = self.ql.os.heap.alloc( - self.FullDllName['MaximumLength']) - ql.mem.write(self.FullDllName['BufferPtr'], - full_dll_name + b"\x00\x00") - - base_dll_name = base_dll_name.encode("utf-16le") - self.BaseDllName = { - 'Length': len(base_dll_name), - 'MaximumLength': len(base_dll_name) + 2 - } - self.BaseDllName['BufferPtr'] = self.ql.os.heap.alloc( - self.BaseDllName['MaximumLength']) - ql.mem.write(self.BaseDllName['BufferPtr'], - base_dll_name + b"\x00\x00") - - self.Flags = flags - self.LoadCount = load_count - self.TlsIndex = tls_index - self.HashLinks = hash_links - self.SectionPointer = section_pointer - self.CheckSum = check_sum - self.TimeDateStamp = time_date_stamp - self.LoadedImports = loaded_imports - self.EntryPointActivationContext = entry_point_activation_context - self.PatchInformation = patch_information - self.ForwarderLinks = forwarder_links - self.ServiceTagLinks = service_tag_links - self.StaticLinks = static_links - self.ContextInformation = context_information - self.OriginalBase = original_base - self.LoadTime = load_time - - def attrs(self): - return ", ".join("{}={}".format(k, getattr(self, k)) - for k in self.__dict__.keys()) - - def print(self): - return "[{}:{}]".format(self.__class__.__name__, self.attrs()) - - def bytes(self): - s = b'' - s += self.ql.pack(self.InLoadOrderLinks['Flink']) # 0x0 - s += self.ql.pack(self.InLoadOrderLinks['Blink']) - s += self.ql.pack(self.InMemoryOrderLinks['Flink']) # 0x8 - s += self.ql.pack(self.InMemoryOrderLinks['Blink']) - s += self.ql.pack(self.InInitializationOrderLinks['Flink']) # 0x10 - s += self.ql.pack(self.InInitializationOrderLinks['Blink']) - s += self.ql.pack(self.DllBase) # 0x18 - s += self.ql.pack(self.EntryPoint) # 0x1c - 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: - 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: - s += self.ql.pack32(0) - s += self.ql.pack(self.BaseDllName['BufferPtr']) - s += self.ql.pack(self.Flags) - s += self.ql.pack(self.LoadCount) - s += self.ql.pack(self.TlsIndex) - s += self.ql.pack(self.HashLinks) - s += self.ql.pack(self.SectionPointer) - s += self.ql.pack(self.CheckSum) - s += self.ql.pack(self.TimeDateStamp) - s += self.ql.pack(self.LoadedImports) - s += self.ql.pack(self.EntryPointActivationContext) - s += self.ql.pack(self.PatchInformation) - s += self.ql.pack(self.ForwarderLinks) - s += self.ql.pack(self.ServiceTagLinks) - s += self.ql.pack(self.StaticLinks) - s += self.ql.pack(self.ContextInformation) - s += self.ql.pack(self.OriginalBase) - s += self.ql.pack(self.LoadTime) - - return s + obj = EPROCESS() + ctypes.resize(obj, obj_size) + + return obj + + +def make_ldr_data(archbits: int): + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + ListEntry = make_list_entry(archbits) + + class PEB_LDR_DATA(Struct): + _fields_ = ( + ('Length', ctypes.c_uint32), + ('Initialized', ctypes.c_uint32), + ('SsHandle', native_type), + ('InLoadOrderModuleList', ListEntry), + ('InMemoryOrderModuleList', ListEntry), + ('InInitializationOrderModuleList', ListEntry), + ('EntryInProgress', native_type), + ('ShutdownInProgress', native_type), + ('selfShutdownThreadId', native_type) + ) + + return PEB_LDR_DATA() + + +def make_ldr_data_table_entry(archbits: int): + pointer_type = __select_pointer_type(archbits) + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + ListEntry = make_list_entry(archbits) + UniString = make_unicode_string(archbits).__class__ + + class RTL_BALANCED_NODE(Struct): + _fields_ = ( + ('Left', pointer_type), + ('Right', pointer_type), + ) + + class LdrDataTableEntry(Struct): + _fields_ = ( + ('InLoadOrderLinks', ListEntry), + ('InMemoryOrderLinks', ListEntry), + ('InInitializationOrderLinks', ListEntry), + ('DllBase', native_type), + ('EntryPoint', native_type), + ('SizeOfImage', native_type), + ('FullDllName', UniString), + ('BaseDllName', UniString), + ('Flags', native_type), + ('ObsoleteLoadCount', ctypes.c_uint16), + ('TlsIndex', ctypes.c_uint16), + ('HashLinks', ListEntry), + ('TimedateStamp', native_type), + ('EntryPointActivationContext', native_type), + ('Lock', native_type), + ('DdagNode', pointer_type), + ('NodeModuleLink', ListEntry), + ('LoadContext', native_type), + ('ParentDllBase', native_type), + ('SwitchBackContext', native_type), + ('BaseAddressIndexNode', RTL_BALANCED_NODE), + ('MappingInfoIndexNode', RTL_BALANCED_NODE), + ('OriginalBase', native_type), + ('LoadTime', LARGE_INTEGER), + ('BaseNameHashValue', native_type), + ('LoadReason', ctypes.c_uint32), + ('ImplicitPathOptions', native_type), + ('ReferenceCount', native_type), + # 1607+ + ('DependentLoadFlags', native_type), + # 1703+ + ('SigningLevel', ctypes.c_uint8) + ) + + return LdrDataTableEntry() class WindowsStruct: @@ -1635,7 +1079,7 @@ def __init__(self, ql): 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 @@ -1855,43 +1299,44 @@ def __eq__(self, other): class Mutex: - def __init__(self, name, type): + def __init__(self, name: str, type: str): self.name = name self.locked = False self.type = type - def lock(self): + def lock(self) -> None: self.locked = True - def unlock(self): + def unlock(self) -> None: self.locked = False - def isFree(self): + def isFree(self) -> bool: return not self.locked -class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): - _fields_ = ( - ('OriginalFirstThunk', ctypes.c_uint32), - ('TimeDateStamp', ctypes.c_uint32), - ('ForwarderChain', ctypes.c_uint32), - ('Name', ctypes.c_uint32), - ('FirstThunk', ctypes.c_uint32) - ) - - -class CLIENT_ID32(ctypes.Structure): - _fields_ = ( - ('UniqueProcess', ctypes.c_uint32), - ('UniqueThread', ctypes.c_uint32) - ) - +# class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): +# _fields_ = ( +# ('OriginalFirstThunk', ctypes.c_uint32), +# ('TimeDateStamp', ctypes.c_uint32), +# ('ForwarderChain', ctypes.c_uint32), +# ('Name', ctypes.c_uint32), +# ('FirstThunk', ctypes.c_uint32) +# ) +# +# +# class CLIENT_ID32(ctypes.Structure): +# _fields_ = ( +# ('UniqueProcess', ctypes.c_uint32), +# ('UniqueThread', ctypes.c_uint32) +# ) +# +# +# class CLIENT_ID64(ctypes.Structure): +# _fields_ = ( +# ('UniqueProcess', ctypes.c_uint64), +# ('UniqueThread', ctypes.c_uint64) +# ) -class CLIENT_ID64(ctypes.Structure): - _fields_ = ( - ('UniqueProcess', ctypes.c_uint64), - ('UniqueThread', ctypes.c_uint64) - ) # typedef struct tagPOINT { # LONG x; # LONG y; @@ -2145,7 +1590,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 +1726,7 @@ def __init__(self, ql, length=None, maxLength=None, buffer=None): 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 c49f166f1..ca36a4772 100644 --- a/qiling/os/windows/thread.py +++ b/qiling/os/windows/thread.py @@ -3,62 +3,14 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn.x86_const import * +from typing import TYPE_CHECKING, cast -from qiling.exception import * -from qiling.os.thread import * -from .utils import * - - -def thread_scheduler(ql, address, size): - if ql.reg.arch_pc == ql.os.thread_manager.THREAD_RET_ADDR: - ql.os.thread_manager.cur_thread.stop() - ql.os.thread_manager.do_schedule() - else: - ql.os.thread_manager.ins_count += 1 - ql.os.thread_manager.do_schedule() - - -# Simple Thread Manager -class QlWindowsThreadManagement(QlThread): - TIME_SLICE = 10 - - def __init__(self, ql, cur_thread): - super(QlWindowsThreadManagement, self).__init__(ql) - self.ql = ql - # main thread - self.cur_thread = cur_thread - self.threads = [self.cur_thread] - self.ins_count = 0 - self.THREAD_RET_ADDR = self.ql.os.heap.alloc(8) - # write nop to THREAD_RET_ADDR - self.ql.mem.write(self.THREAD_RET_ADDR, b"\x90"*8) - self.ql.hook_code(thread_scheduler) - - def append(self, thread): - self.threads.append(thread) - - def need_schedule(self): - return self.cur_thread.is_stop() or self.ins_count % QlWindowsThreadManagement.TIME_SLICE == 0 - - def do_schedule(self): - if self.cur_thread.is_stop() or self.ins_count % QlWindowsThreadManagement.TIME_SLICE == 0: - if len(self.threads) <= 1: - return - else: - for i in range(1, len(self.threads)): - next_id = (self.cur_thread.id + i) % len(self.threads) - next_thread = self.threads[next_id] - # find next thread - if next_thread.status == QlWindowsThread.RUNNING and (not next_thread.has_waitfor()): - if self.cur_thread.is_stop(): - pass - else: - self.cur_thread.suspend() - next_thread.resume() - self.cur_thread = next_thread - break +from qiling import Qiling +from qiling.const import QL_ARCH +from qiling.os.thread import QlThread +if TYPE_CHECKING: + from qiling.os.windows.windows import QlOsWindows class QlWindowsThread(QlThread): # static var @@ -67,8 +19,9 @@ class QlWindowsThread(QlThread): RUNNING = 1 TERMINATED = 2 - def __init__(self, ql, status=1, isFake=False): - super(QlWindowsThread, self).__init__(ql) + def __init__(self, ql: Qiling, status: int = 1, isFake: bool = False): + super().__init__(ql) + self.ql = ql self.id = QlWindowsThread.ID QlWindowsThread.ID += 1 @@ -79,55 +32,107 @@ def __init__(self, ql, status=1, isFake=False): self.fake = isFake # create new thread - def create(self, func_addr, func_params, status): + def create(self, func_addr: int, func_params: int, status: int) -> int: + os = cast('QlOsWindows', self.ql.os) + # create new stack stack_size = 1024 - new_stack = self.ql.os.heap.alloc(stack_size) + stack_size - - self.saved_context = self.ql.reg.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)) - self.saved_context["rcx"] = func_params + new_stack = os.heap.alloc(stack_size) + stack_size + + asize = self.ql.arch.pointersize + context = self.ql.arch.regs.save() + + # set return address + self.ql.mem.write_ptr(new_stack - asize, os.thread_manager.thread_ret_addr) + + # set parameters + if self.ql.arch.type == QL_ARCH.X86: + self.ql.mem.write_ptr(new_stack, func_params) + elif self.ql.arch.type == QL_ARCH.X8664: + context["rcx"] = func_params # set eip/rip, ebp/rbp, esp/rsp - if self.ql.archtype == 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: - self.saved_context["rip"] = func_addr - self.saved_context["rbp"] = new_stack - 8 - self.saved_context["rsp"] = new_stack - 8 + if self.ql.arch.type == QL_ARCH.X86: + context["eip"] = func_addr + context["ebp"] = new_stack - asize + context["esp"] = new_stack - asize - self.status = status - return self.id + elif self.ql.arch.type == QL_ARCH.X8664: + context["rip"] = func_addr + context["rbp"] = new_stack - asize + context["rsp"] = new_stack - asize + self.saved_context = context self.status = status + return self.id - def suspend(self): - self.saved_context = self.ql.reg.save() + def suspend(self) -> None: + self.saved_context = self.ql.arch.regs.save() - def resume(self): - self.ql.reg.restore(self.saved_context) + def resume(self) -> None: + self.ql.arch.regs.restore(self.saved_context) self.status = QlWindowsThread.RUNNING - def stop(self): + def stop(self) -> None: self.status = QlWindowsThread.TERMINATED - def is_stop(self): + def is_stop(self) -> bool: return self.status == QlWindowsThread.TERMINATED - def waitfor(self, thread): + def waitfor(self, thread: 'QlWindowsThread') -> None: self.waitforthreads.append(thread) - def has_waitfor(self): - for each_thread in self.waitforthreads: - if not each_thread.is_stop(): - return True - return False + def has_waitfor(self) -> bool: + return any(not thread.is_stop() for thread in self.waitforthreads) + + +# Simple Thread Manager +class QlWindowsThreadManagement(QlThread): + TIME_SLICE = 10 + + def __init__(self, ql: Qiling, os: 'QlOsWindows', cur_thread: QlWindowsThread): + super().__init__(ql) + + self.ql = ql + # main thread + self.cur_thread = cur_thread + self.threads = [self.cur_thread] + self.icount = 0 + self.thread_ret_addr = os.heap.alloc(8) + + # write nop to thread_ret_addr + ql.mem.write(self.thread_ret_addr, b'\x90' * 8) + + def __thread_scheduler(ql: Qiling, address: int, size: int): + if ql.arch.regs.arch_pc == self.thread_ret_addr: + self.cur_thread.stop() + else: + self.icount += 1 + + self.do_schedule() + + ql.hook_code(__thread_scheduler) + + def append(self, thread: QlWindowsThread): + self.threads.append(thread) + + def need_schedule(self): + return self.cur_thread.is_stop() or (self.icount % QlWindowsThreadManagement.TIME_SLICE) == 0 + + def do_schedule(self) -> None: + if self.need_schedule(): + # if there is less than one thread, this loop won't run + for i in range(1, len(self.threads)): + next_id = (self.cur_thread.id + i) % len(self.threads) + next_thread = self.threads[next_id] + + # find next thread + if next_thread.status == QlWindowsThread.RUNNING and not next_thread.has_waitfor(): + if not self.cur_thread.is_stop(): + self.cur_thread.suspend() + + next_thread.resume() + self.cur_thread = next_thread + + break diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index 6b6cf6a77..bd13a7630 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -4,12 +4,12 @@ # import ctypes -import ntpath -from typing import Tuple, TypeVar +from typing import Iterable, 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,44 +22,27 @@ 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 has_lib_ext(name: str) -> bool: + ext = name.lower().rpartition('.')[-1] -def is_file_library(string: str) -> bool: - string = string.lower() - extension = string.rpartition('.')[-1] - return extension in ("dll", "exe", "sys", "drv") - - -def path_leaf(path): - head, tail = ntpath.split(path) - return tail or ntpath.basename(head) + return ext in ("dll", "exe", "sys", "drv") def io_Write(ql: Qiling, in_buffer: bytes): heap = ql.os.heap - if ql.ostype == QL_OS.WINDOWS: + if ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE] == 0: + # raise error? + return (False, None) - if ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE] == 0: - # raise error? - return (False, None) - - if ql.archbit == 32: - buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(DEVICE_OBJECT32)) - device_object = DEVICE_OBJECT32.from_buffer(buf) - else: - buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(DEVICE_OBJECT64)) - device_object = DEVICE_OBJECT64.from_buffer(buf) + driver_object_cls = ql.loader.driver_object.__class__ + buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(driver_object_cls)) + device_object = driver_object_cls.from_buffer(buf) alloc_addr = [] - def build_mdl(buffer_size, data=None): - if ql.archtype == QL_ARCH.X8664: - mdl = MDL64() - else: - mdl = MDL32() + def build_mdl(buffer_size: int, data=None): + mdl = make_mdl(ql.arch.bits) mapped_address = heap.alloc(buffer_size) alloc_addr.append(mapped_address) @@ -72,27 +55,21 @@ def build_mdl(buffer_size, data=None): ql.mem.write(mapped_address, written) return mdl + # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.archtype == QL_ARCH.X8664: - irp_addr = heap.alloc(ctypes.sizeof(IRP64)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION64() - # setup IRP structure - irp = IRP64() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION64)) - else: - irp_addr = heap.alloc(ctypes.sizeof(IRP32)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION32)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION32() - # setup IRP structure - irp = IRP32() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION32)) + irp = make_irp(ql.arch.bits) + irpstack_class = irp.irpstack._type_ + + irp_addr = heap.alloc(ctypes.sizeof(irp)) + alloc_addr.append(irp_addr) + + irpstack_addr = heap.alloc(ctypes.sizeof(irpstack_class)) + alloc_addr.append(irpstack_addr) + + # setup irp stack parameters + irpstack = irpstack_class() + # setup IRP structure + irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) irpstack.MajorFunction = IRP_MJ_WRITE irpstack.Parameters.Write.Length = len(in_buffer) @@ -107,11 +84,7 @@ def build_mdl(buffer_size, data=None): elif device_object.Flags & DO_DIRECT_IO: # DIRECT_IO mdl = build_mdl(len(in_buffer)) - if ql.archtype == QL_ARCH.X8664: - mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) - else: - mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) - + mdl_addr = heap.alloc(ctypes.sizeof(mdl)) alloc_addr.append(mdl_addr) ql.mem.write(mdl_addr, bytes(mdl)) @@ -130,7 +103,10 @@ def build_mdl(buffer_size, data=None): # set function args # TODO: make sure this is indeed STDCALL ql.os.fcall = ql.os.fcall_select(STDCALL) - ql.os.fcall.writeParams(((POINTER, ql.loader.driver_object.DeviceObject), (POINTER, irp_addr))) + ql.os.fcall.writeParams(( + (POINTER, ql.loader.driver_object.DeviceObject), + (POINTER, irp_addr) + )) try: # now emulate @@ -139,12 +115,8 @@ def build_mdl(buffer_size, data=None): verify_ret(ql, err) # read current IRP state - if ql.archtype == QL_ARCH.X8664: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) - irp = IRP64.from_buffer(irp_buffer) - else: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP32)) - irp = IRP32.from_buffer(irp_buffer) + irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp)) + irp = irp.from_buffer(irp_buffer) io_status = irp.IoStatus # now free all alloc memory @@ -165,159 +137,130 @@ def build_mdl(buffer_size, data=None): # LPDWORD lpBytesReturned, # LPOVERLAPPED lpOverlapped); def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple: - heap = ql.os.heap - def ioctl_code(DeviceType, Function, Method, Access): + allocations = [] + + def __heap_alloc(size: int) -> int: + address = ql.os.heap.alloc(size) + allocations.append(address) + + return address + + def __free_all(allocations: Iterable[int]) -> None: + for address in allocations: + ql.os.heap.free(address) + + def ioctl_code(DeviceType: int, Function: int, Method: int, Access: int) -> int: return (DeviceType << 16) | (Access << 14) | (Function << 2) | Method - alloc_addr = [] def build_mdl(buffer_size, data=None): - if ql.archtype == QL_ARCH.X8664: - mdl = MDL64() - else: - mdl = MDL32() + mdl = make_mdl(ql.arch.bits) - mapped_address = heap.alloc(buffer_size) - alloc_addr.append(mapped_address) + mapped_address = __heap_alloc(buffer_size) mdl.MappedSystemVa.value = mapped_address mdl.StartVa.value = mapped_address mdl.ByteOffset = 0 mdl.ByteCount = buffer_size + if data: written = data if len(data) <= buffer_size else data[:buffer_size] ql.mem.write(mapped_address, written) return mdl - # quick simple way to manage all alloc memory - if ql.ostype == QL_OS.WINDOWS: - # print("DeviceControl callback is at 0x%x" % ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]) - if ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL] == 0: - # raise error? - return (None, None, None) + if ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL] == 0: + # raise error? + return (None, None, None) - # create new memory region to store input data - _ioctl_code, output_buffer_size, in_buffer = params - # extract data transfer method - devicetype, function, ctl_method, access = _ioctl_code + # create new memory region to store input data + _ioctl_code, output_buffer_size, in_buffer = params + # extract data transfer method + devicetype, function, ctl_method, access = _ioctl_code - input_buffer_size = len(in_buffer) - input_buffer_addr = heap.alloc(input_buffer_size) - alloc_addr.append(input_buffer_addr) - ql.mem.write(input_buffer_addr, bytes(in_buffer)) + input_buffer_size = len(in_buffer) + input_buffer_addr = __heap_alloc(input_buffer_size) + ql.mem.write(input_buffer_addr, bytes(in_buffer)) - # create new memory region to store out data - output_buffer_addr = heap.alloc(output_buffer_size) - alloc_addr.append(output_buffer_addr) - - # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.archtype == QL_ARCH.X8664: - irp_addr = heap.alloc(ctypes.sizeof(IRP64)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION64() - # setup IRP structure - irp = IRP64() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION64)) - else: - irp_addr = heap.alloc(ctypes.sizeof(IRP32)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION32)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION32() - # setup IRP structure - irp = IRP32() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION32)) - - #print("32 stack location size = 0x%x" %ctypes.sizeof(IO_STACK_LOCATION32)) - #print("32 status block size = 0x%x" %ctypes.sizeof(IO_STATUS_BLOCK32)) - #print("32 irp size = 0x%x" %ctypes.sizeof(IRP32)) - #print("32 IoStatus offset = 0x%x" %IRP32.IoStatus.offset) - #print("32 UserIosb offset = 0x%x" %IRP32.UserIosb.offset) - #print("32 UserEvent offset = 0x%x" %IRP32.UserEvent.offset) - #print("32 UserBuffer offset = 0x%x" %IRP32.UserBuffer.offset) - #print("32 irpstack offset = 0x%x" %IRP32.irpstack.offset) - #print("irp at %x, irpstack at %x" %(irp_addr, irpstack_addr)) - - ql.log.info("IRP is at 0x%x, IO_STACK_LOCATION is at 0x%x" %(irp_addr, irpstack_addr)) - - irpstack.Parameters.DeviceIoControl.IoControlCode = ioctl_code(devicetype, function, ctl_method, access) - irpstack.Parameters.DeviceIoControl.OutputBufferLength = output_buffer_size - irpstack.Parameters.DeviceIoControl.InputBufferLength = input_buffer_size - irpstack.Parameters.DeviceIoControl.Type3InputBuffer.value = input_buffer_addr # used by IOCTL_METHOD_NEITHER - ql.mem.write(irpstack_addr, bytes(irpstack)) + # create new memory region to store out data + output_buffer_addr = __heap_alloc(output_buffer_size) - if ctl_method == METHOD_NEITHER: - irp.UserBuffer.value = output_buffer_addr # used by IOCTL_METHOD_NEITHER + # allocate memory regions for IRP and IO_STACK_LOCATION + irp = make_irp(ql.arch.bits) + irpstack_class = irp.irpstack._type_ - # allocate memory for AssociatedIrp.SystemBuffer - # used by IOCTL_METHOD_IN_DIRECT, IOCTL_METHOD_OUT_DIRECT and IOCTL_METHOD_BUFFERED - system_buffer_size = max(input_buffer_size, output_buffer_size) - system_buffer_addr = heap.alloc(system_buffer_size) - alloc_addr.append(system_buffer_addr) + irp_addr = __heap_alloc(ctypes.sizeof(irp)) + irpstack_addr = __heap_alloc(ctypes.sizeof(irpstack_class)) - # init data from input buffer - ql.mem.write(system_buffer_addr, bytes(in_buffer)) - irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr + # setup irp stack parameters + irpstack = irpstack_class() + # setup IRP structure + irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) + ql.log.info("IRP is at 0x%x, IO_STACK_LOCATION is at 0x%x" %(irp_addr, irpstack_addr)) + + irpstack.Parameters.DeviceIoControl.IoControlCode = ioctl_code(devicetype, function, ctl_method, access) + irpstack.Parameters.DeviceIoControl.OutputBufferLength = output_buffer_size + irpstack.Parameters.DeviceIoControl.InputBufferLength = input_buffer_size + irpstack.Parameters.DeviceIoControl.Type3InputBuffer.value = input_buffer_addr # used by IOCTL_METHOD_NEITHER + ql.mem.write(irpstack_addr, bytes(irpstack)) + + if ctl_method == METHOD_NEITHER: + irp.UserBuffer.value = output_buffer_addr # used by IOCTL_METHOD_NEITHER + + # allocate memory for AssociatedIrp.SystemBuffer + # used by IOCTL_METHOD_IN_DIRECT, IOCTL_METHOD_OUT_DIRECT and IOCTL_METHOD_BUFFERED + system_buffer_size = max(input_buffer_size, output_buffer_size) + system_buffer_addr = __heap_alloc(system_buffer_size) + + # init data from input buffer + ql.mem.write(system_buffer_addr, bytes(in_buffer)) + irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr + + if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT): + # Create MDL structure for output data + # used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT + mdl = build_mdl(output_buffer_size) + mdl_addr = __heap_alloc(ctypes.sizeof(mdl)) + + ql.mem.write(mdl_addr, bytes(mdl)) + irp.MdlAddress.value = mdl_addr + + # everything is done! Write IRP to memory + ql.mem.write(irp_addr, bytes(irp)) + + # set function args + ql.log.info("Executing IOCTL with DeviceObject = 0x%x, IRP = 0x%x" %(ql.loader.driver_object.DeviceObject, irp_addr)) + # TODO: make sure this is indeed STDCALL + ql.os.fcall = ql.os.fcall_select(STDCALL) + ql.os.fcall.writeParams(( + (POINTER, ql.loader.driver_object.DeviceObject), + (POINTER, irp_addr) + )) + + try: + ql.log.info(f"Executing from: {ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]:#x}") + # now emulate IOCTL's DeviceControl + ql.run(ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]) + except UcError as err: + verify_ret(ql, err) + + # read current IRP state + irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp)) + irp = irp.__class__.from_buffer(irp_buffer) + + io_status = irp.IoStatus + + # read output data + output_data = b'' + if io_status.Status.Status >= 0: + if ctl_method == METHOD_BUFFERED: + output_data = ql.mem.read(system_buffer_addr, io_status.Information.value) if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT): - # 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: - mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) - else: - mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) - - alloc_addr.append(mdl_addr) - - ql.mem.write(mdl_addr, bytes(mdl)) - irp.MdlAddress.value = mdl_addr - - # everything is done! Write IRP to memory - ql.mem.write(irp_addr, bytes(irp)) - - # set function args - ql.log.info("Executing IOCTL with DeviceObject = 0x%x, IRP = 0x%x" %(ql.loader.driver_object.DeviceObject, irp_addr)) - # TODO: make sure this is indeed STDCALL - ql.os.fcall = ql.os.fcall_select(STDCALL) - ql.os.fcall.writeParams(((POINTER, ql.loader.driver_object.DeviceObject), (POINTER, irp_addr))) - - try: - # now emulate IOCTL's DeviceControl - ql.run(ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]) - except UcError as err: - verify_ret(ql, err) - - # read current IRP state - if ql.archtype == QL_ARCH.X8664: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) - irp = IRP64.from_buffer(irp_buffer) - else: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP32)) - irp = IRP32.from_buffer(irp_buffer) - - io_status = irp.IoStatus - - # read output data - output_data = b'' - if io_status.Status.Status >= 0: - if ctl_method == METHOD_BUFFERED: - output_data = ql.mem.read(system_buffer_addr, io_status.Information.value) - if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT): - output_data = ql.mem.read(mdl.MappedSystemVa.value, io_status.Information.value) - if ctl_method == METHOD_NEITHER: - output_data = ql.mem.read(output_buffer_addr, io_status.Information.value) - - # now free all alloc memory - for addr in alloc_addr: - # print("freeing heap memory at 0x%x" %addr) # FIXME: the output is not deterministic?? - heap.free(addr) - #print("\n") - - return io_status.Status.Status, io_status.Information.value, output_data - else: # TODO: IOCTL for non-Windows. - raise NotImplementedError + output_data = ql.mem.read(mdl.MappedSystemVa.value, io_status.Information.value) + if ctl_method == METHOD_NEITHER: + output_data = ql.mem.read(output_buffer_addr, io_status.Information.value) + + # now free all alloc memory + __free_all(allocations) + + return io_status.Status.Status, io_status.Information.value, output_data diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index c7558bde6..6c115d99c 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -3,18 +3,21 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import json -from typing import Callable +import ntpath +from typing import Callable, TextIO, Type 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, SegmentManager, SegmentManager86, SegmentManager64 from qiling.cc import intel -from qiling.const import QL_ARCH, QL_INTERCEPT -from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound +from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT +from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound, QlMemoryMappedError from qiling.os.fcall import QlFunctionCall +from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs +from qiling.os.stats import QlWinStats from . import const from . import fncc @@ -23,13 +26,14 @@ from . import clipboard from . import fiber from . import registry -from . import utils import qiling.os.windows.dlls as api class QlOsWindows(QlOs): + type = QL_OS.WINDOWS + def __init__(self, ql: Qiling): - super(QlOsWindows, self).__init__(ql) + super().__init__(ql) self.ql = ql @@ -44,9 +48,9 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: """ __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,56 +60,115 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: return __selector[atype] - self.fcall_select = __make_fcall_selector(ql.archtype) - self.fcall = None + self.fcall_select = __make_fcall_selector(ql.arch.type) + self.fcall = self.fcall_select(fncc.CDECL) - self.PE_RUN = True + self.stats = QlWinStats() + + ossection = f'OS{self.ql.arch.bits}' + heap_base = self.profile.getint(ossection, 'heap_address') + heap_size = self.profile.getint(ossection, 'heap_size') + + self.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size) + + sysdrv = self.profile.get('PATH', 'systemdrive') + windir = self.profile.get('PATH', 'windir') + username = self.profile.get('USER', 'username') + + self.windir = ntpath.join(sysdrv, windir) + self.winsys = ntpath.join(sysdrv, windir, 'System32') + self.userprofile = ntpath.join(sysdrv, 'Users', username) + self.username = username + + self.PE_RUN = False self.last_error = 0 # variables used inside hooks self.hooks_variables = {} self.syscall_count = {} self.argv = self.ql.argv self.env = self.ql.env - self.pid = self.profile.getint("KERNEL","pid") - 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"] - self.userprofile = self.profile["PATH"]["systemdrive"] + "Users\\" + self.profile["USER"]["username"] + "\\" + self.pid = self.profile.getint('KERNEL', 'pid') + self.services = {} self.load() + # only after handle manager has been set up we can assign the standard streams + self.stdin = self._stdin + self.stdout = self._stdout + self.stderr = self._stderr + + + @QlOs.stdin.setter + def stdin(self, stream: TextIO) -> None: + self._stdin = stream + + handle = self.handle_manager.get(const.STD_INPUT_HANDLE) + assert handle is not None + + handle.obj = stream + + @QlOs.stdout.setter + def stdout(self, stream: TextIO) -> None: + self._stdout = stream + + handle = self.handle_manager.get(const.STD_OUTPUT_HANDLE) + assert handle is not None + + handle.obj = stream + + @QlOs.stderr.setter + def stderr(self, stream: TextIO) -> None: + self._stderr = stream + + handle = self.handle_manager.get(const.STD_ERROR_HANDLE) + assert handle is not None + + handle.obj = stream + def load(self): self.setupGDT() + self.__setup_components() + # hook win api self.ql.hook_code(self.hook_winapi) 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) - - - def setupComponents(self): - # handle manager + gdtm = GDTManager(self.ql) + + segm_class: Type[SegmentManager] = { + 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_available(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE): + raise QlMemoryMappedError('cannot map FS segment, memory location is taken') + + self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]') + + if not self.ql.mem.is_available(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): + raise QlMemoryMappedError('cannot map GS segment, memory location is taken') + + self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') + + + def __setup_components(self): + reghive = self.path.transform_to_real_path(ntpath.join(self.windir, 'registry')) + self.handle_manager = handle.HandleManager() - # registry manger - self.registry_manager = registry.RegistryManager(self.ql) - # clipboard - self.clipboard = clipboard.Clipboard(self.ql.os) - # fibers + self.registry_manager = registry.RegistryManager(self.ql, reghive) + self.clipboard = clipboard.Clipboard(self) self.fiber_manager = fiber.FiberManager(self.ql) - # thread manager + main_thread = thread.QlWindowsThread(self.ql) - self.thread_manager = thread.QlWindowsThreadManagement(self.ql, main_thread) + self.thread_manager = thread.QlWindowsThreadManagement(self.ql, self, main_thread) # more handle manager new_handle = handle.Handle(obj=main_thread) @@ -139,32 +202,12 @@ def hook_winapi(self, ql: Qiling, address: int, size: int): raise QlErrorSyscallError("Windows API Implementation Error") else: - ql.log.warning(f'api {api_name} is not implemented') + ql.log.warning(f'api {api_name} ({entry["dll"]}) is not implemented') if ql.debug_stop: raise QlErrorSyscallNotFound("Windows API implementation not found") - def post_report(self): - self.ql.log.debug("Syscalls called:") - for key, values in self.utils.syscalls.items(): - self.ql.log.debug(f'{key}:') - - for value in values: - self.ql.log.debug(f' {json.dumps(value):s}') - - self.ql.log.debug("Registries accessed:") - for key, values in self.registry_manager.accessed.items(): - self.ql.log.debug(f'{key}:') - - for value in values: - self.ql.log.debug(f' {json.dumps(value):s}') - - self.ql.log.debug("Strings:") - for key, values in self.utils.appeared_strings.items(): - self.ql.log.debug(f'{key}: {" ".join(str(word) for word in values)}') - - def run(self): if self.ql.exit_point is not None: self.exit_point = self.ql.exit_point @@ -172,14 +215,19 @@ def run(self): if self.ql.entry_point is not None: self.ql.loader.entry_point = self.ql.entry_point + entry_point = self.ql.loader.entry_point + exit_point = (self.ql.loader.entry_point + len(self.ql.code)) if self.ql.code else self.exit_point + + self.PE_RUN = True + try: - if self.ql.code: - self.ql.emu_start(self.ql.loader.entry_point, (self.ql.loader.entry_point + len(self.ql.code)), self.ql.timeout, self.ql.count) - else: - self.ql.emu_start(self.ql.loader.entry_point, self.exit_point, self.ql.timeout, self.ql.count) + self.ql.emu_start(entry_point, exit_point, self.ql.timeout, self.ql.count) except UcError: self.emu_error() raise self.registry_manager.save() - self.post_report() + + # display summary + for entry in self.stats.summary(): + self.ql.log.debug(entry) diff --git a/qiling/profiles/dos.ql b/qiling/profiles/dos.ql index 04542dd00..c1bf3edc0 100644 --- a/qiling/profiles/dos.ql +++ b/qiling/profiles/dos.ql @@ -22,4 +22,4 @@ base_address = 0x7000 # usage: append = test1 append = automatize_input = False -current_path = / \ No newline at end of file +current_path = A:\ \ No newline at end of file diff --git a/qiling/profiles/linux.ql b/qiling/profiles/linux.ql index 5b53d982a..f810f94b0 100644 --- a/qiling/profiles/linux.ql +++ b/qiling/profiles/linux.ql @@ -19,7 +19,7 @@ stack_address = 0x7ff0d000 stack_size = 0x30000 load_address = 0x56555000 interp_address = 0x047ba000 -mmap_address = 0x774bf000 +mmap_address = 0x90000000 [KERNEL] @@ -48,4 +48,4 @@ current_path = / # To use IPv6 or not, to avoid binary double bind. ipv6 and ipv4 bind the same port at the same time bindtolocalhost = True # Bind to localhost -ipv6 = False \ No newline at end of file +ipv6 = False diff --git a/qiling/profiles/windows.ql b/qiling/profiles/windows.ql index d8046a125..17488330a 100644 --- a/qiling/profiles/windows.ql +++ b/qiling/profiles/windows.ql @@ -3,18 +3,20 @@ heap_address = 0x500000000 heap_size = 0x5000000 stack_address = 0x7ffffffde000 stack_size = 0x40000 -image_address = 0x400000 +image_address = 0x400000 dll_address = 0x7ffff0000000 entry_point = 0x140000000 +KI_USER_SHARED_DATA = 0xfffff78000000000 [OS32] heap_address = 0x5000000 heap_size = 0x5000000 stack_address = 0xfffdd000 stack_size = 0x21000 -image_address = 0x400000 -dll_address = 0x10000000 -entry_point = 0x40000 +image_address = 0x400000 +dll_address = 0x10000000 +entry_point = 0x40000 +KI_USER_SHARED_DATA = 0xffdf0000 [CODE] # ram_size 0xa00000 is 10MB @@ -38,8 +40,7 @@ split = False # maily for multiple times Ql run with one file # usage: append = test1 append = -automatize_input = False -current_path = / +current_path = C:\ [SYSTEM] # Major Minor ProductType @@ -61,7 +62,7 @@ language = 1093 [PATH] systemdrive = C:\ -windir = Windows\ +windir = Windows [REGISTRY] registry_diff = registry_diff.json diff --git a/qiling/utils.py b/qiling/utils.py index ff6f3489b..dc3703052 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -8,306 +8,200 @@ thoughout the qiling framework """ -import importlib, os, copy, re, pefile, logging, sys, yaml +from functools import partial +from pathlib import Path +import importlib, inspect, os from configparser import ConfigParser -from logging import LogRecord -from typing import Any, Container, Optional, Sequence, Tuple, Type -from enum import Enum +from types import ModuleType +from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Tuple, TypeVar, Union 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 - -FMT_STR = "%(levelname)s\t%(message)s" - -# \033 -> ESC -# ESC [ -> CSI -# CSI %d;%d;... m -> SGR -class COLOR_CODE: - WHITE = '\033[37m' - CRIMSON = '\033[31m' - RED = '\033[91m' - GREEN = '\033[92m' - YELLOW = '\033[93m' - BLUE = '\033[94m' - MAGENTA = '\033[95m' - CYAN = '\033[96m' - ENDC = '\033[0m' - -class QilingColoredFormatter(logging.Formatter): - 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}" - } - - return LEVEL_NAME[record.levelname] - - 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 - else: - record.levelname = f"{record.levelname} {COLOR_CODE.GREEN}{str(cur_thread)}{COLOR_CODE.ENDC}" - - return super().format(record) +from qiling.exception import * +from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER +from qiling.const import debugger_map, arch_map, os_map, arch_os_map -class QilingPlainFormatter(logging.Formatter): - def __init__(self, ql, *args, **kwargs): - super().__init__(*args, **kwargs) - self.ql = ql +if TYPE_CHECKING: + from qiling import Qiling + from qiling.arch.arch import QlArch + from qiling.debugger.debugger import QlDebugger + from qiling.loader.loader import QlLoader + from qiling.os.os import QlOs - def get_level(self, record: LogRecord) -> str: - LEVEL_NAME = { - 'WARNING' : "[!]", - 'INFO' : "[=]", - 'DEBUG' : "[+]", - 'CRITICAL' : "[x]", - 'ERROR' : "[x]" - } +T = TypeVar('T') +QlClassInit = Callable[['Qiling'], T] - return LEVEL_NAME[record.levelname] - - def format(self, record: LogRecord): - record.levelname = self.get_level(record) - - # early logging may access ql.os when it is not yet set +def catch_KeyboardInterrupt(ql: 'Qiling', func: Callable): + def wrapper(*args, **kw): 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) - -class RegexFilter(logging.Filter): - def __init__(self, regexp): - super(RegexFilter, self).__init__() - self.update_filter(regexp) - - def update_filter(self, regexp): - self._filter = re.compile(regexp) - - def filter(self, record: LogRecord): - msg = record.getMessage() - - return re.match(self._filter, msg) is not None - -class QlFileDes: - def __init__(self, init): - self.__fds = init - - def __getitem__(self, idx): - return self.__fds[idx] - - def __setitem__(self, idx, val): - self.__fds[idx] = val - - def __iter__(self): - return iter(self.__fds) - - def __repr__(self): - return repr(self.__fds) - - def save(self): - return self.__fds - - def restore(self, fds): - 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 - + return func(*args, **kw) + except BaseException as e: + ql.stop() + ql._internal_exception = e -def catch_KeyboardInterrupt(ql): - def decorator(func): - def wrapper(*args, **kw): - try: - return func(*args, **kw) - except BaseException as e: - ql.stop() - ql._internal_exception = e + return wrapper - return wrapper +def __name_to_enum(name: str, mapping: Mapping[str, T], aliases: Mapping[str, str] = {}) -> Optional[T]: + key = name.casefold() - return decorator + return mapping.get(aliases.get(key) or key) -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() - -def ql_is_valid_ostype(ostype: QL_OS) -> bool: - return ostype in enum_values(QL_OS) - -def ql_is_valid_arch(arch: QL_ARCH) -> bool: - return arch in enum_values(QL_ARCH) - -def loadertype_convert_str(ostype: QL_OS) -> Optional[str]: - return loader_map.get(ostype) - -def __value_to_key(e: Type[Enum], val: Any) -> Optional[str]: - key = e._value2member_map_[val] - - return None if key is None else key.name - -def ostype_convert_str(ostype: QL_OS) -> Optional[str]: - return __value_to_key(QL_OS, ostype) - -def ostype_convert(ostype: str) -> Optional[QL_OS]: +def os_convert(os: str) -> Optional[QL_OS]: alias_map = { - "darwin": "macos", + 'darwin' : 'macos' } - return os_map.get(alias_map.get(ostype, ostype)) - -def arch_convert_str(arch: QL_ARCH) -> Optional[str]: - return __value_to_key(QL_ARCH, arch) + return __name_to_enum(os, os_map, alias_map) def arch_convert(arch: str) -> Optional[QL_ARCH]: alias_map = { - "x86_64": "x8664", - "riscv32": "riscv", + 'x86_64' : 'x8664', + 'riscv32' : 'riscv' } - - return arch_map.get(alias_map.get(arch, arch)) -def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]: - return arch_os_map.get(arch, QL_OS.MCU) + return __name_to_enum(arch, arch_map, alias_map) def debugger_convert(debugger: str) -> Optional[QL_DEBUGGER]: - return debugger_map.get(debugger) - -def debugger_convert_str(debugger_id: QL_DEBUGGER) -> Optional[str]: - return __value_to_key(QL_DEBUGGER, debugger_id) + return __name_to_enum(debugger, debugger_map) -# Call `function_name` in `module_name`. -# e.g. map_syscall in qiling.os.linux.map_syscall -def ql_get_module_function(module_name: str, function_name: str): +def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]: + return arch_os_map.get(arch) +def ql_get_module(module_name: str) -> ModuleType: try: - imp_module = importlib.import_module(module_name) - except ModuleNotFoundError: - raise QlErrorModuleNotFound(f'Unable to import module {module_name}') - except KeyError: + module = importlib.import_module(module_name, 'qiling') + except (ModuleNotFoundError, KeyError): raise QlErrorModuleNotFound(f'Unable to import module {module_name}') + return module + +def ql_get_module_function(module_name: str, member_name: str): + module = ql_get_module(module_name) + try: - module_function = getattr(imp_module, function_name) + member = getattr(module, member_name) except AttributeError: - raise QlErrorModuleFunctionNotFound(f'Unable to import {function_name} from {imp_module}') + raise QlErrorModuleFunctionNotFound(f'Unable to import {member_name} from {module_name}') - return module_function + return member -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 +def __emu_env_from_pathname(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: + if os.path.isdir(path) and path.endswith('.kext'): + return QL_ARCH.X8664, QL_OS.MACOS, QL_ENDIAN.EL - ident = f.read(512 if size >= 512 else 20) + if os.path.isfile(path): + _, ext = os.path.splitext(path) + + if ext in ('.DOS_COM', '.DOS_MBR', '.DOS_EXE'): + return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL + + return None, None, None + +def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: + # instead of using full-blown elffile parsing, we perform a simple parsing to avoid + # external dependencies for target systems that do not need them. + # + # see: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + + # ei_class + ELFCLASS32 = 1 # 32-bit + ELFCLASS64 = 2 # 64-bit + + #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 + EM_PPC = 20 + + endianess = { + ELFDATA2LSB : (QL_ENDIAN.EL, 'little'), + ELFDATA2MSB : (QL_ENDIAN.EB, 'big') + } - arch = None + machines32 = { + EM_386 : QL_ARCH.X86, + EM_MIPS : QL_ARCH.MIPS, + EM_ARM : QL_ARCH.ARM, + EM_RISCV : QL_ARCH.RISCV, + EM_PPC : QL_ARCH.PPC + } + + 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] - - 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 + with open(path, 'rb') as binfile: + e_ident = binfile.read(16) + e_type = binfile.read(2) + e_machine = binfile.read(2) - if e_machine == b"\x03\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.X86 + # 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) - elif e_machine == b"\x08\x00" and endian == 1 and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.MIPS + 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"\x00\x08" and endian == 2 and elfbit == 1: - archendian = QL_ENDIAN.EB - arch = QL_ARCH.MIPS + if ei_class in classes: + machines = classes[ei_class] - elif e_machine == b"\x28\x00" and endian == 1 and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.ARM + if ei_data in endianess: + archendian, endian = endianess[ei_data] - elif e_machine == b"\x00\x28" and endian == 2 and elfbit == 1: - archendian = QL_ENDIAN.EB - arch = QL_ARCH.ARM + machine = int.from_bytes(e_machine, endian) - elif e_machine == b"\xB7\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.ARM64 + if machine in machines: + archtype = machines[machine] - 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 ei_osabi in abis: + ostype = abis[ei_osabi] -def ql_macho_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: + if blob and b'ldqnx.so' in blob: + ostype = QL_OS.QNX + + return archtype, ostype, archendian + +def __emu_env_from_macho(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: macho_macos_sig64 = b'\xcf\xfa\xed\xfe' macho_macos_sig32 = b'\xce\xfa\xed\xfe' macho_macos_fat = b'\xca\xfe\xba\xbe' # should be header for FAT @@ -335,8 +229,9 @@ def ql_macho_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS return arch, ostype, endian +def __emu_env_from_pe(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: + import pefile -def ql_pe_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: try: pe = pefile.PE(path, fast_load=True) except: @@ -351,9 +246,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 @@ -376,216 +269,215 @@ def ql_pe_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], return arch, ostype, archendian - def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: - arch = None - ostype = None - endian = None - - if os.path.isdir(path) and path.endswith('.kext'): - return QL_ARCH.X8664, QL_OS.MACOS, QL_ENDIAN.EL - - if os.path.isfile(path) and path.endswith('.DOS_COM'): - return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL - - if os.path.isfile(path) and path.endswith('.DOS_MBR'): - return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL + guessing_methods = ( + __emu_env_from_pathname, + __emu_env_from_elf, + __emu_env_from_macho, + __emu_env_from_pe + ) + + for gm in guessing_methods: + arch, ostype, endian = gm(path) + + if None not in (arch, ostype, endian): + break + else: + arch, ostype, endian = (None, ) * 3 - if os.path.isfile(path) and path.endswith('.DOS_EXE'): - return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL + return arch, ostype, endian - arch, ostype, endian = ql_elf_parse_emu_env(path) +def select_loader(ostype: QL_OS, libcache: bool) -> QlClassInit['QlLoader']: + if ostype == QL_OS.WINDOWS: + kwargs = {'libcache' : libcache} - if arch is None or ostype is None or endian is None: - arch, ostype, endian = ql_macho_parse_emu_env(path) + else: + kwargs = {} - if arch is None or ostype is None or endian is None: - arch, ostype, endian = ql_pe_parse_emu_env(path) + module = { + QL_OS.LINUX : r'elf', + QL_OS.FREEBSD : r'elf', + QL_OS.QNX : r'elf', + QL_OS.MACOS : r'macho', + QL_OS.WINDOWS : r'pe', + QL_OS.UEFI : r'pe_uefi', + QL_OS.DOS : r'dos', + QL_OS.EVM : r'evm', + QL_OS.MCU : r'mcu', + QL_OS.BLOB : r'blob' + }[ostype] - return arch, ostype, endian + qlloader_path = f'.loader.{module}' + qlloader_class = f'QlLoader{module.upper()}' + obj = ql_get_module_function(qlloader_path, qlloader_class) -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) + return partial(obj, **kwargs) +def select_component(component_type: str, component_name: str, **kwargs) -> QlClassInit[Any]: + component_path = f'.{component_type}.{component_name}' + component_class = f'Ql{component_name.capitalize()}Manager' -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) + obj = ql_get_module_function(component_path, component_class) + return partial(obj, **kwargs) -def debugger_setup(options, ql): +def select_debugger(options: Union[str, bool]) -> Optional[QlClassInit['QlDebugger']]: if options is True: options = 'gdb' if type(options) is str: objname, *args = options.split(':') + dbgtype = debugger_convert(objname) - if debugger_convert(objname) not in enum_values(QL_DEBUGGER): - raise QlErrorOutput('Debugger not supported') - - obj = ql_get_module_function(f'qiling.debugger.{objname}.{objname}', f'Ql{str.capitalize(objname)}') + if dbgtype == QL_DEBUGGER.GDB: + kwargs = dict(zip(('ip', 'port'), args)) - return obj(ql, *args) + elif dbgtype == QL_DEBUGGER.QDB: + kwargs = {} - 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 __int_nothrow(v: str, /) -> Optional[int]: + try: + return int(v, 0) + except ValueError: + return None - archmanager = f'QlArch{arch_convert_str(archtype).upper()}' + # qdb init args are independent and may include any combination of: rr enable, init hook and script + for a in args: + if a == 'rr': + kwargs['rr'] = True - if archtype in (QL_ARCH.X8664, QL_ARCH.A8086): - arch_str = "x86" - else: - arch_str = arch_convert_str(archtype) + elif __int_nothrow(a) is not None: + kwargs['init_hook'] = a - 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) + else: + kwargs['script'] = a + else: + raise QlErrorOutput('Debugger not supported') -# 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") + obj = ql_get_module_function(f'.debugger.{objname}.{objname}', f'Ql{str.capitalize(objname)}') + return partial(obj, **kwargs) -def os_setup(archtype: QL_ARCH, ostype: QL_OS, ql): - if not ql_is_valid_ostype(ostype): - raise QlErrorOsType("Invalid OSType") + return None - if not ql_is_valid_arch(archtype): - raise QlErrorArch("Invalid Arch %s" % archtype) +def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassInit['QlArch']: + # set endianess and thumb mode for arm-based archs + if archtype == QL_ARCH.ARM: + kwargs = {'endian' : endian, 'thumb' : thumb} - 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) + # set endianess for mips arch + elif archtype == QL_ARCH.MIPS: + kwargs = {'endian' : endian} + else: + kwargs = {} -def profile_setup(ql): - _profile = "Default" + 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', + QL_ARCH.PPC : r'ppc' + }[archtype] - if ql.profile != None: - _profile = ql.profile - debugmsg = "Profile: %s" % _profile + qlarch_path = f'.arch.{module}' + qlarch_class = f'QlArch{archtype.name.upper()}' - if ql.baremetal: - config = {} - if ql.profile: - with open(ql.profile) as f: - config = yaml.load(f, Loader=yaml.Loader) + obj = ql_get_module_function(qlarch_path, qlarch_class) - 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] + return partial(obj, **kwargs) - config = ConfigParser() - config.read(profiles) +def select_os(ostype: QL_OS) -> QlClassInit['QlOs']: + qlos_name = ostype.name + qlos_path = f'.os.{qlos_name.lower()}.{qlos_name.lower()}' + qlos_class = f'QlOs{qlos_name.capitalize()}' - return config, debugmsg + obj = ql_get_module_function(qlos_path, qlos_class) -def ql_resolve_logger_level(verbose: QL_VERBOSE) -> int: - return { - QL_VERBOSE.DISABLED: logging.CRITICAL, - QL_VERBOSE.OFF : logging.WARNING, - QL_VERBOSE.DEFAULT : logging.INFO, - QL_VERBOSE.DEBUG : logging.DEBUG, - QL_VERBOSE.DISASM : logging.DEBUG, - QL_VERBOSE.DUMP : logging.DEBUG - }[verbose] + return partial(obj) -QL_INSTANCE_ID = 114514 +def profile_setup(ostype: QL_OS, filename: Optional[str]): + # mcu uses a yaml-based config + if ostype == QL_OS.MCU: + import yaml -# 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 + if filename: + with open(filename) as f: + config = yaml.load(f, Loader=yaml.Loader) + else: + config = {} - # If there is an override for our logger, then use it. - if log_override is not None: - log = log_override else: - # We should leave the root logger untouched. - log = logging.getLogger(f"qiling{QL_INSTANCE_ID}") - QL_INSTANCE_ID += 1 - - # Disable propagation to avoid duplicate output. - log.propagate = False - # Clear all handlers and filters. - log.handlers = [] - log.filters = [] - - # Do we have console output? - if console: - handler = logging.StreamHandler() - - if not log_plain and not sys.platform == "win32": - formatter = QilingColoredFormatter(ql, FMT_STR) - else: - formatter = QilingPlainFormatter(ql, FMT_STR) + qiling_home = Path(inspect.getfile(inspect.currentframe())).parent + os_profile = qiling_home / 'profiles' / f'{ostype.name.lower()}.ql' - handler.setFormatter(formatter) - log.addHandler(handler) - else: - log.setLevel(logging.CRITICAL) + profiles = [os_profile] - # 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) - handler.setFormatter(formatter) - log.addHandler(handler) + if filename: + profiles.append(filename) - # Remeber to add filters if necessary. - # If there aren't any filters, we do add the filters until users specify any. - log_filter = None + # patch 'getint' to convert integers of all bases + int_converter = partial(int, base=0) - if filters is not None and len(filters) != 0: - log_filter = RegexFilter(filters) - log.addFilter(log_filter) - - log.setLevel(logging.INFO) - - return log, log_filter + config = ConfigParser(converters={'int': int_converter}) + config.read(profiles) + return config # 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 # timeout is acceptable in this case if err.errno in (UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED): - if ql.ostype == QL_OS.MACOS: + if ql.os.type == 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: raise else: - raise \ No newline at end of file + raise + +__all__ = [ + 'catch_KeyboardInterrupt', + 'os_convert', + 'arch_convert', + 'debugger_convert', + 'arch_os_convert', + 'ql_get_module', + 'ql_get_module_function', + 'ql_guess_emu_env', + 'select_os', + 'select_arch', + 'select_loader', + 'select_debugger', + 'select_component', + 'profile_setup', + 'verify_ret' +] diff --git a/qltool b/qltool index 4dbaa1cac..861bbbae3 100755 --- a/qltool +++ b/qltool @@ -8,14 +8,15 @@ import os import sys import ast import pickle -import unicorn + +from unicorn import __version__ as uc_ver +from qiling import __version__ as ql_ver from qiling import Qiling from qiling.arch import utils as arch_utils from qiling.debugger.qdb import QlQdb from qiling.utils import arch_convert from qiling.const import QL_VERBOSE, QL_ENDIAN, os_map, arch_map, verbose_map -from qiling.__version__ import __version__ as ql_version from qiling.extensions.coverage import utils as cov_utils from qiling.extensions import report @@ -28,21 +29,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 +68,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 +86,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 @@ -119,7 +118,8 @@ def handle_run(options: argparse.Namespace): log_file=options.log_file, log_plain=options.log_plain, multithread=options.multithread, - filter=options.filter + filter=options.filter, + libcache=options.libcache ) # attach Qdb at entry point @@ -168,9 +168,9 @@ def handle_examples(parser: argparse.ArgumentParser): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--version', action='version', version=f'qltool for Qiling {ql_version}, using Unicorn {unicorn.__version__}') + parser.add_argument('--version', action='version', version=f'qltool for Qiling {ql_ver}, using Unicorn {uc_ver}') - commands = parser.add_subparsers(title='sub commands', description='select execution mode', dest='subcommand') + commands = parser.add_subparsers(title='sub commands', description='select execution mode', dest='subcommand', required=True) # set "run" subcommand options run_parser = commands.add_parser('run', help='run a program') @@ -184,9 +184,10 @@ 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('--rootfs', default='.', 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') # set "examples" subcommand @@ -199,7 +200,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') @@ -215,21 +216,14 @@ if __name__ == '__main__': comm_parser.add_argument('-c', '--coverage-file', default=None, help='code coverage file name') comm_parser.add_argument('--coverage-format', default='drcov', choices=cov_utils.factory.formats, help='code coverage file format') comm_parser.add_argument('--json', action='store_true', help='print a json report of the emulation') + comm_parser.add_argument('--libcache', action='store_true', help='enable dll caching for windows') options = parser.parse_args() - # subparser argument required=True is not supported in python 3.6 - # manually check whether the user did not specify a subcommand (execution mode) - if not options.subcommand: - parser.error('please select execution mode') - if options.subcommand == 'examples': handle_examples(parser) - if options.debug_stop and options.verbose not in (QL_VERBOSE.DEBUG, QL_VERBOSE.DUMP): - parser.error('the debug_stop option requires verbose to be set to either "debug" or "dump"') - # ql file setup - if options.subcommand == 'run': + elif options.subcommand == 'run': ql = handle_run(options) # ql code setup @@ -246,6 +240,9 @@ if __name__ == '__main__': ql.debugger = argval if options.debug_stop: + if options.verbose not in (QL_VERBOSE.DEBUG, QL_VERBOSE.DUMP): + parser.error('the debug_stop option requires verbose to be set to either "debug" or "dump"') + ql.debug_stop = True if options.root: diff --git a/setup.py b/setup.py index 5b5cf2a39..ca07eec14 100644 --- a/setup.py +++ b/setup.py @@ -2,25 +2,22 @@ # # Python setup for Qiling framework -import sys, os from setuptools import setup, find_packages -here = os.path.abspath(os.path.dirname(__file__)) -gb = {} -with open(os.path.join(here, "qiling", "__version__.py"), "r+") as f: - exec(f.read(), gb) - -VERSION = gb['__version__'] +# NOTE: use "-dev" for dev branch +#VERSION = "1.4.3" + "-dev" +VERSION = "1.4.3" requirements = [ "capstone>=4.0.1", - "unicorn>=2.0.0-rc5", - "pefile>=2021.9.3", + "unicorn>=2.0.0-rc7", + "pefile>=2022.5.30", "python-registry>=1.3.1", "keystone-engine>=0.9.2", - "pyelftools>=0.26", + "pyelftools>=0.28", "gevent>=20.9.0", "multiprocess>=0.70.12.2", + "windows-curses>=2.1.0;platform_system=='Windows'", "pyyaml>=6.0" ] @@ -43,22 +40,14 @@ "cmd2" ], "fuzz" : [ - + "unicornafl>=2.0.0;platform_system=='Windows'", + "fuzzercorn>=0.0.1;platform_system=='Linux'" ] } with open("README.md", "r", encoding="utf-8") as ld: long_description = ld.read() -if "win32" in sys.platform: - requirements += ["windows-curses>=2.1.0"] - -if "win32" not in sys.platform: - extras["fuzz"] += ["unicornafl>=2.0.0"] - -if "linux" in sys.platform: - extras["fuzz"] += ["fuzzercorn>=0.0.1"] - setup( name='qiling', version=VERSION, @@ -96,7 +85,12 @@ packages=find_packages(), scripts=['qltool'], - include_package_data=True, + package_data={ + 'qiling': ['profiles/*.ql'], + 'qiling.debugger.gdb': ['xml/*/*'], + 'qiling.os.uefi': ['guids.csv'], + 'qiling.arch.evm.analysis': ['signatures.json'] + }, install_requires=requirements, extras_require=extras, ) diff --git a/tests/qdb_scripts/arm.qdb b/tests/qdb_scripts/arm.qdb new file mode 100644 index 000000000..5bfa261a9 --- /dev/null +++ b/tests/qdb_scripts/arm.qdb @@ -0,0 +1,13 @@ +# This line is demonstrate comment in qdb script + +x/10wx 0x7ff3cee4 +x $sp +x $sp + 0x10 +x/5i 0x047ba9e0 +b 0x047ba9ec +c +s +n +p +p +q diff --git a/tests/qdb_scripts/mips32el.qdb b/tests/qdb_scripts/mips32el.qdb new file mode 100644 index 000000000..0e8342baf --- /dev/null +++ b/tests/qdb_scripts/mips32el.qdb @@ -0,0 +1,13 @@ +# This line is demonstrate comment in qdb script + +x/10wx 0x7ff3cec0 +x $sp +x $sp + 0x10 +x/5i 0x047bac40 +b 0x047bac50 +c +s +n +p +p +q diff --git a/tests/qdb_scripts/x86.qdb b/tests/qdb_scripts/x86.qdb new file mode 100644 index 000000000..d06623328 --- /dev/null +++ b/tests/qdb_scripts/x86.qdb @@ -0,0 +1,11 @@ +# This line is demonstrate comment in qdb script + +x/4wx 0x7ff3cee0 +x $esp +x $esp + 0x4 +x/5i 0x047bac70 +s +n +p +p +q diff --git a/tests/test_android.py b/tests/test_android.py index 7d838376d..466bb0cf9 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -4,28 +4,57 @@ # import os, platform, sys, unittest +from collections import defaultdict sys.path.append("..") -from qiling import * -from qiling.const import QL_VERBOSE +from qiling import Qiling +from qiling.os.mapper import QlFsMappedObject +from qiling.os.posix import syscall + + +class Fake_maps(QlFsMappedObject): + def __init__(self, ql): + self.ql = ql + def read(self, size): + stack = next(filter(lambda x : x[3]=='[stack]', self.ql.mem.map_info)) + return ('%x-%x %s\n' % (stack[0], stack[1], stack[3])).encode() + def fstat(self): + return defaultdict(int) + def close(self): + return 0 + +def my_syscall_close(ql, fd): + if fd in [0, 1, 2]: + return 0 + return syscall.ql_syscall_close(ql, fd) class TestAndroid(unittest.TestCase): + @unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') def test_android_arm64(self): - if platform.system() != "Linux": - return - - test_binary = "../examples/rootfs/arm64_android/bin/arm64_android_hello" - rootfs = "../examples/rootfs/arm64_android" - # FUTURE FIX: at this stage, need a file called /proc/self/exe in the rootfs - Android linker calls stat against /proc/self/exe and bails if it can't find it - # qiling handles readlink against /proc/self/exe, but doesn't handle it in stat - # https://cs.android.com/android/platform/superproject/+/master:bionic/linker/linker_main.cpp;l=221 - self.assertTrue(os.path.isfile(os.path.join(rootfs, "proc", "self", "exe")), rootfs + - "/proc/self/exe not found, Android linker will bail. Need a file at that location (empty is fine)") + test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_jniart" + rootfs = "../examples/rootfs/arm64_android6.0" + env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"} - ql = Qiling([test_binary], rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True) + ql = Qiling([test_binary], rootfs, env, multithread=True) + ql.os.set_syscall("close", my_syscall_close) + ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql)) ql.run() + del ql + + + #@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') + #def test_android_arm(self): + # test_binary = "../examples/rootfs/arm64_android6.0/bin/arm_android_jniart" + # rootfs = "../examples/rootfs/arm64_android6.0" + # env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"} + + # ql = Qiling([test_binary], rootfs, env, multithread=True) + # ql.os.set_syscall("close", my_syscall_close) + # ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql)) + # ql.run() + # del ql if __name__ == "__main__": diff --git a/tests/test_blob.py b/tests/test_blob.py index 281c998e2..1a702f01c 100644 --- a/tests/test_blob.py +++ b/tests/test_blob.py @@ -20,31 +20,31 @@ def my_getenv(ql, *args, **kwargs): 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_debugger.py b/tests/test_debugger.py index f584e16ed..9003fe1cb 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -75,6 +75,93 @@ def gdb_test_client(): ql.run() del ql + def test_gdbdebug_mips32(self): + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_hello"], "../examples/rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG) + ql.debugger = True + + # some random command test just to make sure we covered most of the command + def gdb_test_client(): + # yield to allow ql to launch its gdbserver + time.sleep(1.337 * 2) + + with SimpleGdbClient('127.0.0.1', 9999) as client: + client.send('qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386') + client.send('vMustReplyEmpty') + client.send('QStartNoAckMode') + client.send('Hgp0.0') + client.send('qXfer:auxv:read::0, 1000') + client.send('?') + client.send('qXfer:threads:read::0,fff') + client.send(f'qAttached:{ql.os.pid}') + client.send('qC') + client.send('g') + client.send('m47ccd10,4') + client.send('qXfer:threads:read::0,1000') + client.send('m56555620,4') + client.send('m5655561c,4') + client.send('m56555620,4') + client.send('m5655561c,4') + client.send('m56555620,4') + client.send('qTStatus') + client.send('qTfP') + client.send('m56555600,40') + client.send('m56555620,4') + client.send('Z0,47ccd10,4') + client.send('QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;97;') + client.send('vCont?') + client.send('vCont;c:pa410.-1') + client.send('c') + client.send('k') + + # yield to make sure ql gdbserver has enough time to receive our last command + time.sleep(1.337) + + threading.Thread(target=gdb_test_client, daemon=True).start() + + ql.run() + del ql + + def test_gdbdebug_armeb(self): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG) + ql.debugger = True + + # some random command test just to make sure we covered most of the command + def gdb_test_client(): + # yield to allow ql to launch its gdbserver + time.sleep(1.337 * 2) + + with SimpleGdbClient('127.0.0.1', 9999) as client: + client.send('qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386') + client.send('vMustReplyEmpty') + client.send('QStartNoAckMode') + client.send('Hgp0.0') + client.send('qXfer:auxv:read::0, 1000') + client.send('?') + client.send('qXfer:threads:read::0,fff') + client.send(f'qAttached:{ql.os.pid}') + client.send('qC') + client.send('g') + client.send('m47ccd10,4') + client.send('qXfer:threads:read::0,1000') + client.send('z0,47ca5fc,4') + client.send('m0,4') + client.send('mfffffffc,4') + client.send('m0,4') + client.send('mfffffffc,4') + client.send('m0,4') + client.send('p1d') + client.send('qTStatus') + client.send('c') + client.send('k') + + # yield to make sure ql gdbserver has enough time to receive our last command + time.sleep(1.337) + + threading.Thread(target=gdb_test_client, daemon=True).start() + + ql.run() + del ql + def test_gdbdebug_shellcode_server(self): X8664_LIN = bytes.fromhex('31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05') diff --git a/tests/test_dos.py b/tests/test_dos.py index 2c2b0803c..449b19a01 100644 --- a/tests/test_dos.py +++ b/tests/test_dos.py @@ -26,8 +26,8 @@ def onenter(ql: Qiling): 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_dos_exe.py b/tests/test_dos_exe.py index 34e385a1a..d98b17303 100644 --- a/tests/test_dos_exe.py +++ b/tests/test_dos_exe.py @@ -10,10 +10,12 @@ class DOSTest(unittest.TestCase): - # TODO: missing implemention of INT 3Ch and INT 03h def test_dos_8086_hello(self): ql = Qiling(["../examples/rootfs/8086/dos/ARKA.DOS_EXE"], "../examples/rootfs/8086/dos") - ql.run() + + # TODO: missing implemention of INT 3Ch and INT 03h + with self.assertRaises(NotImplementedError): + ql.run() if __name__ == "__main__": unittest.main() diff --git a/tests/test_edl.py b/tests/test_edl.py index e14546d7b..7f86845a0 100644 --- a/tests/test_edl.py +++ b/tests/test_edl.py @@ -15,8 +15,8 @@ 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 @@ def test_edl_arm64(self): 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 @@ def devprg_tx_blocking(ql): 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 e4f84378f..7eb18fecc 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -7,7 +7,7 @@ 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,25 +74,27 @@ def dump(ql): 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 + os.remove(snapshot) + def test_elf_linux_x86_snapshot_restore_reg(self): self._test_elf_linux_x86_snapshot_restore_common(reg=True, ctx=False) @@ -108,7 +110,7 @@ def test_elf_linux_x8664(self): 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 +129,9 @@ def write_onexit(ql: Qiling, fd: int, str_ptr: int, str_len: int, retval: int, * 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 +155,11 @@ def my_puts_enter(ql: Qiling): 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 +189,7 @@ def onenter_fopen(ql: Qiling): 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 +235,7 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): 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 +254,7 @@ def test_syscall_write(ql, write_fd, write_buf, write_count, *args): 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 +272,7 @@ def test_syscall_openat(ql, openat_fd, openat_path, openat_flags, openat_mode, * 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 +306,7 @@ def test_syscall_truncate(ql, trunc_pathname, trunc_length, *args): 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 +315,9 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): 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 +328,18 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): 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 +353,7 @@ def my_puts(ql): 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 +460,12 @@ def test_elf_linux_arm_static(self): # 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 @@ -496,6 +498,39 @@ def random_generator(size=6, chars=string.ascii_uppercase + string.digits): del ql + def test_mips32eb_fake_urandom(self): + class Fake_urandom(QlFsMappedObject): + + def read(self, size): + return b"\x01" + + def fstat(self): + return -1 + + def close(self): + return 0 + + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_fetch_urandom"], "../examples/rootfs/mips32_linux") + ql.add_fs_mapper("/dev/urandom", Fake_urandom()) + + ql.exit_code = 0 + ql.exit_group_code = 0 + + def check_exit_group_code(ql, exit_code, *args, **kw): + ql.exit_group_code = exit_code + + def check_exit_code(ql, exit_code, *args, **kw): + ql.exit_code = exit_code + + 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) + self.assertEqual(0, ql.exit_group_code) + del ql + + def test_elf_onEnter_mips32el(self): def my_puts_onenter(ql: Qiling): params = ql.os.resolve_fcall_params(ELFTest.PARAMS_PUTS) @@ -507,7 +542,7 @@ def my_puts_onenter(ql: Qiling): 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 +555,9 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): 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 +569,7 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): 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 +589,7 @@ def test_syscall_write(ql, write_fd, write_buf, write_count, *args): 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 +608,7 @@ def test_syscall_openat(ql, openat_fd, openat_path, openat_flags, openat_mode, * 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 +644,7 @@ def test_syscall_truncate(ql, trunc_pathname, trunc_length, *args): 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 +663,18 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): 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 +702,9 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): 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 +716,7 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): 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 +736,7 @@ def test_syscall_write(ql, write_fd, write_buf, write_count, *args): 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 +754,7 @@ def test_syscall_open(ql, open_pathname, open_flags, open_mode, *args): 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 +788,7 @@ def test_syscall_truncate(ql, trunc_pathname, trunc_length, *args): 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 +806,24 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): 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 + + + def test_elf_linux_powerpc(self): + ql = Qiling(["../examples/rootfs/powerpc_linux/bin/powerpc_hello"], "../examples/rootfs/powerpc_linux", verbose=QL_VERBOSE.DEBUG) ql.run() del ql @@ -794,9 +835,9 @@ def my_syscall_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +854,7 @@ def my_syscall_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +871,18 @@ def my__llseek(ql, *args, **kw): 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 +941,8 @@ def check_exit_group_code(ql, exit_code, *args, **kw): 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 +978,8 @@ def check_exit_group_code(ql, exit_code, *args, **kw): 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 +1000,8 @@ def check_exit_group_code(ql, exit_code, *args, **kw): 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() @@ -972,47 +1016,43 @@ def test_x8664_symlink(self): ql.run() del ql - def test_arm_directory_symlink(self): - ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG) - real_path = ql.os.path.transform_to_real_path("/lib/libsymlink_test.so") - self.assertTrue(real_path.endswith("/examples/rootfs/arm_linux/tmp/media/nand/symlink_test/libsymlink_test.so")) - 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 +1062,24 @@ def test_arm_stat64(self): 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() + + ql.os.stdout.seek(0) + self.assertTrue("bin\n" in ql.os.stdout.read().decode("utf-8")) + + del ql + + def test_elf_linux_armeb(self): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG, profile='profiles/append_test.ql') + ql.run() + del ql + + def test_elf_linux_armeb_static(self): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello_static"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEFAULT) ql.run() - output.seek(0) - self.assertTrue("bin\n" in output.read().decode("utf-8")) del ql # TODO: Disable for now @@ -1046,10 +1099,11 @@ def test_armoabi_le_linux_syscall_elf_static(self): 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 @@ -1067,36 +1121,37 @@ def test_memory_search(self): ql.mem.write(0x1FFB, b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b") # Needle not in haystack - self.assertEqual([], ql.mem.search(re.escape(b"\x3a\x01\x0b\x03\x53\x29\x1b\x1c\x04\x0d\x11"))) + self.assertEqual([], ql.mem.search(b"\x3a\x01\x0b\x03\x53\x29\x1b\x1c\x04\x0d\x11")) # Needle appears several times in haystack - self.assertEqual([0x1000 + 24, 0x2000 + 38, 0x3000 + 24], ql.mem.search(re.escape(b"\x4f\x53\x06\x0d\x1e\x0d\x1a"))) + self.assertEqual([0x1000 + 24, 0x2000 + 38, 0x3000 + 24], ql.mem.search(b"\x4f\x53\x06\x0d\x1e\x0d\x1a")) # Needle inside haystack - self.assertEqual([0x1000 + 13], ql.mem.search(re.escape(b"\x0f\x01\x1e\x0d\x53\x11\x07\x1d\x53\x1d\x18"), begin=0x1000 + 10, end=0x1000 + 30)) + self.assertEqual([0x1000 + 13], ql.mem.search(b"\x0f\x01\x1e\x0d\x53\x11\x07\x1d\x53\x1d\x18", begin=0x1000 + 10, end=0x1000 + 30)) # Needle before haystack - self.assertEqual([], ql.mem.search(re.escape(b"\x04\x0d\x1c\x53\x11\x07\x1d\x53\x0c\x07\x1f\x06"), begin=0x1337)) + self.assertEqual([], ql.mem.search(b"\x04\x0d\x1c\x53\x11\x07\x1d\x53\x0c\x07\x1f\x06", begin=0x1337)) # Needle after haystack - self.assertEqual([], ql.mem.search(re.escape(b"\x1b\x09\x11\x53\x0f\x07\x07\x0c\x0a\x11\x0d"), end=0x3000 + 13)) + self.assertEqual([], ql.mem.search(b"\x1b\x09\x11\x53\x0f\x07\x07\x0c\x0a\x11\x0d", end=0x3000 + 13)) # Needle exactly inside haystack - self.assertEqual([0x2000 + 13], ql.mem.search(re.escape(b"\x1a\x1d\x06\x53\x09\x1a\x07\x1d\x06\x0c"), begin=0x2000 + 13, end=0x2000 + 23)) + self.assertEqual([0x2000 + 13], ql.mem.search(b"\x1a\x1d\x06\x53\x09\x1a\x07\x1d\x06\x0c", begin=0x2000 + 13, end=0x2000 + 23)) # Needle 'tears' two mapped regions - self.assertEqual([], ql.mem.search(re.escape(b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b"), begin=0x1F00, end=0x200F)) + self.assertEqual([], ql.mem.search(b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b", begin=0x1F00, end=0x200F)) # Needle is a regex - self.assertEqual([0x1000 + 11, 0x2000 + 11, 0x3000 + 43], ql.mem.search(b"\x09\x53(\x0f|\x1a|\x04)[^\x0d]")) + self.assertEqual([0x1000 + 11, 0x2000 + 11, 0x3000 + 43], ql.mem.search(re.compile(b"\x09\x53(\x0f|\x1a|\x04)[^\x0d]"))) del ql 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 b3a80b75e..69c61a059 100644 --- a/tests/test_elf_ko.py +++ b/tests/test_elf_ko.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import sys, unittest +import os, sys, unittest from unicorn import UcError @@ -13,74 +13,67 @@ from qiling.os.const import STRING from qiling.os.linux.fncc import linux_kernel_api +IS_FAST_TEST = 'QL_FAST_TEST' in os.environ + class ELF_KO_Test(unittest.TestCase): + @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_demigod_m0hamed_x86(self): + checklist = {} + @linux_kernel_api(params={ "format": STRING }) - def my_printk(ql, address, params): - print("\n") - print("=" * 40) - print(" Enter into my_printk mode") - print("=" * 40) - print("\n") - self.set_api_myprintk = params["format"] + def my_printk(ql: Qiling, address: int, params): + ql.log.info(f'oncall printk: params = {params}') + + checklist['oncall'] = params['format'] + return 0 - ql = Qiling(["../examples/rootfs/x86_linux/kernel/m0hamed_rootkit.ko"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DISASM) + ql = Qiling(["../examples/rootfs/x86_linux/kernel/m0hamed_rootkit.ko"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.set_api("printk", my_printk) + + ba = ql.loader.load_address + 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.run(begin=procfile_read_func_begin, end=procfile_read_func_end) + ql.run(ba + 0x11e0, ba + 0x11fa) except UcError as e: - print(e) - sys.exit(-1) - self.assertEqual("DONT YOU EVER TRY TO READ THIS FILE OR I AM GOING TO DESTROY YOUR MOST SECRET DREAMS", self.set_api_myprintk) - del ql + self.fail(e) + else: + self.assertEqual("DONT YOU EVER TRY TO READ THIS FILE OR I AM GOING TO DESTROY YOUR MOST SECRET DREAMS", checklist['oncall']) def test_demigod_hello_x8664(self): - def my_onenter(ql, address, params): - print("\n") - print("=" * 40) - print(" Enter into my_onenter mode") - print("params: %s" % params) - print("=" * 40) - print("\n") - self.set_api_onenter = params["format"] - return address, params - - ql = Qiling(["../examples/rootfs/x8664_linux/kernel/hello.ko"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DISASM) - 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.run(begin=procfile_read_func_begin, end=procfile_read_func_end) - except UcError as e: - print(e) - sys.exit(-1) - self.assertEqual("\x016Hello, World: %p!\n", self.set_api_onenter) - del ql + checklist = {} + + def my_onenter(ql: Qiling, address: int, params): + ql.log.info(f'onenter printk: params = {params}') + + checklist['onenter'] = params['format'] + + ql = Qiling(["../examples/rootfs/x8664_linux/kernel/hello.ko"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.set_api("printk", my_onenter, QL_INTERCEPT.ENTER) + + ba = ql.loader.load_address + ql.run(ba + 0x1064, ba + 0x107e) + + self.assertEqual("\x016Hello, World: %p!\n", checklist['onenter']) def test_demigod_hello_mips32(self): - def my_onexit(ql, address, params, retval): - print("\n") - print("=" * 40) - print(" Enter into my_exit mode") - print("params: %s" % params) - print("=" * 40) - print("\n") - self.set_api_onexit = params["format"] + checklist = {} + + def my_onexit(ql: Qiling, address: int, params, retval: int): + ql.log.info(f'onexit printk: params = {params}') + + checklist['onexit'] = params['format'] 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.run(begin=begin, end=end) + ql.os.set_api("printk", my_onexit, QL_INTERCEPT.EXIT) + + ba = ql.loader.load_address + ql.run(ba + 0x1060, ba + 0x1084) - self.assertEqual("\x016Hello, World!\n", self.set_api_onexit) - del ql + self.assertEqual("\x016Hello, World!\n", checklist['onexit']) if __name__ == "__main__": unittest.main() diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index 5b2a40375..482a30256 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -5,23 +5,16 @@ 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): + @unittest.skipIf(platform.system() == "Darwin" and platform.machine() == "arm64", 'darwin host') def test_elf_linux_execve_x8664(self): - if platform.system() == "Darwin" and platform.machine() == "arm64": - return - ql = Qiling(["../examples/rootfs/x8664_linux/bin/posix_syscall_execve"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) ql.run() @@ -36,16 +29,13 @@ def test_elf_linux_execve_x8664(self): def test_elf_linux_cloexec_x8664(self): - 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 +55,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +74,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +93,26 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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) + + del ql + + + def test_multithread_elf_linux_mips32eb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + nonlocal buf_out + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + buf_out = buf + except: + pass + buf_out = None + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_multithreading"], "../examples/rootfs/mips32_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -122,7 +131,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +150,26 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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) + + del ql + + + def test_multithread_elf_linux_armeb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + nonlocal buf_out + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + buf_out = buf + except: + pass + buf_out = None + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_multithreading"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -159,7 +187,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +205,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +223,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +241,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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) @@ -221,6 +249,30 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): del ql + def test_tcp_elf_linux_armeb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + if buf.startswith("server send()"): + ql.buf_out = buf + except: + pass + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_tcp_test","20003"], "../examples/rootfs/armeb_linux", multithread=True) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.run() + + self.assertEqual("server send() 14 return 14.\n", ql.buf_out) + + del ql + + + def test_tcp_elf_linux_mips32eb(self): + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_tcp_test","20005"], "../examples/rootfs/mips32_linux", multithread=True) + ql.run() + del ql + + def test_tcp_elf_linux_mips32el(self): ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_tcp_test","20005"], "../examples/rootfs/mips32el_linux", multithread=True) ql.run() @@ -238,7 +290,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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 +309,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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,13 +327,31 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): 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) + + del ql + + def test_udp_elf_linux_armeb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + if buf.startswith("server sendto()"): + ql.buf_out = buf + except: + pass + + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_udp_test","20009"], "../examples/rootfs/armeb_linux", multithread=True) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) del ql - + def test_http_elf_linux_x8664(self): def picohttpd(): ql = Qiling(["../examples/rootfs/x8664_linux/bin/picohttpd"], "../examples/rootfs/x8664_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) @@ -310,6 +380,20 @@ def picohttpd(): f = os.popen("curl http://127.0.0.1:12913") self.assertEqual("httpd_test_successful", f.read()) + def test_http_elf_linux_armeb(self): + def picohttpd(): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/picohttpd"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) + ql.run() + + + picohttpd_thread = threading.Thread(target=picohttpd, daemon=True) + picohttpd_thread.start() + + time.sleep(1) + + f = os.popen("curl http://127.0.0.1:12913") + self.assertEqual("httpd_test_successful", f.read()) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_evm.py b/tests/test_evm.py index c7938ad0c..79cf062bc 100644 --- a/tests/test_evm.py +++ b/tests/test_evm.py @@ -36,7 +36,7 @@ def hookcode_test(ql, *argv): def hookinsn_test(ql, *argv): testcheck.visited_hookinsn = True - def hookaddr_test(ql): + def hookaddr_test(ql, *argv): testcheck.visited_hookaddr = True h0 = ql.hook_code(hookcode_test) @@ -111,5 +111,39 @@ def check_balance(sender, destination): result = check_balance(user2, c1) self.assertEqual(int(result.output.hex()[2:], 16), 452312848583266388373324160190187140051835877600158453279131187530910662654) + def test_abi_encoding(self): + ql = Qiling(code="0x608060405234801561001057600080fd5b506101a4806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063ead710c414610030575b600080fd5b6100e96004803603602081101561004657600080fd5b810190808035906020019064010000000081111561006357600080fd5b82018360208201111561007557600080fd5b8035906020019184600183028401116401000000008311171561009757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561012957808201518184015260208101905061010e565b50505050905090810190601f1680156101565780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b606081905091905056fea2646970667358221220cf43353b75256fc42aaffd9632e06963c5c2aad72a91004bfd2f98cd56ae1a0c64736f6c63430006000033",archtype="evm", verbose=4) + + user1 = ql.arch.evm.create_account(balance=100*10**18) + c1 = ql.arch.evm.create_account() + + # Deploy runtime code + msg0 = ql.arch.evm.create_message(user1, b'', contract_address=c1) + ql.run(code=msg0) + + # # SMART CONTRACT DEPENDENT: transform from user1 to user2 + call_param = ['Hello World'] + call_data = ql.arch.evm.abi.encode_function_call('greet(string)', call_param) + + function_abi = { + 'name': 'greet', + 'type': 'function', + 'inputs': [{ + 'type': 'string', + 'name': '' + }] + } + call_data2 = ql.arch.evm.abi.encode_function_call_abi(function_abi, call_param) + call_data3 = '0xead710c4'+ ql.arch.evm.abi.convert(['string'], call_param) + + self.assertEqual(call_data, call_data2) + self.assertEqual(call_data, call_data3) + + msg1 = ql.arch.evm.create_message(user1, c1, data=call_data) + result = ql.run(code=msg1) + + result_data = ql.arch.evm.abi.decode_params(['string'], result.output) + self.assertEqual(call_param[0], result_data[0]) + if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/tests/test_macho_kext.py b/tests/test_macho_kext.py index eb8bbca8e..2bb23ccf2 100644 --- a/tests/test_macho_kext.py +++ b/tests/test_macho_kext.py @@ -4,7 +4,7 @@ # -import sys, unittest +import os, sys, unittest from pathlib import Path from unicorn import UcError @@ -16,7 +16,10 @@ from qiling.os.macos.structs import * from qiling.os.macos.fncc import macos_kernel_api +IS_FAST_TEST = 'QL_FAST_TEST' in os.environ + class MACHOTest(unittest.TestCase): + @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_macho_macos_superrootkit(self): # https://developer.apple.com/download/more # to download kernel.developmment @@ -82,9 +85,9 @@ def my__strlen(ql, address, params): 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 140681f5b..563924f0c 100644 --- a/tests/test_mcu.py +++ b/tests/test_mcu.py @@ -215,7 +215,7 @@ def crack(passwd): 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 @@ def step(self): 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) @@ -296,8 +296,8 @@ def skip_delay(ql): def test_mcu_blink_gd32vf103(self): - ql = Qiling(['../examples/rootfs/mcu/gd32vf103/blink.hex'], archtype="riscv64", - env=gd32vf103, verbose=QL_VERBOSE.DEFAULT) + ql = Qiling(['../examples/rootfs/mcu/gd32vf103/blink.hex'], + ostype="mcu", archtype="riscv64", env=gd32vf103, verbose=QL_VERBOSE.DEFAULT) ql.hw.create('rcu') ql.hw.create('gpioa') @@ -307,7 +307,7 @@ def test_mcu_blink_gd32vf103(self): 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_onlinux.sh b/tests/test_onlinux.sh index af84612d1..176693cbf 100755 --- a/tests/test_onlinux.sh +++ b/tests/test_onlinux.sh @@ -16,4 +16,5 @@ python3 ./test_android.py && python3 ./test_mcu.py && python3 ./test_evm.py && python3 ./test_blob.py && +python3 ./test_qdb.py && echo "Done Test" diff --git a/tests/test_pathutils.py b/tests/test_pathutils.py index 396206fc6..de56694ed 100644 --- a/tests/test_pathutils.py +++ b/tests/test_pathutils.py @@ -3,127 +3,178 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import pathlib, sys, unittest +import sys, unittest +from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath + +sys.path.append('..') +from qiling.const import QL_OS +from qiling.os.path import QlOsPath + +is_nt_host = PurePath() == PureWindowsPath() +is_posix_host = PurePath() == PurePosixPath() + +def realpath(path: PurePath) -> Path: + return Path(path).resolve() + +def nt_to_native(rootfs: str, cwd: str, path: str) -> str: + p = QlOsPath(rootfs, cwd, QL_OS.WINDOWS) + + return p.virtual_to_host_path(path) + +def posix_to_native(rootfs: str, cwd: str, path: str) -> str: + p = QlOsPath(rootfs, cwd, QL_OS.LINUX) + + return p.virtual_to_host_path(path) -sys.path.append("..") -from qiling import Qiling -from qiling.const import QL_OS, QL_VERBOSE -from qiling.os.path import QlPathManager class TestPathUtils(unittest.TestCase): - def test_convert_win32_to_posix(self): - rootfs = pathlib.Path("../examples/rootfs/x8664_windows").resolve() - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx/yyyy", "..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) - - def test_convert_posix_to_win32(self): - rootfs = pathlib.Path("../examples/rootfs/x8664_linux").resolve() - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/../xxxx/../test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "../xxxx/../test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx/yyyy", "../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) + @unittest.skipUnless(is_posix_host, 'POSIX host only') + def test_convert_nt_to_posix(self): + rootfs = PurePosixPath(r'../examples/rootfs/x86_windows') + + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\xxxx\\..\\test')) + + expected = str(realpath(rootfs) / 'Windows' / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32\\drivers', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\xxxx\\..\\test')) + + @unittest.skipUnless(is_nt_host, 'NT host only') + def test_convert_posix_to_nt(self): + rootfs = PureWindowsPath(r'../examples/rootfs/x86_linux') + + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../xxxx/../test')) + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../xxxx/../test')) + + expected = str(realpath(rootfs) / 'proc' / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys/net', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../xxxx/../test')) 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: - 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"))) - 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\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) - else: - rootfs = pathlib.Path("../examples/rootfs/x8664_linux").resolve() - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/../xxxx/../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "../xxxx/../test"))) + if is_nt_host: + rootfs = PureWindowsPath(r'../examples/rootfs/x86_windows') + + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\xxxx\\..\\test')) + + expected = str(realpath(rootfs) / 'Windows' / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32\\drivers', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\xxxx\\..\\test')) + + elif is_posix_host: + rootfs = PurePosixPath(r'../examples/rootfs/x86_linux') - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../xxxx/../test')) + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../xxxx/../test')) + + expected = str(realpath(rootfs) / 'proc' / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys/net', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../xxxx/../test')) + + # test virtual symlink: absolute virtual path + rootfs = PurePosixPath(r'../examples/rootfs/arm_linux') + expected = str(realpath(rootfs) / 'tmp' / 'media' / 'nand' / 'symlink_test' / 'libsymlink_test.so') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/lib/libsymlink_test.so')) + + # test virtual symlink: relative virtual path + rootfs = PurePosixPath(r'../examples/rootfs/arm_qnx') + expected = str(realpath(rootfs) / 'lib' / 'libm.so.2') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/usr/lib/libm.so.2')) + + else: + self.fail('unexpected host os') - del ql -if __name__ == "__main__": +if __name__ == '__main__': unittest.main() diff --git a/tests/test_pe.py b/tests/test_pe.py index b3e3981b5..176bb894b 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -62,6 +62,7 @@ def write(self, string): self.output[key] = value return len(string) +IS_FAST_TEST = 'QL_FAST_TEST' in os.environ class PETest(unittest.TestCase): @@ -109,10 +110,9 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) + @unittest.skipIf(IS_FAST_TEST, 'fast test') 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): @@ -134,82 +134,86 @@ def close(self): ql.run() del ql return True - + self.assertTrue(QLWinSingleTest(_t).run()) + @unittest.skipIf(IS_FAST_TEST, 'fast test') 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") + def stop(ql: Qiling): + ql.log.info("Ok for now") + ql.emu_stop() - def randomize_config_value(ql, key, subkey): - # https://en.wikipedia.org/wiki/Volume_serial_number - # https://www.digital-detective.net/documents/Volume%20Serial%20Numbers.pdf - if key == "VOLUME" and subkey == "serial_number": - month = random.randint(0, 12) - day = random.randint(0, 30) - first = hex(month)[2:] + hex(day)[2:] - seconds = random.randint(0, 60) - milli = random.randint(0, 100) - second = hex(seconds)[2:] + hex(milli)[2:] - first_half = int(first, 16) + int(second, 16) - hour = random.randint(0, 24) - minute = random.randint(0, 60) - third = hex(hour)[2:] + hex(minute)[2:] - year = random.randint(2000, 2020) - second_half = int(third, 16) + year - result = int(hex(first_half)[2:] + hex(second_half)[2:], 16) - ql.os.profile[key][subkey] = str(result) - elif key == "USER" and subkey == "username": - length = random.randint(0, 15) - new_name = "" - for i in range(length): - new_name += random.choice(st.ascii_lowercase + st.ascii_uppercase) - old_name = ql.os.profile[key][subkey] - # update paths - ql.os.profile[key][subkey] = new_name - for path in ql.os.profile["PATH"]: - val = ql.os.profile["PATH"][path].replace(old_name, new_name) - ql.os.profile["PATH"][path] = val - elif key == "SYSTEM" and subkey == "computername": - length = random.randint(0, 15) - new_name = "" - for i in range(length): - new_name += random.choice(st.ascii_lowercase + st.ascii_uppercase) - ql.os.profile[key][subkey] = new_name - else: - raise QlErrorNotImplemented("API not implemented") + def __rand_serialnum() -> str: + """ + see: https://en.wikipedia.org/wiki/Volume_serial_number + see: https://www.digital-detective.net/documents/Volume%20Serial%20Numbers.pdf + """ + + mon = random.randint(1, 12) + day = random.randint(0, 30) + word1 = (mon << 8) + day + + sec = random.randint(0, 59) + ms = random.randint(0, 99) + word2 = (sec << 8) + ms + + unified1 = word1 + word2 + + hrs = random.randint(0, 23) + mins = random.randint(0, 59) + word1 = (hrs << 8) + mins + + yr = random.randint(2000, 2020) + word2 = yr + + unified2 = word1 + word2 + + return f'{unified1:04x}-{unified2:04x}' + + def __rand_name(minlen: int, maxlen: int) -> str: + name_len = random.randint(minlen, maxlen) + + return ''.join(random.choices(st.ascii_lowercase + st.ascii_uppercase, k=name_len)) + ql = Qiling(["../examples/rootfs/x86_windows/bin/GandCrab502.bin"], "../examples/rootfs/x86_windows", verbose=QL_VERBOSE.DEBUG, profile="profiles/windows_gandcrab_admin.ql") - default_user = ql.os.profile["USER"]["username"] - default_computer = ql.os.profile["SYSTEM"]["computername"] - - ql.hook_address(stop, 0x40860f, user_data=(default_user, default_computer)) - 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 + + ql.hook_address(stop, 0x40860f) + + # randomize username + old_uname = ql.os.profile['USER']['username'] + new_uname = __rand_name(3, 10) + + # update paths accordingly + path_key = ql.os.profile['PATH'] + + for p in path_key: + path_key[p] = path_key[p].replace(old_uname, new_uname) + + ql.os.profile['USER']['username'] = new_uname + + # randomize computer name and serial number + ql.os.profile['SYSTEM']['computername'] = __rand_name(5, 15) + ql.os.profile['VOLUME']['serial_number'] = __rand_serialnum() + ql.run() + num_syscalls_admin = ql.os.stats.position del ql # RUN AS USER 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.position + del ql # let's check that gandcrab behave takes a different path if a different environment is found - if num_syscalls_admin == num_syscalls_user: - return False + return num_syscalls_admin != num_syscalls_user - del ql - return True - self.assertTrue(QLWinSingleTest(_t).run()) def test_pe_win_x86_multithread(self): @@ -221,7 +225,7 @@ def ThreadId_onEnter(ql, address, params): 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): @@ -233,9 +237,9 @@ def ThreadId_onEnter(ql, address, params): self.assertTrue(QLWinSingleTest(_t).run()) - def test_pe_win_x86_clipboard(self): + def test_pe_win_x8664_clipboard(self): def _t(): - ql = Qiling(["../examples/rootfs/x8664_windows/bin//x8664_clipboard_test.exe"], "../examples/rootfs/x8664_windows") + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_clipboard_test.exe"], "../examples/rootfs/x8664_windows") ql.run() del ql return True @@ -243,7 +247,7 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) - def test_pe_win_x86_tls(self): + def test_pe_win_x8664_tls(self): def _t(): ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_tls.exe"], "../examples/rootfs/x8664_windows") ql.run() @@ -285,7 +289,7 @@ def _t(): 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 +299,7 @@ def _t(): 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 +309,7 @@ def _t(): 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 +319,7 @@ def _t(): 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 @@ -323,10 +327,9 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) + @unittest.skipIf(IS_FAST_TEST, 'fast test') 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) @@ -338,7 +341,7 @@ def stop(ql): ql.run() del ql return True - + self.assertTrue(QLWinSingleTest(_t).run()) @@ -354,22 +357,22 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) + @unittest.skipIf(IS_FAST_TEST, 'fast test') 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: - print("BAD") - else: - print("GOOD ") - ql.reg.eip = 0x402ee4 - + # def results(ql): + # + # if ql.arch.regs.ebx == 1: + # print("BAD") + # else: + # print("GOOD ") + # ql.arch.regs.eip = 0x402ee4 + # #ql.hook_address(results, 0x00402e66) + # the program alloc 4 bytes and then tries to write 0x2cc bytes. # I have no idea of why this code should work without this patch ql.patch(0x00401984, b'\xb8\x04\x00\x00\x00') @@ -421,9 +424,9 @@ def my_onexit(ql: Qiling, address: int, params, retval: int): 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,19 +465,17 @@ def check_print(ql: Qiling, address: int, params): arglist = params['_ArgList'] count = format.count("%") - fargs = [ql.unpack(ql.mem.read(arglist + i * ql.pointersize, ql.pointersize)) for i in range(count)] - - target_txt = "" + fargs = [ql.mem.read_ptr(arglist + i * ql.arch.pointersize) for i in range(count)] try: target_txt = ql.mem.string(fargs[1]) except: - pass + target_txt = "" 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 +494,7 @@ def test_pe_win_x86_crackme(self): 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 +502,7 @@ def force_call_dialog_func(ql): 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 56c7f5ae7..f60c58ff4 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -4,12 +4,13 @@ # import platform, sys, unittest +from typing import List 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 * @@ -22,12 +23,6 @@ class PETest(unittest.TestCase): - def hook_third_stop_address(self, ql): - print(" >>>> Third Stop address: 0x%08x" % ql.reg.arch_pc) - self.third_stop = True - ql.emu_stop() - - def test_pe_win_x86_sality(self): def init_unseen_symbols(ql, address, name, ordinal, dll_name): @@ -77,41 +72,14 @@ def hook_CreateThread(ql: Qiling, address: int, params): }) def hook_CreateFileA(ql: Qiling, address: int, params): lpFileName = params["lpFileName"] + if lpFileName.startswith("\\\\.\\"): - if ql.amsint32_driver: + if hasattr(ql, 'amsint32_driver'): return 0x13371337 - else: - return (-1) - else: - ret = _CreateFile(ql, address, params) - return ret + return -1 - def _WriteFile(ql, address, params): - ret = 1 - hFile = params["hFile"] - lpBuffer = params["lpBuffer"] - nNumberOfBytesToWrite = params["nNumberOfBytesToWrite"] - lpNumberOfBytesWritten = params["lpNumberOfBytesWritten"] - #lpOverlapped = params["lpOverlapped"] - - 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)) - 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)) - return ret + return _CreateFile(ql, address, params) @winsdkapi(cc=STDCALL, params={ 'hFile' : HANDLE, @@ -126,21 +94,31 @@ def hook_WriteFile(ql: Qiling, address: int, params): nNumberOfBytesToWrite = params["nNumberOfBytesToWrite"] lpNumberOfBytesWritten = params["lpNumberOfBytesWritten"] + r = 1 + buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) + if hFile == 0x13371337: - buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - try: - r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) - except Exception: - print("Error") - r = 1 - if r: - return 1 - else: - return 0 + success, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) + r = int(success) + + elif hFile == 0xfffffff5: + s = buffer.decode() + + ql.os.stdout.write(s) + ql.os.stats.log_string(s) + else: - return _WriteFile(ql, address, params) + f = ql.os.handle_manager.get(hFile) + if f is None: + ql.os.last_error = 0xffffffff + return 0 + + f.obj.write(bytes(buffer)) + + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) + + return r # BOOL StartServiceA( # SC_HANDLE hService, @@ -153,88 +131,98 @@ def hook_WriteFile(ql: Qiling, address: int, params): 'lpServiceArgVectors' : POINTER }) def hook_StartServiceA(ql: Qiling, address: int, params): + ql.test_set_api = True + hService = params["hService"] service_handle = ql.os.handle_manager.get(hService) - ql.test_set_api = True - if service_handle.name == "amsint32": - if service_handle.name in ql.os.services: - service_path = ql.os.services[service_handle.name] - service_path = ql.os.path.transform_to_real_path(service_path) - ql.amsint32_driver = Qiling([service_path], ql.rootfs, verbose=QL_VERBOSE.DISASM) - init_unseen_symbols(ql.amsint32_driver, ql.amsint32_driver.loader.dlls["ntoskrnl.exe"]+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") - print("load amsint32_driver") - - try: - ql.amsint32_driver.run() - return 1 - except UcError as e: - print("Load driver error: ", e) - return 0 - else: - return 0 - else: + + if service_handle.name != "amsint32": return 1 + if service_handle.name not in ql.os.services: + return 0 - def hook_first_stop_address(ql): - print(" >>>> First Stop address: 0x%08x" % ql.reg.arch_pc) - ql.first_stop = True + service_path = ql.os.services[service_handle.name] + service_path = ql.os.path.transform_to_real_path(service_path) + + amsint32 = Qiling([service_path], ql.rootfs, verbose=QL_VERBOSE.DEBUG) + ntoskrnl = amsint32.loader.get_image_by_name("ntoskrnl.exe") + self.assertIsNotNone(ntoskrnl) + + init_unseen_symbols(amsint32, ntoskrnl.base + 0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") + amsint32.log.info('Loading amsint32 driver') + + setattr(ql, 'amsint32_driver', amsint32) + + try: + amsint32.run() + except UcError as e: + print("Load driver error: ", e) + return 0 + else: + return 1 + + def hook_first_stop_address(ql: Qiling, stops: List[bool]): + ql.log.info(f' >>>> First stop address: {ql.arch.regs.arch_pc:#010x}') + stops[0] = True ql.emu_stop() + def hook_second_stop_address(ql: Qiling, stops: List[bool]): + ql.log.info(f' >>>> Second stop address: {ql.arch.regs.arch_pc:#010x}') + stops[1] = True + ql.emu_stop() - def hook_second_stop_address(ql): - print(" >>>> Second Stop address: 0x%08x" % ql.reg.arch_pc) - ql.second_stop = True + def hook_third_stop_address(ql: Qiling, stops: List[bool]): + ql.log.info(f' >>>> Third stop address: {ql.arch.regs.arch_pc:#010x}') + stops[2] = True ql.emu_stop() + stops = [False, False, False] 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) - #init sality - ql.hook_address(hook_first_stop_address, 0x40EFFB) + 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) + + # run until first stop + ql.hook_address(hook_first_stop_address, 0x40EFFB, stops) ql.run() - # run driver thread # execution is about to resume from 0x4053B2, which essentially jumps to ExitThread (kernel32.dll). # Set ExitThread exit code to 0 - ql.os.fcall = ql.os.fcall_select(STDCALL) - ql.os.fcall.writeParams(((DWORD, 0),)) + fcall = ql.os.fcall_select(STDCALL) + fcall.writeParams(((DWORD, 0),)) - ql.hook_address(hook_second_stop_address, 0x4055FA) + # run until second stop + ql.hook_address(hook_second_stop_address, 0x4055FA, stops) ql.run(begin=0x4053B2) - print("test kill thread") - if ql.amsint32_driver: - utils.io_Write(ql.amsint32_driver, ql.pack32(0xdeadbeef)) - # TODO: Should stop at 0x10423, but for now just stop at 0x0001066a - stop_addr = 0x0001066a - ql.amsint32_driver.hook_address(self.hook_third_stop_address, stop_addr) + # asmint32 driver should have been initialized by now. otherwise we get an exception + amsint32: Qiling = getattr(ql, 'amsint32_driver') + + utils.io_Write(amsint32, ql.pack32(0xdeadbeef)) - # TODO: not sure whether this one is really STDCALL - ql.amsint32_driver.os.fcall = ql.amsint32_driver.os.fcall_select(STDCALL) - ql.amsint32_driver.os.fcall.writeParams(((DWORD, 0),)) + # TODO: not sure whether this one is really STDCALL + fcall = amsint32.os.fcall_select(STDCALL) + fcall.writeParams(((DWORD, 0),)) - ql.amsint32_driver.run(begin=0x102D0) + # run until third stop + # TODO: Should stop at 0x10423, but for now just stop at 0x0001066a + amsint32.hook_address(hook_third_stop_address, 0x0001066a, stops) + amsint32.run(begin=0x102D0) - self.assertEqual(True, ql.first_stop) - self.assertEqual(True, ql.second_stop) - self.assertEqual(True, self.third_stop) - self.assertEqual(True, ql.test_set_api) + self.assertTrue(stops[0]) + self.assertTrue(stops[1]) + self.assertTrue(stops[2]) + self.assertTrue(ql.test_set_api) 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 +245,7 @@ def test_pe_win_x8664_driver(self): # 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_peshellcode.py b/tests/test_peshellcode.py index 2d6ade205..72ed657f4 100644 --- a/tests/test_peshellcode.py +++ b/tests/test_peshellcode.py @@ -5,36 +5,42 @@ import sys, unittest -from binascii import unhexlify - sys.path.append("..") -from qiling import * -from qiling.exception import * -from qiling.const import QL_VERBOSE +from qiling import Qiling -X86_WIN = unhexlify( - 'fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300' -) +X86_WIN = bytes.fromhex(''' + fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c + 617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b5920 + 01d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475 + e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ff + e05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd5 + 3c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300 +''') -X8664_WIN = unhexlify( - 'fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd54831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373616765426f7800' -) +X8664_WIN = bytes.fromhex(''' + fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52 + 183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1 + c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0 + 746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d + 31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b + 40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e + 595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7 + c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd5 + 4831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373 + 616765426f7800 +''') -POINTER_TEST = unhexlify( - '1122334455667788' -) +POINTER_TEST = bytes.fromhex('1122334455667788') class PEShellcodeTest(unittest.TestCase): def test_windowssc_x86(self): - ql = Qiling(code=X86_WIN, archtype="x86", ostype="windows", rootfs="../examples/rootfs/x86_windows", - verbose=QL_VERBOSE.DEFAULT) + ql = Qiling(code=X86_WIN, archtype="x86", ostype="windows", rootfs="../examples/rootfs/x86_windows") ql.run() del ql def test_windowssc_x64(self): - ql = Qiling(code=X8664_WIN, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x8664_windows", - verbose=QL_VERBOSE.DEBUG) + ql = Qiling(code=X8664_WIN, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x8664_windows") ql.run() del ql @@ -50,7 +56,7 @@ def test_read_ptr32(self): del ql def test_read_ptr64(self): - ql = Qiling(code=POINTER_TEST, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x86_windows") + ql = Qiling(code=POINTER_TEST, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x8664_windows") addr = ql.loader.entry_point self.assertEqual(0x11, ql.mem.read_ptr(addr, 1)) diff --git a/tests/test_qdb.py b/tests/test_qdb.py new file mode 100644 index 000000000..0a0da506c --- /dev/null +++ b/tests/test_qdb.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys, unittest + +sys.path.append("..") +from qiling import Qiling + +class DebuggerTest(unittest.TestCase): + + def test_qdb_mips32el_hello(self): + rootfs = "../examples/rootfs/mips32el_linux" + path = rootfs + "/bin/mips32el_hello" + + ql = Qiling([path], rootfs) + ql.debugger = "qdb::rr:qdb_scripts/mips32el.qdb" + ql.run() + del ql + + def test_qdb_arm_hello(self): + rootfs = "../examples/rootfs/arm_linux" + path = rootfs + "/bin/arm_hello" + + ql = Qiling([path], rootfs) + ql.debugger = "qdb::rr:qdb_scripts/arm.qdb" + ql.run() + del ql + + def test_qdb_x86_hello(self): + rootfs = "../examples/rootfs/x86_linux" + path = rootfs + "/bin/x86_hello" + + ql = Qiling([path], rootfs) + ql.debugger = "qdb::rr:qdb_scripts/x86.qdb" + ql.run() + del ql + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_qnx.py b/tests/test_qnx.py index 43f9b252b..0303c7fb3 100644 --- a/tests/test_qnx.py +++ b/tests/test_qnx.py @@ -58,11 +58,11 @@ def my_printf_onexit(ql: Qiling): 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 365f2bd42..f1013fbed 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 @@ def close(ql, fd): 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 @@ def close(ql, fd): 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 @@ def test_riscv64_hello_dyn_linux(self): 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 b5180c360..f29420d91 100644 --- a/tests/test_uefi.py +++ b/tests/test_uefi.py @@ -101,9 +101,9 @@ def my_onexit(ql: Qiling, address: int, params, retval: int): 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()