Skip to main content

unicall

Function-level Unicorn emulation for PE and ELF binaries — call any function with a single statement.

unicall maps a binary into the Unicorn engine, redirects external symbols (GOT/IAT) to Python-implementable trampolines, and exposes a unified call() API for invoking arbitrary functions at arbitrary addresses. It is designed for malware analysis tasks such as string decryption, algorithm extraction, and deobfuscation, where the analyst wants to reuse the binary's own code instead of reimplementing its (possibly customized) cryptography in Python.

特性

  • 双格式统一 API:Emu() 按文件魔数自动识别 ELF / PE(x86/x64),加载、环境搭建、hook 一次完成,调用任意函数只需一行 call()。
  • 类型驱动的参数语义:int 按值传递(若该值是镜像内地址则自然成为指针,与 IDA 显示的地址一致);bytes / str 自动放入模拟内存并传递指针;无需手工构造缓冲区。
  • 调用约定内置:SysV x64、Win x64(含 shadow space 与栈上第 5 个及之后的参数)、cdecl x86;栈对齐自动处理。
  • GOT/IAT 蹦床重定向:外部符号槽位统一改写指向分发页,任意深度的 PLT thunk 均可拦截;hook 支持延迟注册。
  • 严格模式:未注册 hook 的外部符号被调用时抛出异常并提示符号名,避免静默错误产出不可信的结果(可选关闭)。
  • 最小运行环境:栈、bump 堆(支持 realloc 语义)、scratch 缓冲区、TLS 栈 canary(ELF fs:0x28)或 TEB/PEB(PE),以及 14 个以上常用 libc/CR shim。
  • 确定性异常通道:hook 内异常通过 RIP=RET_MAGIC 安全终止模拟后在 Python 侧重现,不依赖 Unicorn 对 hook 异常的传播行为。
  • 调试工具:enable_trace() 逐指令反汇编输出,用于定位模拟偏离的位置。

安装

# 使用 uv(推荐)
uv sync --extra dev

# 或使用 pip
pip install -e .

依赖:unicorn >= 2.0.1、capstone >= 5.0、pyelftools >= 0.29(PE 加载器不依赖 pyelftools)。

快速上手

from unicall import Emu

emu = Emu("sample.bin")                       # ELF / PE 由魔数自动识别

# 注册外部符号 hook(第一个形参是 emu,返回值写入 RAX)
emu.hook_import("malloc", lambda e, n: e.malloc(n))

# 调用任意地址的函数;bytes 自动放入模拟内存并以指针传入
out = emu.call(0x402B80, [blob, 48, 35, key, 8], ret="str")

PE 样本同理,hook 名使用 "kernel32.dll!CreateFileW" 形式,默认调用约定为 Win x64:

emu = Emu("malware.exe")
emu.hook_import("fake.dll!magic_number", lambda e: 0x1337)
ret = emu.call(0x401010, [b"data", 0x10], convention="win", ret="str")

参数语义

Python 类型 传递方式 说明
int 按值放入寄存器 若该值是镜像内地址,即等价于指针(镜像按原始地址映射)
bytes / bytearray 写入 scratch 内存,传指针 数据以常量形式出现时最直接
str UTF-8 编码并补 \0 后同上 C 字符串

实战案例:RotaJakiro 字符串解密

RotaJakiro 是一个 Linux 后门家族,其字符串使用 AES-256 加密并在解密后对每个字节做循环移位(str_decrypt @ 0x402B80)。分析时常见的需求是把全部 60 个调用点的密文批量解出。使用 unicall 时,加密算法本身由二进制原生执行,分析者只需提供每个调用点的参数:

import struct
from unicall import Emu

emu = Emu("RotaJakiro.malware")

blob = raw[0x4187E0 - 0x400000 : 0x4187E0 - 0x400000 + 0xE0]
print(emu.call(0x402B80, [blob, 0xE0, 0xDE, 0x61F300, 8], ret="str"))
# "#system-daemon - configure for system daemon ... exec %s respawn"

blob2 = struct.pack("<6Q", 0xF749A7CADD299C76, 0x11DF18E2058F0AFD,
                    0xCD8E4E37DDD8F707, 0x0C6E9C5005E1A46E,
                    0xBAA9BCA78BA1353A, 0xC59F3C76339D733C)
print(emu.call(0x402B80, [blob2, 48, 35, 0x61F2F0, 8], ret="str"))
# ".dbus/sessions/session-dbus"

ida-example

完整示例见 examples/rotajakiro_str_decrypt.py。

与现有方案的比较

unicall flare-emu Qiling Speakeasy
定位 单函数调用,轻量独立库 IDA/Rizin 插件,脚本式单函数模拟 完整用户态模拟(rootfs/syscall) Windows 恶意软件运行时建模
依赖 无(纯 Python + Unicorn) 需 IDA/Rizin + 其 Python API rootfs、较重 内置 WinAPI 模型
格式 ELF + PE,x86/x64 跟随 IDA 支持的格式(含 ARM 系) ELF/PE/Mach-O 等,多架构 PE/shellcode
批量工作流 内置(调用点骨架 + JSON 批量) iterate API 自行搭建 自行搭建
可嵌入 CI/管道 直接可用 需 IDA 授权环境 可以,配置较重 可以

定位差异:需要 ARM 支持或希望在 IDA 内交互式模拟时,flare-emu 是成熟的现成方案;需要跨平台完整运行环境时选择 Qiling。unicall 的价值在于独立于任何逆向工具、单文件可读、可直接进入自动化管道,且针对“大量解密调用点”这类批量场景提供了一等公民的工作流。

性能

实测环境:Windows 11,Python 3.12,Unicorn 2.1.4(详见 bench/bench_speed.py):

项目 耗时
Emu() 冷启动(RotaJakiro,2.2MB 镜像映射 + hook 安装) ~3.3 ms
call() 框架开销下限(PE add4,纯寄存器运算) ~9 us
单次 str_decrypt(AES-256,3 个块,含逐字节旋转) ~0.5 ms
批量解密全部 60 个调用点 ~31 ms

一个值得注意的实现细节:向 emu_start 传入非零 timeout 会使 Windows 上每次调用固定增加约 15 ms(QEMU 定时器受系统定时器分辨率限制)。因此 call() 默认不启用墙钟超时,以 max_instr 指令数上限作为失控保护;timeout 参数保留给确实需要墙钟限制的长任务。

应用场景

  • 恶意软件字符串解密与配置提取(本项目的直接来源)
  • 提取二进制内的加密、hash、校验算法并在 Python 侧调用
  • 反混淆:执行解混淆例程还原控制流或数据
  • CTF:快速调用题目二进制中的关键函数
  • 安全自动化:作为管道组件批量处理样本(无逆向工具依赖)
  • 坐标解密:懂的都懂

API 速查

emu = Emu(path, base=None, arch=UC_MODE_64, strict_imports=True)
emu.call(addr, args, convention=None, ret="int", max_instr=200_000_000)
emu.hook_import(name, fn)          # 外部符号 hook(fn 首参为 emu)
emu.hook_skip(addr, fn)            # 按地址截获内部函数(fn 或常量)
emu.malloc(n, zero=False)          # 模拟堆分配,返回指针
emu.read_mem / write_mem / read_cstring
emu.enable_trace(limit=500)        # 逐指令调试输出
StopEmulation                      # shim/hook 内抛出以干净终止当前 call()

项目结构

src/unicall/
  __init__.py     # Emu 工厂(魔数自动识别)与公共导出
  _core.py        # 环境搭建、蹦床分发、调用约定、call()、trace
  _elf.py         # ELF 加载器(PT_LOAD、GOT/重定位、PIE)
  _pe.py          # PE 加载器(节映射、.reloc、import directory)
  shims.py        # 内置 libc / WinAPI shim
tests/            # pytest:PE 全链路(自构造样本)+ ELF 全链路(真实样本)
bench/            # 性能基准
examples/         # RotaJakiro 实战示例

测试

uv run pytest -q          # 8 项测试;ELF 用例需要样本,缺失时自动跳过
uv run python bench/bench_speed.py

Roadmap

  • IDA 插件:在 Hex-Rays 伪代码中右键调用点直接模拟,参数自动预填、结果写回注释 已交付,见 ida-unicall
  • ARM / ARM64 支持(Unicorn 本身已具备,需补加载器与调用约定)
  • PE delay-load import 支持

License

MIT

Metadata

Release files for unicall-emu 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for unicall-emu 0.1.0
File Size Uploaded
unicall_emu-0.1.0.tar.gz 112.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for unicall-emu 0.1.0
File Interpreter ABI Platform
unicall_emu-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 129.9 kB

Release files / unicall_emu-0.1.0.tar.gz

Download URL unicall_emu-0.1.0.tar.gz
Size 112.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5ad5a5542f7fcfba299f0d5392ed479ddb9ffdf859b4240d3b142ed0d1466505
BLAKE2b-256 checksum
How to use checksums
33d1f271d296d672ce878fe2215fe932e63aa5be3e2aa728c38ddc870eab66dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / unicall_emu-0.1.0-py3-none-any.whl

Download URL unicall_emu-0.1.0-py3-none-any.whl
Size 17.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3e25669cab20754520280b06c29ea7569fae9ceef8c3144c11f00025834c1df9
BLAKE2b-256 checksum
How to use checksums
a7599709eb7a18feebef7c829294cc4ec447deb7687f159ae0b674efe208129a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page