Example of how to run a PE32 executable/library?
brandonros opened this issue · 7 comments
Is it possible to like... userspace "trace" an .exe/.dll all the way from its entry point with this library?
What do you mean? This library is a run-time assembler. You mean like implementing a tracing JIT compiler with it?
use std::path::Path;
use pelite::{FileMap};
use pelite::pe64::{Pe, PeFile};
use iced_x86::{Decoder, DecoderOptions, Instruction, Formatter, NasmFormatter};
/*
IMAGE_OPTIONAL_HEADER64 { Magic: 20b, LinkerVersion: "14.28", SizeOfCode: 32e00, SizeOfInitializedData: 1b800, SizeOfUninitializedData: 00, AddressOfEntryPoint: a40327, BaseOfCode: 1000, ImageBase: 180000000, SectionAlignment: 1000, FileAlignment: 200, OperatingSystemVersion: "6.0", ImageVersion: "0.0", SubsystemVersion: "6.0", Win32VersionValue: 00, SizeOfImage: 1674000, SizeOfHeaders: 400, CheckSum: 00, Subsystem: 02, DllCharacteristics: 160, SizeOfStackReserve: 100000, SizeOfStackCommit: 1000, SizeOfHeapReserve: 100000, SizeOfHeapCommit: 1000, LoaderFlags: 00, NumberOfRvaAndSizes: 10, DataDirectory: [] }
SectionHeader { Name: ".Rdf2", VirtualAddress: 0xa11000, VirtualSize: 0xc60694, PointerToRawData: 0x1000, SizeOfRawData: 0xc60800, Characteristics: 0x68000060 }
flatEntryPoint = 0xa40327 - 0xa11000 = 0x2F327
*/
fn main() {
// parse PE
let path = Path::new("/tmp/redacted.dll");
let map = FileMap::open(path).unwrap();
let file = PeFile::from_bytes(&map).unwrap();
println!("{:02x?}", file.optional_header());
for section in file.section_headers() {
println!("{:02x?}", section);
}
let imports = file.imports().unwrap();
for desc in imports {
let iat = desc.iat().unwrap();
let int = desc.int().unwrap();
for (va, import) in Iterator::zip(iat, int) {
println!("{:?} {:02x?} {:?}", desc, va, import);
}
}
// feed entrypoint to disassembler
let flat_entry_point = 0x2F327;
let section = file.section_headers().by_name(".Rdf2").unwrap();
let section_bytes = file.get_section_bytes(§ion).unwrap();
let mut decoder = Decoder::new(64, section_bytes, DecoderOptions::NONE);
decoder.set_ip(flat_entry_point);
decoder.set_position(flat_entry_point as usize).unwrap();
while decoder.can_decode() {
let mut instruction = Instruction::default();
decoder.decode_out(&mut instruction);
let mut output = String::new();
let mut formatter = NasmFormatter::new();
formatter.format(&instruction, &mut output);
print!("{:016X} ", instruction.ip());
let start_index = instruction.ip() as usize;
let instr_bytes = §ion_bytes[start_index..start_index + instruction.len()];
for b in instr_bytes.iter() {
print!("{:02X}", b);
}
if instr_bytes.len() < 10 {
for _ in 0..10 - instr_bytes.len() {
print!(" ");
}
}
println!(" {}", output);
}
}
$ cargo run
Compiling pe32-emulator v0.0.1 (/Users/brandonros/Desktop/pe32-emulator)
Finished dev [unoptimized + debuginfo] target(s) in 1.10s
Running `target/debug/pe32-emulator`
IMAGE_OPTIONAL_HEADER64 { Magic: 20b, LinkerVersion: "14.28", SizeOfCode: 32e00, SizeOfInitializedData: 1b800, SizeOfUninitializedData: 00, AddressOfEntryPoint: a40327, BaseOfCode: 1000, ImageBase: 180000000, SectionAlignment: 1000, FileAlignment: 200, OperatingSystemVersion: "6.0", ImageVersion: "0.0", SubsystemVersion: "6.0", Win32VersionValue: 00, SizeOfImage: 1674000, SizeOfHeaders: 400, CheckSum: 00, Subsystem: 02, DllCharacteristics: 160, SizeOfStackReserve: 100000, SizeOfStackCommit: 1000, SizeOfHeapReserve: 100000, SizeOfHeapCommit: 1000, LoaderFlags: 00, NumberOfRvaAndSizes: 10, DataDirectory: [] }
SectionHeader { Name: ".text", VirtualAddress: 0x1000, VirtualSize: 0x32c50, PointerToRawData: 0x0, SizeOfRawData: 0x0, Characteristics: 0x60000020 }
SectionHeader { Name: ".rdata", VirtualAddress: 0x34000, VirtualSize: 0x1011a, PointerToRawData: 0x0, SizeOfRawData: 0x0, Characteristics: 0x40000040 }
SectionHeader { Name: ".data", VirtualAddress: 0x45000, VirtualSize: 0x2f88, PointerToRawData: 0x0, SizeOfRawData: 0x0, Characteristics: 0xc0000040 }
SectionHeader { Name: ".pdata", VirtualAddress: 0x48000, VirtualSize: 0x26ac, PointerToRawData: 0x0, SizeOfRawData: 0x0, Characteristics: 0x40000040 }
SectionHeader { Name: "_RDATA", VirtualAddress: 0x4b000, VirtualSize: 0xfc, PointerToRawData: 0x0, SizeOfRawData: 0x0, Characteristics: 0x40000040 }
SectionHeader { Name: ".Rdf0", VirtualAddress: 0x4c000, VirtualSize: 0x9c3e94, PointerToRawData: 0x0, SizeOfRawData: 0x0, Characteristics: 0x60000020 }
SectionHeader { Name: ".Rdf1", VirtualAddress: 0xa10000, VirtualSize: 0xb78, PointerToRawData: 0x400, SizeOfRawData: 0xc00, Characteristics: 0xc0000040 }
SectionHeader { Name: ".Rdf2", VirtualAddress: 0xa11000, VirtualSize: 0xc60694, PointerToRawData: 0x1000, SizeOfRawData: 0xc60800, Characteristics: 0x68000060 }
SectionHeader { Name: ".reloc", VirtualAddress: 0x1672000, VirtualSize: 0xb8, PointerToRawData: 0xc61800, SizeOfRawData: 0x200, Characteristics: 0x40000040 }
SectionHeader { Name: ".rsrc", VirtualAddress: 0x1673000, VirtualSize: 0x1d5, PointerToRawData: 0xc61a00, SizeOfRawData: 0x200, Characteristics: 0x40000040 }
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(1), int.len: Ok(1) } 14cf050 Ok(ByName { hint: 0, name: "TlsSetValue" })
Imports { dll_name: Ok("USER32.dll"), iat.len: Ok(1), int.len: Ok(1) } 147f302 Ok(ByName { hint: 0, name: "GetMessageW" })
Imports { dll_name: Ok("ole32.dll"), iat.len: Ok(1), int.len: Ok(1) } a5ff48 Ok(ByName { hint: 0, name: "CoCreateInstance" })
Imports { dll_name: Ok("SHLWAPI.dll"), iat.len: Ok(1), int.len: Ok(1) } 1407f88 Ok(ByName { hint: 0, name: "StrToInt64ExA" })
Imports { dll_name: Ok("ADVAPI32.dll"), iat.len: Ok(1), int.len: Ok(1) } 147a384 Ok(ByName { hint: 0, name: "RegOpenKeyExA" })
Imports { dll_name: Ok("OLEAUT32.dll"), iat.len: Ok(1), int.len: Ok(1) } 8000000000000002 Ok(ByOrdinal { ord: 2 })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(1), int.len: Ok(1) } 14f96aa Ok(ByName { hint: 0, name: "GetSystemTimeAsFileTime" })
Imports { dll_name: Ok("USER32.dll"), iat.len: Ok(1), int.len: Ok(1) } 144d9ca Ok(ByName { hint: 0, name: "CharUpperBuffW" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 1426a30 Ok(ByName { hint: 0, name: "LocalAlloc" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } a8fbd2 Ok(ByName { hint: 0, name: "LocalFree" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 143da64 Ok(ByName { hint: 0, name: "GetModuleFileNameW" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 144fbc6 Ok(ByName { hint: 0, name: "GetProcessAffinityMask" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } aafffc Ok(ByName { hint: 0, name: "SetProcessAffinityMask" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 14cead4 Ok(ByName { hint: 0, name: "SetThreadAffinityMask" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 145cc2c Ok(ByName { hint: 0, name: "Sleep" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } a5609e Ok(ByName { hint: 0, name: "ExitProcess" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 13fb03e Ok(ByName { hint: 0, name: "LoadLibraryA" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 14809b2 Ok(ByName { hint: 0, name: "GetModuleHandleA" })
Imports { dll_name: Ok("KERNEL32.dll"), iat.len: Ok(11), int.len: Ok(11) } 1469c64 Ok(ByName { hint: 0, name: "GetProcAddress" })
000000000002F327 6885454997 push 0FFFFFFFF97494585h
000000000002F32C E848A7B600 call 0000000000B99A79h
How would I compile + assemble + "execute" the instructions at 000000000002F327
and 000000000002F32C
?
I came up with this but (probably for extremely obvious reasons), it isn't working.
let path = Path::new("/tmp/redacted.dll");
let map = FileMap::open(path).unwrap();
let file = PeFile::from_bytes(&map).unwrap();
let flat_entry_point = 0x2F327;
let section = file.section_headers().by_name(".Rdf2").unwrap();
let section_bytes = file.get_section_bytes(§ion).unwrap();
let mut decoder = Decoder::new(64, section_bytes, DecoderOptions::NONE);
decoder.set_ip(flat_entry_point);
decoder.set_position(flat_entry_point as usize).unwrap();
let mut instruction = Instruction::default();
decoder.decode_out(&mut instruction);
let start_index = instruction.ip() as usize;
let instr_bytes = §ion_bytes[start_index..start_index + instruction.len()];
let mut assembler = Assembler::new().unwrap();
let assembly_offset = assembler.offset();
for b in instr_bytes.iter() {
assembler.push(*b);
}
println!("{:02x?}", assembler);
let executable_buffer = assembler.finalize().unwrap();
let executable_fn: extern "C" fn() = unsafe { std::mem::transmute(executable_buffer.ptr(assembly_offset)) };
unsafe { executable_fn(); }
000000000002F327 6885454997 push 0FFFFFFFF97494585h
Assembler { ops: [68, 85, 45, 49, 97], memory: MemoryManager { execbuffer: RwLock { data: ExecutableBuffer { length: 00, buffer: Some(Mmap { ptr: 0x104ae8000, len: 1000 }) }, poisoned: false, .. }, execbuffer_size: 1000, asmoffset: 00, execbuffer_addr: 104ae8000 }, labels: LabelRegistry { global_labels: {}, local_labels: {}, dynamic_labels: [] }, relocs: RelocRegistry { global: [], dynamic: [], local: {} }, managed: ManagedRelocs { managed: {} }, error: None }
Illegal instruction: 4
As far as I see you decoded a single instruction, put it in the Assembler, and then called that instruction. However, as there was no instruction afterwards execution just continued and encountered something that wasn't a valid instruction.
That said, this really isn't much of a use of this library, may I suggest getting familiar with a debugger?
lol