This commit is contained in:
nora 2025-02-01 16:05:27 +01:00
parent dc6ef2108d
commit cec97ff44d
3 changed files with 192 additions and 54 deletions

View file

@ -4,6 +4,7 @@ mod sys;
use std::{ use std::{
ffi::CStr, ffi::CStr,
fmt::Debug, fmt::Debug,
ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -76,7 +77,7 @@ struct OptionalHeader {
#[repr(C)] #[repr(C)]
struct DataDirectory { struct DataDirectory {
// RVA // RVA
virtual_address: u32, va: u32,
size: u32, size: u32,
} }
@ -198,13 +199,56 @@ struct ImportDirectoryTableEntry {
import_address_table_rva: u32, import_address_table_rva: u32,
} }
#[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)]
#[repr(C)]
struct ExportDirectoryTable {
export_flags: u32,
time_date_stamp: u32,
major_version: u16,
minor_version: u16,
name_rva: u32,
ordinal_base: u32,
address_table_entries: u32,
number_of_name_pointers: u32,
export_address_table_rva: u32,
name_pointer_rva: u32,
ordinal_table_rva: u32,
}
const _: () = assert!(size_of::<ExportDirectoryTable>() == 40);
const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664;
const IMAGE_FILE_MACHINE_ARM64: u16 = 0xaa64; const IMAGE_FILE_MACHINE_ARM64: u16 = 0xaa64;
pub fn execute(pe: &[u8], executable_path: &Path) { pub fn execute(pe: &[u8], executable_path: &Path) {
let (header, after_header) = parse_header(pe); let image = load(pe, executable_path, false);
match (std::env::consts::ARCH, header.machine) { let entrypoint =
image.opt_header.image_base as usize + image.opt_header.address_of_entry_point as usize;
eprintln!("YOLO to {:#x}", entrypoint);
unsafe {
let result = sys::call_entrypoint_via_stdcall(entrypoint);
eprintln!("result: {result}");
};
}
struct Image<'pe> {
base: usize,
opt_header: &'pe OptionalHeader,
loaded: &'static mut [u8],
}
impl<'pe> Deref for Image<'pe> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.loaded
}
}
fn load<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image<'pe> {
let (coff_header, after_header) = parse_header(pe);
match (std::env::consts::ARCH, coff_header.machine) {
("x86_64", IMAGE_FILE_MACHINE_AMD64) => {} ("x86_64", IMAGE_FILE_MACHINE_AMD64) => {}
("aarch64", IMAGE_FILE_MACHINE_ARM64) => {} ("aarch64", IMAGE_FILE_MACHINE_ARM64) => {}
(arch, machine) => { (arch, machine) => {
@ -212,51 +256,62 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
} }
} }
if !header if !coff_header
.characteristics .characteristics
.contains(Characteristics::IMAGE_FILE_EXECUTABLE_IMAGE) .contains(Characteristics::IMAGE_FILE_EXECUTABLE_IMAGE)
{ {
panic!("unsupported, cannot execute invalid executable") panic!("unsupported, cannot execute invalid executable")
} }
if header if is_dll {
if !coff_header
.characteristics
.contains(Characteristics::IMAGE_FILE_DLL)
{
panic!("unsupported, trying to dll-load an executable")
}
} else {
if coff_header
.characteristics .characteristics
.contains(Characteristics::IMAGE_FILE_DLL) .contains(Characteristics::IMAGE_FILE_DLL)
{ {
panic!("unsupported, cannot execute DLL") panic!("unsupported, cannot execute DLL")
} }
}
if (header.size_of_optional_header as usize) < size_of::<OptionalHeader>() { if (coff_header.size_of_optional_header as usize) < size_of::<OptionalHeader>() {
panic!("file does not have enough of the required optional header (lol)"); panic!("file does not have enough of the required optional header (lol)");
} }
dbg!(header); dbg!(coff_header);
let optional_header: &OptionalHeader = let opt_header: &OptionalHeader =
&bytemuck::cast_slice(&pe[after_header..][..size_of::<OptionalHeader>()])[0]; &bytemuck::cast_slice(&pe[after_header..][..size_of::<OptionalHeader>()])[0];
dbg!(optional_header); dbg!(opt_header);
if optional_header.magic != 0x20b { if opt_header.magic != 0x20b {
panic!("unsupported, only PE32+ is supported"); panic!("unsupported, only PE32+ is supported");
} }
if optional_header.subsystem != 3 { if !is_dll {
if opt_header.subsystem != 3 {
panic!("unsupported, only IMAGE_SUBSYSTEM_WINDOWS_CUI subsystem is supported"); panic!("unsupported, only IMAGE_SUBSYSTEM_WINDOWS_CUI subsystem is supported");
} }
}
if optional_header.number_of_rva_and_sizes < 16 { if opt_header.number_of_rva_and_sizes < 16 {
panic!("unsupported, we want at least 16 data directories") panic!("unsupported, we want at least 16 data directories")
} }
let section_table_offset = after_header + header.size_of_optional_header as usize; let section_table_offset = after_header + coff_header.size_of_optional_header as usize;
let section_table: &[SectionHeader] = bytemuck::cast_slice( let section_table: &[SectionHeader] = bytemuck::cast_slice(
&pe[section_table_offset..] &pe[section_table_offset..]
[..(header.number_of_sections as usize * size_of::<SectionHeader>())], [..(coff_header.number_of_sections as usize * size_of::<SectionHeader>())],
); );
dbg!(section_table); dbg!(section_table);
// let's always load it at the image base for now... // let's always load it at the image base for now...
let base = optional_header.image_base as usize; let base = opt_header.image_base as usize;
let allocation_granularity = crate::sys::allocation_granularity(); let allocation_granularity = crate::sys::allocation_granularity();
@ -266,10 +321,16 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
let total_size = (last_section.virtual_address as usize + last_section.virtual_size as usize) let total_size = (last_section.virtual_address as usize + last_section.virtual_size as usize)
.next_multiple_of(allocation_granularity); .next_multiple_of(allocation_granularity);
let a = unsafe { let loaded = unsafe {
crate::sys::anon_write_map(total_size, std::ptr::with_exposed_provenance(base)).unwrap() crate::sys::anon_write_map(total_size, std::ptr::with_exposed_provenance(base)).unwrap()
}; };
let image = Image {
base,
opt_header,
loaded,
};
// allocate the sections. // allocate the sections.
for section in section_table { for section in section_table {
if section.virtual_size > section.size_of_raw_data { if section.virtual_size > section.size_of_raw_data {
@ -277,7 +338,7 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
} }
eprintln!("mapping section {:?}", section.name); eprintln!("mapping section {:?}", section.name);
let section_a = &mut a[section.virtual_address as usize..]; let section_a = &mut image.loaded[section.virtual_address as usize..];
section_a[..section.size_of_raw_data as usize].copy_from_slice( section_a[..section.size_of_raw_data as usize].copy_from_slice(
&pe[section.pointer_to_raw_data as usize..][..section.size_of_raw_data as usize], &pe[section.pointer_to_raw_data as usize..][..section.size_of_raw_data as usize],
@ -285,8 +346,8 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
} }
let import_directory_table = bytemuck::cast_slice::<_, ImportDirectoryTableEntry>( let import_directory_table = bytemuck::cast_slice::<_, ImportDirectoryTableEntry>(
&a[optional_header.import_table.virtual_address as usize..] &image.loaded[opt_header.import_table.va as usize..]
[..optional_header.import_table.size as usize], [..opt_header.import_table.size as usize],
) )
.to_vec(); .to_vec();
@ -294,7 +355,8 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
for import_directory in import_directory_table { for import_directory in import_directory_table {
dbg!(import_directory); dbg!(import_directory);
let dll_name = CStr::from_bytes_until_nul(&a[import_directory.name_rva as usize..]) let dll_name =
CStr::from_bytes_until_nul(&image.loaded[import_directory.name_rva as usize..])
.unwrap() .unwrap()
.to_owned(); .to_owned();
let dll_name = dll_name.to_str().unwrap(); let dll_name = dll_name.to_str().unwrap();
@ -304,15 +366,32 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
} }
eprintln!("loading dll {dll_name}"); eprintln!("loading dll {dll_name}");
let dll = find_dll(&dll_name, executable_path); enum LoadedDll {
match dll { Emulated,
Some(DllLocation::Emulated) => eprintln!(" emulating {dll_name:?}"), Real(Image<'static>),
Some(DllLocation::Found(path)) => todo!("unsupported, loading dll at {path:?}"),
None => panic!("could not find dll {dll_name:?}"),
} }
let dll = find_dll(&dll_name, executable_path);
let dll = match dll {
Some(DllLocation::Emulated) => {
eprintln!(" emulating {dll_name:?}");
LoadedDll::Emulated
}
Some(DllLocation::Found(path)) => {
let file = std::fs::File::open(&path).unwrap();
// leak the mapping object to get a &'static
let mmap =
std::mem::ManuallyDrop::new(unsafe { memmap2::Mmap::map(&file).unwrap() });
let mmap = unsafe { &*(&**mmap as *const [u8]) };
let image = load(&mmap, &path, true);
LoadedDll::Real(image)
}
None => panic!("could not find dll {dll_name:?}"),
};
let import_lookups = bytemuck::cast_slice::<u8, u64>( let import_lookups = bytemuck::cast_slice::<u8, u64>(
&a[import_directory.import_address_table_rva as usize..], &image.loaded[import_directory.import_address_table_rva as usize..],
) )
.to_vec(); .to_vec();
for (i, import_lookup) in import_lookups.iter().enumerate() { for (i, import_lookup) in import_lookups.iter().enumerate() {
@ -327,19 +406,84 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
todo!("unsupported, import by ordinal"); todo!("unsupported, import by ordinal");
} else { } else {
let hint_name_table_rva = import_lookup & 0xFFFF_FFFF; let hint_name_table_rva = import_lookup & 0xFFFF_FFFF;
let hint = let hint = bytemuck::cast_slice::<u8, u16>(
bytemuck::cast_slice::<u8, u16>(&a[hint_name_table_rva as usize..][..2])[0]; &image.loaded[hint_name_table_rva as usize..][..2],
)[0];
let func_name = let func_name =
CStr::from_bytes_until_nul(&a[hint_name_table_rva as usize + 2..]).unwrap(); CStr::from_bytes_until_nul(&image.loaded[hint_name_table_rva as usize + 2..])
.unwrap();
eprintln!(" import by name: hint={hint} name={func_name:?}"); eprintln!(" import by name: hint={hint} name={func_name:?}");
let resolved_va = emulated::emulate(dll_name, func_name.to_str().unwrap())
let resolved_va = match &dll {
LoadedDll::Emulated => emulated::emulate(dll_name, func_name.to_str().unwrap())
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!("could not find function {func_name:?} in dll {dll_name:?}") panic!("could not find function {func_name:?} in dll {dll_name:?}")
}); }),
LoadedDll::Real(img) => {
// Read the single export directory table from the front
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#export-directory-table
// The export table consists of 3 tables.
// The Export Address Table is indexed by unbiased ordinals and contains the function pointers.
// If you have an ordinal, you need to subtract OrdinalBase to get the actual ordinal.
// If you do not have an ordinal but just have a name, you need to figure out the ordinal from the name.
// To do this, you binary search the asc sorted Name Pointer Table to find the index there.
// Then you look up the found index in the Ordinal Table (which has the same size) and grab the ordinal from there.
// Proceed with that ordinal to the Export Address Table as usual.
// You may be able to skip the binary search of the "hint" of the import is the correct index already.
let export_directory_table = bytemuck::cast_slice::<u8, ExportDirectoryTable>(
&img[img.opt_header.export_table.va as usize..]
[..size_of::<ExportDirectoryTable>()],
)[0];
dbg!(export_directory_table);
// This is not aligned..?
let mut names = vec![];
for name_ptr in (export_directory_table.name_pointer_rva as usize..)
.step_by(4)
.take(export_directory_table.number_of_name_pointers as usize)
{
let name = u32::from_ne_bytes(img[name_ptr..][..4].try_into().unwrap());
let name = CStr::from_bytes_until_nul(&img[name as usize..]).unwrap();
names.push(name);
dbg!(name);
}
let idx = if names.get(hint as usize) == Some(&func_name) {
hint as usize
} else {
names.binary_search(&func_name).unwrap_or_else(|_| {
panic!("could not find function {func_name:?} in dll {dll_name}")
})
};
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#export-ordinal-table
let ordinal_table = bytemuck::cast_slice::<u8, u16>(
&img[export_directory_table.ordinal_table_rva as usize..]
[..2 * export_directory_table.number_of_name_pointers as usize],
);
let unbiased_ordinal = ordinal_table[idx];
let eat_addr_rva = export_directory_table.export_address_table_rva as usize
+ (unbiased_ordinal as usize * 4);
let export_rva =
u32::from_ne_bytes(img[eat_addr_rva..][..4].try_into().unwrap());
if (img.opt_header.export_table.va
..(img.opt_header.export_table.va + img.opt_header.export_table.size))
.contains(&export_rva)
{
todo!("symbol forwarding of {func_name:?} in {dll_name}")
}
img.base + export_rva as usize
}
};
assert_eq!(size_of::<usize>(), size_of::<u64>()); assert_eq!(size_of::<usize>(), size_of::<u64>());
a[import_directory.import_address_table_rva as usize..][i * size_of::<u64>()..] image.loaded[import_directory.import_address_table_rva as usize..]
[..size_of::<u64>()] [i * size_of::<u64>()..][..size_of::<u64>()]
.copy_from_slice(&resolved_va.to_ne_bytes()); .copy_from_slice(&resolved_va.to_ne_bytes());
} }
} }
@ -361,7 +505,7 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
crate::sys::Mode::Read crate::sys::Mode::Read
}; };
let section_a = &a[section.virtual_address as usize..]; let section_a = &image.loaded[section.virtual_address as usize..];
crate::sys::protect( crate::sys::protect(
section_a.as_ptr().cast(), section_a.as_ptr().cast(),
@ -371,14 +515,7 @@ pub fn execute(pe: &[u8], executable_path: &Path) {
.unwrap(); .unwrap();
} }
let entrypoint = image
optional_header.image_base as usize + optional_header.address_of_entry_point as usize;
eprintln!("YOLO to {:#x}", entrypoint);
unsafe {
let result = sys::call_entrypoint_via_stdcall(entrypoint);
eprintln!("result: {result}");
};
} }
fn parse_header(pe: &[u8]) -> (&CoffHeader, usize) { fn parse_header(pe: &[u8]) -> (&CoffHeader, usize) {
@ -405,7 +542,6 @@ fn parse_header(pe: &[u8]) -> (&CoffHeader, usize) {
#[derive(Debug)] #[derive(Debug)]
enum DllLocation { enum DllLocation {
Emulated, Emulated,
#[expect(dead_code)]
Found(PathBuf), Found(PathBuf),
} }

View file

@ -1,3 +1,4 @@
SHELL = bash
RUSTC = rustc --target x86_64-pc-windows-msvc -Cpanic=abort -Clinker=lld-link -Clink-arg=/NODEFAULTLIB -Clink-arg=/debug:none -Cdebuginfo=0 RUSTC = rustc --target x86_64-pc-windows-msvc -Cpanic=abort -Clinker=lld-link -Clink-arg=/NODEFAULTLIB -Clink-arg=/debug:none -Cdebuginfo=0
build: empty_exe.exe one_dll.exe build: empty_exe.exe one_dll.exe
@ -13,4 +14,5 @@ small_dll.dll: small_dll.rs
.PHONY: clean .PHONY: clean
clean: clean:
rm *.exe *.pdb *.dll shopt -s nullglob
rm *.exe *.pdb *.dll *.lib

View file

@ -9,7 +9,7 @@ fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! {
#[no_mangle] #[no_mangle]
pub extern "C" fn my_export() -> u32 { pub extern "C" fn my_export() -> u32 {
42 43
} }
#[no_mangle] #[no_mangle]