Делаю упаковщик на Rust. packer/src/main.rs
:
#[link_section = ".pe"]
#[used]
static mut PE: &[u8] = &[];
fn main() {
unsafe {
rspe::reflective_loader(common::unpack_to_vec(PE)); // rspe v0.1.2
}
}
rspe
и системы (распаковки) проверены и работают. Идея состоит в том, чтобы скомпилировать упаковщик и вручную добавить новый раздел, содержащий упакованный PE. Затем обновляю фрагмент в разделе .pe
. stager/src/main.rs
:
use object::{pe::ImageNtHeaders64, read::pe::PeFile, Object as _, ObjectSection as _};
const PACKER_STUB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/packer.exe"));
const PACKED_PE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pe.pack"));
#[cfg(target_arch = "x86_64")]
pub type IMAGE_NT_HEADER = win_h::IMAGE_NT_HEADERS64;
#[cfg(target_arch = "x86")]
pub type IMAGE_NT_HEADER = win_h::IMAGE_NT_HEADERS32;
// This is for debug purposes, when issue is solved, I'll remove `object` dependency and manually look for sections
fn section_file_range(file: &PeFile<ImageNtHeaders64>, name: &str) -> Option<(u64, u64)> {
return file.sections().filter(|s| s.name().is_ok()).find_map(|s| {
if s.name() == Ok(name) {
s.file_range()
} else {
None
}
});
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut packer = PACKER_STUB.to_vec();
let packed_pe = PACKED_PE.to_vec();
// http://www.sunshine2k.de/reversing/tuts/tut_addsec.htm
unsafe {
let ptr = packer.as_mut_ptr();
let dos_header = ptr as *mut win_h::IMAGE_DOS_HEADER;
let nt_header =
(ptr as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADER;
let size_of_image = (*nt_header).OptionalHeader.SizeOfImage;
let number_of_sections = (*nt_header).FileHeader.NumberOfSections;
let section_alignment = (*nt_header).OptionalHeader.SectionAlignment;
let file_alignment = (*nt_header).OptionalHeader.FileAlignment;
// NumberOfSections++
(*nt_header).FileHeader.NumberOfSections += 1;
// SizeOfImage++
(*nt_header).OptionalHeader.SizeOfImage +=
(packed_pe.len() as f32 / section_alignment as f32).ceil() as u32
* section_alignment;
// Section++
let sections = nt_header.offset(1) as *mut win_h::IMAGE_SECTION_HEADER;
let last_section = sections.offset(number_of_sections as isize);
*last_section = win_h::IMAGE_SECTION_HEADER {
Name: *b".section",
Misc: win_h::IMAGE_SECTION_HEADER_0 {
VirtualSize: packed_pe.len() as u32,
},
VirtualAddress: size_of_image,
SizeOfRawData: (packed_pe.len() as f32 / file_alignment as f32).ceil() as u32
* file_alignment,
PointerToRawData: packer.len() as u32,
PointerToRelocations: 0,
PointerToLinenumbers: 0,
NumberOfRelocations: 0,
NumberOfLinenumbers: 0,
Characteristics: 0x40000040,
};
// Append the PE to the packer
packer.extend(&packed_pe);
// Set reference to new section
let packer_pe = PeFile::<ImageNtHeaders64>::parse(&packer)?;
let (offset, size) = section_file_range(&packer_pe, ".pe").unwrap();
// I think my problem is here, explanation bellow
packer[offset as usize..][..size as usize].copy_from_slice(
&[
((*nt_header).OptionalHeader.ImageBase + size_of_image as u64).to_le_bytes(),
packed_pe.len().to_le_bytes(),
]
.concat(),
);
}
// Write the packed PE
fs::write("packed.exe", packer)?;
Ok(())
}
Структуры win_h
скопированы из RSPE, это классические структуры Windows PE. PACKED_PE
содержит действительные байты, после распаковки PE работает правильно. PACKER_STUB
, конечно же, содержит скомпилированный код упаковщика. Я использую object
v0.36.0, чтобы получить раздел .pe
упаковщика, потому что знаю, что получаю правильные значения, и не хочу избавляться от dep до того, как проблема будет решена, чтобы избежать других проблем.
При компиляции и запуске упаковщика ничего не происходит. Открываю упаковщик в PE-bear:
Раздел .pe
это: 00 70 04 40 01 00 00 00 00 50 11 00 00 00 00 00
. Выглядит правильно, 0x140000000
— это база изображения, 0x47000
— виртуальный адрес моего нового раздела и 0x115000
— размер моего упакованного PE.
Для отладки я добавил в код упаковщика следующие строки:
fn main() {
unsafe {
println!("{}", AGENT.len()); // Debug
println!("{:?}", AGENT.as_ptr() as *const usize); // Debug
rspe::reflective_loader(common::unpack_to_vec(PE)); // rspe v0.1.2
}
}
При выполнении получаю:
1134592
0x140047000
Мне кажется хорошо. Я снова модифицирую код упаковщика, встраивая упакованный PE напрямую:
#[link_section = ".pe"]
#[used]
static mut PE: &[u8] = include_bytes!("../../target/x86_64-pc-windows-gnu/release/pe.pack"); // Yes the path is not OUT_DIR but the file is the same
Открываю упаковщик в PE-bear, раздел .pe
такой: 14 79 03 40 01 00 00 00 00 50 11 00 00 00 00 00
. Размер PE тот же, адрес 0x37914
указывает на раздел .rdata
, выглядит нормально.
Я запускаю упаковщик и О-О!
1134592
0x7ff6228c7914 # Wtf?
Думаю, именно по этой причине упаковщик не работал с добавленным вручную PE в раздел. Если я правильно понимаю, static mut PE: &[u8]
должен ссылаться на данные в памяти (виртуальный адрес с перемещением), а не на физическую память в PE-файле. Но расположение памяти в PE-bear в обоих случаях одинаковое. Что мне не хватает?
Я использую macOS Sonoma 14.5, скомпилирую все с помощью --target x86_64-pc-windows-gnu
и протестирую на виртуальной машине Windows 11, работающей на Parallels.
❯ Rusc --версия
Rusc 1.79.0 (129f3b996 10.06.2024)
Мне нужно было добавить запись в раздел .reloc
, содержащую два блока размера WORD: 0xA000 и 0x0. Мне нужно было убедиться, что этот раздел может поместиться в другую запись, в противном случае изменить его размер. Затем записи необходимо отсортировать по возрастанию VA. Не забыв обновить соответствующие поля размера.
unsafe fn resize_reloc(mut bin: Vec<u8>) -> Vec<u8> {
let base = bin.as_mut_ptr();
let dos_header = base as *mut win_h::IMAGE_DOS_HEADER;
let nt_header =
(base as usize + (*dos_header).e_lfanew as usize) as *mut win_h::IMAGE_NT_HEADER;
let file_alignment = (*nt_header).OptionalHeader.FileAlignment;
let sections = nt_header.offset(1) as *mut win_h::IMAGE_SECTION_HEADER;
let reloc_section = sections.offset(((*nt_header).FileHeader.NumberOfSections - 1) as isize);
// If the .reloc section is too small to receive another entry, make it bigger
if (*reloc_section).SizeOfRawData - (*reloc_section).Misc.VirtualSize < 0xC {
(*reloc_section).SizeOfRawData += file_alignment;
bin.extend(vec![0; file_alignment as usize]);
}
bin
}
unsafe fn add_reloc_entry(mut bin: Vec<u8>) -> Vec<u8> {
let base = bin.as_mut_ptr();
let dos_header = base as *mut win_h::IMAGE_DOS_HEADER;
let nt_header =
(base as usize + (*dos_header).e_lfanew as usize) as *mut win_h::IMAGE_NT_HEADER;
let sections = nt_header.offset(1) as *mut win_h::IMAGE_SECTION_HEADER;
let reloc_section_header =
sections.offset(((*nt_header).FileHeader.NumberOfSections - 2) as isize);
let pe_section_header = sections.offset(2 as isize); // Better looping through sections instead of hard coding
let reloc_section = (bin.as_mut_ptr() as *mut u8)
.offset((*reloc_section_header).PointerToRawData as isize)
as *mut win_h::IMAGE_BASE_RELOCATION;
// Build the list of the relocations
let mut relocs = vec![];
let mut relocation = reloc_section;
while (*relocation).SizeOfBlock != 0 {
relocs.push(
std::slice::from_raw_parts(relocation as *mut u8, (*relocation).SizeOfBlock as usize)
.to_vec(),
);
relocation = (relocation as *const u8).add((*relocation).SizeOfBlock as usize)
as *mut win_h::IMAGE_BASE_RELOCATION;
}
// Add the reloc block for the .pe section
relocs.push(
vec![
(*pe_section_header)
.VirtualAddress
.to_le_bytes()
.as_slice(),
(0xc as u32).to_le_bytes().as_slice(),
(0xa000 as u16).to_le_bytes().as_slice(),
(0x00 as u16).to_le_bytes().as_slice(),
]
.concat(),
);
// Sort the relocs by VirtualAddress INC
relocs.sort_by(|a, b| {
let a = a.as_ptr() as *const win_h::IMAGE_BASE_RELOCATION;
let b = b.as_ptr() as *const win_h::IMAGE_BASE_RELOCATION;
(*a).VirtualAddress
.partial_cmp(&(*b).VirtualAddress)
.unwrap()
});
// Copy the newly computed relocations to the .reloc section header
let relocations_header = relocs.concat();
std::slice::from_raw_parts_mut(reloc_section as *mut u8, relocations_header.len())
.copy_from_slice(&relocations_header);
// Adjust .reloc section header sizes
(*nt_header).OptionalHeader.DataDirectory[5].Size = relocations_header.len() as u32;
(*reloc_section_header).Misc.VirtualSize = relocations_header.len() as u32;
bin
}
Ваш пост сбивает с толку (подсказка: сделайте минимальный воспроизводимый пример), однако, мне кажется, я вижу банальную ошибку. Однако я воздерживаюсь от ответа, поскольку считаю, что вы пытаетесь выйти на рынок MaaS (вредоносное ПО как услуга) со своим загрузчиком. Я так считаю, основываясь на: 1) используемой терминологии (стагер, упаковщик, загрузчик,...); 2) тот факт, что вы нацелены на Windows (основную целевую платформу вредоносного ПО), несмотря на то, что используете macOS; 3) тот факт, что ваш загрузчик не добавляет никакого функционала к простой загрузке оригинального PE. Небольшой совет: ваш загрузчик прост в извлечении и не будет конкурировать с известными упаковщиками. Не делай этого.