Washi1337 / AsmResolver

A library for creating, reading and editing PE files and .NET modules.
https://docs.washi.dev/asmresolver/
MIT License
826 stars 125 forks source link

Mixed Mode / Unmanaged PE File Builders #554

Closed Washi1337 closed 1 month ago

Washi1337 commented 1 month ago

Includes:

Bugfixes:

Washi1337 commented 1 month ago

The current implementation works, except for one limitation.

As it stands now, the new PE builder injects small chunks of native code to trampoline slots in the original IAT and VTable fixup tables to their newly added counterparts (based on the idea in this blog post). This works really well for functions and has been tested thoroughly. However, functions are not the only type of symbol that can be imported using the IAT: Global variables (such as std::cout in C++ binaries) can also appear in these tables. Since global variables are not code segments, they will never be called by the original PE either using e.g., a call or jmp instruction, and thus they cannot be trampolined.

One way to still support binaries that import global variables is to let the newly constructed PE file manually initialize these original IAT entries based on our new IAT. This requires injecting some additional native code (executed after the new IAT has been initialized by the Windows PE loader but before the PE's entry point is actually called) that copies the populated entries of our new IAT to the old IAT.

Some open problems with this:

  1. The PE file format does not tell us which symbols are functions are which are fields. How do we resolve this?
    • Option A: Add bool ImportedSymbol::TreatAsGlobalVariable { get; set; }, which the user can set directly on the symbol instance.
    • Option B: Add ICollection<ImportedSymbol> GlobalVariables { get; } to UnmanagedPEFileBuilder, letting the user add symbols they know are imported global variables as opposed to functions.
    • Option C: Add a ISymbolClassifier or similar delegate to UnmanagedPEFileBuilder that takes imported symbols and determines whether it is a global variable or not. Users can then customize programmatically this behavior.
  2. Where do we inject the initialization code?
    • Option A: Hijack the AddressOfEntryPoint native entry point.
    • Option B: Add as a TLS callback, which are executed even before the entry point.
  3. Can we somehow automatically make a good guess on which are the global variables?
    • Option A: Try resolve the declaring PE and see if the symbol falls in an executable section or not. This is at best a heuristic though.
    • Option B: Do not do this, it may be out of scope of AsmResolver as just a reader/writer