libmwemu 0.25.4

x86 32/64bits and system internals emulator, for securely emulating malware and other stuff.
Documentation
use crate::tests::helpers;
use crate::windows::structures;
use crate::*;
use std::collections::HashSet;
use std::path::Path;

const CORE_MODULES: [&str; 4] = ["loader.exe", "ntdll.dll", "kernel32.dll", "kernelbase.dll"];

fn assert_module_image(emu: &Emu, name: &str, base: u64) {
    assert!(base != 0, "{} must have a nonzero DllBase", name);
    assert_eq!(
        emu.maps.read_word(base),
        Some(0x5a4d),
        "{} DllBase 0x{:x} must point to an MZ image",
        name,
        base
    );

    let map_name = format!(
        "{}.pe",
        name.strip_suffix(".dll")
            .or_else(|| name.strip_suffix(".exe"))
            .unwrap_or(name)
    );
    let map = emu
        .maps
        .get_map_by_name(&map_name)
        .unwrap_or_else(|| panic!("{} must have map {}", name, map_name));
    assert_eq!(
        map.get_base(),
        base,
        "{} LDR DllBase must match {} base",
        name,
        map_name
    );
}

fn assert_x64_loader_order(emu: &Emu) {
    let peb_map = emu.maps.get_mem("peb");
    let peb_addr = peb_map.get_base();
    let ldr_map = emu.maps.get_mem("ldr");
    let ldr_addr = ldr_map.get_base();
    let peb = structures::PEB64::load(peb_addr, &emu.maps);
    let ldr = structures::PebLdrData64::load(ldr_addr, &emu.maps);

    assert_eq!(peb.ldr, ldr_addr, "PEB.Ldr must point to the LDR map");
    assert_ne!(ldr.initializated, 0, "PEB_LDR_DATA must be initialized");

    let sentinel = ldr_addr + 0x10;
    let mut current = ldr.in_load_order_module_list.flink;
    let mut visited = HashSet::new();
    let mut names = Vec::new();

    while current != sentinel {
        assert!(current != 0, "x64 LDR list reached a null entry");
        assert!(visited.insert(current), "x64 LDR list contains a cycle");
        assert!(
            visited.len() <= 4096,
            "x64 LDR list exceeded the traversal guard"
        );

        let entry = structures::LdrDataTableEntry64::load(current, &emu.maps);
        let name = emu.maps.read_wide_string(entry.base_dll_name.buffer);
        assert_eq!(
            entry.in_memory_order_links.flink,
            entry.in_load_order_links.flink + 0x10,
            "x64 {} memory-order Flink is inconsistent",
            name
        );
        assert_eq!(
            entry.in_initialization_order_links.flink,
            entry.in_load_order_links.flink + 0x20,
            "x64 {} initialization-order Flink is inconsistent",
            name
        );
        assert_eq!(
            entry.in_memory_order_links.blink,
            entry.in_load_order_links.blink + 0x10,
            "x64 {} memory-order Blink is inconsistent",
            name
        );
        assert_eq!(
            entry.in_initialization_order_links.blink,
            entry.in_load_order_links.blink + 0x20,
            "x64 {} initialization-order Blink is inconsistent",
            name
        );

        if names.len() < CORE_MODULES.len() {
            assert_eq!(
                name,
                CORE_MODULES[names.len()],
                "x64 LDR prefix so far: {:?}",
                names
            );
            assert_module_image(emu, &name, entry.dll_base);
        }
        names.push(name);
        current = entry.in_load_order_links.flink;
    }

    assert!(
        names.len() >= CORE_MODULES.len(),
        "x64 LDR list has only {:?}; expected the core prefix {:?}",
        names,
        CORE_MODULES
    );
}

fn assert_x86_loader_order(emu: &Emu) {
    let peb_map = emu.maps.get_mem("peb");
    let peb_addr = peb_map.get_base();
    let ldr_map = emu.maps.get_mem("ldr");
    let ldr_addr = ldr_map.get_base();
    let peb = structures::PEB::load(peb_addr, &emu.maps);
    let ldr = structures::PebLdrData::load(ldr_addr, &emu.maps);

    assert_eq!(
        peb.ldr as u64, ldr_addr,
        "PEB.Ldr must point to the LDR map"
    );
    assert_ne!(ldr.initializated, 0, "PEB_LDR_DATA must be initialized");

    let first = ldr.in_load_order_module_list.flink as u64;
    assert_ne!(first, 0, "x86 LDR list must have a first entry");
    let mut current = ldr.in_load_order_module_list.flink as u64;
    let mut visited = HashSet::new();
    let mut names = Vec::new();
    let mut last_entry = 0;

    loop {
        assert!(current != 0, "x86 LDR list reached a null entry");
        if current == first && !names.is_empty() {
            break;
        }
        assert!(
            visited.insert(current),
            "x86 LDR list contains an invalid cycle"
        );
        assert!(
            visited.len() <= 4096,
            "x86 LDR list exceeded the traversal guard"
        );

        let entry = structures::LdrDataTableEntry::load(current, &emu.maps);
        last_entry = current;
        let name = emu.maps.read_wide_string(entry.base_dll_name.buffer as u64);
        assert_eq!(
            entry.in_memory_order_links.flink,
            entry.in_load_order_links.flink + 0x08,
            "x86 {} memory-order Flink is inconsistent",
            name
        );
        assert_eq!(
            entry.in_initialization_order_links.flink,
            entry.in_load_order_links.flink + 0x10,
            "x86 {} initialization-order Flink is inconsistent",
            name
        );
        assert_eq!(
            entry.in_memory_order_links.blink,
            entry.in_load_order_links.blink + 0x08,
            "x86 {} memory-order Blink is inconsistent",
            name
        );
        assert_eq!(
            entry.in_initialization_order_links.blink,
            entry.in_load_order_links.blink + 0x10,
            "x86 {} initialization-order Blink is inconsistent",
            name
        );

        if names.len() < CORE_MODULES.len() {
            assert_eq!(
                name,
                CORE_MODULES[names.len()],
                "x86 LDR prefix so far: {:?}",
                names
            );
            assert_module_image(emu, &name, entry.dll_base as u64);
        }
        names.push(name);
        current = entry.in_load_order_links.flink as u64;
    }

    assert_eq!(
        ldr.in_load_order_module_list.blink as u64, last_entry,
        "x86 load-order list head Blink must point to the tail"
    );

    assert!(
        names.len() >= CORE_MODULES.len(),
        "x86 LDR list has only {:?}; expected the core prefix {:?}",
        names,
        CORE_MODULES
    );
}

#[test]
fn loader_exe_peb_module_order_x86() {
    helpers::setup();

    let mut emu = emu32();
    emu.cfg.maps_folder = helpers::win32_maps_folder();
    let loader = Path::new(&emu.cfg.maps_folder).join("loader.exe");
    assert!(
        loader.is_file(),
        "missing support executable: {}",
        loader.display()
    );

    emu.load_code(loader.to_str().expect("loader path must be UTF-8"));
    assert_x86_loader_order(&emu);
}

#[test]
fn loader_exe_peb_module_order_x64() {
    helpers::setup();

    let mut emu = emu64();
    emu.cfg.maps_folder = helpers::win64_maps_folder();
    let loader = Path::new(&emu.cfg.maps_folder).join("loader.exe");
    assert!(
        loader.is_file(),
        "missing support executable: {}",
        loader.display()
    );

    emu.load_code(loader.to_str().expect("loader path must be UTF-8"));
    assert_x64_loader_order(&emu);
}