Skip to content

feat(emu): resume out of a parked syscall via stack unwind#14

Merged
ricardojrdez merged 1 commit into
mainfrom
feat/emu-resume-from-syscall
Jun 28, 2026
Merged

feat(emu): resume out of a parked syscall via stack unwind#14
ricardojrdez merged 1 commit into
mainfrom
feat/emu-resume-from-syscall

Conversation

@ricardojrdez

Copy link
Copy Markdown
Member

Why

A slice is frequently captured with its thread blocked in a library/kernel call (a Sleep, a wait, a recv), so the captured PC sits in ntdll/kernel. Stepping forward from there immediately hits a syscall the emulator can't service — so the most common capture state was awkward to drive with memslicer-emu.

The fix is the standard manual trick, made first-class: don't emulate the kernel — read the caller's return address off the stack and continue there.

What

  • MSLEmulator.resume_from_syscall(image_range=None, max_depth=256, pop_bytes=0) — scans the stack from SP for the nearest return address that points into the program image (executable, captured memory), then sets PC/SP so emulation continues in the caller as if the call had returned. Returns the CallerFrame used (or None).
  • The program image is identified by its .exe module (main_image()); --image-range LO:HI overrides it for odd cases / missing module lists.
  • --pop-bytes N additionally discards the stdcall argument bytes the callee's ret N would have cleaned up (e.g. 4 for Sleep) so the caller's stack is left balanced. Default 0 = a plain ret.
  • Supporting library helpers: find_caller_frame(), in_system_call(), main_image(), module_at(), and a public sp_name.
  • CLI: memslicer-emu --resume-from-syscall / -R, composes with --steps / --until / --registers.

The walker skips nested system frames (return addresses into ntdll/kernel32) and stack junk, landing on the first slot that returns into the analyzed binary — exactly the instruction after the call that entered the library.

memslicer-emu parked.msl -R --until 0xb1070 -r
#   [resume-from-syscall] pc = 0x7c90e514  (ntdll.dll+0x...)
#     caller return @ 0xb3275  (sample.exe)  [stack 0x..., depth 3]
#     resumed: pc -> 0xb3275, esp -> 0x...

Tests

tests/test_emu.py: a synthetic "parked in ntdll" slice (module list + system region + crafted stack) covering image/system identification, frame-walk skipping system+junk frames, the SP fixup, --pop-bytes, the no-caller case, and CLI output (incl. --image-range).

Full suite: 1142 passed, 10 skipped locally (the only failures are 4 TestEncryptedMSLWriter cases that need the cryptography dep, unmodified by this PR). ruff clean on the changed files.

README (new Parked in a syscall subsection) and CHANGELOG updated.

🤖 Generated with Claude Code

A slice is frequently captured with its thread blocked in a library/kernel
call (a Sleep, a wait, a recv), so the captured PC sits in ntdll/kernel.
Stepping forward from there hits a syscall the emulator can't service, so
the captured state was hard to drive.

Add `MSLEmulator.resume_from_syscall()`: scan the stack from SP for the
nearest return address that points into the program image (executable,
captured memory), then set PC/SP so emulation continues in the caller as if
the call had returned. The program image is identified by its `.exe` module;
`--image-range` overrides it. `--pop-bytes N` additionally discards stdcall
argument bytes the callee's `ret N` would have cleaned up (e.g. 4 for Sleep).

Supporting helpers: `find_caller_frame()`, `in_system_call()`, `main_image()`,
`module_at()`, and a public `sp_name`. Exposed on the CLI as
`memslicer-emu --resume-from-syscall/-R`.

Adds engine + CLI tests (skip-walking system/junk frames, the SP fixup,
pop-bytes, the no-caller case, and CLI output). README + CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ricardojrdez ricardojrdez force-pushed the feat/emu-resume-from-syscall branch from aee0053 to d3324b7 Compare June 28, 2026 15:24
@ricardojrdez ricardojrdez merged commit 217de6e into main Jun 28, 2026
7 checks passed
@ricardojrdez ricardojrdez deleted the feat/emu-resume-from-syscall branch June 28, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant