这是一个纯 Rust 实现的硬件模拟器 + 操作系统进程模拟器。
这种方法非常适合于恶意软件分析以及其他用途(PE、shellcode 等)
操作系统主要是 Windows,它模拟了一个 Windows 进程,对 Linux 有非常基础的支持。
硬件是 x86 32/64 位,快速且可靠。

该项目已从 scemu 更名为 mwemu。
r2con2025 使用 mwemu 中的 radare2 进行静态分析和模拟时刻的可视化。
📦 Rust 安全性,适合模拟恶意软件。
⚡ 极快的模拟速度
基于 iced-x86 Rust 反汇编器(出色的库)构建。
提供命令行工具、Rust 库和 Python 库。
迭代检测器。
内存和寄存器跟踪。
彩色输出。
可在特定时刻停止并探索状态或修改它。
已实现 339 条 CPU 指令。
已实现 260 个 32 位 WinAPI(涵盖 15 个 DLL)。
已实现 204 个 64 位 WinAPI(涵盖 10 个 DLL)。
所有 Linux 系统调用。
SEH 链。
向量化异常处理程序。
PEB、TEB 结构。
动态链接。
IAT 绑定。
延迟加载。
内存分配器。
响应 int3。
非调试模式的 cpuid。
32 位和 64 位 shellcode 模拟。
PE32 和 PE64 可执行文件模拟。
完整模拟已知载荷:
对复杂恶意软件函数的部分模拟:
在模拟过程中,可以看到已模拟的 CPU 指令数量,这是一个用于标识模拟时刻的唯一 ID。
使用 -c 标志可以在特定时刻停止模拟,并通过控制台检查当前状态。
MWEMU 恶意软件模拟器 0.7.11
@sha0coder
用法:
mwemu [FLAGS] [OPTIONS]
FLAGS:
-6, --64bits 启用 64 位架构模拟
--banzai 跳过未实现的指令,并继续模拟可模拟的部分
--flags 在每条指令中跟踪标志的十六进制值。
-F, --fpu 跟踪 FPU 状态。
-h, --handle 处理 Ctrl+C 以生成控制台
--help 打印帮助信息
-l, --loops 显示循环迭代次数,速度较慢。
-m, --memory 跟踪所有内存访问(读和写)。
-n, --nocolors 不带颜色打印,以便重定向到文件 >out
-r, --regs 在每个步骤中打印寄存器值。
-p, --stack_trace 在 push/pop 时跟踪堆栈
-t, --test 测试模式
--version 打印版本信息
-v, --verbose -vv 查看汇编,-v 仅显示消息,无详细输出则仅显示 API 调用,速度更快
OPTIONS:
-A, --args <ARGS> 为 EXE 提供参数,例如:--args '"aa" "bb"'
--cmd <COMMAND> 启动一个控制台命令
-b, --base <ADDRESS> 设置代码基地址
-c, --console <NUMBER> 选择在哪个时刻生成控制台以进行检查。
-C, --console_addr <ADDRESS> 当第一个 eip 等于该地址时生成控制台
-d, --dump <FILE> 从转储文件加载。
-a, --entry <ADDRESS> shellcode 的入口点,默认从开头开始。
-e, --exit <POSITION> shellcode 的退出位置
-f, --filename <FILE> 设置 shellcode 二进制文件。
-i, --inspect <DIRECTION> 监控内存,例如:-i 'dword ptr [ebp + 0x24]'
--iso <ISO> 从 Windows ISO 中提取真实 system32 DLL 并用作 maps 文件夹,例如 --iso ~/Downloads/win11.iso
-L, --log <LOG_FILENAME> 将输出记录到文件
-M, --maps <PATH> 选择内存映射文件夹
--mxcsr <MXCSR> 设置 mxcsr 寄存器
--r10 <R10> 设置 r10 寄存器
--r11 <R11> 设置 r11 寄存器
--r12 <R12> 设置 r12 寄存器
--r13 <R13> 设置 r13 寄存器
--r14 <R14> 设置 r14 寄存器
--r15 <R15> 设置 r15 寄存器
--r8 <R8> 设置 r8 寄存器
--r9 <R9> 设置 r9 寄存器
--rax <RAX> 设置 rax 寄存器
--rbp <RBP> 设置 rbp 寄存器
--rbx <RBX> 设置 rbx 寄存器
--rcx <RCX> 设置 rcx 寄存器
--rdi <RDI> 设置 rdi 寄存器
--rdx <RDX> 设置 rdx 寄存器
-R, --reg <REGISTER1,REGISTER2> 在每个步骤中跟踪特定寄存器及其值和内容
--rflags <RFLAGS> 设置 rflags 寄存器
--rsi <RSI> 设置 rsi 寄存器
--rsp <RSP> 设置 rsp 寄存器
-x, --script <SCRIPT> 启动模拟脚本,参见 scripts_examples 文件夹
--stack_address <ADDRESS> 设置堆栈地址
-s, --string <ADDRESS> 监控指定地址处的字符串
-T, --trace <TRACE_FILENAME> 将跟踪输出到指定文件
-S, --trace_start <TRACE_START> 在指定位置开始跟踪
-V, --verbose_at <NUMBER> 在特定位置开始显示汇编(相当于在该时刻启用 -vv)
64 位需要 -6 标志,-vv 用于查看 ASM,-c 用于在特定时刻生成控制台:
cargo run --release -- -f /tmp/shellcode.bin -6 -vv -c 19291
cargo run --release -- -f /bin/ls -6 -A '"-l"' -v
cargo run --release -- -f calc.exe -6 --winver win11
cargo run --release -- -f calc.exe -6 --winver win11 --syscall-mode
make tests
mwemu 模拟一个简单的 shellcode,检测 execve() 中断。
注意,我将此工具从 scemu 重命名为 mwemu(在意大利语中 scemu 是一个贬义词)

我们选择要停止的行并检查内存。

在 Linux 上模拟了将近 200 万条指令的 GuLoader win32,途中伪造了 cpuid 和其他技巧后,到达一个 sigtrap 以混淆调试器。

API 加载器上的内存转储示例。

默认情况下有几个 maps,并且可以通过 LoadLibraryA 等 API 或从控制台手动创建更多 maps。

模拟基于 LdrLoadDLl() 的基本 Windows shellcode,该代码打印一条消息:

控制台允许查看和编辑 CPU 的当前状态:
--- 控制台 ---
=>h
--- 帮助 ---
q ...................... 退出
cls .................... 清除屏幕
h ...................... 帮助
s ...................... 堆栈
v ...................... 变量
r ...................... 显示所有寄存器
r reg .................. 显示指定寄存器
rc ..................... 更改寄存器
f ...................... 显示所有标志
fc ..................... 清除所有标志
fz ..................... 切换零标志
fs ..................... 切换符号标志
c ...................... 继续
ba ..................... 在地址上设置断点
bi ..................... 在指令编号上设置断点
bmr .................... 在内存读取上设置断点
bmw .................... 在内存写入上设置断点
bc ..................... 清除断点
n ...................... 下一条指令
eip .................... 更改 eip
push ................... 向堆栈压入双字
pop .................... 从堆栈弹出双字
fpu .................... 查看 FPU
md5 .................... 检查内存映射的 md5
seh .................... 查看 SEH
veh .................... 查看向量化异常指针
m ...................... 内存映射
ma ..................... 内存分配
mc ..................... 创建内存映射
mn ..................... 根据地址获取映射名称
ml ..................... 将文件内容加载到映射
mr ..................... 读取内存,例如:mr dword ptr [esi]
mw ..................... 写入内存,例如:mw dword ptr [esi] 然后输入:1af
md ..................... 内存转储
mrd .................... 读取内存双字
mds .................... 转储内存字符串
mdw .................... 转储内存宽字符串
mdd .................... 转储内存到磁盘
mt ..................... 测试内存
ss ..................... 搜索字符串
sb ..................... 搜索字节
sba .................... 在所有映射中搜索字节
ssa .................... 在所有映射中搜索字符串
ll ..................... 遍历链表
d ...................... 反汇编
dt ..................... 转储结构
enter .................. 步入
Cobalt Strike 的 API 加载器与 Metasploit 相同,模拟它:

调用的 Cobalt Strike API:

调用的 Metasploit 逆向 shell API:

Metasploit SGN 编码器使用少量 FPU 来隐藏多态性:

Metasploit shikata-ga-nai 编码器同样以 FPU 开始:

显示 PEB 结构:
=>dt
structure=>peb
address=>0x7ffdf000
PEB {
reserved1: [
0x0,
0x0,
],
being_debugged: 0x0,
reserved2: 0x0,
reserved3: [
0xffffffff,
0x400000,
],
ldr: 0x77647880,
process_parameters: 0x2c1118,
reserved4: [
0x0,
0x2c0000,
0x77647380,
],
alt_thunk_list_ptr: 0x0,
reserved5: 0x0,
reserved6: 0x6,
reserved7: 0x773cd568,
reserved8: 0x0,
alt_thunk_list_ptr_32: 0x0,
reserved9: [
0x0,
...
显示 PEB_LDR_DATA 结构:
=>dt
structure=>PEB_LDR_DATA
address=>0x77647880
PebLdrData {
length: 0x30,
initializated: 0x1,
sshandle: 0x0,
in_load_order_module_list: ListEntry {
flink: 0x2c18b8,
blink: 0x2cff48,
},
in_memory_order_module_list: ListEntry {
flink: 0x2c18c0,
blink: 0x2cff50,
},
in_initialization_order_module_list: ListEntry {
flink: 0x2c1958,
blink: 0x2d00d0,
},
entry_in_progress: ListEntry {
flink: 0x0,
blink: 0x0,
},
}
=>
显示 LDR_DATA_TABLE_ENTRY 和第一个模块名称:
=>dt
structure=>LDR_DATA_TABLE_ENTRY
address=>0x2c18c0
LdrDataTableEntry {
reserved1: [
0x2c1950,
0x77647894,
],
in_memory_order_module_links: ListEntry {
flink: 0x0,
blink: 0x0,
},
reserved2: [
0x0,
0x400000,
],
dll_base: 0x4014e0,
entry_point: 0x1d000,
reserved3: 0x40003e,
full_dll_name: 0x2c1716,
reserved4: [
0x0,
0x0,
0x0,
0x0,
0x0,
0x0,
0x0,
0x0,
],
reserved5: [
0x17440012,
0x4000002c,
0xffff0000,
],
checksum: 0x1d6cffff,
reserved6: 0xa640002c,
time_date_stamp: 0xcdf27764,
}
=>
恶意软件在异常中隐藏了一些内容:
3307726 0x4f9673: push ebp
3307727 0x4f9674: push edx
3307728 0x4f9675: push eax
3307729 0x4f9676: push ecx
3307730 0x4f9677: push ecx
3307731 0x4f9678: push 4F96F4h
3307732 0x4f967d: push dword ptr fs:[0]
Reading SEH 0x0
-------
3307733 0x4f9684: mov eax,[51068Ch]
--- 控制台 ---
=>
让我们检查异常结构:
--- 控制台 ---
=>r esp
esp: 0x22de98
=>dt
structure=>cppeh_record
address=>0x22de98
CppEhRecord {
old_esp: 0x0,
exc_ptr: 0x4f96f4,
next: 0xfffffffe,
exception_handler: 0xfffffffe,
scope_table: PScopeTableEntry {
enclosing_level: 0x278,
filter_func: 0x51068c,
handler_func: 0x288,
},
try_level: 0x288,
}
=>
这里我们得到了错误例程 0x4f96f4 和过滤器 0x51068c。