libmwemu 0.25.4

x86 32/64bits and system internals emulator, for securely emulating malware and other stuff.
Documentation
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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
use crate::emu;
//use crate::serialization;
use crate::winapi::winapi64;

//use crate::windows::constants;
//use crate::winapi::helper;

pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String {
    let api = winapi64::kernel32::guess_api_name(emu, addr);
    let api = api.split("!").last().unwrap_or(&api);
    match api {
        "PathCombineA" => PathCombineA(emu),
        "PathCombineW" => PathCombineW(emu),
        "CharLowerBuffW" => CharLowerBuffW(emu),
        "IsCharAlphaNumericA" => IsCharAlphaNumericA(emu),
        "GetTokenInformation" => GetTokenInformation(emu),
        "GetFileVersionInfoSizeA" => GetFileVersionInfoSizeA(emu),
        "GetFileVersionInfoA" => GetFileVersionInfoA(emu),
        "VerQueryValueA" => VerQueryValueA(emu),
        "_initterm_e" => _initterm_e(emu),
        "_initterm" => _initterm(emu),
        "exit" => exit(emu),
        "_exit" => _exit(emu),
        "atexit" => atexit(emu),
        "SetUnhandledExceptionFilter" => SetUnhandledExceptionFilter(emu),
        "LocalAlloc" => LocalAlloc(emu),

        _ => {
            // kernelbase is bridge to kernel32
            winapi64::kernel32::gateway(addr, emu);
        }
    }

    String::new()
}

pub fn PathCombineA(emu: &mut emu::Emu) {
    let dst: u64 = emu.regs().rcx;
    let dir = emu.regs().rdx;
    let file = emu.regs().r8;

    let mut path1 = String::new();
    let mut path2 = String::new();

    if dir > 0 {
        path1 = emu.maps.read_string(dir);
    }
    if file > 0 {
        path2 = emu.maps.read_string(file);
    }

    log_red!(
        emu,
        "kernelbase!PathCombineA path1: {} path2: {}",
        path1,
        path2
    );

    if dst != 0 && !path1.is_empty() && !path2.is_empty() {
        emu.maps.write_string(dst, &format!("{}\\{}", path1, path2));
    }

    emu.regs_mut().rax = dst;
}

pub fn PathCombineW(emu: &mut emu::Emu) {
    let dst: u64 = emu.regs().rcx;
    let dir = emu.regs().rdx;
    let file = emu.regs().r8;

    let mut path1 = String::new();
    let mut path2 = String::new();

    if dir > 0 {
        path1 = emu.maps.read_wide_string(dir);
    }
    if file > 0 {
        path2 = emu.maps.read_wide_string(file);
    }

    log_red!(
        emu,
        "kernelbase!PathCombineW path1: {} path2: {}",
        path1,
        path2
    );

    if dst != 0 && !path1.is_empty() && !path2.is_empty() {
        emu.maps
            .write_wide_string(dst, &format!("{}\\{}", path1, path2));
    }

    emu.regs_mut().rax = dst;
}

pub fn IsCharAlphaNumericA(emu: &mut emu::Emu) {
    let c = emu.regs().rcx as u8 as char;

    log_red!(emu, "kernelbase!IsCharAlphaNumericA char: {}", c);

    emu.regs_mut().rax = if c.is_ascii_alphanumeric() { 1 } else { 0 };
}

pub fn GetTokenInformation(emu: &mut emu::Emu) {
    let token_handle = emu.regs().rdx;
    let token_information_class = emu.regs().rcx;
    let token_information = emu.regs().r8;
    let token_information_length = emu.regs().r9;
    let return_length = emu.maps.read_qword(emu.regs().rsp + 0x20);

    log_red!(
        emu,
        "kernelbase!GetTokenInformation token_information_class: 0x{:x}",
        token_information_class
    );

    emu.regs_mut().rax = 1;
}

/*
DWORD GetFileVersionInfoSizeA(
  [in]            LPCSTR  lptstrFilename,
  [out, optional] LPDWORD lpdwHandle
);
*/
fn GetFileVersionInfoSizeA(emu: &mut emu::Emu) {
    let lptstr_filename = emu.regs().rcx;
    let lpdw_handle = emu.regs().rdx as usize;

    let filename = if lptstr_filename > 0 {
        emu.maps.read_string(lptstr_filename)
    } else {
        "unknown".to_string()
    };

    log_red!(
        emu,
        "** {} kernelbase!GetFileVersionInfoSizeA filename: {} lpdw_handle: 0x{:x}",
        emu.pos,
        filename,
        lpdw_handle
    );

    if filename == "comctl32.dll" {
        let dll_path = format!("{}/comctl32.dll", emu.cfg.maps_folder);
        let metadata = std::fs::metadata(dll_path).unwrap();
        let file_size = metadata.len() as u64;
        emu.regs_mut().rax = file_size;
    } else {
        unimplemented!("TODO: {}", filename);
    }
}

/*
BOOL GetFileVersionInfoA(
  [in]  LPCSTR lptstrFilename,
        DWORD  dwHandle,
  [in]  DWORD  dwLen,
  [out] LPVOID lpData
);
*/
fn GetFileVersionInfoA(emu: &mut emu::Emu) {
    let lptstr_filename = emu.regs().rcx;
    let dw_handle = emu.regs().rdx as usize;
    let dw_len = emu.regs().r8 as usize;
    let lp_data = emu.regs().r9 as usize;

    let filename = if lptstr_filename > 0 {
        emu.maps.read_string(lptstr_filename)
    } else {
        "unknown".to_string()
    };

    log_red!(
        emu,
        "** {} kernelbase!GetFileVersionInfoA filename: {} dw_handle: 0x{:x} dw_len: 0x{:x} lp_data: 0x{:x}",
        emu.pos,
        filename,
        dw_handle,
        dw_len,
        lp_data
    );

    if filename == "comctl32.dll" {
        use crate::windows::structures::{VS_FIXEDFILEINFO, VS_VERSIONINFO};

        let mut version_info = VS_VERSIONINFO::new();

        // Set comctl32.dll specific values based on the actual file
        version_info.value = VS_FIXEDFILEINFO {
            dw_signature: 0xFEEF04BD,
            dw_struc_version: 0x00010000,
            dw_file_version_ms: 0x0006000A,    // 6.10
            dw_file_version_ls: 0x585D11BD,    // 22621.4541
            dw_product_version_ms: 0x000A0000, // 10.0
            dw_product_version_ls: 0x585D11BD, // 22621.4541
            dw_file_flags_mask: 0x0000003F,
            dw_file_flags: 0x00000000,
            dw_file_os: 0x00040004,   // VOS_NT_WINDOWS32
            dw_file_type: 0x00000002, // VFT_DLL
            dw_file_subtype: 0x00000000,
            dw_file_date_ms: 0x00000000,
            dw_file_date_ls: 0x00000000,
        };

        version_info.write(emu, lp_data as u64);

        emu.regs_mut().rax = 1; // Success
    } else {
        unimplemented!("TODO: {}", filename);
    }
}

/*
BOOL VerQueryValueA(
  [in]  LPCVOID pBlock,
  [in]  LPCSTR  lpSubBlock,
  [out] LPVOID  *lplpBuffer,
  [out] PUINT   puLen
);
*/
fn VerQueryValueA(emu: &mut emu::Emu) {
    let p_block = emu.regs().rcx as usize;
    let lp_sub_block = emu.regs().rdx;
    let lplp_buffer = emu.regs().r8 as usize;
    let pu_len = emu.regs().r9 as usize;

    let sub_block = if lp_sub_block > 0 {
        emu.maps.read_string(lp_sub_block)
    } else {
        "\\".to_string()
    };

    log_red!(
        emu,
        "** {} kernelbase!VerQueryValueA p_block: 0x{:x} lp_sub_block: {} lplp_buffer: 0x{:x} pu_len: 0x{:x}",
        emu.pos,
        p_block,
        sub_block,
        lplp_buffer,
        pu_len
    );

    if sub_block == "\\" {
        // Root query returns pointer to VS_FIXEDFILEINFO
        // The VS_FIXEDFILEINFO starts at offset 0x28 in the version block
        let fixed_info_ptr = (p_block + 0x28) as u64;
        emu.maps.write_qword(lplp_buffer as u64, fixed_info_ptr);
        emu.maps.write_dword(pu_len as u64, 52); // Size of VS_FIXEDFILEINFO
        emu.regs_mut().rax = 1;
    } else if sub_block.starts_with("\\StringFileInfo\\") {
        // String queries - allocate and return string data
        let string_data = match sub_block.as_str() {
            "\\StringFileInfo\\040904B0\\CompanyName" => "Microsoft Corporation\0",
            "\\StringFileInfo\\040904B0\\FileDescription" => "User Experience Controls Library\0",
            "\\StringFileInfo\\040904B0\\FileVersion" => "6.10 (WinBuild.160101.0800)\0",
            "\\StringFileInfo\\040904B0\\InternalName" => "comctl32\0",
            "\\StringFileInfo\\040904B0\\LegalCopyright" => {
                "© Microsoft Corporation. All rights reserved.\0"
            }
            "\\StringFileInfo\\040904B0\\OriginalFilename" => "comctl32.DLL\0",
            "\\StringFileInfo\\040904B0\\ProductName" => "Microsoft® Windows® Operating System\0",
            "\\StringFileInfo\\040904B0\\ProductVersion" => "10.0.22621.4541\0",
            _ => "\0",
        };

        let string_addr = emu
            .maps
            .alloc(string_data.len() as u64)
            .expect("out of memory");
        emu.maps.write_string(string_addr, string_data);
        emu.maps.write_qword(lplp_buffer as u64, string_addr);
        emu.maps
            .write_dword(pu_len as u64, string_data.len() as u32);
        emu.regs_mut().rax = 1;
    } else if sub_block == "\\VarFileInfo\\Translation" {
        // Translation array
        let trans_addr = emu.maps.alloc(4).expect("out of memory");
        emu.maps.write_dword(trans_addr, 0x04B00409); // Language and codepage
        emu.maps.write_qword(lplp_buffer as u64, trans_addr);
        emu.maps.write_dword(pu_len as u64, 4);
        emu.regs_mut().rax = 1;
    } else {
        log::trace!("VerQueryValueA: Unknown sub_block: {}", sub_block);
        emu.regs_mut().rax = 0; // Failure
    }
}

fn _initterm_e(emu: &mut emu::Emu) {
    log_red!(emu, "kernelbase!_initterm_e");
    emu.regs_mut().rax = 0;
}

fn _initterm(emu: &mut emu::Emu) {
    log_red!(emu, "kernelbase!_initterm");
    emu.regs_mut().rax = 0;
}

fn exit(emu: &mut emu::Emu) {
    log_red!(emu, "kernelbase!exit");
    emu.process_terminated = true;
}

fn _exit(emu: &mut emu::Emu) {
    log_red!(emu, "kernelbase!_exit");
    emu.process_terminated = true;
}

fn atexit(emu: &mut emu::Emu) {
    let fptr = emu.regs().rcx;
    log_red!(emu, "kernelbase!atexit fptr: 0x{:x}", fptr);
    emu.regs_mut().rax = 0;
}

/*
DWORD CharLowerBuffW(
  [in, out] LPWSTR lpsz,
  [in]      DWORD  cchLength
);
*/
pub fn CharLowerBuffW(emu: &mut emu::Emu) {
    let lpsz = emu.regs().rcx; // Buffer pointer (LPWSTR)
    let cch_length = emu.regs().rdx; // Length in characters (DWORD)

    log_red!(
        emu,
        "kernelbase!CharLowerBuffW lpsz: 0x{:x} cchLength: {}",
        lpsz,
        cch_length
    );

    if lpsz == 0 || cch_length == 0 {
        emu.regs_mut().rax = 0;
        return;
    }

    let mut processed_count = 0;

    // Process each character in the buffer
    for i in 0..cch_length {
        let char_addr = lpsz + (i * 2); // Each wide character is 2 bytes

        if let Some(wide_char) = emu.maps.read_word(char_addr) {
            // Convert UTF-16 code unit to char for processing
            if let Some(unicode_char) = char::from_u32(wide_char as u32) {
                // Convert to lowercase
                let lowercase_char = unicode_char.to_lowercase().next().unwrap_or(unicode_char);

                // Convert back to UTF-16 code unit
                let mut utf16_buf = [0u16; 2];
                let utf16_encoded = lowercase_char.encode_utf16(&mut utf16_buf);

                // Write the lowercase character back (take first code unit for BMP characters)
                let lowercase_code_unit = utf16_encoded[0];
                emu.maps.write_word(char_addr, lowercase_code_unit);

                processed_count += 1;
            } else {
                // Invalid Unicode, but still count as processed
                processed_count += 1;
            }
        } else {
            // Couldn't read memory, break early
            break;
        }
    }

    log_red!(
        emu,
        "CharLowerBuffW processed {} characters",
        processed_count
    );

    // Return the number of characters processed
    emu.regs_mut().rax = processed_count;
}

fn SetUnhandledExceptionFilter(emu: &mut emu::Emu) {
    let ptr1 = emu.regs().rcx;

    log::trace!(
        "{}** {} kernelbase!SetUnhandledExceptionFilter 0x{:x} {}",
        emu.colors.light_red,
        emu.pos,
        ptr1,
        emu.colors.nc
    );

    emu.set_uef(ptr1);
    emu.regs_mut().rax = 0;
}

fn LocalAlloc(emu: &mut emu::Emu) {
    let flags = emu.regs().rcx;
    let size = emu.regs().rdx;

    let addr = emu.maps.alloc(size).unwrap_or_default();

    log_red!(emu, "kernelbase!LocalAlloc {} =0x{:x}", size, addr);

    emu.stack_pop32(false);
    emu.stack_pop32(false);
    emu.regs_mut().rax = addr;
}