Skip to content

Commit e3bc5ad

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 981d1c5 + bb67d9d commit e3bc5ad

File tree

15 files changed

+392
-14
lines changed

15 files changed

+392
-14
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,36 @@
66
[![Docs.rs](https://docs.rs/libmwemu/badge.svg)](https://docs.rs/libmwemu)
77
[![codecov](https://codecov.io/gh/sha0coder/mwemu/branch/main/graph/badge.svg)](https://codecov.io/gh/sha0coder/mwemu)
88

9-
x86 32/64bits emulator and windows/linux simulator, for securely emulating malware and other stuff.
9+
10+
## What is this?
11+
12+
It's a hardware emulator + OS process simulator implemented in pure rust.
13+
14+
This approach is very conviniento to malware analysis and other stuff (PE, shellcode etc)
15+
16+
The OS is mainly windows, it emulates a windows process, some very basic support for linux.
17+
18+
The hardware is x86 32/64bits it's fast and reliable.
19+
1020

1121
![MWEMU Logo](./docs/pics/mwemu_logo.png)
1222

13-
## Some Videos
23+
## Three ways of using the software
1424

15-
https://www.youtube.com/@JesusOlmos-wm8ch/videos
25+
- mwemu commandline https://github.com/sha0coder/mwemu
26+
- libmwemu crate https://crates.io/crates/libmwemu
27+
- pymwemu https://pypi.org/project/pymwemu/
1628

17-
https://www.youtube.com/watch?v=yJ3Bgv3maq0
1829

19-
## Automation
30+
## note about scemu
2031

21-
Python apps https://pypi.org/search/?q=pymwemu
32+
The project was renamed from scemu to mwemu.
2233

23-
Rust apps https://crates.io/crates/libmwemu
34+
## Some Videos
35+
36+
[r2con2025](https://www.youtube.com/watch?v=-TFL-_-nIqA) using radare2 from mwemu for doing static analisys and visualization inside an emulation moment.
37+
38+
[some demos](https://www.youtube.com/@JesusOlmos-wm8ch/videos)
2439

2540
## Features
2641

@@ -80,6 +95,12 @@ Rust apps https://crates.io/crates/libmwemu
8095
- gozi bss decrypt and dga predictor.
8196

8297

98+
## Capture an emulation moment
99+
100+
During the emulation you can see the numer of cpu insturctions emulated, that's an unique ID for an emulation moment.
101+
102+
With -c flag you stop the emulation in a specific moment and inspect what is going on with the console.
103+
83104
## Usage
84105

85106
```

crates/libmwemu/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "libmwemu"
3-
version = "0.22.1"
3+
version = "0.22.2"
44
edition = "2018"
55
authors = ["sha0coder"]
66
license = "MIT"

crates/libmwemu/src/constants.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ pub const ERROR_BUFFER_OVERFLOW: u64 = 0x6f;
4343
pub const ERROR_INVALID_PARAMETER: u64 = 0x57;
4444
pub const ERROR_INSUFFICIENT_BUFFER: u64 = 0x7a;
4545

46+
47+
pub const HRESULT_E_INVALID_ARG: u64 = 0x80070057;
48+
/*
49+
* HRESULT STRUCTURE:
50+
* 0x8 -> severity: error
51+
* 0x007 -> facility: FACILITY_WIN32
52+
* 0x0057 -> ERROR_INVALID_PARAMETER
53+
*/
54+
4655
pub const CP_UTF7: u64 = 65000;
4756
pub const CP_UTF8: u64 = 65001;
4857

crates/libmwemu/src/serialization/thread_context.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub struct SerializableThreadContext {
3030
pub fls: Vec<u32>,
3131
pub fs: BTreeMap<u64, u64>,
3232
pub call_stack: Vec<(u64, u64)>, // the first address is the source of the call location and the second address is the destination of the call
33+
pub handle: u64
3334
}
3435

3536
impl From<&ThreadContext> for SerializableThreadContext {
@@ -56,6 +57,7 @@ impl From<&ThreadContext> for SerializableThreadContext {
5657
fls: thread.fls.clone(),
5758
fs: thread.fs.clone(),
5859
call_stack: thread.call_stack.clone(),
60+
handle: thread.handle
5961
}
6062
}
6163
}
@@ -84,6 +86,7 @@ impl From<SerializableThreadContext> for ThreadContext {
8486
fls: serialized.fls,
8587
fs: serialized.fs,
8688
call_stack: serialized.call_stack,
89+
handle: serialized.handle,
8790
}
8891
}
8992
}

crates/libmwemu/src/thread_context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct ThreadContext {
2525
pub fls: Vec<u32>,
2626
pub fs: BTreeMap<u64, u64>,
2727
pub call_stack: Vec<(u64, u64)>,
28+
pub handle: u64,
2829
}
2930

3031
impl ThreadContext {
@@ -51,6 +52,7 @@ impl ThreadContext {
5152
fls: Vec::new(),
5253
fs: BTreeMap::new(),
5354
call_stack: Vec::with_capacity(10000),
55+
handle: 0
5456
}
5557
}
5658
}

crates/libmwemu/src/winapi/winapi32/kernel32/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ pub use wide_char_to_multi_byte::*;
345345
pub use win_exec::*;
346346
pub use write_file::*;
347347
pub use write_process_memory::*;
348+
use crate::emu::Emu;
348349

349350
pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String {
350351
let api = guess_api_name(emu, addr);
@@ -390,6 +391,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String {
390391
"FreeLibrary" => FreeLibrary(emu),
391392
"FreeResource" => FreeResource(emu),
392393
"GetACP" => GetACP(emu),
394+
"GetThreadId" => GetThreadId(emu),
393395
"GetCommandLineA" => GetCommandLineA(emu),
394396
"GetCommandLineW" => GetCommandLineW(emu),
395397
"GetComputerNameA" => GetComputerNameA(emu),
@@ -545,6 +547,26 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String {
545547
String::new()
546548
}
547549

550+
fn GetThreadId(emu: &mut Emu) {
551+
let hndl = emu
552+
.maps
553+
.read_dword(emu.regs().get_esp() + 4)
554+
.expect("kernel32!GetThreadId bad handle parameter") as u64;
555+
556+
emu.stack_pop32(false);
557+
558+
559+
for i in 0..emu.threads.len() {
560+
if emu.threads[i].handle == hndl {
561+
emu.regs_mut().rax = emu.threads[i].id;
562+
log_red!(emu, "kernel32!GetThreadId hndl:{} (requested handle exists and its tid {})", hndl, emu.threads[i].id);
563+
return;
564+
}
565+
}
566+
log_red!(emu, "kernel32!GetThreadId hndl:{} (requested handle doesn't exist, returning a fake handle for now but should return zero.)", hndl);
567+
emu.regs_mut().rax = 0x2c2878; // if handle not found should return zero.
568+
}
569+
548570
lazy_static! {
549571
static ref COUNT_READ: Mutex<u32> = Mutex::new(0);
550572
static ref COUNT_WRITE: Mutex<u32> = Mutex::new(0);

crates/libmwemu/src/winapi/winapi32/ntdll.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::winapi::winapi32::kernel32;
99

1010
use crate::maps::mem64::Permission;
1111
use scan_fmt::scan_fmt_some;
12+
use crate::emu::Emu;
13+
use crate::winapi::winapi64::kernel32::InitializeCriticalSection;
1214

1315
const PAGE_NOACCESS: u32 = 0x01;
1416
const PAGE_READONLY: u32 = 0x02;
@@ -36,6 +38,8 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String {
3638
"NtQueryPerformanceCounter" => NtQueryPerformanceCounter(emu),
3739
"RtlGetProcessHeaps" => RtlGetProcessHeaps(emu),
3840
"RtlDosPathNameToNtPathName_U" => RtlDosPathNameToNtPathName_U(emu),
41+
"RtlInitializeCriticalSection" => InitializeCriticalSection(emu),
42+
"RtlZeroMemory" => RtlZeroMemory(emu),
3943
"NtCreateFile" => NtCreateFile(emu),
4044
"RtlFreeHeap" => RtlFreeHeap(emu),
4145
"NtQueryInformationFile" => NtQueryInformationFile(emu),
@@ -80,6 +84,26 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String {
8084
String::new()
8185
}
8286

87+
fn RtlZeroMemory(emu: &mut Emu) {
88+
let dest = emu
89+
.maps
90+
.read_dword(emu.regs().get_esp() + 4)
91+
.expect("bad RtlZeroMemory address pointer parameter") as u64;
92+
let length = emu
93+
.maps
94+
.read_dword(emu.regs().get_esp() + 8)
95+
.expect("bad RtlZeroMemory address length parameter") as u64;
96+
97+
log_red!(
98+
emu,
99+
"ntdll!RtlZeroMemory dest: 0x{:x} length: {}",
100+
dest,
101+
length
102+
);
103+
104+
emu.maps.memset(dest, 0, length as usize);
105+
}
106+
83107
fn NtAllocateVirtualMemory(emu: &mut emu::Emu) {
84108
/*
85109
__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(

crates/libmwemu/src/winapi/winapi32/ws2_32.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,119 @@ lazy_static! {
6363
static ref COUNT_RECV: Mutex<u32> = Mutex::new(0);
6464
}
6565

66+
fn getaddrinfo(emu: &mut emu::Emu) {
67+
let node_name_ptr = emu
68+
.maps
69+
.read_dword(emu.regs().get_esp() + 4)
70+
.expect("ws2_32!getaddrinfo cannot read node_name_ptr");
71+
let service_name_ptr = emu
72+
.maps
73+
.read_dword(emu.regs().get_esp() + 8)
74+
.expect("ws2_32!getaddrinfo cannot read service_name_ptr");
75+
let hints_ptr = emu
76+
.maps
77+
.read_dword(emu.regs().get_esp() + 12)
78+
.expect("ws2_32!getaddrinfo cannot read hints_ptr");
79+
let result_ptr_ptr = emu
80+
.maps
81+
.read_dword(emu.regs().get_esp() + 16)
82+
.expect("ws2_32!getaddrinfo cannot read result_ptr_ptr");
83+
84+
let node_name = if node_name_ptr != 0 {
85+
emu.maps.read_string(node_name_ptr as u64)
86+
} else {
87+
"NULL".to_string()
88+
};
89+
90+
let service_name = if service_name_ptr != 0 {
91+
emu.maps.read_string(service_name_ptr as u64)
92+
} else {
93+
"NULL".to_string()
94+
};
95+
96+
log_red!(emu, "ws2_32!getaddrinfo node: `{}` service: `{}`", node_name, service_name);
97+
98+
// Read hints if provided
99+
let mut hints_flags = 0;
100+
let mut hints_family = 0;
101+
let mut hints_socktype = 0;
102+
let mut hints_protocol = 0;
103+
104+
if hints_ptr != 0 {
105+
hints_flags = emu.maps.read_dword(hints_ptr as u64).unwrap_or(0) as i32;
106+
hints_family = emu.maps.read_dword((hints_ptr + 4) as u64).unwrap_or(0) as i32;
107+
hints_socktype = emu.maps.read_dword((hints_ptr + 8) as u64).unwrap_or(0) as i32;
108+
hints_protocol = emu.maps.read_dword((hints_ptr + 12) as u64).unwrap_or(0) as i32;
109+
}
110+
111+
// Create a dummy ADDRINFO structure
112+
let addrinfo_size = 48; // Size of ADDRINFOA structure (approximate)
113+
let sockaddr_in_size = 16; // Size of sockaddr_in structure
114+
115+
// Allocate memory for the result
116+
let heap_management = emu.heap_management.as_mut().unwrap();
117+
let addrinfo_addr = heap_management.allocate((addrinfo_size + sockaddr_in_size + 100) as usize).unwrap();
118+
let sockaddr_addr = addrinfo_addr + addrinfo_size;
119+
let canonname_addr = sockaddr_addr + sockaddr_in_size;
120+
121+
// Create a dummy sockaddr_in (IPv4 address 127.0.0.1, port based on service)
122+
let ip_addr = 0x0100007f; // 127.0.0.1 in network byte order
123+
124+
// Determine port based on service name
125+
let port = if service_name == "http" || service_name == "80" {
126+
80u16
127+
} else if service_name == "https" || service_name == "443" {
128+
443u16
129+
} else if service_name == "ftp" || service_name == "21" {
130+
21u16
131+
} else if service_name == "ssh" || service_name == "22" {
132+
22u16
133+
} else if service_name == "smtp" || service_name == "25" {
134+
25u16
135+
} else if service_name == "dns" || service_name == "53" {
136+
53u16
137+
} else {
138+
service_name.parse().unwrap_or(80u16)
139+
};
140+
141+
// Write sockaddr_in structure
142+
emu.maps.write_word(sockaddr_addr, 2); // AF_INET = 2
143+
emu.maps.write_word(sockaddr_addr + 2, port.to_be()); // Port in network byte order
144+
emu.maps.write_dword(sockaddr_addr + 4, ip_addr); // IP address (127.0.0.1)
145+
emu.maps.memset(sockaddr_addr + 8, 0, 8); // Zero out the rest
146+
147+
// Write ADDRINFO structure
148+
emu.maps.write_dword(addrinfo_addr, hints_flags as u32); // ai_flags
149+
emu.maps.write_dword(addrinfo_addr + 4, if hints_family != 0 { hints_family as u32 } else { 2 }); // ai_family (AF_INET)
150+
emu.maps.write_dword(addrinfo_addr + 8, if hints_socktype != 0 { hints_socktype as u32 } else { 1 }); // ai_socktype (SOCK_STREAM)
151+
emu.maps.write_dword(addrinfo_addr + 12, if hints_protocol != 0 { hints_protocol as u32 } else { 6 }); // ai_protocol (IPPROTO_TCP)
152+
emu.maps.write_qword(addrinfo_addr + 16, sockaddr_in_size as u64); // ai_addrlen
153+
emu.maps.write_qword(addrinfo_addr + 24, canonname_addr); // ai_canonname
154+
emu.maps.write_qword(addrinfo_addr + 32, sockaddr_addr); // ai_addr
155+
156+
// Set ai_canonname to the node name or "localhost"
157+
let canon_name = if node_name == "NULL" || node_name.is_empty() || node_name == "localhost" {
158+
"localhost.localdomain".to_string()
159+
} else if node_name == "127.0.0.1" {
160+
"localhost".to_string()
161+
} else {
162+
format!("{}.localdomain", node_name)
163+
};
164+
emu.maps.write_string(canonname_addr, &canon_name);
165+
166+
// ai_next is NULL (end of list)
167+
emu.maps.write_qword(addrinfo_addr + 40, 0);
168+
169+
// Store the result pointer in the ppResult parameter
170+
emu.maps.write_qword(result_ptr_ptr as u64, addrinfo_addr);
171+
172+
log::info!("\tcreated dummy ADDRINFO for {}:{} at 0x{:x}", node_name, service_name, addrinfo_addr);
173+
log::info!("\tsockaddr at 0x{:x}, canonname at 0x{:x}", sockaddr_addr, canonname_addr);
174+
175+
// Return 0 for success (WSA error code)
176+
emu.regs_mut().rax = 0;
177+
}
178+
66179
fn WsaStartup(emu: &mut emu::Emu) {
67180
log_red!(emu, "ws2_32!WsaStartup");
68181

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use crate::emu;
2+
use crate::constants;
3+
4+
pub fn api_DeviceIoControl(emu: &mut emu::Emu) {
5+
let hDevice = emu.regs().rcx;
6+
let dwIoControlCode = emu.regs().rdx;
7+
let lpInBuffer = emu.regs().r8;
8+
let lpOutBuffer = emu.regs().r9;
9+
let rsp = emu.regs().rsp;
10+
let nOutBufferSize = emu.maps.read_qword(rsp + 0x20).expect("DeviceIoControl arg6 stack error.");
11+
let lpBytesReturned = emu.maps.read_qword(rsp + 0x28).expect("DeviceIoControl arg7 stack error.");
12+
13+
log_red!(emu, "kernel32!DeviceIoControl hDev: 0x{:x} code: 0x{:x} buff: 0x{:x}", hDevice, dwIoControlCode, lpInBuffer);
14+
15+
emu.regs_mut().rax = constants::TRUE;
16+
}

0 commit comments

Comments
 (0)