libpeconv

repository·master·Indexed 23 days ago

https://github.com/hasherezade/libpeconv

A C++ library for the custom loading and manipulation of Portable Executable (PE) files. It provides functionality for remapping sections, applying relocations via relocate_module, loading imports using load_imports, parsing resources, and rebuilding IATs. The library supports manually loading and executing PE files (EXE or DLL) and includes a RunPE demo for performing Process Hollowing on 32-bit and 64-bit architectures.

Tokens
2.2K
Snippets
2
Records
12
Agent score
80%

What's inside libpeconv

  1. Use the RunPE demo to perform Process Hollowing

    master

    The RunPE demo project demonstrates how to use libpeconv to perform Process Hollowing (also known as RunPE). This technique allows injecting a new PE (the payload) into a remote process (the target) to impersonate that process.

    Supported Architectures

    The implementation supports both 32-bit and 64-bit PEs, subject to the loader's architecture:

    • 32-bit Loader: Can only inject a 32-bit payload into a 32-bit target.
    • 64-bit Loader: Can inject a 64-bit payload into a 64-bit target, OR a 32-bit payload into a 32-bit target.
  2. Customize relocation processing with RelocBlockCallback

    master

    To implement custom logic during the relocation process (e.g., logging, specialized address transformations, or filtering), inherit from RelocBlockCallback and override the processRelocField method.

    processRelocField is called for every relocation field encountered. It receives the absolute memory address of the relocation field (relocField). Returning true continues the process, while false aborts the relocation application.

  3. Customize IAT resolution with ImportThunksCallback

    master

    The peconv::ImportThunksCallback class is an abstract base class that allows you to intercept the import processing loop. By inheriting from this class and overriding processThunks, you can implement custom logic for every import found in the PE.

    The processThunks signature: virtual bool processThunks(LPSTR lib_name, ULONG_PTR origFirstThunkPtr, ULONG_PTR firstThunkPtr)

    • lib_name: The name of the DLL being imported.
    • origFirstThunkPtr: Pointer to the original thunk data (used to find names or ordinals).
    • firstThunkPtr: Pointer to the actual IAT entry (where the resolved address will be written).
  4. Manually load and run a PE file

    master

    You can use libPeConv to manually load a PE file (EXE or DLL) from either a file path or a memory buffer and execute it. The process typically involves:

    1. Loading the PE: Use peconv::load_pe_executable to map the PE into memory. If loading from a file, use peconv::load_file first to get a buffer.
    2. PEB Integration: If the loaded PE needs to access resources, call peconv::set_main_module_in_peb to connect it to the Process Environment Block (PEB).
    3. Handling Imports: Use peconv::load_delayed_imports to resolve delayed imports.
    4. TLS Callbacks: If the PE uses Thread Local Storage, run peconv::run_tls_callbacks before reaching the Entry Point.
    5. Execution: Retrieve the Entry Point RVA using peconv::get_entry_point_rva, calculate its Virtual Address (VA), and call it.

    Note: Applications using MUI (Multilingual User Interface) are not supported.

    #include <Windows.h>
    #include <iostream>
    #include <peconv.h>
    
    int main(int argc, char *argv[])
    {
        if (argc < 2) {
            std::cout << "Args: <path to the exe>" << std::endl;
            return 0;
        }
        LPCSTR pe_path = argv[1];
    
        // manually load the PE file using libPeConv:
        size_t v_size = 0;
    #ifdef LOAD_FROM_PATH
        //if the PE is dropped on the disk, you can load it from the file:
        BYTE* my_pe = peconv::load_pe_executable(pe_path, v_size);
    #else
        size_t bufsize = 0;
        BYTE *buffer = peconv::load_file(pe_path, bufsize);
    
        // if the file is NOT dropped on the disk, you can load it directly from a memory buffer:
        BYTE* my_pe = peconv::load_pe_executable(buffer, bufsize, v_size);
    #endif
        if (!my_pe) {
            return -1;
        }
    	
        // if the loaded PE needs to access resources, you may need to connect it to the PEB:
        peconv::set_main_module_in_peb((HMODULE)my_pe);
        
        // load delayed imports (if present):
        const ULONGLONG load_base = (ULONGLONG)my_pe;
        peconv::load_delayed_imports(my_pe, load_base);
      
        // if needed, you can run TLS callbacks before the Entry Point:
        peconv::run_tls_callbacks(my_pe, v_size);
    	
        //calculate the Entry Point of the manually loaded module
        DWORD ep_rva = peconv::get_entry_point_rva(my_pe);
        if (!ep_rva) {
            return -2;
        }
        ULONG_PTR ep_va = ep_rva + (ULONG_PTR) my_pe;
        //assuming that the payload is an EXE file (not DLL) this will be the simplest prototype of the main:
        int (*new_main)() = (int(*)())ep_va;
    
        //call the Entry Point of the manually loaded PE:
        return new_main();
    }
  5. Run the RunPE demo via command line

    master

    To use the RunPE application, provide two command-line arguments:

    1. [payload_path]: The path to the PE you want to execute (the payload).
    2. [target_path]: The path to the legitimate PE you want to impersonate (the target).
    [payload_path] [target_path]
  6. Relocate a module with relocate_module()

    master

    Use peconv::relocate_module to apply base relocations to a PE module in memory. This function updates the absolute addresses within the module's relocation table so that the module can be correctly loaded at a newBase instead of its original oldBase.

    If oldBase is provided as 0, the function will automatically attempt to determine the current image base using get_image_base.

  7. Collect import metadata using collect_imports()

    master

    Use peconv::collect_imports to extract detailed information about all imported functions into an ImportsCollection. This maps the RVA of the thunk to an ExportedFunc object containing the DLL name, function name, and ordinal.

    Output:

    • collection.thunkToFunc: A std::map<DWORD, ExportedFunc*> where the key is the RVA of the thunk and the value is a pointer to the metadata.
  8. Convert Virtual Address to RVA with virtual_addr_to_rva()

    master

    The peconv::virtual_addr_to_rva function converts a Virtual Address (VA) to a Relative Virtual Address (RVA).

    It is aware of the module's relocation table. If the provided callback_addr is found within the relocation entries, it is treated as a VA and converted by subtracting the image base. If the address is not in the relocation table, it is treated as an RVA and validated against the module size.

    If you do not provide an existing set of relocations via the _relocs parameter, the function will automatically collect them by traversing the relocation table using process_relocation_table.

  9. Load imports into a PE module using load_imports()

    master

    Use peconv::load_imports to resolve and fill the Import Address Table (IAT) of a PE module in memory. This function iterates through the import descriptors and uses a function resolver to find the addresses of imported functions, writing them into the module's thunks.

    Key requirements:

    • The bitness of the loader (your application) must match the bitness of the PE module being loaded. A 32-bit loader cannot fill imports for a 64-bit PE, and vice versa.
    • You can provide a custom t_function_resolver to control how function addresses are looked up. If nullptr is passed, a default_func_resolver is used.
  10. Verify if a PE has a valid import table using has_valid_import_table()

    master

    Use peconv::has_valid_import_table to check if a PE module contains a structurally sound import directory. This function validates the directory entry, the existence of descriptors, and the integrity of the thunk/caller pointer pairs.

    Parameters:

    • modulePtr: Pointer to the PE.
    • moduleSize: Size of the PE in memory.
    • maxCount (optional): A hard limit on the number of valid records to check before returning.