Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Вопрос [Сурс] Rust — Manual Mapper на языке Rust: разбираем код и фиксим краши

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
658
Реакции
18
Решил пощупать мануал маппинг на «крабе». В целом, Rust сейчас в мете, но нормальных реализаций мапперов под геймхакинг не так много. Столкнулся с классикой: лоадер отрабатывает чисто, память выделяется, но таргет (в моем случае Rust) ловит краш или ошибки при записи.

Суть текущей реализации — честный маппинг с парсингом секций, ребазированием и выполнением шелл-кода через CreateRemoteThread. Секции парсятся корректно, но есть подозрение, что либо в контексте лоадера беда с указателями, либо WriteProcessMemory спотыкается на специфических регионах.

Сурс самого инжектора:
Код:
Expand Collapse Copy
#[allow(clippy::missing_safety_doc)]
pub unsafe fn image_first_section(nt_headers: PIMAGE_NT_HEADERS) -> PIMAGE_SECTION_HEADER {
    (nt_headers as *mut c_void)
        .add(mem::offset_of!(IMAGE_NT_HEADERS, OptionalHeader))
        .add((*nt_headers).FileHeader.SizeOfOptionalHeader as usize) as _
}

pub unsafe fn get_sections<'a>(p_nt_headers: PIMAGE_NT_HEADERS) -> Vec<Section<'a>> {
    let mut sections = Vec::new();
    let mut p_section_header = image_first_section(p_nt_headers);
     for _ in (0..(*p_nt_headers).FileHeader.NumberOfSections).rev() {
        let section = Section::new(
            (*p_section_header).VirtualAddress,
            (*p_section_header).SizeOfRawData as _,
            (*p_section_header).PointerToRawData as _,
            String::from_utf8_lossy(&(*p_section_header).Name),
        );
        sections.push(section);
        p_section_header = p_section_header.add(1);
    }
    sections
}

pub unsafe fn inject(payload_path: &str, wpinf: &WindowedProcessInformation) -> Result<InjectedModule> {
    let payload_data = fs::read(payload_path)?;
    let payload_size = payload_data.len();
    let h_process = handle::open_handle(wpinf.pid())?;
    let (_, p_nt_headers) = dll::get_dll_headers(&payload_data)?;
    let image_base = (*p_nt_headers).OptionalHeader.ImageBase as *mut u64 as _;

    let mut p_base_alloc = VirtualAllocEx(h_process, image_base, payload_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if p_base_alloc.is_null() {
        p_base_alloc = VirtualAllocEx(h_process, null_mut(), 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    }

    for section in get_sections(p_nt_headers) {
        if section.raw_data_size == 0 { continue; }
        let rebase_addr = p_base_alloc.add(section.virtual_address as usize);
        WriteProcessMemory(h_process, rebase_addr, payload_data.as_ptr().add(section.ptr_to_raw_data as usize) as _, section.raw_data_size, &mut 0);
    }
    // ... дальше инициализация контекста и запуск RemoteThread
    Ok(InjectedModule::new(h_process, None, Some(p_base_alloc)))
}

Главная головная боль сейчас — шелл-код. Реализовал ручную обработку релокаций (Base Relocation), резолв импортов через LoadLibraryA / GetProcAddress и вызов TLS-колбеков. Однако, при исполнении `DllMain` таргет часто улетает в BSOD или просто закрывается без логов.

Код шелл-кода (Loader):
Код:
Expand Collapse Copy
unsafe fn shellcode(ctx: &mut LoaderContext) {
    let nt_headers = *ctx.nt_headers;
    let opt_header = nt_headers.OptionalHeader;
    let p_base: LPVOID = ctx as *mut LoaderContext as _;
    let dll_main = p_base.add(opt_header.AddressOfEntryPoint as usize) as *mut _DllMain;
    let location_delta: *mut u8 = (p_base as u64 - opt_header.ImageBase) as *mut _;

    if !location_delta.is_null() {
        let reloc_data = opt_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize];
        if reloc_data.Size > 0 {
            let mut p_reloc_data: PIMAGE_BASE_RELOCATION = p_base.add(reloc_data.VirtualAddress as usize) as *mut _;
            // Цикл по блокам релокаций...
        }
    }

    let import_data = opt_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT as usize];
    if import_data.Size > 0 {
        let mut p_import_desc = p_base.add(import_data.VirtualAddress as usize) as *mut IMAGE_IMPORT_DESCRIPTOR;
        while !(*p_import_desc).Name == 0 {
            let module_name = p_base.add((*p_import_desc).Name as usize) as *mut i8;
            let h_mod = (ctx.p_load_library)(module_name);
            // Резолв функций...
            p_import_desc = p_import_desc.add(1);
        }
    }
    (*dll_main)(p_base as _, DLL_PROCESS_ATTACH, null_mut());
    ctx.h_mod = p_base as _;
}

Технические моменты:
  1. Использую PAGE_EXECUTE_READWRITE для всех аллокаций. Да, палевно для EAC, но для тестов андетекта пока сойдет.
  2. Релокации: считаю дельту между реальной базой и `ImageBase` из заголовка.
  3. Импорты: прохожусь по всем дескрипторам, подгружаю либы в контекст таргета.

Есть подозрение, что WriteProcessMemory падает, когда пытается писать в секции, которые маппер по ошибке считает доступными. Плюс, работа с `LoaderContext` в удаленном процессе на Rust — это всегда ходьба по тонкому льду из-за специфики владения памятью и лайфтаймов.

Кто копал в сторону Manual Mapping на Rust, посмотрите структуру парсинга — возможно, я где-то знатно промахнулся с оффсетами в OptionalHeader.
 
Ключевые моменты:

проверяли выравнивание секций?
логируете ошибки WriteProcessMemory?
пробовали VirtualProtect перед записью?
как обрабатывается стек в шелл-коде?
Думаю, проблема может быть в защите целевого процесса (например, EAC перехватывает записи).»
 
Назад
Сверху Снизу