diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d99b6c52..8fdba415 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,8 @@ on: pull_request: branches: [master] +permissions: read-all + jobs: lint: runs-on: ubuntu-latest diff --git a/speakeasy/version.py b/speakeasy/version.py index 4b3c3ea5..9b8d3ee1 100644 --- a/speakeasy/version.py +++ b/speakeasy/version.py @@ -1 +1 @@ -__version__ = "2.0.0a1" +__version__ = "2.0.0b1" diff --git a/speakeasy/windows/winemu.py b/speakeasy/windows/winemu.py index ea8f1145..db2edc82 100644 --- a/speakeasy/windows/winemu.py +++ b/speakeasy/windows/winemu.py @@ -1626,8 +1626,16 @@ def _hook_mem_unmapped(self, emu, access, address, size, value, ctx): elif address == winemu.API_CALLBACK_HANDLER_ADDR: run = self.get_current_run() if run.api_callbacks: - pc, orig_func, args = run.api_callbacks.pop(0) - self.do_call_return(len(args), pc) + ret, orig_func, args = run.api_callbacks.pop(0) + if run.api_callbacks: + next_ret, next_func, next_args = run.api_callbacks[0] + if next_ret is None: + run.api_callbacks[0] = (ret, next_func, next_args) + sp = self.get_stack_ptr() + self.set_func_args(sp, winemu.API_CALLBACK_HANDLER_ADDR, *next_args) + self.set_pc(next_func) + else: + self.do_call_return(len(args), ret) self._unset_emu_hooks() return True return self._handle_invalid_fetch(emu, address, size, value, ctx) diff --git a/speakeasy/winenv/api/usermode/msvcrt.py b/speakeasy/winenv/api/usermode/msvcrt.py index d944ce29..24c45770 100644 --- a/speakeasy/winenv/api/usermode/msvcrt.py +++ b/speakeasy/winenv/api/usermode/msvcrt.py @@ -179,6 +179,17 @@ def _wcsnicmp(self, emu, argv, ctx={}): return rv + def _read_func_table(self, pfbegin, pfend): + ptrsize = self.get_ptr_size() + funcs = [] + ptr = pfbegin + while ptr < pfend: + addr = int.from_bytes(self.mem_read(ptr, ptrsize), "little") + if addr: + funcs.append(addr) + ptr += ptrsize + return funcs + # Reference: https://wiki.osdev.org/Visual_C%2B%2B_Runtime @apihook("_initterm_e", argc=2, conv=e_arch.CALL_CONV_CDECL) def _initterm_e(self, emu, argv, ctx={}): @@ -186,22 +197,20 @@ def _initterm_e(self, emu, argv, ctx={}): static int _initterm_e(_PIFV * pfbegin, _PIFV * pfend) """ - pfbegin, pfend = argv - - rv = 0 - - return rv + funcs = self._read_func_table(pfbegin, pfend) + if funcs: + argv.append(", ".join(f"0x{f:x}" for f in funcs)) + return 0 @apihook("_initterm", argc=2, conv=e_arch.CALL_CONV_CDECL) def _initterm(self, emu, argv, ctx={}): """static void _initterm (_PVFV * pfbegin, _PVFV * pfend)""" - pfbegin, pfend = argv - - rv = 0 - - return rv + funcs = self._read_func_table(pfbegin, pfend) + if funcs: + argv.append(", ".join(f"0x{f:x}" for f in funcs)) + return 0 @apihook("__getmainargs", argc=5) def __getmainargs(self, emu, argv, ctx={}): diff --git a/tests/test_initterm.py b/tests/test_initterm.py new file mode 100644 index 00000000..c51fbb88 --- /dev/null +++ b/tests/test_initterm.py @@ -0,0 +1,32 @@ +def test_initterm_reports_function_table(config, load_test_bin, run_test): + """_initterm and _initterm_e should parse the function pointer table + and include the entries in the API event args.""" + data = load_test_bin("argv_test_x86.exe.xz") + report = run_test(config, data) + ep = report.entry_points + + initterm_events = [] + for evt in ep[0].events or []: + if evt.event == "api" and "_initterm" in evt.api_name: + initterm_events.append(evt) + + assert len(initterm_events) > 0, "expected at least one _initterm call" + + for evt in initterm_events: + assert len(evt.args) == 3, f"expected 3 args (pfbegin, pfend, func_table) but got {len(evt.args)}: {evt.args}" + func_table_str = evt.args[2] + assert "0x" in func_table_str, f"expected hex addresses in func table: {func_table_str}" + + +def test_initterm_does_not_crash_emulation(config, load_test_bin, run_test): + """_initterm should not crash the emulation - main() should still execute.""" + data = load_test_bin("argv_test_x86.exe.xz") + report = run_test(config, data, argv=["arg1"]) + ep = report.entry_points + + printfs = [] + for evt in ep[0].events or []: + if evt.event == "api" and "__stdio_common_vfprintf" in evt.api_name: + printfs.append(evt) + + assert len(printfs) > 0, "main() should have executed and called printf"