Skip to content

Instantly share code, notes, and snippets.

@gavz
Forked from daaximus/expmod.cpp
Created April 1, 2025 21:36
Show Gist options
  • Save gavz/78fad525892152192dbd4f46125ec5ce to your computer and use it in GitHub Desktop.
Save gavz/78fad525892152192dbd4f46125ec5ce to your computer and use it in GitHub Desktop.

Revisions

  1. @daaximus daaximus revised this gist Apr 1, 2025. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion expmod.cpp
    Original file line number Diff line number Diff line change
    @@ -68,7 +68,6 @@ struct expmod
    std::vector<exp_entry> old_exp;
    std::vector<exp_entry> new_exp;

    public:
    bool load_file( const std::string& p )
    {
    log_debug( "loading file: " << p );
  2. @daaximus daaximus revised this gist Apr 1, 2025. No changes.
  3. @daaximus daaximus created this gist Apr 1, 2025.
    1,336 changes: 1,336 additions & 0 deletions expmod.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1336 @@
    #include <windows.h>
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <ctime>
    #include <memory>
    #include <optional>
    #include <random>
    #include <string_view>
    #include <unordered_map>
    #include <charconv>

    #define DEBUG_LOGGING 0

    #if DEBUG_LOGGING
    #define log_debug(msg) std::cout << "[DEBUG] " << msg << std::endl
    #else
    #define log_debug(msg)
    #endif

    #define log_info(msg) std::cout << "[INFO] " << msg << std::endl
    #define log_error(msg) std::cerr << "[ERROR] " << msg << std::endl

    struct exp_entry
    {
    std::string name;
    DWORD rva;
    };

    struct expmod
    {
    struct mmf
    {
    HANDLE map = nullptr;
    void* data = nullptr;
    SIZE_T size = 0;

    ~mmf()
    {
    if ( data )
    {
    FlushViewOfFile( data, 0 );
    UnmapViewOfFile( data );
    }
    if ( map )
    {
    CloseHandle( map );
    }
    }
    };

    std::unique_ptr<mmf> mem;
    std::string path;

    IMAGE_DOS_HEADER* dos = nullptr;
    IMAGE_NT_HEADERS64* nt = nullptr;
    IMAGE_SECTION_HEADER* sec = nullptr;

    IMAGE_EXPORT_DIRECTORY* exp_dir = nullptr;
    DWORD exp_rva = 0;
    DWORD exp_size = 0;

    DWORD* func_addr = nullptr;
    DWORD* name_addr = nullptr;
    WORD* name_ord = nullptr;

    std::vector<exp_entry> old_exp;
    std::vector<exp_entry> new_exp;

    public:
    bool load_file( const std::string& p )
    {
    log_debug( "loading file: " << p );

    path = p;
    mem = std::make_unique<mmf>();

    HANDLE file = CreateFileA( p.c_str(), GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ, nullptr, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, nullptr );

    if ( file == INVALID_HANDLE_VALUE )
    {
    DWORD err = GetLastError();
    log_error( "failed to open file: " << p << ", Error: " << err );
    return false;
    }

    DWORD size_high;
    DWORD size_low = GetFileSize( file, &size_high );
    mem->size = ( ( SIZE_T ) size_high << 32 ) | size_low;

    log_debug( "file size: " << mem->size << " bytes" );

    mem->map = CreateFileMappingA( file, nullptr, PAGE_READWRITE,
    size_high, size_low, nullptr );
    CloseHandle( file );

    if ( !mem->map )
    {
    DWORD err = GetLastError();
    log_error( "failed to create file mapping, Error: " << err );
    return false;
    }

    log_debug( "file mapping created successfully" );

    mem->data = MapViewOfFile( mem->map, FILE_MAP_ALL_ACCESS, 0, 0, 0 );

    if ( !mem->data )
    {
    DWORD err = GetLastError();
    log_error( "failed to map view of file, Error: " << err );
    return false;
    }

    log_debug( "file mapped into memory at address: " << std::hex << mem->data << std::dec );

    return init_headers();
    }

    bool init_headers()
    {
    log_debug( "initializing PE headers" );

    if ( !mem->data )
    {
    return false;
    }

    dos = static_cast< IMAGE_DOS_HEADER* >( mem->data );
    if ( dos->e_magic != IMAGE_DOS_SIGNATURE )
    {
    log_error( "invalid DOS signature" );
    return false;
    }

    nt = reinterpret_cast< IMAGE_NT_HEADERS64* >( ( BYTE* ) ( mem->data ) + dos->e_lfanew );
    if ( nt->Signature != IMAGE_NT_SIGNATURE )
    {
    log_error( "invalid NT signature" );
    return false;
    }

    if ( nt->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 )
    {
    log_error( "not a 64b PE" );
    return false;
    }

    log_debug( "number of sections: " << nt->FileHeader.NumberOfSections );
    log_debug( "size of optional header: " << nt->FileHeader.SizeOfOptionalHeader );

    sec = reinterpret_cast< IMAGE_SECTION_HEADER* >(
    reinterpret_cast< BYTE* >( &nt->OptionalHeader ) +
    nt->FileHeader.SizeOfOptionalHeader );

    log_debug( "section headers at: " << std::hex << sec << std::dec );
    log_debug( "checking export directory" );
    log_debug( "export directory RVA: 0x" << std::hex
    << nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress
    << std::dec );
    log_debug( "export directory Size: 0x" << std::hex
    << nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].Size
    << std::dec );

    if ( nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress == 0 )
    {
    log_info( "no export directory found. Will create one." );
    return true;
    }

    exp_rva = nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress;
    exp_size = nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].Size;

    exp_dir = ( IMAGE_EXPORT_DIRECTORY* ) ( rva_to_va( exp_rva ) );

    log_debug( "export directory at VA: " << std::hex << exp_dir << std::dec );

    if ( !exp_dir )
    {
    log_error( "failed to get export directory" );
    return false;
    }

    if ( DEBUG_LOGGING )
    {
    log_debug( "export directory details:" );
    log_debug( " Characteristics: 0x" << std::hex << exp_dir->Characteristics << std::dec );
    log_debug( " TimeDateStamp: 0x" << std::hex << exp_dir->TimeDateStamp << std::dec );
    log_debug( " MajorVersion: " << exp_dir->MajorVersion );
    log_debug( " MinorVersion: " << exp_dir->MinorVersion );
    log_debug( " Name RVA: 0x" << std::hex << exp_dir->Name << std::dec );

    char* name = ( char* ) ( rva_to_va( exp_dir->Name ) );
    if ( name )
    {
    log_debug( " Name: " << name );
    }
    else
    {
    log_debug( " Name: <invalid>" );
    }

    log_debug( " Base: " << exp_dir->Base );
    log_debug( " NumberOfFunctions: " << exp_dir->NumberOfFunctions );
    log_debug( " NumberOfNames: " << exp_dir->NumberOfNames );
    log_debug( " AddressOfFunctions RVA: 0x" << std::hex << exp_dir->AddressOfFunctions << std::dec );
    log_debug( " AddressOfNames RVA: 0x" << std::hex << exp_dir->AddressOfNames << std::dec );
    log_debug( " AddressOfNameOrdinals RVA: 0x" << std::hex << exp_dir->AddressOfNameOrdinals << std::dec );
    }

    func_addr = ( DWORD* ) ( rva_to_va( exp_dir->AddressOfFunctions ) );
    name_addr = ( DWORD* ) ( rva_to_va( exp_dir->AddressOfNames ) );
    name_ord = ( WORD* ) ( rva_to_va( exp_dir->AddressOfNameOrdinals ) );

    log_debug( "AddressOfFunctions VA: " << std::hex << func_addr << std::dec );
    log_debug( "AddressOfNames VA: " << std::hex << name_addr << std::dec );
    log_debug( "AddressOfNameOrdinals VA: " << std::hex << name_ord << std::dec );

    if ( !func_addr || !name_addr || !name_ord )
    {
    log_error( "failed to get export tables" );
    return false;
    }

    load_exports();

    return true;
    }

    void load_exports()
    {
    log_debug( "loading existing exports" );

    if ( !exp_dir )
    {
    log_debug( "no export directory, nothing to load" );
    return;
    }

    old_exp.clear();

    for ( DWORD i = 0; i < exp_dir->NumberOfNames; i++ )
    {
    char* name = ( char* ) ( rva_to_va( name_addr[ i ] ) );
    if ( !name )
    {
    log_debug( "export " << i << ": Name pointer is invalid" );
    continue;
    }

    WORD ord = name_ord[ i ];
    if ( ord >= exp_dir->NumberOfFunctions )
    {
    log_debug( "export " << i << ": ordinal " << ord << " is out of range" );
    continue;
    }

    DWORD rva = func_addr[ ord ];

    log_debug( "export " << i << ": name=" << name << ", ordinal=" << ord << ", RVA=0x" << std::hex << rva << std::dec );

    exp_entry e{ std::string( name ), rva };
    old_exp.push_back( e );
    }

    log_debug( "loaded " << old_exp.size() << " existing exports" );
    }

    void* rva_to_va( DWORD rva ) const
    {
    if ( !mem->data || rva == 0 )
    {
    return nullptr;
    }

    DWORD off = rva_to_off( rva );
    if ( off == 0 && rva != 0 )
    {
    return nullptr;
    }

    if ( off >= mem->size )
    {
    return nullptr;
    }

    void* va = ( BYTE* ) ( mem->data ) + off;
    return va;
    }

    DWORD rva_to_off( DWORD rva ) const
    {
    if ( !mem->data || rva == 0 )
    {
    return 0;
    }

    for ( int i = 0; i < nt->FileHeader.NumberOfSections; i++ )
    {
    if ( rva >= sec[ i ].VirtualAddress &&
    rva < sec[ i ].VirtualAddress + sec[ i ].SizeOfRawData )
    {

    DWORD off = rva - sec[ i ].VirtualAddress + sec[ i ].PointerToRawData;
    return off;
    }
    }

    return rva;
    }

    DWORD off_to_rva( DWORD off ) const
    {
    if ( !mem->data || off == 0 )
    {
    return 0;
    }

    for ( int i = 0; i < nt->FileHeader.NumberOfSections; i++ )
    {
    if ( off >= sec[ i ].PointerToRawData &&
    off < sec[ i ].PointerToRawData + sec[ i ].SizeOfRawData )
    {
    DWORD rva = off - sec[ i ].PointerToRawData + sec[ i ].VirtualAddress;
    return rva;
    }
    }

    return off;
    }

    bool add_export( const std::string& name, DWORD64 va )
    {
    log_debug( "adding export: " << name << " -> 0x" << std::hex << va << std::dec );

    if ( !mem->data )
    {
    log_error( "file data is NULL" );
    return false;
    }

    DWORD rva = static_cast< DWORD >( va - nt->OptionalHeader.ImageBase );
    log_debug( "converted VA 0x" << std::hex << va << " to RVA 0x" << rva << std::dec );

    exp_entry e{ name, rva };
    new_exp.push_back( e );

    log_debug( "added export to list, total new exports: " << new_exp.size() );

    return true;
    }

    bool update_exports()
    {
    log_debug( "adding to existing export directory" );

    if ( !mem->data )
    {
    return false;
    }

    if ( new_exp.empty() )
    {
    log_debug( "no exports to add" );
    return true;
    }

    if ( !exp_dir )
    {
    log_debug( "no existing export directory, will create new one" );
    return create_exp_dir();
    }

    log_debug( "found existing export directory" );

    int sec_idx = -1;
    for ( int i = 0; i < nt->FileHeader.NumberOfSections; i++ )
    {
    if ( exp_rva >= sec[ i ].VirtualAddress &&
    exp_rva < sec[ i ].VirtualAddress + sec[ i ].Misc.VirtualSize )
    {
    sec_idx = i;
    break;
    }
    }

    if ( sec_idx == -1 )
    {
    log_error( "could not find section containing export directory" );
    return false;
    }

    log_debug( "export directory is in section " << sec_idx );

    char name[ 9 ] = { 0 };
    memcpy( name, sec[ sec_idx ].Name, 8 );
    log_debug( "export section name: " << name );

    std::vector<exp_entry> back = old_exp;

    if ( create_exp_dir() )
    {
    log_debug( "successfully created new export directory with all exports" );
    return true;
    }

    log_error( "failed to create new export directory" );
    return false;
    }

    bool create_new_sec( DWORD need_size, const std::vector<exp_entry>& all_exp )
    {
    log_debug( "creating new section for export directory" );

    if ( nt->FileHeader.NumberOfSections >= 96 )
    {
    log_error( "maximum number of sections reached" );
    return false;
    }

    DWORD align_size = ( need_size + 0xFFF ) & ~0xFFF;
    log_debug( "aligned section size: 0x" << std::hex << align_size << std::dec );

    IMAGE_SECTION_HEADER* last = &sec[ nt->FileHeader.NumberOfSections - 1 ];

    char lname[ 9 ] = { 0 };
    memcpy( lname, last->Name, 8 );
    log_debug( "prev section: " << lname );
    log_debug( "prev section VirtualAddress: 0x" << std::hex << last->VirtualAddress << std::dec );
    log_debug( "prev section VirtualSize: 0x" << std::hex << last->Misc.VirtualSize << std::dec );
    log_debug( "prev section PointerToRawData: 0x" << std::hex << last->PointerToRawData << std::dec );
    log_debug( "prev section SizeOfRawData: 0x" << std::hex << last->SizeOfRawData << std::dec );

    DWORD new_rva = ( last->VirtualAddress + last->Misc.VirtualSize + 0xFFF ) & ~0xFFF;
    log_debug( "new section RVA: 0x" << std::hex << new_rva << std::dec );

    DWORD align = nt->OptionalHeader.FileAlignment;
    DWORD new_off = ( last->PointerToRawData + last->SizeOfRawData + align - 1 ) & ~( align - 1 );
    log_debug( "new section raw offset: 0x" << std::hex << new_off << std::dec );

    DWORD new_size = new_off + align_size;
    log_debug( "current file size: 0x" << std::hex << mem->size << std::dec );
    log_debug( "new file size: 0x" << std::hex << new_size << std::dec );

    if ( new_size > mem->size )
    {
    log_debug( "need to expand file from 0x" << std::hex << mem->size << " to 0x" << new_size << std::dec );

    UnmapViewOfFile( mem->data );
    CloseHandle( mem->map );

    HANDLE file = CreateFileA( path.c_str(), GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ, nullptr, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, nullptr );

    if ( file == INVALID_HANDLE_VALUE )
    {
    DWORD err = GetLastError();
    log_error( "failed to reopen file for resizing: " << path << ", Error: " << err );
    return false;
    }

    LARGE_INTEGER dist;
    dist.QuadPart = new_size;
    if ( !SetFilePointerEx( file, dist, nullptr, FILE_BEGIN ) )
    {
    DWORD err = GetLastError();
    log_error( "Failed to set file pointer: " << err );
    CloseHandle( file );
    return false;
    }

    if ( !SetEndOfFile( file ) )
    {
    DWORD err = GetLastError();
    log_error( "failed to set end of file: " << err );
    CloseHandle( file );
    return false;
    }

    mem->map = CreateFileMappingA( file, nullptr, PAGE_READWRITE, 0, 0, nullptr );
    CloseHandle( file );

    if ( !mem->map )
    {
    DWORD err = GetLastError();
    log_error( "failed to create new file mapping: " << err );
    return false;
    }

    mem->data = MapViewOfFile( mem->map, FILE_MAP_ALL_ACCESS, 0, 0, 0 );

    if ( !mem->data )
    {
    DWORD err = GetLastError();
    log_error( "failed to map view of expanded file: " << err );
    return false;
    }

    mem->size = new_size;
    log_debug( "file expanded successfully to 0x" << std::hex << mem->size << std::dec );

    dos = static_cast< IMAGE_DOS_HEADER* >( mem->data );
    nt = reinterpret_cast< IMAGE_NT_HEADERS64* >( ( BYTE* ) ( mem->data ) + dos->e_lfanew );
    sec = reinterpret_cast< IMAGE_SECTION_HEADER* >(
    reinterpret_cast< BYTE* >( &nt->OptionalHeader ) +
    nt->FileHeader.SizeOfOptionalHeader );
    last = &sec[ nt->FileHeader.NumberOfSections - 1 ];
    }

    IMAGE_SECTION_HEADER new_sec;
    memset( &new_sec, 0, sizeof( IMAGE_SECTION_HEADER ) );

    memcpy( new_sec.Name, ".edata", 6 );

    new_sec.Misc.VirtualSize = need_size;
    new_sec.VirtualAddress = new_rva;
    new_sec.SizeOfRawData = align_size;
    new_sec.PointerToRawData = new_off;
    new_sec.PointerToRelocations = 0;
    new_sec.PointerToLinenumbers = 0;
    new_sec.NumberOfRelocations = 0;
    new_sec.NumberOfLinenumbers = 0;
    new_sec.Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;

    memcpy( &sec[ nt->FileHeader.NumberOfSections ], &new_sec, sizeof( IMAGE_SECTION_HEADER ) );
    nt->FileHeader.NumberOfSections++;

    log_debug( "added new section '.edata' at index " << ( nt->FileHeader.NumberOfSections - 1 ) );

    DWORD new_img_size = new_rva + align_size;
    if ( new_img_size > nt->OptionalHeader.SizeOfImage )
    {
    nt->OptionalHeader.SizeOfImage = new_img_size;
    log_debug( "updated SizeOfImage to 0x" << std::hex << new_img_size << std::dec );
    }

    memset( ( BYTE* ) ( mem->data ) + new_off, 0, align_size );

    DWORD exp_off = new_off;
    DWORD exp_rva = new_rva;

    if ( exp_rva % 16 != 0 )
    {
    DWORD a = 16 - ( exp_rva % 16 );
    exp_rva += a;
    exp_off += a;
    }

    log_debug( "placing export directory in new section at RVA 0x" << std::hex << exp_rva
    << " (offset 0x" << exp_off << ")" << std::dec );

    void* exp_va = ( BYTE* ) ( mem->data ) + exp_off;
    log_debug( "new export directory VA: " << std::hex << exp_va << std::dec );

    auto* new_dir = ( IMAGE_EXPORT_DIRECTORY* ) ( exp_va );

    new_dir->Characteristics = 0;
    new_dir->TimeDateStamp = static_cast< DWORD >( time( nullptr ) );
    new_dir->MajorVersion = 0;
    new_dir->MinorVersion = 0;
    new_dir->Base = 1;
    new_dir->NumberOfFunctions = all_exp.size();
    new_dir->NumberOfNames = all_exp.size();

    DWORD cur_off = exp_off + sizeof( IMAGE_EXPORT_DIRECTORY );
    DWORD cur_rva = exp_rva + sizeof( IMAGE_EXPORT_DIRECTORY );

    std::string dname = path.substr( path.find_last_of( "/\\" ) + 1 );
    log_debug( "setting up DLL name at offset 0x" << std::hex << cur_off << std::dec );
    char* nptr = ( char* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    strcpy_s( nptr, dname.length() + 1, dname.c_str() );
    new_dir->Name = cur_rva;
    cur_off += dname.length() + 1;
    cur_rva += dname.length() + 1;

    if ( cur_off % 4 != 0 )
    {
    DWORD a = 4 - ( cur_off % 4 );
    cur_off += a;
    cur_rva += a;
    }

    log_debug( "setting up functions table at offset 0x" << std::hex << cur_off << std::dec );
    new_dir->AddressOfFunctions = cur_rva;
    DWORD* ftbl = ( DWORD* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    cur_off += all_exp.size() * sizeof( DWORD );
    cur_rva += all_exp.size() * sizeof( DWORD );

    log_debug( "setting up names table at offset 0x" << std::hex << cur_off << std::dec );
    new_dir->AddressOfNames = cur_rva;
    DWORD* ntbl = ( DWORD* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    cur_off += all_exp.size() * sizeof( DWORD );
    cur_rva += all_exp.size() * sizeof( DWORD );

    log_debug( "setting up ordinals table at offset 0x" << std::hex << cur_off << std::dec );
    new_dir->AddressOfNameOrdinals = cur_rva;
    WORD* otbl = ( WORD* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    cur_off += all_exp.size() * sizeof( WORD );
    cur_rva += all_exp.size() * sizeof( WORD );

    for ( size_t i = 0; i < all_exp.size(); i++ )
    {
    log_debug( "processing export " << i << ": " << all_exp[ i ].name
    << " -> 0x" << std::hex << all_exp[ i ].rva << std::dec );

    ftbl[ i ] = all_exp[ i ].rva;
    otbl[ i ] = static_cast< WORD >( i );

    log_debug( "setting up name string at offset 0x" << std::hex << cur_off << std::dec );
    char* nstr = ( char* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    strcpy_s( nstr, all_exp[ i ].name.length() + 1, all_exp[ i ].name.c_str() );
    ntbl[ i ] = cur_rva;
    cur_off += all_exp[ i ].name.length() + 1;
    cur_rva += all_exp[ i ].name.length() + 1;
    }

    DWORD dir_size = cur_rva - exp_rva;
    log_debug( "new export directory size: 0x" << std::hex << dir_size << std::dec );

    log_debug( "updating data directory" );
    nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress = exp_rva;
    nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].Size = dir_size;

    log_debug( "export directory created in new section successfully" );
    return true;
    }

    bool create_exp_dir()
    {
    log_debug( "creating new export directory" );

    if ( !mem->data )
    {
    log_error( "file data is null" );
    return false;
    }

    std::vector<exp_entry> all = old_exp;
    all.insert( all.end(), new_exp.begin(), new_exp.end() );

    DWORD n = all.size();
    log_debug( "total exports will be: " << n );

    DWORD need = sizeof( IMAGE_EXPORT_DIRECTORY );
    need += sizeof( DWORD ) * n;
    need += sizeof( DWORD ) * n;
    need += sizeof( WORD ) * n;

    std::string dname = path.substr( path.find_last_of( "/\\" ) + 1 );
    need += dname.length() + 1;
    log_debug( "size after adding DLL name (" << dname << "): " << need << " bytes" );

    for ( const auto& e : all )
    {
    need += e.name.length() + 1;
    log_debug( "adding size for export name '" << e.name << "': " << e.name.length() + 1 << " bytes" );
    }

    need = ( need + 15 ) & ~15;
    log_debug( "total needed size (aligned): " << need << " bytes" );

    int rdata_idx = find_rdata_sec();
    if ( rdata_idx == -1 )
    {
    log_error( "could not find .rdata section or suitable section for exports" );
    return false;
    }

    log_debug( "using section index " << rdata_idx << " for exports" );

    IMAGE_SECTION_HEADER& rsec = sec[ rdata_idx ];

    char sname[ 9 ] = { 0 };
    memcpy( sname, rsec.Name, 8 );
    log_debug( "export section name: " << sname );
    log_debug( "export section VirtualAddress: 0x" << std::hex << rsec.VirtualAddress << std::dec );
    log_debug( "export section VirtualSize: 0x" << std::hex << rsec.Misc.VirtualSize << std::dec );
    log_debug( "export section PointerToRawData: 0x" << std::hex << rsec.PointerToRawData << std::dec );
    log_debug( "export section SizeOfRawData: 0x" << std::hex << rsec.SizeOfRawData << std::dec );

    DWORD avail = rsec.SizeOfRawData - rsec.Misc.VirtualSize;

    if ( avail >= need )
    {
    DWORD exp_off = rsec.PointerToRawData + rsec.Misc.VirtualSize;
    DWORD exp_rva = rsec.VirtualAddress + rsec.Misc.VirtualSize;

    if ( exp_rva % 16 != 0 )
    {
    DWORD a = 16 - ( exp_rva % 16 );
    exp_rva += a;
    exp_off += a;
    }

    log_debug( "placing export directory at RVA 0x" << std::hex << exp_rva
    << " (offset 0x" << exp_off << ")" << std::dec );

    if ( exp_off + need > rsec.PointerToRawData + rsec.SizeOfRawData )
    {
    log_error( "export directory would extend beyond section raw data" );
    return create_new_sec( need, all );
    }

    DWORD v_size = exp_rva + need - rsec.VirtualAddress;
    log_debug( "new virtual size for section: 0x" << std::hex << v_size << std::dec );

    if ( v_size > rsec.SizeOfRawData )
    {
    log_error( "new virtual size would exceed raw data size" );
    return create_new_sec( need, all );
    }

    void* exp_va = ( BYTE* ) ( mem->data ) + exp_off;
    log_debug( "new export directory VA: " << std::hex << exp_va << std::dec );

    memset( exp_va, 0, need );

    auto* new_dir = ( IMAGE_EXPORT_DIRECTORY* ) ( exp_va );

    new_dir->Characteristics = 0;
    new_dir->TimeDateStamp = static_cast< DWORD >( time( nullptr ) );
    new_dir->MajorVersion = 0;
    new_dir->MinorVersion = 0;
    new_dir->Base = 1;
    new_dir->NumberOfFunctions = n;
    new_dir->NumberOfNames = n;

    DWORD cur_off = exp_off + sizeof( IMAGE_EXPORT_DIRECTORY );
    DWORD cur_rva = exp_rva + sizeof( IMAGE_EXPORT_DIRECTORY );

    log_debug( "setting up DLL name at offset 0x" << std::hex << cur_off << std::dec );
    char* nptr = ( char* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    strcpy_s( nptr, dname.length() + 1, dname.c_str() );
    new_dir->Name = cur_rva;
    cur_off += dname.length() + 1;
    cur_rva += dname.length() + 1;

    if ( cur_off % 4 != 0 )
    {
    DWORD a = 4 - ( cur_off % 4 );
    cur_off += a;
    cur_rva += a;
    }

    log_debug( "setting up functions table at offset 0x" << std::hex << cur_off << std::dec );
    new_dir->AddressOfFunctions = cur_rva;
    DWORD* ftbl = ( DWORD* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    cur_off += n * sizeof( DWORD );
    cur_rva += n * sizeof( DWORD );

    log_debug( "setting up names table at offset 0x" << std::hex << cur_off << std::dec );
    new_dir->AddressOfNames = cur_rva;
    DWORD* ntbl = ( DWORD* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    cur_off += n * sizeof( DWORD );
    cur_rva += n * sizeof( DWORD );

    log_debug( "setting up ordinals table at offset 0x" << std::hex << cur_off << std::dec );
    new_dir->AddressOfNameOrdinals = cur_rva;
    WORD* otbl = ( WORD* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    cur_off += n * sizeof( WORD );
    cur_rva += n * sizeof( WORD );

    for ( size_t i = 0; i < n; i++ )
    {
    log_debug( "processing export " << i << ": " << all[ i ].name
    << " -> 0x" << std::hex << all[ i ].rva << std::dec );

    ftbl[ i ] = all[ i ].rva;
    otbl[ i ] = static_cast< WORD >( i );

    log_debug( "setting up name string at offset 0x" << std::hex << cur_off << std::dec );
    char* nstr = ( char* ) ( ( BYTE* ) ( mem->data ) + cur_off );
    strcpy_s( nstr, all[ i ].name.length() + 1, all[ i ].name.c_str() );
    ntbl[ i ] = cur_rva;
    cur_off += all[ i ].name.length() + 1;
    cur_rva += all[ i ].name.length() + 1;
    }

    DWORD dir_size = cur_rva - exp_rva;
    log_debug( "new export directory size: 0x" << std::hex << dir_size << std::dec );

    log_debug( "updating data directory" );
    nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress = exp_rva;
    nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].Size = dir_size;

    if ( rsec.Misc.VirtualSize < v_size )
    {
    log_debug( "increasing section virtual size from 0x" << std::hex
    << rsec.Misc.VirtualSize << " to 0x" << v_size << std::dec );
    rsec.Misc.VirtualSize = v_size;
    }

    log_debug( "export directory created successfully in existing section" );
    return true;
    }

    log_debug( "not enough space in existing section. Creating new section." );
    return create_new_sec( need, all );
    }

    int find_rdata_sec() const
    {
    log_debug( "finding .rdata section or suitable section for exports" );

    for ( int i = 0; i < nt->FileHeader.NumberOfSections; i++ )
    {
    char name[ 9 ] = { 0 };
    memcpy( name, sec[ i ].Name, 8 );
    log_debug( "checking section " << i << ": " << name );

    if ( _stricmp( name, ".rdata" ) == 0 )
    {
    log_debug( "found .rdata section at index " << i );
    return i;
    }
    }

    for ( int i = 0; i < nt->FileHeader.NumberOfSections; i++ )
    {
    if ( ( sec[ i ].Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) &&
    ( sec[ i ].Characteristics & IMAGE_SCN_MEM_READ ) &&
    !( sec[ i ].Characteristics & IMAGE_SCN_MEM_WRITE ) )
    {
    log_debug( "found suitable read-only data section at index " << i );
    return i;
    }
    }

    log_debug( "no suitable section found" );
    return -1;
    }

    void print_exports() const
    {
    log_debug( "printing exports" );

    if ( !mem->data )
    {
    log_debug( "file data is null, cannot show exports" );
    return;
    }

    DWORD exp_rva = nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress;
    log_debug( "current export directory RVA: 0x" << std::hex << exp_rva << std::dec );

    if ( exp_rva == 0 )
    {
    log_info( "no export directory found." );
    return;
    }

    DWORD exp_off = rva_to_off( exp_rva );
    if ( exp_off == 0 && exp_rva != 0 )
    {
    log_error( "could not convert export directory RVA to file offset" );
    return;
    }

    auto* dir = ( IMAGE_EXPORT_DIRECTORY* ) ( ( BYTE* ) ( mem->data ) + exp_off );
    log_debug( "export directory VA: " << std::hex << dir << std::dec );

    if ( ( BYTE* ) ( dir ) < ( BYTE* ) ( mem->data ) ||
    ( BYTE* ) ( dir ) >= ( BYTE* ) ( mem->data ) + mem->size ||
    exp_off + sizeof( IMAGE_EXPORT_DIRECTORY ) > mem->size )
    {
    log_error( "export directory is out of bounds" );
    return;
    }

    DWORD f_off = rva_to_off( dir->AddressOfFunctions );
    DWORD n_off = rva_to_off( dir->AddressOfNames );
    DWORD o_off = rva_to_off( dir->AddressOfNameOrdinals );

    if ( f_off == 0 || n_off == 0 || o_off == 0 )
    {
    log_error( "failed to get offsets for export tables" );
    return;
    }

    DWORD* funcs = ( DWORD* ) ( ( BYTE* ) ( mem->data ) + f_off );
    DWORD* names = ( DWORD* ) ( ( BYTE* ) ( mem->data ) + n_off );
    WORD* ords = ( WORD* ) ( ( BYTE* ) ( mem->data ) + o_off );

    log_debug( "functions table VA: " << std::hex << funcs << std::dec );
    log_debug( "names table VA: " << std::hex << names << std::dec );
    log_debug( "ordinals table VA: " << std::hex << ords << std::dec );

    if ( ( BYTE* ) ( funcs ) < ( BYTE* ) ( mem->data ) ||
    ( BYTE* ) ( funcs ) >= ( BYTE* ) ( mem->data ) + mem->size ||
    ( BYTE* ) ( names ) < ( BYTE* ) ( mem->data ) ||
    ( BYTE* ) ( names ) >= ( BYTE* ) ( mem->data ) + mem->size ||
    ( BYTE* ) ( ords ) < ( BYTE* ) ( mem->data ) ||
    ( BYTE* ) ( ords ) >= ( BYTE* ) ( mem->data ) + mem->size )
    {
    log_error( "export tables are out of bounds" );
    return;
    }

    DWORD name_off = rva_to_off( dir->Name );
    char* dname = ( char* ) ( ( BYTE* ) ( mem->data ) + name_off );

    std::cout << "Export Directory Information:\n";
    std::cout << " Characteristics: 0x" << std::hex << dir->Characteristics << '\n';
    std::cout << " TimeDateStamp: 0x" << dir->TimeDateStamp << '\n';
    std::cout << " MajorVersion: " << std::dec << dir->MajorVersion << '\n';
    std::cout << " MinorVersion: " << dir->MinorVersion << '\n';

    if ( dname && ( BYTE* ) ( dname ) >= ( BYTE* ) ( mem->data ) &&
    ( BYTE* ) ( dname ) < ( BYTE* ) ( mem->data ) + mem->size )
    {
    std::cout << " Name: " << dname << '\n';
    }
    else
    {
    std::cout << " Name: <invalid>\n";
    }

    std::cout << " Base: " << dir->Base << '\n';
    std::cout << " NumberOfFunctions: " << dir->NumberOfFunctions << '\n';
    std::cout << " NumberOfNames: " << dir->NumberOfNames << '\n';
    std::cout << " Exported Functions:\n";

    for ( DWORD i = 0; i < dir->NumberOfNames; i++ )
    {
    if ( i >= dir->NumberOfFunctions )
    {
    log_debug( "export index " << i << " exceeds function count" );
    break;
    }

    DWORD name_rva = names[ i ];
    WORD ord = ords[ i ];

    if ( ord >= dir->NumberOfFunctions )
    {
    log_debug( "export " << i << ": ordinal " << ord << " is out of range" );
    continue;
    }

    DWORD func_rva = funcs[ ord ];

    DWORD name_off = rva_to_off( name_rva );
    if ( name_off == 0 && name_rva != 0 )
    {
    log_debug( "export " << i << ": failed to get name offset for RVA 0x"
    << std::hex << name_rva << std::dec );
    continue;
    }

    char* exp_name = ( char* ) ( ( BYTE* ) ( mem->data ) + name_off );

    if ( ( BYTE* ) ( exp_name ) < ( BYTE* ) ( mem->data ) ||
    ( BYTE* ) ( exp_name ) >= ( BYTE* ) ( mem->data ) + mem->size )
    {
    log_debug( "export " << i << ": name pointer is out of bounds" );
    continue;
    }

    std::cout << " " << exp_name << " -> "
    << "0x" << std::hex << func_rva << std::dec << '\n';
    }

    log_debug( "finished printing exports" );
    }

    DWORD64 get_image_base() const
    {
    if ( !nt )
    {
    log_error( "nt headers not set" );
    return 0;
    }
    return nt->OptionalHeader.ImageBase;
    }
    };

    std::string gen_rand_name( int min_len, int max_len )
    {
    static const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    std::random_device rd;
    std::mt19937 gen( rd() );
    std::uniform_int_distribution len_dist( min_len, max_len );
    std::uniform_int_distribution<> char_dist( 0, sizeof( chars ) - 2 );

    int len = len_dist( gen );
    std::string result;
    result.reserve( len );

    for ( int i = 0; i < len; ++i )
    result += chars[ char_dist( gen ) ];

    return result;
    }

    DWORD64 gen_rand_va( DWORD64 min_va, DWORD64 max_va )
    {
    std::random_device rd;
    std::mt19937_64 gen( rd() );
    std::uniform_int_distribution dist( min_va, max_va );
    return dist( gen );
    }

    struct args_parser
    {
    std::unordered_map<std::string_view, std::string_view> option_map;
    std::vector<std::string_view> positional_args;

    args_parser( int argc, char* argv[] )
    {
    for ( int it = 1; it < argc; it++ )
    {
    std::string_view arg = argv[ it ];

    if ( arg.starts_with( "--" ) )
    {
    auto pos = arg.find( '=' );
    if ( pos != std::string_view::npos )
    {
    std::string_view key = arg.substr( 0, pos );
    std::string_view value = arg.substr( pos + 1 );
    option_map[ key ] = value;
    }
    else if ( it + 1 < argc && !std::string_view( argv[ it + 1 ] ).starts_with( "-" ) )
    {
    option_map[ arg ] = argv[ ++it ];
    }
    else
    {
    option_map[ arg ] = "";
    }
    }
    else if ( arg.starts_with( "-" ) && arg.size() > 1 )
    {
    if ( it + 1 < argc && !std::string_view( argv[ it + 1 ] ).starts_with( "-" ) )
    {
    option_map[ arg ] = argv[ ++it ];
    }
    else
    {
    option_map[ arg ] = "";
    }
    }
    else
    {
    positional_args.push_back( arg );
    }
    }
    }

    bool has_flag( std::string_view flag ) const
    {
    return option_map.contains( flag );
    }

    std::optional<std::string_view> get_option( std::string_view name ) const
    {
    auto it = option_map.find( name );
    if ( it != option_map.end() )
    {
    return it->second;
    }
    return std::nullopt;
    }

    std::optional<std::string_view> get_arg( size_t index ) const
    {
    if ( index < positional_args.size() )
    {
    return positional_args[ index ];
    }
    return std::nullopt;
    }

    std::optional<uint64_t> get_address( std::string_view name ) const
    {
    auto value = get_option( name );
    if ( !value || value->empty() )
    {
    return std::nullopt;
    }

    uint64_t result = 0;
    std::string_view str = *value;

    if ( str.size() > 2 && str.substr( 0, 2 ) == "0x" )
    {
    str.remove_prefix( 2 );
    auto [ptr, ec] = std::from_chars( str.data(), str.data() + str.size(), result, 16 );
    if ( ec != std::errc() )
    {
    return std::nullopt;
    }
    }
    else
    {
    auto [ptr, ec] = std::from_chars( str.data(), str.data() + str.size(), result, 10 );
    if ( ec != std::errc() )
    {
    return std::nullopt;
    }
    }

    return result;
    }

    std::optional<int> get_int( std::string_view name ) const
    {
    const auto value = get_option( name );
    if ( !value || value->empty() )
    {
    return std::nullopt;
    }

    int result = 0;
    auto [ptr, ec] = std::from_chars( value->data(), value->data() + value->size(), result, 10 );
    if ( ec != std::errc() )
    {
    return std::nullopt;
    }

    return result;
    }

    static uint64_t parse_address( std::string_view str )
    {
    uint64_t result = 0;

    if ( str.size() > 2 && str.substr( 0, 2 ) == "0x" )
    {
    str.remove_prefix( 2 );
    auto [ptr, ec] = std::from_chars( str.data(), str.data() + str.size(), result, 16 );
    if ( ec != std::errc() )
    {
    return 0;
    }
    }
    else
    {
    auto [ptr, ec] = std::from_chars( str.data(), str.data() + str.size(), result, 10 );
    if ( ec != std::errc() )
    {
    return 0;
    }
    }

    return result;
    }
    };

    void print_usage()
    {
    std::cout << R"(
    EXPMOD - just adds exports to 64-bit PE's
    Usage:
    exp <file_path> <export_name> <export_address> [--va | --rva]
    exp <file_path> --random [count]
    Options:
    <file_path> Path to the 64-bit PE file to modify
    <export_name> Name of the export to add
    <export_address> Address of the export (hex with 0x prefix or decimal)
    --va Specify that export_address is a Virtual Address (default)
    --rva Specify that export_address is a Relative Virtual Address
    --random, -r Add random exports
    [count] Number of random exports to add (optional, default: 512)
    --min-va=<addr> Minimum virtual address for random exports (hex or decimal)
    --max-va=<addr> Maximum virtual address for random exports (hex or decimal)
    --min-rva=<addr> Minimum RVA for random exports (hex or decimal)
    --max-rva=<addr> Maximum RVA for random exports (hex or decimal)
    Example:
    exp mydll.dll MyExport 0x140001000
    exp mydll.dll MyExport 0x1000 --rva
    exp mydll.dll --random 1000 --min-va=0x140010000 --max-va=0x140020000
    exp mydll.dll --random 100 --min-rva=0x1000 --max-rva=0x2000
    )";
    }

    int main( int argc, char* argv[] )
    {
    log_debug( "Starting pe_export_adder" );

    args_parser args( argc, argv );

    auto file_path = args.get_arg( 0 );
    if ( !file_path )
    {
    print_usage();
    return 1;
    }

    expmod e;
    if ( !e.load_file( std::string( *file_path ) ) )
    {
    log_error( "Failed to load file" );
    return 1;
    }

    bool ok = false;
    DWORD64 img_base = e.get_image_base();

    if ( args.has_flag( "--random" ) || args.has_flag( "-r" ) )
    {
    // this is just for examples sake... change it or remove it
    //
    int count = 512;
    DWORD64 min_va = 0x140001000;
    DWORD64 max_va = 0x140003000;

    auto count_opt = args.get_option( "--random" );
    if ( !count_opt ) count_opt = args.get_option( "-r" );

    if ( count_opt && !count_opt->empty() && !count_opt->starts_with( "-" ) )
    {
    try
    {
    count = std::stoi( std::string( *count_opt ) );
    if ( count <= 0 ) count = 512;
    }
    catch ( ... )
    {
    }
    }
    else if ( auto count_arg = args.get_arg( 1 ) )
    {
    if ( !count_arg->starts_with( "-" ) )
    {
    try
    {
    count = std::stoi( std::string( *count_arg ) );
    if ( count <= 0 ) count = 512;
    }
    catch ( ... )
    {
    }
    }
    }

    if ( auto val = args.get_address( "--min-va" ) )
    {
    min_va = *val;
    }
    if ( auto val = args.get_address( "--max-va" ) )
    {
    max_va = *val;
    }
    if ( auto val = args.get_address( "--min-rva" ) )
    {
    min_va = *val + img_base;
    }
    if ( auto val = args.get_address( "--max-rva" ) )
    {
    max_va = *val + img_base;
    }

    log_info( "adding " << count << " random exports with VA range 0x"
    << std::hex << min_va << " - 0x" << max_va << std::dec );

    for ( int i = 0; i < count; i++ )
    {
    std::string name = gen_rand_name( 8, 16 );
    DWORD64 addr = gen_rand_va( min_va, max_va );

    if ( !e.add_export( name, addr ) )
    {
    log_error( "failed to add random export #" << ( i + 1 ) );
    return 1;
    }
    }

    ok = true;
    }
    else
    {
    auto name = args.get_arg( 1 );
    auto addr_str = args.get_arg( 2 );

    if ( name && addr_str )
    {
    try
    {
    DWORD64 addr = args.parse_address( *addr_str );
    bool is_rva = args.has_flag( "--rva" );

    if ( is_rva )
    {
    addr += img_base;
    log_info( "adding export: " << *name << " -> RVA 0x" << std::hex
    << ( addr - img_base ) << " (VA 0x" << addr << ")" << std::dec );
    }
    else
    {
    log_info( "adding export: " << *name << " -> VA 0x" << std::hex
    << addr << " (RVA 0x" << ( addr - img_base ) << ")" << std::dec );
    }

    if ( !e.add_export( std::string( *name ), addr ) )
    {
    log_error( "failed to add export" );
    return 1;
    }

    ok = true;
    }
    catch ( const std::exception& e )
    {
    log_error( "error parsing address: " << e.what() );
    return 1;
    }
    }
    else
    {
    print_usage();
    return 1;
    }
    }

    if ( ok )
    {
    if ( !e.update_exports() )
    {
    log_error( "failed to update export directory" );
    return 1;
    }

    e.print_exports();
    log_info( "exports successfully added to " << *file_path );
    }

    return 0;
    }