Intro

Welcome to official MWEMU documentation, you can do scrolling down or using the direct links that are on the left.

Github repo: https://github.com/sha0coder/mwemu

What is MWEMU?

MWEMU is a hardware emulator and Windows process simulator written in pure Rust from scratch, It was created by @sha0coder and It's open source. The contributors of this software have improved the quality of the project a lot, click here to know them.

The implemented hardware is x86/x64, unlike other emulators also implemt some parts of the OS (mainly windows) because asm code soon or later is going to do system calls (WinAPI, linux syscalls, etc).

It resulted extremely useful for malware deobfuscation, but this don't replace reversing, I't needed a previous reversing to prepare well the emulation initial state, and emulate only small group of functions, like decryption, keygen etc. In some specific cases mwemu can do full-emulation, wihth simple packers, encoded shellcodes, etc.

The emulation and simulation is implemented from scratch, but the awesome Rust library iced-x86 is used to the translation from a bunch of bytes to instruction objects. I implemented more than 300 x86 instructions, flags, exceptions, some loaders PE32/PE64/ELF64/shellcode32/shellcode64 and many other stuff.

MWEMU is blazing fast and also is memory-safe thanks to the Rust magic compiler.

The 3 ways of using MWEMU

1. The first way is commandline, see commandline for more details, this is for trying full-emulation.

2. Second way is creating python scripts with pymwemu module, can be installed with pip or git.

pypi: https://pypi.org/project/pymwemu/

If you need features or bug-fixes implemented recently use the git, will need to compile the full project and then the python bindings with maturin, more details in this section pymwemu instalation

3. Third way is using the rust crate published in crates.io https://crates.io/crates/libmwemu from a rust application.

Architectures

You can run MWEMU from Windows, Linux and Mac (x86 and also m1)

But only can be emulated x86 code (32bits and 64bits) mainly for Windows, there is linux shellcode support even syscalls are implemented but regarding ELF only 64bits, only static compiled and quite basic support for now, but no problem with shellcodes.

libc can be emulated well despite it is plenty of ymm instructions, but the linker cannot be emulated totally so far, my plan is fully emulating the linker, in this case I don't need to implement the hole linking process (.got and .plt creation etc)

A bit of internals

For now, just a basic overview of the internals.

In the past I named the project SCEMU, and was stored in 3 separated repos for mwemu (the commandline) pymwemu (pyhon module) and (libmwemu) the engine where all is implemented and also it's the Rust Crate module on crates.io.

Then it was renamed to MWEMU, because SCEMU is more specific to shellcode and because it's an italian bad word.

So, now it's one repo with a crates/ folder with the 3 crates.

The tests are implemented on crates/libmwemu/src/tests/ and are described lately.

Most of files were splitted in small files.

Shell

~/s/mwemu ❯❯❯ ls crates/libmwemu/src/
banzai.rs       emu/               kuser_shared.rs
breakpoint.rs   emu_context.rs     lib.rs   
colors.rs       engine/            macros.rs
config.rs       err.rs             maps/    
console.rs      exception.rs       ntapi/   
constants.rs    exception_type.rs  pe/      
context/        flags.rs           peb/     
crit_state.rs   fpu/               regs64.rs    
definitions.rs  fpu.rs             script.rs
eflags.rs       global_locks.rs    serialization/
elf/            hooks.rs           structures/
syscall/        tests/             thread_context.rs
threading.rs    tools/             tracing.rs
utils.rs w      inapi/
              

emu/ contains emu methods and sub-bojects that are involved in the emulation itself.

In engine/ there are the implementation of all the instructions.

winapi/ contains the WinAPI implemntations divided in winapi32/ and winapi64/

there are other thigngs like contants.rs structures/ etc.

Test System

For triggering locally use make tests this downloads some binaries and launch the test system cargo test

Don't use --release, always is more convinient do the tests without aplying the optimizations, that could ignore some type of errors. Actually the github CI is configured to do cargo test and also cargo test --release to check both modes.

Every git push or pull-request will trigger the CI in gibhut to perform all the tests in Linux, Windows and Mac. In in the case of PR is mandatory, in the case of a git push is only informative.

PR also triggers a coverage analysis of the tests, which is currently only 32%

Project Contributtors

Brandon Ros

Archeron2302

ElCapor

There are also other people that suggested intereting ideas and optimizations.

Regarding wit00 it's a github glitch for git pushing having a bad configuration in git config. (the bug was reported to github)

I'm @sha0coder and I created this software to empower my reversing engineering works, and I'm sharing this because I think it's useful for some cases.

Some graphs:https://github.com/sha0coder/mwemu/graphs/contributors

Videos and pics

radare2 integration on mwemu - r2con2025

my youtube channel, there are some demos

zloader emulation - NcNLabs2022

Fixing FPU emulation

License

Actually there are several licenses, the source code is GPLv3, but the crates.io rust module and pypi python module are MIT for having less restrictions in distributing software using libmwemu or pymwemu.

https://github.com/sha0coder/mwemu/blob/main/LICENSE

Don't hesitate in contact me for creating technologies based of this software.

email: sha0 at badchecksum dot net

MWEMU commandline tool

The commandline is a quick way of using mwemu, and there are many features like register/memory/call/string tracing or capturing emulation moments.

If the packer is simple probably can be full-emulated using the commandline tool, but if you need more control use pymwemu and for total control libmwemu.

In rust you can compile and exec together with cargo run, use --release mode for faster execution, example:

Shell

❯❯❯ cargo run --release -- -6 -f file -vv -c 100
              

This is equivalent of doing:

Shell

❯❯❯ cargo build --release
❯❯❯ target/release/mwemu -6 -f file -vv -c 100
              

MWEMU Installation

1. First you need to install Rust and Cargo, and the best way is using rustup.

https://rustup.rs/

For instance in linux or mac:

Shell

❯❯❯ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
              

In the default setup binaries are in ~/.cargo/bin/ but you need to add this to the path, ideally on last section of the path.

Shell

❯❯❯ export PATH="$PATH:/home/username/.cargo/bin/"
              

The installer says how to put in the bashrc or other shells rc.

2. Then there are 2 options to install this

--help

Use help option for viewing the commandline options, note that the before the "--" there are cargo flags and after the "--" are the parameters of the program, in this case mwemu commandline.

Shell

~/s/mwemu ❯❯❯ cargo run --release -- --help                                                                                                        Finished `release` profile [optimized] target(s) in 0.06s
Running `target/release/mwemu --help`
MWEMU emulator for malware 0.7.12
@sha0coder

USAGE:
    mwemu [FLAGS] [OPTIONS]

FLAGS:
    -6, --64bits             enable 64bits architecture emulation
        --banzai             skip unimplemented instructions, and keep up emulating what can be emulated
        --call               enable call tracer
        --entropy            display changes in the entropy
        --flags              trace the flags hex value in every instruction.
    -F, --fpu                trace the fpu states.
    -h, --handle             handle Ctrl+C to spawn console
        --help               Prints help information
    -l, --loops              show loop interations, it is slow.
        --multithread        enable multithread emulation
    -n, --nocolors           print without colors for redirecting to a file >out
    -p, --stack_trace        trace stack on push/pop
    -t, --test               test mode
    -m, --trace_memory       trace all the memory accesses read and write.
    -r, --trace_registers    print the register values in every step.
        --version            Prints version information
    -v, --verbose            -vv for view the assembly, -v only messages, without verbose only see the api calls and
                             goes faster

OPTIONS:
    -A, --args                              provide arguments to the EXE like: --args '"aa" "bb"'
        --cmd                            launch a console command
    -b, --base <ADDRESS>                          set base address for code
    -c, --console                         select in which moment will spawn the console to inspect.
    -C, --console_addr <ADDRESS>                  spawn console on first eip = address
    -d, --dump                              load from dump.
    -a, --entry <ADDRESS>                         entry point of the shellcode, by default starts from the beginning.
    -e, --exit                          exit position of the shellcode
    -f, --filename                          set the shellcode binary file.
    -i, --inspect                      monitor memory like: -i 'dword ptr [ebp + 0x24]
    -L, --log <LOG_FILENAME>                      log output to file
    -M, --maps                              select the memory maps folder
        --mxcsr                            set mxcsr register
        --r10                                set r10 register
        --r11                                set r11 register
        --r12                                set r12 register
        --r13                                set r13 register
        --r14                                set r14 register
        --r15                                set r15 register
        --r8                                  set r8 register
        --r9                                  set r9 register
        --rax                                set rax register
        --rbp                                set rbp register
        --rbx                                set rbx register
        --rcx                                set rcx register
        --rdi                                set rdi register
        --rdx                                set rdx register
        --rflags                          set rflags register
        --rip                                set rip register
        --rsi                                set rsi register
        --rsp                                set rsp register
    -x, --script <SCRIPT>                         launch an emulation script, see scripts_examples folder
        --stack_address <ADDRESS>                 set stack address
    -s, --string <ADDRESS>                        monitor string on a specific address
    -T, --trace_filename          output trace to specified file
    -R, --trace_register     trace a specific register in every step, value and content
    -S, --trace_start                start trace at specified position
    -V, --verbose_at 
            start displaying assembly at specific position (is like -vv enabled in specific moment)
              

Full Emulation

Main options are:

Shell

❯❯❯ cargo run --release -- -6 -f test/elf64lin_cpu_arithmetics1.bin -vv  
              

Capturing a moment

MWEMU displays always the number of emulated instructions, and this is a unique identificator of a moment.

The moment 1 is the first assembly instruction, if you add the flag -c 1 the emulator will stop before emulating instruction 1

Shell

~/s/mwemu ❯❯❯ cargo run --release -- -6 -f test/elf64lin_cpu_arithmetics1.bin -vv -c 1
    Finished `release` profile [optimized] target(s) in 0.07s
     Running `target/release/mwemu -6 -f test/elf64lin_cpu_arithmetics1.bin -vv -c 1`
static elf64 detected.
Entry point pointing to .text 0x401000
-------
1 0x401000: inc   al
--- console ---
=>
              

The instruction 1 will not be colored, this means that that instruction is going to be emulated in next step.

This spawns the console in that emulator state, and you can press enters to emulate steps or "h" command for viewing options.

If verbose mode is not set, only WinAPI, syscalls etc will be displayed, but there will be also a number that represent the emulated instructions until that state.

There is the option of stopping the emulator at specific address with uppercase "-C addr" but note that the address can be triggered multiple times and is not an unique identificator like the -c moment

Verbosity

There are 4 verbosity levels:

Use -V or --verbose_at for enabling verbose mode at specific point.

The verbose mode is automatically enabled 100 instructions previous to the configured -c moment to stop.

Logging

It's possible to redirect the output to a file, ie:

Shell

❯❯❯ cargo run --release -- -6 -f test/elf64lin_cpu_arithmetics1.bin -vv -c 1 > /tmp/log
              

But note that colors are terminal escape bytes and will be logged making parsing more difficult, if you do cat /tmp/log you will see the colors but if you use an editor you will see those bytes

It is more convinient using --log option for a clean logs.

Shell

❯❯❯ cargo run --release -- -6 -f test/elf64lin_cpu_arithmetics1.bin -vv -c 1 --log /tmp/log
              

Initialize registers

There are some cases like emulating dlls or chunk of code that needs some initial values in the registers.

The commandline tool allows to set registers using those options:

Shell

        --mxcsr <MXCSR>                           set mxcsr register
        --r10 <R10>                               set r10 register
        --r11 <R11>                               set r11 register
        --r12 <R12>                               set r12 register
        --r13 <R13>                               set r13 register
        --r14 <R14>                               set r14 register
        --r15 <R15>                               set r15 register
        --r8 <R8>                                 set r8 register
        --r9 <R9>                                 set r9 register
        --rax <RAX>                               set rax register
        --rbp <RBP>                               set rbp register
        --rbx <RBX>                               set rbx register
        --rcx <RCX>                               set rcx register
        --rdi <RDI>                               set rdi register
        --rdx <RDX>                               set rdx register
        --rflags <RFLAGS>                         set rflags register
        --rip <RIP>                               set rip register
        --rsi <RSI>                               set rsi register
        --rsp <RSP>                               set rsp register
              

But note that colors are terminal escape bytes and will be logged making parsing more difficult, if you do cat /tmp/log you will see the colors but if you use an editor you will see those bytes

It is more convinient using --log option for a clean logs.

Shell

❯❯❯ cargo run --release -- -6 -f test/elf64lin_cpu_arithmetics1.bin -c 1 --rax 0x123 --rbx 0x1337
Finished `release` profile [optimized] target(s) in 0.08s
Running `target/release/mwemu -6 -f test/elf64lin_cpu_arithmetics1.bin -c 1 --rax 0x123 --rbx 0x1337`
static elf64 detected.
Entry point pointing to .text 0x401000
--- console ---
=>r rax
rax: 0x123 291
=>r rbx
rbx: 0x1337 4919
              

Trace Registers

Use option -R <registers to trace> to trace some registers

Shell

❯❯❯ cargo run --release -- -6 -f test/elf64lin_cpu_arithmetics1.bin -vv -R rax,rsp
...
57 0x4010a2: rcr   al,1
58 rax: 0x82f61001 2197164033
58 rsp: 0x7fffffffe270
58 0x4010a4: rcr   ax,1
59 rax: 0x82f60800 2197161984
59 rsp: 0x7fffffffe270
59 0x4010a7: rcr   eax,1
60 rax: 0xc17b0400 3246064640
60 rsp: 0x7fffffffe270
60 0x4010a9: rcr   rax,1
61 rax: 0x60bd8200 1623032320
61 rsp: 0x7fffffffe270
61 0x4010ac: rcr   al,cl
62 rax: 0x60bd8200 1623032320
62 rsp: 0x7fffffffe270
62 0x4010ae: rcr   ax,cl
63 rax: 0x60bd8200 1623032320
63 rsp: 0x7fffffffe270
63 0x4010b1: rcr   eax,cl
64 rax: 0x60bd8200 1623032320
64 rsp: 0x7fffffffe270
64 0x4010b3: rcr   rax,cl
65 rax: 0x60bd8200 1623032320
65 rsp: 0x7fffffffe270
65 0x4010b6: mov   eax,90909090h ; 0x90909090
66 rax: 0x90909090 2425393296
66 rsp: 0x7fffffffe270
66 0x4010bb: mov   edx,90909090h ; 0x90909090
67 rax: 0x90909090 2425393296
67 rsp: 0x7fffffffe270
67 0x4010c0: mov   ecx,0 ; 0x0
68 rax: 0x90909090 2425393296
68 rsp: 0x7fffffffe270
68 0x4010c5: shrd  eax,edx,cl
69 rax: 0x90909090 2425393296
69 rsp: 0x7fffffffe270
69 0x4010c8: ret ; ret-addr: 0x0 ret-value: 0x90909090
              

You can trace one or multiple registers at same time with -R option but with no spaces between registers.

Trace Memory

There are 2 ways of tracing memory:

Shell

❯❯❯ cargo run --release -- -6 -f test/sc32win_donut.bin -vv -m -c 494253 -S 490000
              

Shell

❯❯❯ cargo run --release -- -6 -f test/sc32win_donut.bin -vv -m -i 'qword ptr [rsp + 0x8]'
              

Trace String

having the address of the string to trace, use -s <addr> to trace it

Shell

❯❯❯ cargo run --release -- -6 -f test/sc64lin_strgen.bin -vv  -s 0x329ec8
              

Doing full verbose of millions of instructions is slower than non verbose mode, so let's enable the verbose mode only when it's needed.

Shell

❯❯❯ cargo run --release -- -6 -f test/sc64lin_strgen.bin -s 0x329ec8 -V 191
              

Other option is using tracers with no verbose mode.

Shell

❯❯❯ cargo run --release -- -6 -f test/sc64lin_strgen.bin -s 0x329ec8
              

Trace Function Calls

Following the call paths can be useful when combining emulation with static analysis to see where we are coming from.

Shell

❯❯❯ cargo run --release -- -6 -f test/exe64win_enigma.bin --call
              

In this case, it is more convinient not to use verbose mode.

Interactive Console

With the option -c <num> mwemu stops the emulation when will reached that num of emulated instructions, then spawn a console.

For instance, we don't want to emulate 102063765 instructions in verbose mode, It's faster not using verbose mode. The -c option will enable verbose mode and tracers 100 instructions before reaching that number, so when console is spawned we have some previous context.

Shell

❯❯❯ cargo run --release -- -6 -f test/exe64win_enigma.bin -c 102063765
dll_path: maps/maps64//ntdll.dll dll: ntdll.dll
PE64 header detected.
loading memory maps
dll_path: maps/maps64//kernel32.dll dll: kernel32.dll
dll_path: maps/maps64//kernelbase.dll dll: kernelbase.dll
dll_path: maps/maps64//iphlpapi.dll dll: iphlpapi.dll
dll_path: maps/maps64//ws2_32.dll dll: ws2_32.dll
dll_path: maps/maps64//advapi32.dll dll: advapi32.dll
dll_path: maps/maps64//comctl32.dll dll: comctl32.dll
dll_path: maps/maps64//winhttp.dll dll: winhttp.dll
dll_path: maps/maps64//wininet.dll dll: wininet.dll
dll_path: maps/maps64//dnsapi.dll dll: dnsapi.dll
dll_path: maps/maps64//shell32.dll dll: shell32.dll
dll_path: maps/maps64//shlwapi.dll dll: shlwapi.dll
dll_path: maps/maps64//kernel32.dll dll: kernel32.dll
dll_path: maps/maps64//user32.dll dll: user32.dll
dll_path: maps/maps64//advapi32.dll dll: advapi32.dll
dll_path: maps/maps64//oleaut32.dll dll: oleaut32.dll
dll_path: maps/maps64//gdi32.dll dll: gdi32.dll
dll_path: maps/maps64//shell32.dll dll: shell32.dll
dll_path: maps/maps64//version.dll dll: version.dll
dll_path: maps/maps64//ole32.dll dll: ole32.dll
IAT Bound.
Delay load binding started ...
delay load bound!
entry point at 0x14072ce04
base: 0x140000000
102063740 0x14072d2ac: jne   000000014072D2A4h taken
102063741 0x14072d2a4: xor   [rax],dl
mem_trace: pos = 102063741 rip = 14072d2a4 op = read bits = 8 address = 0x14072d4cb value = 0x59 name = 'exe64win_enigma593000'
mem_trace: pos = 102063741 rip = 14072d2a4 op = write bits = 8 address = 0x14072d4cb value = 0x54 name = 'exe64win_enigma593000'
102063742 0x14072d2a6: inc   rax
102063743 0x14072d2a9: dec   rcx
102063744 0x14072d2ac: jne   000000014072D2A4h taken
102063745 0x14072d2a4: xor   [rax],dl
mem_trace: pos = 102063745 rip = 14072d2a4 op = read bits = 8 address = 0x14072d4cc value = 0x44 name = 'exe64win_enigma593000'
mem_trace: pos = 102063745 rip = 14072d2a4 op = write bits = 8 address = 0x14072d4cc value = 0x49 name = 'exe64win_enigma593000'
102063746 0x14072d2a6: inc   rax
102063747 0x14072d2a9: dec   rcx
102063748 0x14072d2ac: jne   000000014072D2A4h taken
102063749 0x14072d2a4: xor   [rax],dl
mem_trace: pos = 102063749 rip = 14072d2a4 op = read bits = 8 address = 0x14072d4cd value = 0x42 name = 'exe64win_enigma593000'
mem_trace: pos = 102063749 rip = 14072d2a4 op = write bits = 8 address = 0x14072d4cd value = 0x4f name = 'exe64win_enigma593000'
102063750 0x14072d2a6: inc   rax
102063751 0x14072d2a9: dec   rcx
102063752 0x14072d2ac: jne   000000014072D2A4h taken
102063753 0x14072d2a4: xor   [rax],dl
mem_trace: pos = 102063753 rip = 14072d2a4 op = read bits = 8 address = 0x14072d4ce value = 0x43 name = 'exe64win_enigma593000'
mem_trace: pos = 102063753 rip = 14072d2a4 op = write bits = 8 address = 0x14072d4ce value = 0x4e name = 'exe64win_enigma593000'
102063754 0x14072d2a6: inc   rax
102063755 0x14072d2a9: dec   rcx
102063756 0x14072d2ac: jne   000000014072D2A4h not taken
102063757 0x14072d2b2: jmp   000000014072D2BBh
mem_trace: pos = 102063758 rip = 14072d2bb op = read bits = 32 address = 0x14000303c value = 0x80 name = 'exe64win_enigma3000'
102063758 0x14072d2bb: mov   edi,[rsi+3Ch] ; 0x80
mem_trace: pos = 102063759 rip = 14072d2be op = read bits = 32 address = 0x140003110 value = 0x571000 name = 'exe64win_enigma3000'
102063759 0x14072d2be: mov   edi,[rdi+rsi+90h] ; 0x571000
102063760 0x14072d2c5: add   rdi,rsi
102063761 0x14072d2c8: cmp   dword ptr [rdi+0Ch],0
mem_trace: pos = 102063761 rip = 14072d2c8 op = read bits = 32 address = 0x14057400c value = 0x5722ac name = 'exe64win_enigma3000'
cmp: 0x5722ac > 0x0
102063762 0x14072d2cc: je    000000014072D370h not taken
mem_trace: pos = 102063763 rip = 14072d2d2 op = read bits = 32 address = 0x14057400c value = 0x5722ac name = 'exe64win_enigma3000'
102063763 0x14072d2d2: mov   ecx,[rdi+0Ch] ; 0x5722ac
102063764 0x14072d2d5: add   rcx,rsi
-------
102063765 0x14072d2d8: call  qword ptr [rbp+5960B4h]
--- console ---
=>
              

Help Command

press h to see the available commands:

mwemu console

=>h
--- help ---
q ...................... quit
cls .................... clear screen
h ...................... help
s ...................... stack
v ...................... vars
sv ..................... set verbose level 0, 1 or 2
r ...................... register show all
r reg .................. show reg
rc ..................... register change
f ...................... show all flags
fc ..................... clear all flags
fz ..................... toggle flag zero
fs ..................... toggle flag sign
c ...................... continue
b ...................... breakpoint list
ba ..................... breakpoint on address
bi ..................... breakpoint on instruction number
bmr .................... breakpoint on read memory
bmw .................... breakpoint on write memory
bmx .................... breakpoint on execute memory
bcmp ................... break on next cmp or test
bc ..................... clear breakpoint
n ...................... next instruction
eip .................... change eip
rip .................... change rip
push ................... push dword to the stack
pop .................... pop dword from stack
fpu .................... fpu view
md5 .................... check the md5 of a memory map
seh .................... view SEH
veh .................... view vectored execption pointer
m ...................... memory maps
ms ..................... memory filtered by keyword string
ma ..................... memory allocs
mc ..................... memory create map
mn ..................... memory name of an address
ml ..................... memory load file content to map
mr ..................... memory read, speficy ie: dword ptr [esi]
mw ..................... memory write, speficy ie: dword ptr [esi]  and then: 1af
mwb .................... memory write bytes, input spaced bytes
md ..................... memory dump
mrd .................... memory read dwords
mrq .................... memory read qwords
mds .................... memory dump string
mdw .................... memory dump wide string
mdd .................... memory dump to disk
mdda ................... memory dump all allocations to disk
mt ..................... memory test
r2 [addr] .............. spawn radare2 console if it's isntalled
ss ..................... search string
sb ..................... search bytes
sba .................... search bytes in all the maps
ssa .................... search string in all the maps
ll ..................... linked list walk
d ...................... dissasemble
dt ..................... dump structure
pos .................... print current position
enter .................. step into
tr ..................... trace reg
trc .................... trace regs clear
ldr .................... show ldr linked list
iat .................... find api name in all iat's
iatx ................... addr to api name
iatd ................... dump the iat of specific module
dump ................... dump current state to disk
---
=>
              

Register Commands

Use r for viewing all the registers, or r [reg] to see specific register.

mwemu console

=>r rsp
    rsp: 0x329f40
=>r
    rax: 0x0 0
    rbx: 0x0 0
    rcx: 0x0 0
    rdx: 0x2f 47
    rsi: 0x0 0
    rdi: 0x0 0
    rsp: 0x329f40
    rbp: 0x329ff8
    rip: 0x3c006e
    r8 : 0x0 0
    r9 : 0x0 0
    r10: 0x0 0
    r11: 0x0 0
    r12: 0x0 0
    r13: 0x0 0
    r14: 0x0 0
    r15: 0x0 0
              

mwemu console

=>rc rax
command not found, type h
=>rc
register name=>rax
value=>0x1337
=>r rax
    rax: 0x1337 4919
=>
                

you can use 64bits and 32bits registers only, 16/8bits is not allowed for now ("r ax" or "r al")

Maps Command

press m to list all the memory maps and addresses.

mwemu console

=>m
--- maps ---
oleaut32.data       0x7ff001dc7000 - 0x7ff001dca000 (12288)
ldr                 0x7ff000000000 - 0x7ff0000000b4 (180)
ntdll.pe            0x7ff000003000 - 0x7ff000004000 (4096)
kernelbase.text     0x7ff0002b6000 - 0x7ff0003c8000 (1122304)
ws2_32.didat        0x7ff00060f000 - 0x7ff000610000 (4096)
advapi32.pe         0x7ff000623000 - 0x7ff000624000 (4096)
winhttp.text        0x7ff000960000 - 0x7ff000a0f000 (716800)
iphlpapi.text       0x7ff00057f000 - 0x7ff0005a8000 (167936)
version.rsrc        0x7ff001e0c000 - 0x7ff001e0d000 (4096)
dnsapi.rdata        0x7ff000d6d000 - 0x7ff000d8f000 (139264)
kernelbase.rsrc     0x7ff000555000 - 0x7ff000556000 (4096)
comctl32.dll.ldr    0x7fe00000a000 - 0x7fe00000a940 (2368)
shell32.rsrc        0x7ff0012d8000 - 0x7ff001b1d000 (8671232)
advapi32.reloc      0x7ff0006cb000 - 0x7ff0006cd000 (8192)
advapi32.rdata      0x7ff00068a000 - 0x7ff0006bf000 (217088)
wininet.rsrc        0x7ff000cbe000 - 0x7ff000cd7000 (102400)
wininet.didat       0x7ff000cbd000 - 0x7ff000cbe000 (4096)
dnsapi.didat        0x7ff000d99000 - 0x7ff000d9a000 (4096)
ole32.pdata         0x7ff001f38000 - 0x7ff001f47000 (61440)
exe64win_enigma3000 0x140003000 - 0x140593000 (5832704)
user32.rsrc         0x7ff001c30000 - 0x7ff001d12000 (925696)
comctl32.pdata      0x7ff0008f9000 - 0x7ff00090f000 (90112)
oleaut32.text       0x7ff001d14000 - 0x7ff001da1000 (577536)
kernelbase.data     0x7ff000540000 - 0x7ff000545000 (20480)
winhttp.pdata       0x7ff000a3c000 - 0x7ff000a46000 (40960)
ws2_32.rdata        0x7ff0005fd000 - 0x7ff00060a000 (53248)
...
              

We can see map name, start address, end address and size in bytes.

Other memory related commands:

mwemu console

m ...................... memory maps
ms ..................... memory filtered by keyword string
ma ..................... memory allocs
mc ..................... memory create map
mn ..................... memory name of an address
ml ..................... memory load file content to map
mr ..................... memory read, speficy ie: dword ptr [esi]
mw ..................... memory write, speficy ie: dword ptr [esi]  and then: 1af
mwb .................... memory write bytes, input spaced bytes
md ..................... memory dump
mrd .................... memory read dwords
mrq .................... memory read qwords
mds .................... memory dump string
mdw .................... memory dump wide string
mdd .................... memory dump to disk
mdda ................... memory dump all allocations to disk
mt ..................... memory test
              

Get Map details from Address

if the code is using an address and you want more details, use command mn.

mwemu console

=>mn
address=>0x140000008
map: exe64win_enigma.pe 0x140000000-0x140001000 (4096)
=>
              

Note that mwemu commands dont accept parameters direcly, first type the command + enter then the parameter will prompted.

except for the r2 command that needs an address to spawn radare2, ie: r2 0x140000008

Search Commands

There are four commands to search.

mwemu console

=>ssa  (search string in all the maps)
string=>http://something.com/
found at 0x329ec8 'http://something.com/'
found at 0x329fc8 'http://something.com/'
map not found

=>mds (display string on an address)
address=>0x329ec8
0x329ec8: 'http://something.com/'
=>mds
address=>0x329fc8
0x329fc8: 'http://something.com/'
=> 

=>mn (which map is that address?)
address=>0x329ec8
map: stack 0x22a000-0x32c000 (1056768)
=>mn
address=>0x329fc8
map: stack 0x22a000-0x32c000 (1056768)
=>

=>ss (search string on specific address)
map name=>stack
string=>http://something.com/
found 0x329ec8 'http://something.com/'
found 0x329fc8 'http://something.com/'

=>sb (search spaced bytes on specific address, ie searching hexlified "http://")
map name=>stack
spaced bytes=>68 74 74 70 3a 2f 2f
found at 0x329ec8
found at 0x329fc8
=>
              

Breakpoint Commands

There are four types of breakpoints, but only one breakpoint can be set for each type at once.

Use command "b" to see the state of the 4 types of breakpoints.There are four commands to search.

mwemu console

=>b
break on address: []
break on instruction: []
break on memory read: []
break on memory write: []
              

Use these commands to set the breakpoints:

mwemu console

b ...................... breakpoint list
ba ..................... breakpoint on address
bi ..................... breakpoint on instruction number
bmr .................... breakpoint on read memory
bmw .................... breakpoint on write memory
bmx .................... breakpoint on execute memory
bcmp ................... break on next cmp or test instruction
bc ..................... clear breakpoint
              

Some examples:

mwemu console

--- console ---
=>bi
instruction number=>100
=>c
18 0x3c006a: jne   short 00000000003C004Ah taken
19 0x3c004a: mov   rax,[rbp-8] ; 0x329f10
20 0x3c004e: movzx edx,byte ptr [rax]
21 0x3c0051: mov   rax,[rbp-10h] ; 0x329e10
22 0x3c0055: mov   [rax],dl ; 0x68
23 0x3c0057: add   qword ptr [rbp-8],1
24 0x3c005c: add   qword ptr [rbp-10h],1
25 0x3c0061: mov   rax,[rbp-8] ; 0x329f11
...
96 0x3c0065: movzx eax,byte ptr [rax]
97 0x3c0068: test  al,al
98 0x3c006a: jne   short 00000000003C004Ah taken
99 0x3c004a: mov   rax,[rbp-8] ; 0x329f18
-------
100 0x3c004e: movzx edx,byte ptr [rax]   (this instruction is the next to be emulated, it was not emulated yet)
--- console ---
=>
              

Change Verbosity

If you type the "sv" command, mwemu will ask you for the new verbosity level number, these are the possible verbose levels:

For instance, we want to emulate quiclky the first 200 insturctions, and then enable the verbosity, this could be dont with -V, but let's do it from the console with "sv" command:

mwemu console

~/s/mwemu ❯❯❯ cargo run --release -- -6 -f test/sc64win_strgen.bin -c 200
...
200 0x3c004e: movzx edx,byte ptr [rax]    (instruction 200 is not emulated yet, it's the next instruction to be emulated)
--- console ---
=>sv
verbose level=>3
=> [enter for emulating instruction 200]
=> c  (continue emulating now with maximum verbosity) 
...
              

View LDR

The LDR is a triple circular liked list that cointains all the linked modules (not only DLL, also EXE)

MWEMU provides several commands to view and query the LDR.

mwemu console

--- console ---
=>ldr
0x7fe000000000 loader.exe flink:7fe000004000 blink:7fe00000f000 base:7ff001b7e000 pe_hdr:f8 5045
0x7fe000004000 ntdll.dll flink:7fe000005000 blink:7fe000000000 base:7ff000003000 pe_hdr:e8 5045
0x7fe000005000 kernel32.dll flink:7fe000006000 blink:7fe000004000 base:7ff0001f8000 pe_hdr:f0 5045
0x7fe000006000 kernelbase.dll flink:7fe000007000 blink:7fe000005000 base:7ff0002b5000 pe_hdr:f0 5045
0x7fe000007000 iphlpapi.dll flink:7fe000008000 blink:7fe000006000 base:7ff00057e000 pe_hdr:f8 5045
0x7fe000008000 ws2_32.dll flink:7fe000009000 blink:7fe000007000 base:7ff0005b8000 pe_hdr:f0 5045
0x7fe000009000 advapi32.dll flink:7fe00000a000 blink:7fe000008000 base:7ff000623000 pe_hdr:100 5045
0x7fe00000a000 comctl32.dll flink:7fe00000b000 blink:7fe000009000 base:7ff0006cd000 pe_hdr:f0 5045
0x7fe00000b000 winhttp.dll flink:7fe00000c000 blink:7fe00000a000 base:7ff00095f000 pe_hdr:f8 5045
0x7fe00000c000 wininet.dll flink:7fe00000d000 blink:7fe00000b000 base:7ff000a4f000 pe_hdr:f0 5045
0x7fe00000d000 dnsapi.dll flink:7fe00000e000 blink:7fe00000c000 base:7ff000cd9000 pe_hdr:f8 5045
0x7fe00000e000 shell32.dll flink:7fe00000f000 blink:7fe00000d000 base:7ff000da4000 pe_hdr:f0 5045
0x7fe00000f000 shlwapi.dll flink:7fe000000000 blink:7fe00000e000 base:7ff001b2c000 pe_hdr:f0 5045
              

View Structures

The windows debugger windbg has a unique feature that is dt command to see information about structures, is quite useful and uniq.

MWEMU implements a similar dt command but for specific structures, that could be useful in some situations.

Let's use dt to inspect PEB structure.

mwemu console

=>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,
...
              

Let's use dt to inspect PEB_LDR_DATA structure.

mwemu console

=>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,
    },
}
=>
              

Let's use dt to inspect LDR_DATA_TABLE_ENTRY structure, that represents one LDR entry in the linked list of a specific linked module.

mwemu console

=>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,
}
=>
              

Example: a malware is hiding something in an exception.

mwemu console

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]
--- console ---
=>
              

Let's inspect exception structures:

mwemu console

--- console ---
=>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,
}
=>
              

And here we have the error routine 0x4f96f4 and the filter 0x51068c.

View Data

There are multiple commands to view data, but currently I'm using the "r2 addr" command which is better bot for code and data. Note that r2 command executes the radare2 and transfer the memory map of the selected address, and sync radare2 with mwemu, but this command needs to have installed radare2 in the path, for instance from the git. More details on radare2 chapter. It worth to install radare2.

Commands for displaying information:

mwemu console

mr ..................... memory read, speficy ie: dword ptr [esi]
mw ..................... memory write, speficy ie: dword ptr [esi]  and then: 1af
mwb .................... memory write bytes, input spaced bytes
md ..................... memory dump
mrd .................... memory read dwords
mrq .................... memory read qwords
mds .................... memory dump string
mdw .................... memory dump wide string
mdd .................... memory dump to disk
mdda ................... memory dump all allocations to disk
mt ..................... memory test
              

Example with md command:

mwemu console

--- console ---
=>md
address=>0x329ec8
0x329ec8: 68 74 74 70 3a 2f 2f 73 6f 6d 65 74 68 69 6e 67     http://something
0x329ed8: 2e 63 6f 6d 2f 00 00 00 00 00 00 00 00 00 00 00     .com/...........
0x329ee8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
0x329ef8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
0x329f08: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
0x329f18: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
0x329f28: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
0x329f38: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
=>
              

Radare2 Command

This command gives a lot of power to MWEMU because we can use radare2 from inside of a MWEMU emualtion moment.

Note that this executes another program (also free software) named radare2 that has to be installed and accesible from the path.

The instalation is simple I will explan it later.

Spawning radare over a data address:

Using q to return form r2 prompt to mwemu prompt and spawn radare again with a code address:

Radare are recognizing functions and decompiling them with the multiple decompilers, even we can use decai for AI based decompiled in python etc.

More info specific of radare commands check the r2book: https://book.rada.re/

Regarding the radare2 installation:

mwemu console

❯❯❯ git clone https://github.com/radareorg/radare2.git
❯❯❯ cd radare2
❯❯❯ sys/install.sh
              

The install.sh script does all the installation, It will prompt sudo to copy binaries to folders that are on the path.

If radare2 is in the path, It would be possible to trigger it from mwemu r2 command.

MWEMU from python scripts

This is probably the most practical use-case of MWEMU, using the pymwemu python module.

pymwemu Instalation

They easiest way to install this is using pip, the package is published at pypi https://pypi.org/project/pymwemu/

For using latest version use git, but it's more tricky to install, because you need rust, cargo and maturin.

It's precompiled for linux 64bits, so in linux in theory you don't need to install rust first.

In linux you just do:

mwemu console

❯❯❯ pip3 install pymwemu --break-system-packages
              

In mac, windows or if the pip requires it, install first rust.

Install rust from rustup, make sure cargo is on the path, and do pip or pip3:

mwemu console

❯❯❯ pip install --upgrade pip
❯❯❯ pip3 install --upgrade pip
❯❯❯ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
❯❯❯ pip install pymwemu
❯❯❯ pip3 install pymwemu
              

In mac, if there is a problem with !tapi-tbd the solution is:

Shell

❯❯❯ sudo xcode-select --switch /Library/Developer/CommandLineTools
              

If there is the error: Caused by: feature `edition2024` is required. then update your rust: rustup update

To verify the installation we can import the module in python console.

Shell

❯❯❯ python3
Python 3.13.5 (main, Jun 25 2025, 18:55:22) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pymwemu
Initialized logging
>>> emu = pymwemu.init64()
>>> emu.version()
            '0.10.5'

              

build pymwemu (only for developers)

shell

sudo apt install python3.13-venv
python3 -m venv .venv
source .venv/bin/activate.fish
pip install -U pip maturin
maturin develop --release
python
import pymwemu
Initialized logging  # loaded ok
^D
MATURIN_PYPI_TOKEN="..." maturin publish
        

remember to update version both in Crates.toml and pyproject.toml

Create the emulation object

First of all, import the module and init the 32bits or the 64bits engine.

Python Console

>>> import pymwemu
Initialized logging
>>> emu = pymwemu.init32()
            or
>>> emu = pymwemu.init64()
              

Configure the Emulator

Then there are some initial configurations you can do.

Python Console

>>> emu.set_verbose(3)     # to show all the assembly, normally this is not needed. By default is 0 verbose.
>>> emu.enable_console()   # by default is disabled.
>>> emu.spawn_console_at_pos(6) # useful for debugging the script.
loading memory maps
>>>
>>>
              

All the configurations available:

Loading Maps

In most of cases you will need to load all the Windows OS simulation part, to have all the PEB+TEB+LDR linkedlist and WinAPI infra.

For doing this you can use emu.load_maps(folder:str)

If you are going to emulate pure assembly, with no api calls, and no access to windows structures, you don't need to load the maps.

So does if you are emulating linux elf64 or shellcodes (remember, elf64 is not well suported for now)

Example:

Python Console

>>> emu.load_maps('/home/sha0/src/mwemu/maps/maps64/')
loading memory maps
dll_path: /home/sha0/src/mwemu/maps/maps64//ntdll.dll dll: ntdll.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//kernel32.dll dll: kernel32.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//kernelbase.dll dll: kernelbase.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//iphlpapi.dll dll: iphlpapi.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//ws2_32.dll dll: ws2_32.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//advapi32.dll dll: advapi32.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//comctl32.dll dll: comctl32.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//winhttp.dll dll: winhttp.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//wininet.dll dll: wininet.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//dnsapi.dll dll: dnsapi.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//shell32.dll dll: shell32.dll
dll_path: /home/sha0/src/mwemu/maps/maps64//shlwapi.dll dll: shlwapi.dll
>>>
              

Note that this need to have the maps, so git clone the repo:

Shell

❯❯❯ git clone https://github.com/sha0coder/mwemu.git 
                

loading ELF, PE, or shellcodes

Example:

Python Console

>>> emu.load_binary('shellcodes32/shikata.bin')
>>>
              

It detects if it's ELF, PE and otherwise is a shellcode.

In the case of PE or shellcode the win32 simulation is initialized automatically.

In the case of ELF the linux64 simulation is initialized automatically.

But if for any reason you don't use load_binary() and you need the windows simulator with peb/ldr/dlls loaded on the memory, you need to call init_win32 (even for 64bits, it will detect the arch)

And if you dont use load_binary() but you need the linux simulator with libc etc loaded, use init_linux64. Note that linux implementation is very basic and only for static 64bits

Note that calling init_win32() requires previously to have done load_maps to set the maps folder.

Python Console

>>> emu.init_win32()
            or
>>> emu.init_linux64(is_dynamic)
              

Creating Buffers

You can load sections from disk to the emulator's virtual memory, and also can allocate buffers.

Examples:

Python Console

>>> addr = emu.alloc("mybuffer", 1024)
>>> emu.alloc_at("mybuffer", 0x1000, 1024)
>>> emu.load_map("mybuffer", "something.dll", 0x1000)
        

Registers

You can view the register values and modify it in any moment.

For instance to prepare the context previous to the emulation, preparing an emulation state.

Examples:

Python Console

>>> rax = emu.get_reg('rax')
>>> emu.set_reg('rax', rax+1)
        

Memory operations

It could be useful for pinpoint the emulation state before starting the emulation, but other option is checking the memory after the emulation or altering it during the emulation.

Note that this read/write are inside the emulation virtual memory.

Examples of preparing context previous to start emulation:

Python Console

>>> some_data_structure_needed = open('blob.bin','rw').read()
>>> addr = emu.alloc("struct1", len(some_data_structure_needed))
>>> emu.write_bytes(addr, some_data_structure_needed)
>>> emu.set_reg('rdi', addr)
>>> rax = emu.call32(0x40323, [addr, 0])
        

Another example:

Python Script

import pymwemu

emu = pymwemu.init64()
buffer_addr = emu.alloc("mybuffer", 1024)

# Write a string
emu.write_string(buffer_addr, "Hello MWEMU!")

# Read it back
text = emu.read_string(buffer_addr, 12)
print(f"Read: {text}")

# Write dword
emu.write_dword(buffer_addr + 0x100, 0x12345678)
value = emu.read_dword(buffer_addr + 0x100)
print(f"DWORD: {hex(value)}")

# Write raw bytes
data = b"\x90\x90\x90\xc3"  # nop nop nop ret
emu.write_buffer(buffer_addr + 0x200, data)
              

Start Emulation

Once everything is configured, you can start the emulation.

  • run(end_addr) -> int provide the address to end the emulation or None to emulate as further as possible.

  • run_to(position: int) -> int emulate a specific number of instructions.

  • call32(address: int, params: list[int]) -> int call a funcion using microsoft 32bits calling convention.

  • call64(address: int, params: list[int]) -> int call a functon using microsoft 64bits calling convention.

  • linux_call64(address: int, params: list[int]) -> int call a function using linux 64bits calling convention.

  • stop() stop emulation, i never used this call.

  • run_until_return() -> int

  • run_until_apicall() -> (int, str)

  • step() -> bool you can use a while step(): loop and control the situation step by step. This is slow.

  • handle_winapi(addr: int)

Python Script - basic_run.py

import pymwemu

emu = pymwemu.init64()
emu.load_binary('shellcode.bin')

rip = emu.run(None)

print("Emulation finished")
print(f"Final RAX: {hex(emu.get_rax())}")
print(f"Instructions executed: {emu.get_pos()}")
              

Calling Functions Directly

You can call functions at specific addresses with call32/call64:

Python Script - call_function.py

import pymwemu

# Example: call a decryption function
emu = pymwemu.init64()
emu.load_maps('/path/to/maps/maps64/')
emu.load_binary('malware.exe')

# Allocate buffer for output
output_buffer = emu.alloc("output", 1024)

# Call function at 0x401234 with arguments
# Function signature: decrypt(char* input, char* output, int size)
encrypted_str = 0x404000  # address of encrypted data
result = emu.call64(0x401234, [encrypted_str, output_buffer, 100])

# Read decrypted result
decrypted = emu.read_string(output_buffer, 100)
print(f"Decrypted: {decrypted}")
print(f"Return value: {hex(result)}")
              

Python Script - call32_example.py

import pymwemu

# Example with 32-bit malware
emu = pymwemu.init32()
emu.load_binary('malware32.exe')

# Call a string decryption routine
# Function: char* decrypt_string(int index)
decrypt_func = 0x00401200

for i in range(10):
    # Call function with index
    result_addr = emu.call32(decrypt_func, [i])
    
    # Read decrypted string
    if result_addr != 0:
        decrypted = emu.read_string(result_addr, 256)
        print(f"String {i}: {decrypted}")
              

Step-by-Step Emulation

For debugging or detailed control, execute instruction by instruction:

Python Script - step_by_step.py

import pymwemu

emu = pymwemu.init64()
emu.set_verbose(2)  # show assembly
emu.load_binary('shellcode.bin')

# Execute 10 instructions one by one
for i in range(10):
    emu.step()
    rip = emu.get_rip()
    rax = emu.get_rax()
    print(f"Step {i+1}: RIP={hex(rip)} RAX={hex(rax)}")

print(f"Total instructions: {emu.get_pos()}")
              

Additional Useful Methods

Other helpful methods for emulation control:

  • emu.get_pos() -> int get current instruction position

  • emu.set_verbose(level:int) set verbosity (0=quiet, 1=API, 2=asm, 3=all)

  • emu.spawn_console() spawn interactive console

  • emu.print_maps() print the full list of maps.

  • emu.print_maps_by_keyword(kw: str) print the maps that match with that keyword.

  • emu.search_bytes(pattern:bytes) -> list search for byte pattern in memory.

Python Console

>>> # Run and then spawn console for inspection
>>> emu.run(1000)
>>> emu.spawn_console()  # interactive debugging
>>> 
>>> 
>>> # Search for strings
>>> addresses = emu.search_bytes(b"http://")
>>> for addr in addresses:
...     print(f"Found at: {hex(addr)}")
>>>
              

Practical Example: String Decryption

A typical use case is decrypting malware strings:

Python Script - string_decrypt.py

#!/usr/bin/env python3
import pymwemu

# Initialize
emu = pymwemu.init32()  # 32-bit malware
emu.load_binary('malware32.exe')

# Find encrypted strings in .data section
encrypted_strings = [
    0x00401000,
    0x00401100,
    0x00401200,
]

# Decrypt function address (from IDA/Ghidra)
decrypt_func = 0x00402340

decrypted = []

for enc_addr in encrypted_strings:
    # Set parameters
    emu.set_eax(enc_addr)  # pointer to encrypted string
    emu.set_eip(decrypt_func)  # start at decrypt function
    
    # Run until return
    emu.run(10000)
    
    # Read decrypted result (assume in EAX)
    result_addr = emu.get_eax()
    decrypted_str = emu.read_string(result_addr, 256)
    
    print(f"Encrypted {hex(enc_addr)} -> {decrypted_str}")
    decrypted.append(decrypted_str)

# Save results
with open('decrypted_strings.txt', 'w') as f:
    for s in decrypted:
        f.write(s + '\n')
              

Stack operations

There are methods for altering the stack, note that you also could do emu.write_qword(emu.get_reg('rsp'), 123)

But with this meethods you trigger all the stack logic, also incrementing rsp/esp.

  • stack_push32(value: int) -> bool

  • stack_push64(value: int) -> bool

  • stack_pop32() -> int

  • stack_pop64() -> int

This example simulates a stack based calling convention, but note that you can also use call32() and call64() and linux_call64() methods.

Python Console

>>> emu.stack_push32(ret_addr)
>>> emu.stack_push32(param1)
>>> emu.stack_push32(param2)
>>> emu.set_reg('rip', 0x40123)
>>> emu.run(None)
        

Breakpoints

mwemu's breakpoitns are simple, and most of times there are better ways to trigger the emulation to stop.

You can bp an adderss (only one at time ...) or instruction, also memory read/write

  • bp_show()

  • bp_clear_all()

  • bp_set_addr(addr: int)

  • bp_get_addr() -> Vec

  • bp_set_inst(ins: int)

  • bp_get_inst() -> Vec

  • bp_set_mem_read(addr: int)

  • bp_get_mem_read() -> Vec

  • bp_set_mem_write(addr: int)

  • bp_get_mem_write() -> Vec

Example

Python Console

>>> emu.set_bp_addr(0x11223344)
>>> emu.run(None)
        

Memory dump

there are several ways do do a memory dump

  • save_all_allocs(path: str) only save to disk allocations done by the emulated code.

  • save(addr: int, size: int, filename: str) dump specific blob to disk.

Example

Python Console

>>> emu.print_maps()
...
>>> emu.save(0x40324234, 1024, "/tmp/blob.bin")
        

View info

There are some less used methods to fetch different type of information.

  • version() -> str

    get current pymwemu version.
  • get_prev_mnemonic() -> str get last emulated mnemonic.

  • reset_pos() reset the emulated instruction count to zero.

  • is_64bits() -> bool detect in which mode is the emulator.

  • is_32bits() -> bool detect in which mode is the emulator.

  • get_position() -> int get current pos, (amount of emulated instructions).

  • disassemble(addr: int, amount: int) -> str dissassemble bytes.

  • print_maps() print all the maps (allocs, linked dlls, etc)

  • print_maps_by_keyword(kw: str) print the maps that contain a keyword.

  • get_addr_base(addr: int) -> int get the begining of a map.

  • is_mapped(addr: int) -> bool check if an address is allocated.

  • get_addr_name(addr: int) -> str get in which map name is the address.

  • dump(addr: int) print bytes.

  • dump_n(addr: int, amount: int) print n bytes.

  • dump_qwords(addr: int, n: int) print a list of qwords.

  • dump_dwords(addr: int, n: int) print a list of dwords.

  • allocated_size() -> int show the total allocated memory.

  • memory_overlaps(addr: int, sz: int) -> bool check if a memory block overlaps with existing map.

  • show_allocs() print all the allocations.

  • mem_test() -> bool do a automatic memory test to look for map overlapps.

  • api_addr_to_name(addr: int) -> str provide an address pointing to an API and will fetch the API name.

  • api_name_to_addr(name: str) -> int get the address of an api name.

Example

Python Console

>>> name = api_addr_to_name(0x11223344)
>>> print( name )
        MessageBoxA
        

Change architecture bits.

There are methods for changing from 32bits to 64bits and vice versa.

  • set_64bits()

  • set_32bits()

  • inspect_seq(s: str)

But better don't change the architecture on the fly, re-instantiate the emulation object like:

Example

Python Console

>>> import pymwemu
>>> emu = pymwemu.init32()
>>> ... 
>>> emu = pymwemu.init64()
        

Real case examples.

Find some examples here:

https://github.com/sha0coder/mwemu/tree/main/crates/pymwemu/examples/scripts

And some jupyter nobebooks here:

https://github.com/sha0coder/mwemu/tree/main/crates/pymwemu/examples

MWEMU for Rust apps

Core and bindings are everything implemented in Rust, and all the logic is inside libmwemu, so a rust program can have even more control of the emulator than pymwemu.

So having said that, from Rust you can use all the power of MWEMU, and access to most of the objects because are public most of them, so you can have a good control of the emulator.

https://docs.rs/libmwemu/0.23.5/libmwemu/

https://crates.io/crates/libmwemu

Start a project

First of all you need the rustc compiler and the cargo tool, best way of installing this is rustup.rs

Shell

  ❯❯❯ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
                

Then you have to create a rust project.

Shell

  ❯❯❯ cargo init --bin myproject
  ❯❯❯ cd myproject
                

This creates a folder with src/main.rs and also Cargo.toml

Add de dependency to Cargo.toml manually or better automatically using cargo too.

Shell

❯❯❯ cargo add libmwemu

❯❯❯ cat Cargo.toml
[package]
name = "myproject"
version = "0.1.0"
edition = "2024"

[dependencies]
libmwemu = "0.23.5"
              

Then you can add code to src/main.rs

build project

If you need to run the project remember to use the release mode (adding --release)

In release mode the emulator is blazing fast.

shell

  ❯❯❯ cargo run --release
        

The compilation will be slower the first time because have to download the libmwemu and compile it automaticall.

What cargo run does internally is compiling like (cargo build --release) and then execute the binary.

You can pass parameter to cargo and also to your code in this way:

cargo run [cargo args] -- [you program args] example:

shell

  ❯❯❯ cargo run --release -- samples/binary.exe
        

Create the emulation object

First of all, use the emu32 or emu64 object depending what you need.

If you will do memory allocations you al will need to use the Permission object.

main.rs

use libmwemu::emu32;
use libmwemu::maps::mem64::Permission;
        

Note that even in 32bits the Permission object implementation is implemented in mem64, actually internally all is 64bits

Then create the emulation object for the chosen architecture.

main.rs

let mut emu = emu32();  // or also emu64()
        

Configure the Emulator

Then optionally you can use modificators to configure the emulator, even access directly to Config object

All the configurations available:

  • emu.set_verbose(n: u32) change verbosity, 0: only show winapi calls, 1: also messages, 2: also every asm instructions, 3: also every rep iteration.

  • emu.set_stack_address(addr: u64)

  • emu.set_base_address(addr: u64) set the base address of the code, the initial emulable blob, by default mwemu gets it from the PE/ELF structures.

  • emu.disable_ctrlc()

  • emu.disable_console()

  • emu.disable_banzai()

  • emu.disable_color()

  • emu.disable_interrupt()

  • emu.disable_exception()

  • emu.disable_memory_read()

  • emu.disable_memory_write()

  • emu.disable_pre_instruction()

  • emu.disable_post_instruction()

  • emu.disable_winapi_call()

  • emu.spawn_console()

  • emu.spawn_console_at(exp: u64)

  • emu.spawn_console_at_addr(addr: u64)

  • emu.update_ldr_entry_base(libname: &str, base: u64)

From rust you can also modify directly the Config structure, example:

main.rs

            emu.cfg.arguments = "--exe_sample_some_arg".to_string();
            emu.cfg.shellcode = true;
        

Find here all the configs available inConfig object.

But main configs has function wrappers in the main Emu base object.

You can also create a Config object and set it to the emulator with emu.set_config(cfg: Config) but normaly is more convinien to access to emul.cfg as seen above.

Configuring Maps Folder

In most of cases you will need to load all the Windows OS simulation part, to have all the PEB+TEB+LDR linked-list doing windows Process Simulation.

For doing this you can use emu.set_maps_folder(folder:str)

If you are going to emulate pure assembly, with no api calls, and no access to windows structures, you don't need to load the maps.

Example:

main.rs

let mut emu = emu64();
emu.set_maps_folder("/home/sha0/src/mwemu/maps/maps64/");
emu.init_logger();
        

Note that this need to have the maps, so git clone the repo:

Shell

❯❯❯ git clone https://github.com/sha0coder/mwemu.git 
                

This call configure the path, but don't load it for now!

Later we will use one of those 3 to trigger the win32 simulator:

  • emu.load_code(file: &str) if it's PE or shellcode will call internally init_win32

  • emu.init_win32(clear_registers: bool, clear_flags: bool) you can trigger directly the win32 simulation, but don't call it twice.

So, if you need the maps and win32 stuff but you don't do load_sample, then you will need to call directly the init_win32

loading ELF, PE, or shellcodes

In most of cases you will need to load a sample, from file or from vector.

Load your main sample with one of these methods, and only once.

For loading aditional maps, use the load/allocation methods that are explained later.

  • emu.load_code(filename: &str) load the sample to be emulated.

  • emu.load_code_bytes(opcodes: &[u8]) if you have the bytes already on a variable.

Example1:

main.rs

emu.load_code("/samples/sample.bin");
        

Example2:

main.rs

let mut opcodes: Vec<u8> = Vec::new();
... // fill the opcodes
let mut emu = emu32();
emu.set_maps_folder("/home/sha0/src/mwemu/maps/maps64/");
emu.load_code_bytes(&opcodes);
emu.init_win32(false, false);
        

Example3:

main.rs

let mut emu = emu32();
emu.load_code_bytes(&opcodes);
emu.init_linux64();  // load_code("sample") would trigger init_win32 or init_linux64 depending on file format. 
                     // but with no load_code we must trigger manually the init if we need it.
        

It detects if it's ELF, PE and otherwise is a shellcode.

In the case of PE or shellcode the win32 simulation is initialized automatically.

In the case of ELF the linux64 simulation is initialized automatically.

But if for any reason you don't use load_code() and you need the windows simulator with peb/ldr/dlls loaded on the memory, you need to call init_win32 (even for 64bits, it will detect the arch)

And if you dont use load_binary() but you need the linux simulator with libc etc loaded, use init_linux64. Note that linux implementation is very basic and only for static 64bits

Note that calling init_win32() requires previously to have done load_maps to set the maps folder.

Creating Buffers

You can load sections from disk to the emulator's virtual memory, and also can allocate buffers.

  • emu.alloc(map_name: &str, size: u64, permission: Permission) -> u64 simplest way to alloc a buffer, it returns the address.

  • emu.maps.alloc(sz: u64) -> Option<u64> this is used by the winapi and linux syscalls, this just find a free block of size sz, but don't allocate it, you need then to do emu.maps.create_map

  • emu.maps.create_map(name: &str, base: u64, size: u64, permission: Permission

    create an empty map at specific location.
  • emu.free(map_name: &str); deallocate by map name.

  • emu.maps.dealloc(addr: u64); deallocate by address.

  • emu.link_library(filepath: &smp;str) -> u64 dynamic link a custom dll to the LDR struct.

Example1:

main.rs

use libmwemu::maps::mem64::Permission;
use libmwemu::emu32;
...
let ptr_encoded = emu.alloc("enc", 1024, Permission::READ_WRITE_EXECUTE);
let ptr_decoded = emu.alloc("dec", 1024, Permission::READ_WRITE_EXECUTE);
emu.maps.write_bytes(ptr_encoded, &data);
let rax = emu.call64(decoder_func_addr, &[ptr_encoded, ptr_decoded]);
        

Example2:

main.rs

let addr = emu.maps.alloc(1024);
let map = emu
            .maps
            .create_map("section_memorydump", addr, 1024, Permission::READ_WRITE_EXECUTE)
            .expect("load_map out of memory");
map.load("dumped_section.bin");
        

map is a Mem64 object, and it has multiple methods. But for most of cases emu and emu.maps methods are enoguth.

Registers

You can view the register values and modify it in any moment.

For instance to prepare the context previous to the emulation, preparing an emulation state.

    Reading a 64bits register or bigger is very direct

    let rax = emu.regs().rax;

    let rip = emu.regs().rip;

    let xmm15 = emu.regs().xmm15;

    Settings a 64bits register or bigger is very direct.

    emu.regs_mut().rax = 0x123;

    emu.regs_mut().rip = 0x4012312;

    emu.regs_mut().xmm15 = 0x11223344_11223344_11223344_11223344u128;

    Getters for non 64bits (it return always u64 type)

    let eax = emu.regs().get_eax() as u32;

    let eip = emu.regs().get_eip() as u32;

    let al = emu.regs().get_eip() as u8;

    Setters for non 64bits (provide always u64 type)

    emu.regs_mut().set_eax(123u8);

    emu.regs_mut().set_eip(0x123123);

    emu.regs_mut().set_al(0xff);

    emu.regs_mut().set_sil(0xff);

    emu.regs_mut().set_dil(0xff);

    r1 to r15

    let r15 = emu.regs().r15();

    let n = emu.regs().get_r12w();

    emu.regs_mut().set_r8h(0x00);

    emu.regs_mut().set_r8l(0xff);

    emu.regs_mut().r8 = 0;

    let lower_32bits = emu.regs().set_r8d();

    let upper_32bits = emu.regs().set_r8u();

Regs64 is fully featured, there are also some uncommon registers like ymm, cr, gs, fs, tr and msr.

    helper methods

    emu.regs_mut().clear::<u8>(); clear the lower byte of all the registers.

    emu.regs_mut().rand(); randomize register values.

    emu.regs().print::<u32>(); print main registers.

And there are much more stuff, for more check the Regs64 object.

Note that you can change emu.regs_mut().set_eip(addr); but most of times what you really need is emu.set_eip(addr); which emulates the jmp logic triggering WinAPI if the address is a lib.

Memory operations

It could be useful for pinpoint the emulation state before starting the emulation, but other option is checking the memory after the emulation or altering it during the emulation.

Note that this read/write are inside the emulation virtual memory.

    memory read

  • emu.maps.read_byte(addr: u64) -> Option<u8>

  • emu.maps.read_f64(addr: u64) -> Option<f64>

  • emu.maps.read_f32(addr: u64) -> Option<f32>

  • emu.maps.read_128bits_be(addr: u64) -> Option<u128>

  • emu.maps.read_128bits_le(addr: u64) -> Option<u128>

  • emu.maps.read_qword(addr: u64) -> Option<u64>

  • emu.maps.read_dword(addr: u64) -> Option<u32>

  • emu.maps.read_word(addr: u64) -> Option<u16>

  • emu.maps.read_buffer(from: u64, sz: usize) -> Vec<u8>

  • emu.maps.read_bytes(addr: u64, sz: usize) -> &[u8]

  • emu.maps.read_bytes_option(addr: u64, sz: usize) -> Option<&[u8]>

  • emu.maps.read_string_of_bytes(addr: u64, sz: usize) -> String

  • emu.maps.read_string(addr: u64) -> String

  • emu.maps.read_wide_string_nocrash(addr: u64) -> String

  • emu.maps.read_wide_string(addr: u64) -> String

  • emu.maps.read_wide_string_n(addr: u64, max_chars: usize) -> String

  • memory write

  • emu.maps.write_byte(addr: u64, value: u8) -> bool

  • emu.maps.write_qword(addr: u64, value: u64) -> bool

  • emu.maps.write_dword(addr: u64, value: u32) -> bool

  • emu.maps.write_word(addr: u64, value: u16) -> bool

  • emu.maps.write_bytes(addr: u64, data: Vec<u8>) -> bool

  • emu.maps.write_string(to: u64, from: &str)

  • emu.maps.write_wide_string(to: u64, from: &str)

  • emu.maps.write_buffer(to: u64, from: &[u8])

  • emu.maps.write_spaced_bytes(addr: u64, sbs: &str) -> bool

  • libc style memory operations:

  • emu.maps.memset(addr: u64, b: u8, amount: usize)

  • emu.maps.memcpy(to: u64, from: u64, size: usize) -> bool

  • emu.maps.sizeof_wide(unicode_str_ptr: u64) -> usize

  • search methods:

  • emu.maps.search_string(kw: &str, map_name: &str) -> Option<Vec<u64>>

  • emu.maps.search_spaced_bytes_from(sbs: &str, saddr: u64) -> u64

  • emu.maps.search_spaced_bytes_from_bw(spaced_bytes: &str, start_address: u64) -> u64

  • emu.maps.search_spaced_bytes(sbs: &str, map_name: &str) -> Vec<u64>

  • emu.maps.search_spaced_bytes_in_all(sbs: &str) -> Vec<u64>

  • emu.maps.search_string_in_all(kw: String)

  • emu.maps.search_bytes(bkw: Vec<u8>, map_name: &str) -> Vec<u64>

Examples of preparing context previous to start emulation:

main.rs

let mut some_data_structure_needed: Vec = Vec::new();
load_blob_from_disk("blob.bin", &mut some_data_structure_needed);

let mut emu = libmwemu::emu32();
let addr = emu.alloc("struct1", some_data_structure_needed.len());
emu.maps.write_bytes(addr, some_data_structure_needed);
let rax = emu.call32(0x40323, &[addr, some_data_structure_needed.len()]);
        

Another example:

main.rs

let mut emu = libmwemu::emu32();
let addr = emu.alloc("buff1", 100);
emu.maps.write_string(addr, "Hello MWEMU!");
let text = emu.maps.read_string(addr, 12);
println!("{}", text);

// Write dword
let buffer_addr = emu.alloc("buff2", 4);
emu.maps.write_dword(buffer_addr + 0x100, 0x12345678);
let value = emu.maps.read_dword(buffer_addr + 0x100);
println!("dword: 0x{:x}", value);

// emulate some opcodes
let buff = emu.alloc("code_blob", 1024);
let opcodes = vec![0x90, 0x90, 0x90, 0xc3]; // nop nop nop ret
emu.maps.write_buffer(buff, opcodes);
emu.regs_mut().set_eip(buff);
emu.run(Some(buff+3));
println!("eip: 0x{:x}", emu.regs().get_eip());
// just an example, probalby more convinient using emu.load_code_bytes(&opcodes) for emulating shellcode that is in a variable.
              

Start Emulation

Once everything is configured, you can start the emulation.

    You can start the mulation using run functions

  • emu.run(end_addr: Option<u64>) -> Result<u64, MwemuError>

  • emu.run_until_ret() -> Result<u64, MwemuError>

  • emu.run_to(end_pos: u64) -> Result<u64, MwemuError>

  • emu.run_multi_threaded(end_addr: Option<u64>) -> Result<u64, MwemuError>

  • emu.run_single_threaded(end_addr: Option<u64>) -> Result<u64, MwemuError>

  • emu.stop()

  • But this ways is wide more confortable because mimics calling conventions.

  • emu.call32(addr: u64, args: &[u32]) -> Result<u32, MwemuError> microsoft 32bits calling convention.

  • emu.call64(addr: u64, args: &[u64]) -> Result<u64, MwemuError> microsoft 64bits calling convention.

  • emu.linux_call64(addr: u64, args: &[u64]) -> Result<u64, MwemuError> ABI SysV AMD64 calling conventionused on linux64 user space.

  • For more control but slow way you can use step, but note that also can use hooks.

  • emu.step() -> bool

  • emu.step_single_threaded() -> bool

  • emu.step_multi_threaded() -> bool

Additional Useful Methods

Other helpful stuff for emulation control:

  • emu.pos this is the emulated instruction counter, you can access even modify it.

  • emu.set_verbose(n: u32) set verbose 0 for go faster to a specific point and then set 2 to view the asm only in that pos ranges.

  • emu.cfg.console_enabled = true enable the console autospawn.

  • emu.spawn_console() in specific case spawn the console to inspect manually the situation.

emu.rs

fn main() {
    let mut emu = limwemu::emu64();
    emu.init_logger();
    emu.load_code("/bin/ls.static");
    emu.cfg.console_enabled = true;
    emu.run(None).unwrap();
    emu.spawn_console();
}
        

emu.rs

let mut emu = libmwemu::emu64();
let vm_buff = emu.alloc("buff", 1024, Permission::READ_WRITE_EXECUTE);
let vm: Vec = vec![
    0x17, 0xC6, 0x04, 0x4D, 0xFF,
    0x10, 0x5C, 0x3D, 0x86, 0x09,
    0xC9, 0x30, 0xAC, 0x42, 0x05,
    0x59, 0x8C, 0x21, 0x07, 0xD2,
];

emu.load_code_bytes(&shellcode);
let decoder = 0x3c000;
emu.call64(decoder, &[&vm_buff]);
          

emu.rs

fn main() {
    let mut emu = limwemu::emu64();
    emu.init_logger();
    emu.load_code("/bin/ls.static");
    emu.cfg.console_enabled = true;
    emu.run(None).unwrap();
    emu.spawn_console();
}
        

Stack operations

There are methods for altering the stack, note that you also could do with emu.maps writes.

But with this meethods you trigger all the stack logic, also incrementing rsp/esp.

  • stack_push32(value: u32) -> bool

  • stack_push64(value: u53) -> bool

  • stack_pop32() -> Option<u32>

  • stack_pop64() -> Option<u64>

This example simulates a stack based calling convention, but note that you can also use call32() and call64() and linux_call64() methods.

emu.rs

loop {
    emu.maps.memset(str_buff, 0, 1024);
    emu.maps.memset(str_decoded, 0, 1024);
    emu.regs_mut().set_eip(decrypt_strings_func);
    emu.stack_push32(i);
    emu.stack_push32(str_buff as u32);
    emu.stack_push32(thread_ctx as u32);
    emu.stack_push32(ret_addr as u32);
    emu.run(Some(ret_addr)).unwrap();
    ...
}
              

Breakpoints

Like a debugger you can configure different types of breakpoints, but the implementation is simple, but are other ways of trigger the emulation to stop.

  • emu.bp.show()

  • emu.bp.clear_bp()

  • emu.bp.add_bp(addr: u64)

  • emu.bp.addr.clone() get a vector of address based breakpoints.

  • emu.bp.add_bp_instruction(ins: u64)

  • emu.bp.instruction.clone()

  • emu.bp.add_bp_mem_read(addr: u64)

  • emu.bp.mem_read_addr.clone()

  • emu.bp.add_bp_mem_write(addr: u64)

  • emu.bp.mem_write_addr.clone()

For more info check Breakpoint object reference.

Memory dump

there are several ways do do a memory dump

  • emu.maps.save_all_allocs(path: &str) only save to disk allocations done by the emulated code.

  • emu.maps.save(addr: u64, size: u64, filename: String) dump specific blob to disk.

Example

main.rs

emu.maps.print_maps()
emu.maps.save(0x40324234, 1024, "/tmp/blob.bin")
        

View info

There are some less used methods to fetch different type of information.

  • let mut output = String::new(); self.emu .formatter .format(&self.emu.instruction.unwrap(), &mut output); get last emulated mnemonic.

  • emu.pos = 0; reset the emulated instruction count to zero.

  • emu.cfg.is_64bits detect in which mode is the emulator.

  • emu.disassemble(addr: u64, amount: u32) -> String dissassemble bytes.

  • emu.maps.print_maps() print all the maps (allocs, linked dlls, etc)

  • emu.maps.print_maps_keyword(kw: &str) print the maps that contain a keyword.

  • let base = match emu.maps.get_addr_base(addr) { Some(v) => Ok(v), None => ..., };

  • emu.maps.is_mapped(addr: u64) -> bool check if an address is allocated.

  • emu.maps.get_addr_name(addr: u64) -> String get in which map name is the address.

  • emu.maps.dump(addr: u64) print bytes.

  • emu.maps.dump_n(addr: u64, amount: u64) print n bytes.

  • emu.maps.dump_qwords(addr: u64, n: u64) print a list of qwords.

  • emu.maps.dump_dwords(addr: u64, n: u64) print a list of dwords.

  • emu.maps.size() -> usize show the total allocated memory.

  • emu.maps.overlaps(addr: u64, sz: u64) -> bool check if a memory block overlaps with existing map.

  • emu.maps.show_allocs() print all the allocations done by the emulated code.

  • emu.maps.mem_test() -> bool do a automatic memory test to look for map overlapps.

  • emu.api_addr_to_name(addr: u64) -> String provide an address pointing to an API and will fetch the API name.

  • emu.api_name_to_addr(name: &str) -> u64 get the address of an api name.

Example

main.rs

let name = emu.api_addr_to_name(0x11223344)
println!("{}", name)
   MessageBoxA
        

Switch architecture bits.

There are methods for changing from 32bits to 64bits and vice versa.

  • set_64bits()

  • set_32bits()

  • inspect_seq(s: str)

But better don't change the architecture on the fly, re-instantiate the emulation object like:

Example

main.rs

let mut emu = libmwemu::emu64();
...
let mut emu = libmwemu::emu32();
        

Or even create both objtects.

Hooks

The Hooks object allow to extend the emulator without blockign the emulation.

    Enable a hook

  • emu.hooks.on_memory_read(trace_memory_read); the hook is triggered after every read.

  • emu.hooks.on_memory_write(trace_memory_write); the hook is triggered before every write, and the hook can change the value to write by returning it.

  • emu.hooks.on_interrupt(trace_interrupt); the hook is triggered before handling it, the hook can decide with the return value if mwemu do the handling or not.

  • emu.hooks.on_exception(trace_exceptions); the hook is triggered before handling it, the hook can decide with the return value if mwemu do the handling or not.

  • emu.hooks.on_pre_instruction(trace_pre_instruction); the hook is triggered before emulating the instruction.

  • emu.hooks.on_post_instruction(trace_post_instruction); the hook is triggered after emulating the instruction.

  • emu.hooks.on_winapi_call(trace_winapi_call); you can implement a Windows API.

  • Disable a hook

  • emu.hooks.disable_memory_read();

  • emu.hooks.disable_memory_write();

  • emu.hooks.disable_interrupt();

  • emu.hooks.disable_exception();

  • emu.hooks.disable_pre_instruction();

  • emu.hooks.disable_post_instruction();

  • emu.hooks.disable_winapi_call();

Example

main.rs

use libmwemu::emu32;

//need iced_x86 crate only for instruction hooks, to get the
//instruction object, so cargo add iced-x86
use iced_x86::{Instruction};  

fn trace_memory_read(emu:&mut libmwemu::emu::Emu, ip_addr:u64, 
                     mem_addr:u64, sz:u8) {
    log::info!("0x{:x}: reading {} at 0x{:x}", ip_addr, sz, mem_addr);
    if mem_addr == 0x22dff0 {
        emu.stop();
    }
}

fn trace_memory_write(emu:&mut libmwemu::emu::Emu, ip_addr:u64, 
                      mem_addr:u64, sz:u8, value:u128) -> u128 {
    log::info!("0x{:x}: writing {} '0x{:x}' at 0x{:x}", ip_addr, sz, 
             value, mem_addr);
    value   // I could change the value to write
}

fn trace_interrupt(emu:&mut libmwemu::emu::Emu, ip_addr:u64, 
                   interrupt:u64) -> bool {
    log::info!("interrupt {} triggered at eip: 0x{:x}", interrupt, 
             ip_addr);
    true  // do handle interrupts
}   

fn trace_exceptions(emu:&mut libmwemu::emu::Emu, ip_addr:u64, ex_type: libmwemu::exception_type::ExceptionType) -> bool {
    log::info!("0x{:x} triggered an exception {}", ip_addr, ex_type);
    if (ex_type == libmwemu::exception_type::ExceptionType::Int3) {
        // do handle SIGTRAP for example
    }
    true // do handle exceptions
}

fn trace_pre_instruction(emu:&mut libmwemu::emu::Emu, ip_addr:u64, 
                         ins:&Instruction, sz:usize) -> bool{
                            // return false to skip the instruction
                            true
}

fn trace_post_instruction(emu:&mut libmwemu::emu::Emu, ip_addr:u64, 
                          ins:&Instruction, sz:usize, emu_ok:bool) {
}

fn trace_winapi_call(emu:&mut libmwemu::emu::Emu, ip_addr:u64, api_addr:u64) -> bool {
    return true; // handle api calls
}

fn main() {
    let mut emu = emu32();
    emu.set_maps_folder("../mwemu/maps32/"); // download the maps, ideally from mwemu git.
    emu.init();

    emu.load_code("/home/sha0/src/mwemu/shellcodes32/mars.exe");
    emu.hooks.on_memory_read(trace_memory_read);
    emu.hooks.on_memory_write(trace_memory_write);
    emu.hooks.on_interrupt(trace_interrupt);
    emu.hooks.on_exception(trace_exceptions);
    emu.hooks.on_pre_instruction(trace_pre_instruction);
    emu.hooks.on_post_instruction(trace_post_instruction);
    emu.hooks.on_winapi_call(trace_winapi_call);
    emu.run(None).unwrap();
    log::info!("end!");
}