When to use / when not to use
- Use: the target cannot run on this machine (architecture/OS mismatch, no hardware, missing dependencies); unpacking assistance (extract the packer's internal decryption routine and emulate it to obtain plaintext); anti-debugging bypass (emulators have no debugger markers, see the exception in pitfall 2); isolated execution verification of a single function/single code segment
- Use: deterministic tasks that do not require full OS semantics (no process/thread/network stack dependencies)
- Do not use: when a full OS environment is required (multithreading, network stack, complete API semantics) → use QEMU full-system ([[re-fw-emulate]]) or actual execution in a sandbox ([[re-sandbox]])
- Do not use: when only dynamic debugging is needed ([[re-gdb]] / [[re-x64dbg]] / [[re-windbg]])
- Do not use: the target can already run on this machine—running it directly in a sandbox is more realistic ([[re-analyze/platform-tips]] highest principle); reserve emulation for scenarios where the environment cannot be brought up
Tool preparation
Emulation execution is dynamic execution, default to sandbox + network isolation ([[re-analyze/platform-tips]] highest principle); the three-piece set in this skill are all pip packages, common to Linux/macOS/Windows, directly usable in WSL ([[re-analyze/platform-tips]] WSL branch).
unicorn
- Install:
pip install unicorn(Python 3) - Verify:
python3 -c "import unicorn; print(unicorn.__version__)" - Version: 2.x is the current line (2.1.4 as of 2025-09); the hook callback signature in 2.x is consistent with 1.x (
(uc, address, size, user_data)), pip installs 2.x by default; for 1.x→2.x differences see [[gotchas]]
qiling (depends on unicorn / capstone / pefile, installed automatically)
- Install:
pip install qiling(currently 1.4.x) - Verify:
python3 -c "import qiling; print(qiling.__version__)"(pure import verification, zero dependencies; the pip wheel does not include examples/ and rootfs; to run examples you need to git clone the official repository to obtain them)
capstone (for disassembly/decoding output)
- Install:
pip install capstone - Verify:
python3 -c "import capstone; print(capstone.__version__)"
Operation steps
Execute in order, record the result of each step; archive the sha256 of emulation artifacts (memory snapshots/plaintext segments) ([[re-triage]] evidence preservation approach).
-
Scenario determination:
- What is the target, what problem does emulation solve—unpacking assistance (the packer's decryption routine is a single function; emulating that routine yields the plaintext without fully running the packer)? Anti-debugging bypass (the sample detects debuggers but may not detect emulators, see pitfall 2)? Running without an environment (architecture mismatch/missing OS)?
- Choose tool: only running a code segment/single function → Unicorn (minimal, controllable); need file/system call semantics → Qiling (handles system calls automatically); need a full OS → switch to [[re-fw-emulate]]
-
Unicorn minimal framework:
from unicorn import * from unicorn.x86_const import * CODE = bytes.fromhex("b8 2a 00 00 00 c3") # mov eax, 0x2a; ret mu = Uc(UC_ARCH_X86, UC_MODE_32) mu.mem_map(0x1000, 0x1000) # 先映射一页(地址页对齐) mu.mem_write(0x1000, CODE) mu.reg_write(UC_X86_REG_ESP, 0x2000) # 设好栈(不设 ESP 会崩) mu.emu_start(0x1000, 0x1000 + len(CODE)) # 执行到结束地址 print(hex(mu.reg_read(UC_X86_REG_EAX))) # 0x2aKey points: memory must be
mem_map(page-aligned) beforemem_write; stack registers must be set;emu_start(begin, until)'s until must cover the code end address, otherwise it runs to an invalid address -
Qiling full-system emulation:
from qiling import Qiling ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux") # 文件名以实际 rootfs 为准;examples/ 与 rootfs 需 git clone 官方仓库获取(pip 不含) ql.run()- rootfs: pip does not include examples/ and rootfs; you need to git clone the official qiling repository and then take
qiling/examples/rootfs/(x8664_linux, arm_linux, x86_windows, etc.); when making your own rootfs, copy the target program's libc/ld-linux and runtime files into it - Files/system calls are taken over by Qiling (open/read/write mapped to rootfs), more worry-free than Unicorn (countermeasure for pitfall 1)
- Windows program:
Qiling(["sample.exe"], "rootfs/x86_windows")(This rootfs includes a basic wine environment; it is relatively heavy but usable)
- rootfs: pip does not include examples/ and rootfs; you need to git clone the official qiling repository and then take
-
Hooks (hook_code / hook_mem):
def hook_code(uc, address, size, user_data): if address == 0x401234: print("hit target", hex(address)) mu.hook_add(UC_HOOK_CODE, hook_code) # 每指令回调 mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem) # 内存写回调(脱壳记解密落点) mu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_bad) # 未映射访问回调- Qiling equivalent:
ql.hook_address(fn, addr)(address breakpoint),ql.hook_mem_read/write/unmapped(memory type) - Commonly used for unpacking: hook_mem_write pauses/counts when the target section is hit—capturing the timing of the decryption loop's writes and the plaintext
- Qiling equivalent:
4.5. State save/restore and memory dumping to disk:
- Qiling(1.1.x+):
ql.save(snapshot="stage1.qsave")/ql.restore(snapshot="stage1.qsave")—save checkpoints at multiple stages of the multi-stage decryption flow, then return to continue emulation - Unicorn: snapshots require manually dumping
mem_readall related segments + register table to disk (.json+.bin), and on restoremem_map+mem_write+reg_write; if you do not want to save everything, use a hook to dump the target segment at key pointsmem_readout to a file (dumping unpacked plaintext to disk follows this idea) - Archive the sha256 of the dumped artifact ([[re-triage]] evidence preservation approach)
- Combined with the unpacking scenario in [[re-anti-analysis]]:
- Extract the decryption routine from the packer (located via [[re-unpack-simple]] / [[re-unpack-advanced]]) → Unicorn loads that routine's code segment + input snapshot → execute → read the output area = plaintext data (strings/key segments)
- Or have Qiling emulate the entire packer program flow, hook to OEP and dump memory (emulator memory is the snapshot; transfer to [[re-memdump]] approach for archiving)
- Anti-debugging bypass: the emulator has no debugger markers, but the sample may have environment checks (pitfall 2)—first statically confirm the detection point before deciding which path to hook
- Verification: sha256 of emulation artifacts + re-run comparison in sandbox ([[re-sandbox]]), then return to the [[re-anti-analysis]] flow to continue
Cross-domain combination
- [[re-anti-analysis]]: unpacking assistance (emulated execution of decryption routines) for fixed scenarios
- [[re-binary-core]]: emulation verification of decompiled artifacts/single-function logic
- [[re-fw-emulate]]: requires full OS/firmware boot → QEMU full-system; this skill only covers single-program/single-code-segment emulation and does not encroach on that domain
- [[re-sandbox]]: emulation is dynamic execution and by default is performed inside a sandbox ([[re-analyze/platform-tips]] highest principle)
- [[re-deobfuscate]]: emulated execution verification of deobfuscation results
- [[re-crypto-decrypt]]: use emulated execution to evaluate decryption algorithms instead of hand-written scripts
- [[re-memdump]]: dumping emulator memory snapshots to disk and subsequent analysis
Common pitfalls and traps
-
System calls not handled → crash: symptom—Unicorn running a real program stops or errors at
syscall/int 2e; cause—Unicorn does not emulate the kernel, so bare execution fails upon encountering a system call; countermeasure—switch to Qiling (automatically forwards system calls, less hassle); in non-Qiling scenarios, hook the target syscall to return a fake value (after hook_code detects the syscall instruction, modify registers + skip) -
Self-check/environment-detection samples detect emulation: symptom—emulation results do not match real execution (wrong branches, self-destruction); cause—the sample uses CPUID/rdtsc timing, API probing (e.g.
GetModuleHandleAspecial return values) to detect a non-real environment; countermeasure—first statically locate the detection points ([[re-triage]] / [[re-deobfuscate]]), hook that path to return real environment values, or directly patch to skip the hard check; conclusions must be cross-validated with real sandbox execution -
Memory permission error: symptom—execution reports
Invalid memory read/write/fetch; cause—accessed an unmapped page or wrote to a read-only page; countermeasure—check according to the reported addressmu.mem_mapCoverage andmu.mem_protect(addr, size, UC_PROT_ALL), patch the stack page mapping and setUC_PROT_READ|UC_PROT_WRITE -
Simulation ≠ real execution: symptom — programs that depend on time/threads behave abnormally; cause — Unicorn is single-threaded,
rdtsctiming is unreliable and requires hook handling, and some API semantics are incomplete; countermeasure — use simulation only for deterministic tasks (solving algorithms, decryption, unpacking to extract functions), and switch timing/multithreading/network tasks to QEMU or a real sandbox; compare simulation conclusions with real traces using [[re-tracing]] -
Prefer unidbg for Android so: symptom — running Android .so bare with Unicorn/Qiling lacks JNI/syscall semantics and cannot run; cause — the so depends on a JNI environment (Java callbacks, DVM objects); countermeasure — use unidbg (Java, emulates Android JNI + syscall), loadLibrary then call JNI_OnLoad to start
-
Iterative mode of filling in JNI callbacks one by one: symptom — simulated execution reports
UnsupportedOperationException: 类->方法(签名); cause — the so calls an unimplemented Java callback, which isa normal iteration signal, not a dead end; countermeasure — implement callbacks one by one according to the error signatures (logging/config/field read-write), run one round at a time, and similar projects all have complete callback sets that can be ported;simulation producing a normal result does not mean it is correct— the environment may be detected and return a decoy, so verify with a real environment/server response -
Use Chomper for iOS so black-box calls: symptom — iOS hardened/obfuscated so algorithms (signature type) are hard to statically restore, and you want to call them directly on PC to get results; cause — the so depends on the OC runtime and specific initialization (Token/context); countermeasure — Chomper (a Unicorn wrapper that emulates the iOS environment) black-box calling flow: frida-trace first to understand the initialization functions and algorithm calling logic → do initialization after dlopen(hook the dlopen timing; frida-trace can only see calls, not initialization values) → construct input parameter objects (NSMutableURLRequest etc., use pyobj2nsobj to convert dict to OC objects) → actively call to get results; initialize Token/context first then call the algorithm function, and construct parameter objects according to the complete structure printed by frida-trace; when the target accesses an external file (such as security configuration
yw_1222.jpg) and reports ENOENT → copy the file from the original package to the same directory as the sample, load the module and then callforward_pathto map the access to the specified path; for instance methods, first+[Class instance]get the instance then call -
Device-side function HTTP serviceization (r1rpc mode): symptom — capabilities depend on a real device environment (DeviceCheck/App Attest, device state, SDK context, anti-tamper checks), the pure computation cannot be extracted, and simulation execution is also difficult; cause — these functions are essentially "device-side functions" and do not work outside the environment; countermeasure — engineered black box:HTTP entry/exit + WebSocket device-side communication + real-device resident client execution: the backend sends HTTP requests as usual, the Server selects an online device by group, delivers the task over WebSocket, the real device executes and returns the result, and the caller still receives a standard HTTP response; applicable scenarios: iOS device capabilities, in-app signatures, capabilities that must use a real device/specific SDK — much lower cost than forcibly extracting the algorithm or forcibly migrating the backend
-
For API quick reference (commands and constants for the Unicorn/Qiling families) and combination patterns, see [[commands]]; for version differences and memory/architecture boundaries, see [[gotchas]]