diff --git a/examples/crackme_x86_windows_auto.py b/examples/crackme_x86_windows_auto.py index b8bf1072..8911199d 100644 --- a/examples/crackme_x86_windows_auto.py +++ b/examples/crackme_x86_windows_auto.py @@ -8,28 +8,64 @@ sys.path.append("..") from qiling import Qiling from qiling.extensions import pipe +from qiling.const import QL_INTERCEPT, QL_VERBOSE +from qiling.os.windows.api import HWND, UINT, LONG -def force_call_dialog_func(ql: Qiling): - # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.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.arch.regs.eip = lpDialogFunc +def hook_DialogBoxParamA_onexit(ql: Qiling, address: int, params, retval: int): + # extract lpDialogFunc value + # [see arguments list at 'qiling/os/windows/dlls/user32.py' -> 'hook_DialogBoxParamA'] + lpDialogFunc = params['lpDialogFunc'] + + def 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) + 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") + ql.os.stdin.write(b'Ea5yR3versing\n') + + # [step #2] + # intercept DialogBoxParamA on exit + ql.os.set_api('DialogBoxParamA', hook_DialogBoxParamA_onexit, QL_INTERCEPT.EXIT) - ql.hook_address(force_call_dialog_func, 0x00401016) ql.run() if __name__ == "__main__": - # Flag is : Ea5yR3versing our_sandbox(r"rootfs/x86_windows/bin/Easy_CrackMe.exe", r"rootfs/x86_windows") diff --git a/examples/hello_x8664_linux_disasm.py b/examples/hello_x8664_linux_disasm.py index 9adcfe94..9719828d 100644 --- a/examples/hello_x8664_linux_disasm.py +++ b/examples/hello_x8664_linux_disasm.py @@ -6,9 +6,29 @@ import sys sys.path.append("..") +from typing import Mapping from capstone import Cs + from qiling import Qiling +def __map_regs() -> Mapping[int, int]: + """Map Capstone x86 regs definitions to Unicorn's. + """ + + from capstone import x86_const as cs_x86_const + from unicorn import x86_const as uc_x86_const + + def __canonicalized_mapping(module, prefix: str) -> Mapping[str, int]: + return dict((k[len(prefix):], getattr(module, k)) for k in dir(module) if k.startswith(prefix)) + + cs_x86_regs = __canonicalized_mapping(cs_x86_const, 'X86_REG') + uc_x86_regs = __canonicalized_mapping(uc_x86_const, 'UC_X86_REG') + + return dict((cs_x86_regs[k], uc_x86_regs[k]) for k in cs_x86_regs if k in uc_x86_regs) + +# capstone to unicorn regs mapping +CS_UC_REGS = __map_regs() + def trace(ql: Qiling, address: int, size: int, md: Cs): """Emit tracing info for each and every instruction that is about to be executed. @@ -19,27 +39,36 @@ def trace(ql: Qiling, address: int, size: int, md: Cs): md: initialized disassembler object """ + # read current instruction bytes and disassemble it buf = ql.mem.read(address, size) + insn = next(md.disasm(buf, address)) + nibbles = ql.arch.bits // 4 + color_faded = '\033[2m' + color_reset = '\033[0m' - esc_dgray = "\x1b[90m" - esc_reset = "\x1b[39m" + # 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]) - for insn in md.disasm(buf, address): - opcode = ''.join(f'{b:02x}' for b in insn.bytes) + # 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)}' - # BUG: insn.regs_read doesn't work well, so we use insn.regs_access()[0] instead - reads = (f'{md.reg_name(reg)} = {ql.arch.regs.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)}' - - # 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") + # 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()