1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
use iced_x86::Register;
use crate::arch::Arch;
use crate::emu::Emu;
use crate::loaders::elf::elf32::Elf32;
use crate::loaders::elf::elf64::Elf64;
use crate::loaders::macho::macho64::Macho64;
use crate::loaders::pe::{
IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_ARM64, IMAGE_FILE_MACHINE_I386, pe_machine_type,
};
use crate::maps::mem64::Permission;
use crate::winapi::winapi64;
use crate::windows::constants;
use crate::windows::peb::{peb32, peb64};
mod elf;
mod macho;
mod pe;
impl Emu {
/// Load a sample. It can be PE32, PE64, ELF32, ELF64 or shellcode.
/// If its a shellcode cannot be known if is for windows or linux, it triggers also init() to
/// setup windows simulator.
/// For now mwemu also don't know if shellcode is for 32bits or 64bits, in commandline -6 has
/// to be selected for indicating 64bits, and from python or rust the emu32() or emu64()
/// construtor dtermines the engine.
pub fn load_code(&mut self, filename: &str) {
self.filename = filename.to_string();
self.cfg.filename = self.filename.clone();
// ELF32
if Elf32::is_elf32(filename) && !self.cfg.shellcode {
self.os = crate::arch::OperatingSystem::Linux;
self.cfg.arch = Arch::X86;
log::trace!("elf32 detected.");
let mut elf32 = Elf32::parse(filename).unwrap();
elf32.load(&mut self.maps);
self.regs_mut().rip = (elf32.elf_hdr.e_entry as u64) + elf32.base();
let stack_sz = 0x30000;
let stack = self.alloc("stack", stack_sz, Permission::READ_WRITE);
self.regs_mut().rsp = stack + (stack_sz / 2);
self.elf32 = Some(elf32);
// ELF64 AArch64
} else if Elf64::is_elf64_aarch64(filename) && !self.cfg.shellcode {
self.os = crate::arch::OperatingSystem::Linux;
self.cfg.arch = Arch::Aarch64;
self.maps.is_64bits = true;
self.maps.clear();
log::trace!("elf64 aarch64 detected.");
// load_elf64 handles thread conversion (via init_linux64_aarch64)
// and sets PC via set_pc()
self.load_elf64(filename);
// Mach-O AArch64
} else if Macho64::is_macho64_aarch64(filename) && !self.cfg.shellcode {
self.cfg.arch = Arch::Aarch64;
self.maps.is_64bits = true;
self.maps.clear();
// Set maps folder for macOS dylibs (try repo root, then relative from crate)
if self.cfg.maps_folder.is_empty() {
if std::path::Path::new("maps/macos/aarch64").exists() {
self.cfg.maps_folder = "maps/macos/aarch64/".to_string();
} else if std::path::Path::new("../../maps/macos/aarch64").exists() {
self.cfg.maps_folder = "../../maps/macos/aarch64/".to_string();
}
}
log::trace!("macho64 aarch64 detected.");
self.load_macho64(filename);
// Mach-O x86_64
} else if Macho64::is_macho64_x64(filename) && !self.cfg.shellcode {
self.cfg.arch = Arch::X86_64;
self.maps.is_64bits = true;
self.maps.clear();
// Set maps folder for macOS dylibs (try repo root, then relative from crate)
if self.cfg.maps_folder.is_empty() {
if std::path::Path::new("maps/macos/x86_64").exists() {
self.cfg.maps_folder = "maps/macos/x86_64/".to_string();
} else if std::path::Path::new("../../maps/macos/x86_64").exists() {
self.cfg.maps_folder = "../../maps/macos/x86_64/".to_string();
}
}
log::trace!("macho64 x86_64 detected.");
self.load_macho64(filename);
// ELF64 x86_64
} else if Elf64::is_elf64_x64(filename) && !self.cfg.shellcode {
self.os = crate::arch::OperatingSystem::Linux;
self.cfg.arch = Arch::X86_64;
self.maps.clear();
log::trace!("elf64 x86_64 detected.");
self.load_elf64(filename);
// PE: use COFF Machine field to distinguish x86 / x86_64 / ARM64
} else if !self.cfg.shellcode && pe_machine_type(filename) == Some(IMAGE_FILE_MACHINE_I386)
{
log::trace!(
"PE32 x86 header detected (Machine=0x{:04x}).",
IMAGE_FILE_MACHINE_I386
);
let clear_registers = false; // TODO: this needs to be more dynamic, like if we have a register set via args or not
let clear_flags = false; // TODO: this needs to be more dynamic, like if we have a flag set via args or not
self.cfg.arch = Arch::X86;
self.os = crate::arch::OperatingSystem::Windows;
// Set maps folder for Windows DLLs (try repo root, then relative from crate)
if self.cfg.maps_folder.is_empty() {
if std::path::Path::new("maps/windows/x86").exists() {
self.cfg.maps_folder = "maps/windows/x86/".to_string();
} else if std::path::Path::new("../../maps/windows/x86").exists() {
self.cfg.maps_folder = "../../maps/windows/x86/".to_string();
}
}
self.init_win32(clear_registers, clear_flags);
let (base, _pe_off) = self.load_pe32(filename, true, 0);
let ep = self.regs().rip;
// emulating tls callbacks
/*
for i in 0..self.tls_callbacks.len() {
self.regs_mut().rip = self.tls_callbacks[i];
log::trace!("emulating tls_callback {} at 0x{:x}", i + 1, self.regs().rip);
self.stack_push32(base);
self.run(Some(base as u64));
}*/
self.regs_mut().rip = ep;
// PE64 ARM64
} else if !self.cfg.shellcode && pe_machine_type(filename) == Some(IMAGE_FILE_MACHINE_ARM64)
{
log::trace!(
"PE64 ARM64 header detected (Machine=0x{:04x}). Windows AArch64 PE recognized.",
IMAGE_FILE_MACHINE_ARM64
);
self.cfg.arch = Arch::Aarch64;
self.os = crate::arch::OperatingSystem::Windows;
self.maps.is_64bits = true;
// Set maps folder for Windows ARM64 DLLs
if self.cfg.maps_folder.is_empty() {
if std::path::Path::new("maps/windows/aarch64").exists() {
self.cfg.maps_folder = "maps/windows/aarch64/".to_string();
} else if std::path::Path::new("../../maps/windows/aarch64").exists() {
self.cfg.maps_folder = "../../maps/windows/aarch64/".to_string();
}
}
let clear_registers = false;
let clear_flags = false;
self.init_win32(clear_registers, clear_flags);
let (base, _pe_off) = self.load_pe64(filename, true, 0);
let ep = self.pc();
match self.pe64 {
Some(ref pe64) => {
if pe64.is_dll() {
let regs = self.regs_aarch64_mut();
regs.x[0] = base; // hinstDLL
regs.x[1] = 1; // fdwReason = DLL_PROCESS_ATTACH
regs.x[2] = 0; // lpvReserved
}
}
_ => {
log::error!("No Pe64 found inside self");
}
}
self.set_pc(ep);
// PE64 x86_64
} else if !self.cfg.shellcode && pe_machine_type(filename) == Some(IMAGE_FILE_MACHINE_AMD64)
{
log::trace!(
"PE64 x86_64 header detected (Machine=0x{:04x}).",
IMAGE_FILE_MACHINE_AMD64
);
let clear_registers = false; // TODO: this needs to be more dynamic, like if we have a register set via args or not
let clear_flags = false; // TODO: this needs to be more dynamic, like if we have a flag set via args or not
self.cfg.arch = Arch::X86_64;
self.os = crate::arch::OperatingSystem::Windows;
// Set maps folder for Windows DLLs (try repo root, then relative from crate)
if self.cfg.maps_folder.is_empty() {
if std::path::Path::new("maps/windows/x86_64").exists() {
self.cfg.maps_folder = "maps/windows/x86_64/".to_string();
} else if std::path::Path::new("../../maps/windows/x86_64").exists() {
self.cfg.maps_folder = "../../maps/windows/x86_64/".to_string();
}
}
self.init_win32(clear_registers, clear_flags);
let (base, _pe_off) = self.load_pe64(filename, true, 0);
let ep = self.regs().rip;
match self.pe64 {
Some(ref pe64) => {
// start loading dll
if pe64.is_dll() {
self.regs_mut().set_reg(Register::RCX, base);
self.regs_mut().set_reg(Register::RDX, 1);
self.regs_mut().set_reg(Register::R8L, 0);
}
}
_ => {
log::error!("No Pe64 found inside self");
}
}
// Optional SSDT loader bootstrap: call ntdll!LdrInitializeThunk to perform loader init.
if self.cfg.emulate_winapi {
let ldr_init = winapi64::kernel32::resolve_api_name_in_module(
self,
"ntdll.dll",
"LdrInitializeThunk",
);
if ldr_init != 0 {
// Arrange return to entrypoint so execution continues normally.
self.regs_mut().rip = ep;
log::trace!("Initializing win32 64bits emulating ntdll!LdrInitializeThunk");
// LdrInitializeThunk(PCONTEXT Context, PVOID NtdllBase, PVOID Unused)
// The second argument must be ntdll's image base so the loader
// can parse its own PE headers during init.
let ntdll_base = self.maps.get_mem("ntdll.pe").get_base();
// Build a minimal x64 CONTEXT structure so LdrInitializeThunk does not
// null-deref when it reads CONTEXT.Rip to find the process entry point.
// x64 CONTEXT size is 0x4D0 bytes; key offsets:
// +0x30 ContextFlags (DWORD)
// +0x98 Rsp (QWORD)
// +0xF8 Rip (QWORD)
const CTX_SIZE: u64 = 0x4D0;
const CONTEXT_FULL: u32 = 0x10_007F;
let ctx_addr = self
.maps
.lib64_alloc(CTX_SIZE)
.expect("cannot alloc CONTEXT for LdrInitializeThunk");
self.maps
.create_map("ldr_context", ctx_addr, CTX_SIZE, Permission::READ_WRITE)
.expect("cannot create ldr_context map");
// ContextFlags
let _ = self.maps.write_dword(ctx_addr + 0x30, CONTEXT_FULL);
// Rsp: current stack pointer
let _ = self.maps.write_qword(ctx_addr + 0x98, self.regs().rsp);
// Rip: entry point — NtContinue will redirect execution here
let _ = self.maps.write_qword(ctx_addr + 0xF8, ep);
let _ = self.call64(ldr_init, &[ctx_addr, ntdll_base, 0]);
// Switch API dispatch to virtual stubs so PE code uses Rust
// implementations rather than executing real DLL machine code.
// Real DLL code writes non-volatile registers to shadow space,
// which can overlap with PE frame locals and corrupt saved RSP.
self.ldr_init_done = true;
log::trace!("ntdll!LdrInitializeThunk emulated completely.");
// Guest loader often clears `PEB+0x90`; restore before walking modules / main EP.
peb64::ensure_peb_system_dependent_07(self);
// LdrInitializeThunk was expected to bind the main image IAT but
// our emulation does not run LdrpProcessInitializationComplete fully.
// Bind it now so imported functions resolve to real stubs.
// Must run BEFORE rebuild_ldr_lists so that kernelbase/kernel32 .pe
// maps exist when the list is built.
let exe_base = self.base;
if let Some(mut pe) = self.pe64.take() {
pe.iat_binding(self, exe_base);
pe.delay_load_binding(self, exe_base);
self.pe64 = Some(pe);
}
// Ensure essential Windows DLLs are loaded with valid PE content so
// the PEB module list is usable by manual walkers (e.g. MinGW CRT).
// LdrInitializeThunk maps these via NtMapViewOfSection which only creates
// empty placeholder regions; we load the real PE files here.
for essential in &["kernelbase.dll", "kernel32.dll", "user32.dll"] {
winapi64::kernel32::load_library(self, essential);
}
// LdrInitializeThunk reinitializes the Ldr lists; if it didn't
// complete successfully the lists may be empty. Rebuild them
// from the actually-loaded PE images so user code can walk them.
peb64::rebuild_ldr_lists(self);
} else if self.cfg.verbose >= 1 {
log::trace!("ssdt: could not resolve ntdll!LdrInitializeThunk");
}
}
// emulating tls callbacks
/*
for i in 0..self.tls_callbacks.len() {
self.regs_mut().rip = self.tls_callbacks[i];
log::trace!("emulating tls_callback {} at 0x{:x}", i + 1, self.regs().rip);
self.stack_push64(base);
self.run(Some(base));
}*/
self.regs_mut().rip = ep;
// Shellcode
} else {
log::trace!("shellcode detected.");
let clear_registers = false; // TODO: this needs to be more dynamic, like if we have a register set via args or not
let clear_flags = false; // TODO: this needs to be more dynamic, like if we have a flag set via args or not
self.init_win32(clear_registers, clear_flags);
let exe_name = self.cfg.exe_name.clone();
if self.cfg.is_x64() {
let (base, _pe_off) =
self.load_pe64(&format!("{}/{}", self.cfg.maps_folder, exe_name), false, 0);
peb64::update_ldr_entry_base(&exe_name, base, self);
} else {
let (base, _pe_off) =
self.load_pe32(&format!("{}/{}", self.cfg.maps_folder, exe_name), false, 0);
peb32::update_ldr_entry_base(&exe_name, base as u64, self);
}
if !self
.maps
.create_map(
"code",
self.cfg.code_base_addr,
0,
Permission::READ_WRITE_EXECUTE,
)
.expect("cannot create code map")
.load(filename)
{
log::trace!("shellcode not found, select the file with -f");
return;
}
let code = self.maps.get_mem_mut("code");
code.extend(0xffff); // this could overlap an existing map
}
if self.cfg.entry_point != constants::CFG_DEFAULT_BASE {
self.regs_mut().rip = self.cfg.entry_point;
}
/*if self.cfg.code_base_addr != constants::CFG_DEFAULT_BASE {
let code = self.maps.get_mem("code");
code.update_base(self.cfg.code_base_addr);
code.update_bottom(self.cfg.code_base_addr + code.size() as u64);
}*/
}
/// Load a shellcode from a variable.
/// This assumes that there is no headers like PE/ELF and it's direclty code.
/// Any OS simulation is triggered, but init() could be called by the user
pub fn load_code_bytes(&mut self, bytes: &[u8]) {
if self.cfg.verbose >= 1 {
log::trace!("Loading shellcode from bytes");
}
self.init_cpu();
let code = self
.maps
.create_map(
"code",
self.cfg.code_base_addr,
bytes.len() as u64,
Permission::READ_WRITE_EXECUTE,
)
.expect("cannot create code map");
let base = code.get_base();
code.write_bytes(base, bytes);
self.set_pc(base);
}
}