- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 658
- Реакции
- 18
Решил пощупать мануал маппинг на «крабе». В целом, Rust сейчас в мете, но нормальных реализаций мапперов под геймхакинг не так много. Столкнулся с классикой: лоадер отрабатывает чисто, память выделяется, но таргет (в моем случае Rust) ловит краш или ошибки при записи.
Суть текущей реализации — честный маппинг с парсингом секций, ребазированием и выполнением шелл-кода через CreateRemoteThread. Секции парсятся корректно, но есть подозрение, что либо в контексте лоадера беда с указателями, либо WriteProcessMemory спотыкается на специфических регионах.
Сурс самого инжектора:
Главная головная боль сейчас — шелл-код. Реализовал ручную обработку релокаций (Base Relocation), резолв импортов через LoadLibraryA / GetProcAddress и вызов TLS-колбеков. Однако, при исполнении `DllMain` таргет часто улетает в BSOD или просто закрывается без логов.
Код шелл-кода (Loader):
Технические моменты:
Есть подозрение, что WriteProcessMemory падает, когда пытается писать в секции, которые маппер по ошибке считает доступными. Плюс, работа с `LoaderContext` в удаленном процессе на Rust — это всегда ходьба по тонкому льду из-за специфики владения памятью и лайфтаймов.
Кто копал в сторону Manual Mapping на Rust, посмотрите структуру парсинга — возможно, я где-то знатно промахнулся с оффсетами в OptionalHeader.
Суть текущей реализации — честный маппинг с парсингом секций, ребазированием и выполнением шелл-кода через CreateRemoteThread. Секции парсятся корректно, но есть подозрение, что либо в контексте лоадера беда с указателями, либо WriteProcessMemory спотыкается на специфических регионах.
Сурс самого инжектора:
Код:
#[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):
Код:
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 _;
}
Технические моменты:
- Использую PAGE_EXECUTE_READWRITE для всех аллокаций. Да, палевно для EAC, но для тестов андетекта пока сойдет.
- Релокации: считаю дельту между реальной базой и `ImageBase` из заголовка.
- Импорты: прохожусь по всем дескрипторам, подгружаю либы в контекст таргета.
Есть подозрение, что WriteProcessMemory падает, когда пытается писать в секции, которые маппер по ошибке считает доступными. Плюс, работа с `LoaderContext` в удаленном процессе на Rust — это всегда ходьба по тонкому льду из-за специфики владения памятью и лайфтаймов.
Кто копал в сторону Manual Mapping на Rust, посмотрите структуру парсинга — возможно, я где-то знатно промахнулся с оффсетами в OptionalHeader.