CensoredUsername/dynasm-rs

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(&section).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 = &section_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(&section).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 = &section_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