Неверная ссылка на фрагмент PE-файла в добавленном вручную разделе

Делаю упаковщик на 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:

  • FileHeader.NumberOfSections верен (12, раньше было 11)
  • НеобязательныйHeader.SizeOfImage верен (0x15C000).
  • Созданный вручную IMAGE_SECTION_HEADER также выглядит правильно (адреса добавляются правильно)

Раздел .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)

Ваш пост сбивает с толку (подсказка: сделайте минимальный воспроизводимый пример), однако, мне кажется, я вижу банальную ошибку. Однако я воздерживаюсь от ответа, поскольку считаю, что вы пытаетесь выйти на рынок MaaS (вредоносное ПО как услуга) со своим загрузчиком. Я так считаю, основываясь на: 1) используемой терминологии (стагер, упаковщик, загрузчик,...); 2) тот факт, что вы нацелены на Windows (основную целевую платформу вредоносного ПО), несмотря на то, что используете macOS; 3) тот факт, что ваш загрузчик не добавляет никакого функционала к простой загрузке оригинального PE. Небольшой совет: ваш загрузчик прост в извлечении и не будет конкурировать с известными упаковщиками. Не делай этого.

Margaret Bloom 21.06.2024 13:01
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
1
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Мне нужно было добавить запись в раздел .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
}

Другие вопросы по теме