windows下一些Structure定义(pe、IMAGE_IMPORT_DESCRIPTOR

本文详细解析了可移植执行(PE)文件中的导入符号表结构及其工作原理。介绍了PE文件如何通过导入地址表和导入名称表来引用外部DLL模块中的函数,并解释了这些表在加载过程中的作用。
=================================
Structure of Import Symbols table
=================================
A portable executable (PE) file contains 

 IMAGE_NT_HEADERS
 
 This struct's members are:
 
	DWORD Signature
	IMAGE_FILE_HEADER FileHeader
	IMAGE_OPTIONAL_HEADER OptionalHeader	// not really optional, fyi

Then the IMAGE_OPTIONAL_HEADER struct contains:

    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Reserved1;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    
Then the DataDirectory array :

 0'th member:	Export Symbols
 1'st member:	Import Symbols
 2'nd member:	Resources
 ...
 11'th member:	Bound Import
 12'th member:	Import Address Table
 ...
 
The IMAGE_DATA_DIRECTORY struct consists of two fields:

    DWORD   VirtualAddress;		// the RVA of the data structure
    DWORD   Size;			// the size of the data structure
    
Then if we have a PE that imports two modules, we'll have:

 (1) one IMAGE_DATA_DIRECTORY structure for Import Symbols
 
 (2) let's say that struct's VirtualAddress is 0xc7d8 -- recall this is an RVA
 
 (3) then 0xc7d8 is where the first IMAGE_IMPORT_DESCRIPTOR lives
 
 (4) since we are importing two modules, there will be 3 IMAGE_IMPORT_DESCRIPTOR
     structs in our array (which starts at 0xc7d8, recall)
     
     --> the 3rd IMAGE_IMPORT_DESCRIPTOR is all zeroes
     
 (5) then the Size field above (for this IMAGE_DATA_DIRECTORY) will be 3 * 
     sizeof( IMAGE_IMPORT_DESCRIPTOR ) 
     
  The IMAGE_IMPORT_DESCRIPTOR struct consists of five fields :

  Union {
	DWORD Characteristics;
	PIMAGE_THUNK_DATA OriginalFirstThunk;
  };
  DWORD TimeDateStamp;
  DWORD ForwarderChain;
  DWORD Name;
  PIMAGE_THUNK_DATA FirstThunk;

  --> Each of these fields is 4 bytes long so sizeof( IMAGE_IMPORT_DESCRIPTOR ) is
      20 bytes == 0x1C
      
  --> So the Size field will be 60 bytes == 0x3C
  
Now let's examine the contents of the Import Symbols table; recall that this table
is -- in this case -- an array of 3 IMAGE_IMPORT_DESCRIPTOR structs.

Now here are some potential values for the fields:

-------------------------------------
// 3rd IMAGE_IMPORT_DESCRIPTOR struct
-------------------------------------
0xc810	FirstThunk		:			0
0xc80c	Name			:			0
0xc808	ForwarderChain		:			0
0xc804	TimeDateStamp		:			0
0xc800	OriginalFirstThunk	:			0

-------------------------------------
// 2nd IMAGE_IMPORT_DESCRIPTOR struct
-------------------------------------
0xc7fc	FirstThunk		:			0x8000
0xc7f8	Name			:			0xc398  (the RVA of the name of this module)
0xc7f4	ForwarderChain		:			0
0xc7f0	TimeDateStamp		:			0
0xc7ec	OriginalFirstThunk	:			0xc084

-------------------------------------
// 1st IMAGE_IMPORT_DESCRIPTOR struct
-------------------------------------
0xc7e8	FirstThunk		:			0xc178
0xc7e4	Name			:			0xc2c0  (the RVA of the name of this module)
0xc7e0	ForwarderChain		:			0
0xc7dc	TimeDateStamp		:			0
0xc7d8	OriginalFirstThunk	:			0xc030

All addresses are RVAs.

------------------------------
Now at locations (from above):	
------------------------------
	0xc2c0		contains	"MSVCR80.DLL"
	0xc398		contains	"KERNEL32.DLL"

Now OriginalFirstThunk points to the Import Name Table for the module and
FirstThunk points to the Import Address Table for the module.

Each of these two tables (Import Name and Import Address) consist of an 
array of IMAGE_THUNK_DATA structs. An IMAGE_THUNK_DATA struct consists of:

  Union {
	PBYTE ForwarderString;
	PDWORD Function;
	DWORD Ordinal;
	PIMAGE_IMPORT_BY_NAME AddressOfData;
  };
  
In our case the Union member "AddressOfData" will be the member used for
each struct in these two tables. This value (AddressOfData) contains the
RVA for an IMAGE_IMPORT_BY_NAME struct. The contents of an IMAGE_IMPORT_NAME
struct are :

    WORD Hint;		// recall that WORDs are two bytes each
    BYTE Name[1];       // this is NOT really 1 byte long; This is a variable-sized field
   
For the PE -- before it's loaded -- the Import Name Table and the Import Address
Table are merely two copies of the same table. For example let's say we're 
importing twenty or so symbols from MSVCR80.dll then our Import Name Table starts
at 0xc030 (OriginalFirstThunk address above) and our Import Address Table starts
at 0xc178 (FirstThunk address above).

-------------------------------------
So we might have something like this:
-------------------------------------

------------------------------------
// Import Name Table for MSVCR80.dll
------------------------------------
0xc080	AddressOfData		:		0			// null terminate table
...
0xc044	AddressOfData		:		0xc300
0xc040	AddressOfData		:		0xc2f8
0xc03c	AddressOfData		:		0xc2ea
0xc038	AddressOfData		:		0xc2e0
0xc034	AddressOfData		:		0xc2d8
0xc030	AddressOfData		:		0xc2cc

---------------------------------------
// Import Address Table for MSVCR80.dll
---------------------------------------
0xc1c8	AddressOfData		:		0			// null terminate table
...
0xc18c	AddressOfData		:		0xc300
0xc188	AddressOfData		:		0xc2f8
0xc184	AddressOfData		:		0xc2ea
0xc180	AddressOfData		:		0xc2e0
0xc17c	AddressOfData		:		0xc2d8
0xc178	AddressOfData		:		0xc2cc

Note that the AddressOfData values don't increase by a fixed amount; that's 
because the data contained at those addresses is variable length. So what
is the data contained at those addresses?

Starting at 0xc2cc is an array of IMAGE_IMPORT_BY_NAME structs. Each such
struct contains the name of a function (imported by this module which in
this case is MSVCR80.DLL) as well as a "Hint" for this function.

---------------------------------------------------------------------
Continuing the above example, we have the following values in memory:
---------------------------------------------------------------------
...

0xc312	2-byte hint for mbstowcs		
0xc308	"mbstowcs"				// null-terminated is 9 bytes long

0xc306	2-byte hint for fopen
0xc300	"fopen"					// null-terminated is 6 bytes long

0xc2fe	2-byte hint for atoi
0xc2f8	"atoi"					// null-terminated is 5 bytes long

0xc2f6	2-byte hint for _vsnprintf
0xc2ea	"_vsnprintf"				// null-terminated is 11 bytes long

0xc2e8	2-byte hint for _strdup
0xc2e0	"_strdup"				// null-terminated is 8 bytes long

0xc2de	2-byte hint for _open
0xc2d8	"_open"					// null-terminated is 6 bytes long

0xc2d6	2-byte hint for _memccpy		
0xc2cc	"_memccpy"				// null-terminated is 9 bytes long

0xc2c0	"MSVCR80.DLL"				// null-terminated is 12 bytes long

So now you see what the AddressOfData values -- contained in the Import Name
Table and the Import Address Table -- refer to. One small detail, when the
null-terminated name of a function is an odd number, then an extra byte must
be added to the name so that the WORD hint field starts on a WORD-aligned
boundary. This is why, for example, the size of the IMAGE_IMPORT_BY_NAME
struct for _memccpy is 12 bytes even though null-terminated "_memccpy" is
9 bytes and the WORD hint adds 2 bytes (total of 11 bytes); that extra byte
is to align the hint on a WORD boundary.

===============================================
So why do we have two copies of the same table?
===============================================
Perceptive question, Watson!

These two tables are used by the PE loader to map the function names to 
addresses when loading the PE. Since we don't know at compile time WHERE
the various functions will exist in the address space (since these functions
are part of DLLs), the loader does that resolution for us. In particular 
the loader will replace each entry in the Import Address Table (IAT) with 
the actual address of the function. 

For example, the first entry in the MSVCR80.DLL IAT is 0xc2cc and that 
corresponds to "_memccpy". Usually MSVCR80.DLL loads at address 0x00360000,
i.e. that's the base address for this DLL. Moreover the RVA for _memccpy
is 0x49F10. Therefore if MSVCR80.dll is loaded in its usual spot then 
the value stored at 0xc178 (which is originally 0xc2cc) will be changed
to ( 0x00360000 + 0x49F10 ) == 0x003a9f10 since that's the address where
the _memccpy code lives in memory at the time this PE is loaded.

Note that because we have *two* copies of this same initial table, after
we replace 0xc2cc in the IAT with the address for _memccpy (as above),
if for any reason we need to go in the other direction (i.e. to know
which function that address corresponds to) we can consult the Import
Name Table at 0xc030 which still contains the RVA 0xc2cc and see that
"_memccpy" is the function for this resolved address.

===========================
From : C:/lcc/include/win.h
===========================

//
// there's one IMAGE_IMPORT_DESCRIPTOR for every DLL that the PE uses
//

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

	union {
		DWORD Characteristics;

		// points to Import Name Table (INT)
		// contains RVA of an array of IMAGE_THUNK_DATA structs
		PIMAGE_THUNK_DATA OriginalFirstThunk;
	} ;

	DWORD TimeDateStamp;

	DWORD ForwarderChain;

	DWORD Name;			// e.g. "USER32.dll"

	PIMAGE_THUNK_DATA FirstThunk;	// points to IAT

} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;


//
// the IAT is an array of IMAGE_THUNK_DATA structs
//

typedef struct _IMAGE_THUNK_DATA {

	union {				// is one of the following 4 things

		PBYTE ForwarderString;	// 4 bytes

		PDWORD Function;	// 4 bytes

		DWORD Ordinal;		// 4 bytes

		// ------------- is usually this ------------- //
		PIMAGE_IMPORT_BY_NAME AddressOfData; // 4 bytes

	} ;

} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;


//
// as above, usually we interpret an IMAGE_THUNK_DATA struct as a POINTER to an :
// "IMAGE_IMPORT_BY_NAME" struct
//

typedef struct _IMAGE_IMPORT_BY_NAME {

	WORD Hint;	// 2 bytes
			// contaisn index into the export table of the DLL
			// from which we get (import) this function


	BYTE Name[1];	// 1 byte; contains name of the import function
			// is an ASCIIZ string

} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;
用c++写一个反射dll注入 (0) 1 打开dll文件(CreateFile),获取dll长度(GetFileSize) (1) 2 分配内存(HealAlloc),读取文件(ReadFile) (0) 3 打开目标进程(OpenProcess) (2,3) 4 调用反射注入函数(LoadLibraryR.c>LoadRemoteLibraryR) (2) 5 获取反射加载函数的文件偏移(LoadLibraryR.c>GetReflectiveLoaderOffset) (2,3) 6 在目标进程中分配空间(VirtualAllocEx),写入dll(WriteProcessMemory) (6) 7 修改目标进程中的空间为可执行(VirtualProtectEx) (5,7) 8 创建远程线程,执行反射加载函数(CreateRemoteThread) ======================= 反射加载函数(运行在被注入进程的新建线程中): ======================= 1 获取基地址 2 获取需要的kernel32.dll及ntdll.dll的函数的va 3 分配空间作为映像空间,并复制pe头到新的位置 4 复制所有段到映像的对应位置 5 处理导入表,填写iat 6 重定位 7 跳转到ep(_DllMainCRTStartup) 8 返回entry point地址注入器 主要流程 首先打开dll文件,获取长度,并在堆中分配空间读取文件。 1 2 3 hFile = CreateFileW(dllPathname, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); dwDllLen = GetFileSize(hFile, 0); lpDll = HeapAlloc(GetProcessHeap(), 0, dwDllLen); 然后找到dll中ReflectiveLoader的入口点。 最后以RW申请空间,写入dll后改成RX,然后以ReflectiveLoader作为线程函数创建远程线程。 计算fa LoadLibraryR中有个函数Rva2Offset用于获取rva对应的fa。 原理是遍历区块获取区块的section_rva和section_fa,然后比较rva和section_rva找到rva所在的section,最后计算出fa。再用fa+baseAddr得到内存中的位置。 1 2 3 4 5 6 7 8 9 10 DWORD Rva2Fa(DWORD rva, PIMAGE_SECTION_HEADER sections, int sectionNum) { for (int i = 0; i < sectionNum; i++) { int sectionVa = sections[i].VirtualAddress; if ((rva >= sectionVa) && ((sectionVa + sections[i].SizeOfRawData) > rva)) return rva - (sectionVa - sections[i].PointerToRawData); } return 0; } 获取ReflectivelLoader位置(输出表) 通过nt头,计算出sections的fa,以及通过nt头的optionalheader获取输出表的rva。 然后遍历section找到输出表的fa,接着遍历输出表的函数名字rva表,计算出rva对应fa得到导出函数名字,与需要的导出函数做对比,确定要找的函数在函数名表中的下标。用此下标在序号表中找到序号,最后再用序号去地址表找到地址。 反射加载函数 1. 获取基址 首先获取代码的位置,然后再往前找dos头。 _ReturnAddress()返回当前调用函数的返回地址。所以在loader中调用一个函数,该函数再调用_ReturnAddress(),返回调用函数的返回地址,即loader中调用函数的下一条语句的地址。 其中 __declspec(noinline) 用于防止编译器优化该函数成内联函数,否则返回的就是loader的返回地址。 使用_ReturnAddress需要intrin.h,并使用#pragma intrinsic防止内联优化。 1 2 3 4 5 6 #include<intrin.h> #pragma intrinsic(_ReturnAddress) __declspec(noinline) PVOID NextAddr() { return (PVOID)_ReturnAddress(); } 根据pe格式可知,dos头(IMAGE_DOS_HEADER)中有有一个e_magic标志,值是0x5A4D(MZ)。 所以向前遍历内存,直到找到MZ标志,再检查pe头的PE标志,这样就找到dos头了。 需要注意的是,检查PE标志时要检查pe头偏移是否正确,防止错误的内存访问。 1 2 3 4 5 6 7 8 9 10 11 while (TRUE) { if (dosHeadAddr->e_magic == 0x5A4D) { LONG e_lfanew = dosHeadAddr->e_lfanew; if (e_lfanew >= sizeof(IMAGE_DOS_HEADER) && e_lfanew < 1024) { ntHeadAddr = (PIMAGE_NT_HEADERS)((PVOID)dosHeadAddr + (PVOID)e_lfanew); if (ntHeadAddr->Signature == 0x4550) break; } } dosHeadAddr--; } 这里也可以取巧,远程线程是可以传递一个参数的,对于我们这个简单的dll,imagebase实际上就是分配空间的首地址,可以作为参数传入。 2. 获取需要的内核导出函数的va 目标 接下来的步骤中需要用到一些ntdll.dll,kernel32.dll中的导出函数,所以需要先找到这些函数的va。这些系统模块都是已经加载了的,可以在peb中找到其加载的位置。 这里利用hash避免直接比较字符串。 我们需要LoadLibraryA、GetProcAddress加载导入表中的dll的对应的函数。 需要VirtualAlloc分配内存给我们把pe文件加载到其中。 需要NtFlushInstructionCache刷新指令缓存。 LDR_DATA_TABLE_ENTRY InMemoryOrderModuleList对应的链表是一个环形双向链表,且有一个头节点(或者说哨兵节点)。InMemoryOrderModuleList的Flink指向链表的第一个节点,Blink指向链表最后一个节点。头节点的Flink是第一个节点,可以以此为跳出条件遍历该链表。 这里借用一张网图。 ldr链 思路 首先从peb中找到ldr,然后遍历InMemoryOrderModuleList,通过hash(BaseName)找到kernel32.dll和ntdll.dll对应的LDR_DATA_TABLE_ENTRY结构。 找到dll对应的LDR_DATA_TABLE_ENTRY后,获取其imagebase,然后解析pe头,计算出导出表位置。同样利用hash比较字符串找到所需的导出函数,并计算出va。 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 找pLdrDataTableEnrty DWORD pPeb = __readfsdword(0x30); DWORD pLdr = *(DWORD*)(pPeb + 0xc); DWORD pInMemoryOrderModuleList = pLdr + 0x14; // 第一个节点的二级指针 DWORD pLdrDataTableEnrty = *(DWORD*)(pInMemoryOrderModuleList + 0); // 遍历LdrDataTableEnrty do{ WCHAR* name = (WCHAR*)*(DWORD*)(pLdrDataTableEnrty + 0x24 + 0x4); hash = YourHashFun(name); // 使用你自己的函数计算hash值 if(hash == DLLHASH) { // DLLHASH由你自己的函数计算得出 DWORD baseAddr = *(DWORD*)(pLdrDataTableEnrty + 0x10); // 解析pe头过程省略 for(int i = 0; i < funcNum; i++) { // funcNum是导出函数的个数 char* name = (char*)(baseAddr + ((DWORD*)nameRvas)[i]); DWORD hash = YourHashFun(name); if (hash == FUNCHASH) { pFunc = (FUNC)(baseAddr + ((DWORD*)funcRvas)[((WORD*)ordRvas)[i]]); } } } }while(*(DWORD*)(pLdrDataTableEnrty) != *(DWORD*)(pInMemoryOrderModuleList)) 3. 给映像分配空间,并加载pe头 新分配大小等于sizeOfImga的内存作为映像加载的空间,然后把pe头复制到新内存里,这里我只更新了新nt头的imagebase地址。太简单就不贴代码了。 4. 加载段 遍历section_header获取fa和rva,计算出section在旧内存中的va和新内存中的va。然后复制section到新内存中的对应位置。 1 2 oldVA = oldImageBase + sections[i].PointerToRawData; newVA = newImageBase + sections[i].VirtualAddress; 5. 处理导入表 目标 找到导入表,然后遍历导入表,依次加载对应的dll,及需要的dll的导出函数,并填写对应iat。 导入表结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // winnt.h typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME (补充一下,这是个rva) } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32; typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; CHAR Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; 和导出表不同,导入表是一个结构体数组。它不提供结构体数量,最后一个结构体仅作为结束标志,不包含导入信息,其成员Characteristics为0,这可以作为遍历的退出条件。 对于每个导入表,在文件中时OriginalFirstThunk和FirstThunk都是RVA,指向同一个IMAGE_THUNK_DATA结构体数组。 当加载到内存时,FirstThunk改为函数的VA,即iat。 文件中时,OriginalFirstThunk和FirstThunk指向的结构体数组中,每一个IMAGE_THUNK_DATA的成员u1都被解释为Ordinal,若该函数应该通过序号导入,则Ordinal的最高位会被置为1。 思路 见实现代码注释。 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pNewDosHeader + pNewNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); for (; pImportDescriptor->Characteristics; pImportDes++) { // 加载dll HMODULE libraryAddress = pLoadLibraryA((LPCSTR)((DWORD)pNewDosHeader + pImportDes->Name)); if (!libraryAddress) continue; // parsing pe structure PIMAGE_THUNK_DATA32 pOriginalThunk = (PIMAGE_THUNK_DATA32)((DWORD)pNewDosHeader + pImportDes->OriginalFirstThunk); PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32)((DWORD)pNewDosHeader + pImportDes->FirstThunk); PIMAGE_NT_HEADERS32 pLibNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)libraryAddress + ((PIMAGE_DOS_HEADER)libraryAddress)->e_lfanew); PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((DWORD)libraryAddress + pLibNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PDWORD funcRvas = (PDWORD)((DWORD)libraryAddress + pExportDir->AddressOfFunctions); while (*(DWORD*)pThunk) { if (pOriginalThunk && pOriginalThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) { // import by ord WORD ord = pOriginalThunk->u1.Ordinal - pExportDir->Base; *(DWORD*)pThunk = ((DWORD)libraryAddress + funcRvas[ord]); } else { // import by name (this is a rva) *(DWORD*)pThunk = (DWORD)pGetProcAddress(libraryAddress, ((PIMAGE_IMPORT_BY_NAME)((DWORD)pNewDosHeader + pThunk->u1.AddressOfData))->Name); } pThunk++; if (pOriginalThunk) pOriginalThunk++; } } 6. 重定位 目标 完成重定位过程。 重定位表结构 重定位表是一个结构体数组,DataDirectory中的重定位表项保存着第一个重定位表的rva,遍历每一个重定位表,并遍历重定位表中的表项,根据其重定位类型,执行重定位操作。 1 2 3 4 5 6 7 8 9 typedef struct { WORD offset : 12; WORD type : 4; } RELOC; typedef struct { DWORD VA; DWORD size; // RELOC reloc[]; } IMAGE_BASE_RELOCATION; 其中每一个重定位表保存着一个rva,重定位实际上就是遍历IMAGE_BASE_RELOCATION的成员reloc,然后执行*(rva+baseAddr+reloc[i].offset) += baseAddr - ImageBase。 思路 两层循环,遍历重定位表,再遍历每个表的 RELOC reloc[]。然后根据重定位类型进行重定位。 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // 解析pe,并计算offset PIMAGE_DATA_DIRECTORY pDDBaseReloc = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; PIMAGE_BASE_RELOCATION pBaseRelocation; ULONG_PTR offset = (ULONG_PTR)pNewDosHeader - (ULONG_PTR)pNtHeaders->OptionalHeader.ImageBase; if (pDDBaseReloc->Size) { DWORD size = pDDBaseReloc->Size; pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pNewDosHeader + pDDBaseReloc->VirtualAddress); // 遍历重定位表结构体 while (size && pBaseRelocation->SizeOfBlock) { DWORD va = (DWORD)pNewDosHeader + pBaseRelocation->VirtualAddress; DWORD num = (pBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC); // 计算reloc[]大小 PIMAGE_RELOC reloc = (PIMAGE_RELOC)((DWORD)pBaseRelocation + sizeof(IMAGE_BASE_RELOCATION)); // 遍历reloc[],根据重定位类型重定位 while (num--) { DWORD type = reloc->type; if (type == IMAGE_REL_BASED_HIGH) { *(WORD*)(va + reloc->offset) += HIWORD(offset); } else if (type == IMAGE_REL_BASED_LOW) { *(WORD*)(va + reloc->offset) += LOWORD(offset); } else if (type == IMAGE_REL_BASED_HIGHLOW) { *(DWORD*)(va + reloc->offset) += (DWORD)offset; } reloc++; } size -= pBaseRelocation->SizeOfBlock; pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock); } } 7. 跳转到ep 跳转到dll的ep。实际上就是执行dll原本的_DllMainCRTStartup函数。该函数会完成一些初始化工作并转到dllMain,让我们的dllMain像正常dllmain那样运行,但又不在peb中留下dll加载的痕迹。 1 2 3 4 5 6 7 typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID); PVOID entryPoint = (PVOID)((DWORD)pNewDosHeader + pNewNtHeaders->OptionalHeader.AddressOfEntryPoint); pNtFlushInstructionCache((HANDLE)-1, NULL, 0); ((DLLMAIN)entryPoint)((HMODULE)pNewDosHeader, DLL_PROCESS_ATTACH, lpParameter);
最新发布
11-18
#pragma once #include <ntifs.h> #include <ntddk.h> #include <ntimage.h> #define RELOC_FLAG64(ReInfo) ((ReInfo>>0x0C)==IMAGE_REL_BASED_DIR64) #define RELOC_FLAG RELOC_FLAG64 UINT64 g_fnLoadLibrary = 0; UINT64 g_fnGetProcAddress = 0; UINT64 g_fnRtlAddFuntion = 0; void Log(const char* sz_info, bool is_error, ULONG err_code); EXTERN_C NTSTATUS MmCopyVirtualMemory( IN PEPROCESS FromProcess, IN CONST VOID* FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesCopied ); EXTERN_C NTSTATUS NTAPI ZwSetInformationProcess( __in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, __in_bcount (ProcessInformationLength) PVOID ProcessInformation, __in ULONG ProcessInformationLength ); typedef PVOID HINSTANCE, HMODULE; using f_LoadLibraryA = HMODULE(_stdcall*)(const char* lpLibFileName); using f_GetProcAddress = PVOID(_stdcall*)(HMODULE hModule, LPCSTR lpProcName); using f_DLL_ENTRY_POINT = BOOLEAN(_stdcall*)(PVOID DllHandle, ULONG Reason, PVOID Reserved); using f_RtlAddFunctionTable = BOOLEAN(_stdcall*)(_IMAGE_RUNTIME_FUNCTION_ENTRY* FunctionTable, DWORD32 EntryCount, DWORD64 BaseAddress); struct Manual_Mapping_data { //用于重定位iat f_LoadLibraryA pLoadLibraryA; f_GetProcAddress pGetProcAddress; //x64专有 f_RtlAddFunctionTable pRtlAddFunctionTable; char* pBase; DWORD32 dwReadson; //设置为0线程开始时执行dll PVOID reserveParam; BOOLEAN bFirst;//只允许第一次系统调用进入 BOOLEAN bStart;//开始执行shellcode了 可以同步关闭instCallBack BOOLEAN bContinue;//用于继续执行 去掉pe头后执行dllmain size_t DllSize; }; void __stdcall InstruShellCode(Manual_Mapping_data* pData); #pragma pack(push) #pragma pack(1) struct shellcode_t { private: char padding[43]; public: uintptr_t manual_data; private: char pdding[47]; public: uintptr_t rip; uintptr_t shellcode; }; char g_instcall_shel1code[] = { 0x50, 0x51, 0x52, 0x53, 0x55, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48,0x89 ,0x25,0x4C,0x00,0x00,0x00, 0x48, 0x83, 0xEC, 0x38, 0x48,0x81,0xE4,0xF0,0xFF,0xFF,0xFF, 0x48,0xB9,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, 0xFF,0x15,0x29,0x00,0x00,0x00,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,//call 0x48, 0x8B, 0x25, 0x22,0x00,0x00,0x00, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5D, 0x5B, 0x5A, 0x59, 0x58, 0x41, 0xFF, 0xE2, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 }; #pragma pack(pop) NTSTATUS inst_callback_set_callback(PVOID inst_callback) { NTSTATUS status = STATUS_SUCCESS; PVOID InstCallBack = inst_callback; PACCESS_TOKEN Token{ 0 }; PULONG TokenMask{ 0 }; Token = PsReferencePrimaryToken(IoGetCurrentProcess()); TokenMask = (PULONG)((ULONG_PTR)Token + 0x40);//取_TOKEN结构下Privileges TokenMask[0] |= 0x100000; TokenMask[1] |= 0x100000; TokenMask[2] |= 0x100000; status = ZwSetInformationProcess(NtCurrentProcess(), ProcessInstrumentationCallback, &InstCallBack, sizeof(PVOID)); if (!NT_SUCCESS(status)) { Log("failed to set instrcallback !", true, status); } else { Log("failed to set instrcallback SUCCESS!", false, 0); } return status; } void Log(const char* sz_info, bool is_error, ULONG err_code) { if (is_error) { DbgPrintEx(77, 0, "[instCallBack Error] : %s err_code:%x\n", sz_info, err_code); } else { DbgPrintEx(77, 0, "[instCallBack] : %s \n", sz_info); } } PUCHAR inst_callback_get_dll_memory(UNICODE_STRING* us_dll_path) { NTSTATUS status = STATUS_SUCCESS; HANDLE hFile = 0; OBJECT_ATTRIBUTES objattr = { 0 }; IO_STATUS_BLOCK IoStatusBlock = { 0 }; LARGE_INTEGER lainter = { 0 }; LARGE_INTEGER byteOffset = { 0 }; FILE_STANDARD_INFORMATION fileinfo = { 0 }; ULONG64 fileSize = 0; PUCHAR pDllMemory = 0; InitializeObjectAttributes(&objattr, us_dll_path, OBJ_CASE_INSENSITIVE, 0, 0); status = ZwCreateFile(&hFile, GENERIC_READ, &objattr, &IoStatusBlock, &lainter, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, FILE_OPEN, 0, 0, 0); if (!NT_SUCCESS(status)) { Log("failed to Create File!", true, status); return 0; } status = ZwQueryInformationFile(hFile, &IoStatusBlock, &fileinfo, sizeof(fileinfo), FileStandardInformation); fileSize = (ULONG)fileinfo.AllocationSize.QuadPart; if (!NT_SUCCESS(status)) { Log("failed to get File size!", true, status); return 0; } fileSize += 0x1000; fileSize = (ULONG64)PAGE_ALIGN(fileSize); //ExAllocatePoolWithTag pDllMemory = (PUCHAR)ExAllocatePoolWithTag(PagedPool, fileSize, 'Dllp'); //pDllmem = (PUCHAR)ExAllocatePool3(NonPagedPool, fileSize, 'Dllp', NULL, NULL);//NonPagedPool PagedPool RtlSecureZeroMemory(pDllMemory, fileSize); status = ZwReadFile(hFile, 0, 0, 0, &IoStatusBlock, pDllMemory, fileSize, &byteOffset, 0); ZwFlushBuffersFile(hFile, &IoStatusBlock); if (!NT_SUCCESS(status)) { ExFreePool(pDllMemory); ZwClose(hFile); Log("failed to read File context!", true, status); return 0; } ZwClose(hFile); return pDllMemory; } NTSTATUS inst_callback_alloc_memory(PUCHAR p_dll_memory, _Out_ PVOID* inst_callbak_addr, PVOID* p_manual_data) { PEPROCESS Process{ 0 }; IMAGE_NT_HEADERS* pNtHeader = nullptr; IMAGE_FILE_HEADER* pFileHeader = nullptr; IMAGE_OPTIONAL_HEADER* pOptheader = nullptr; NTSTATUS status = STATUS_SUCCESS; PVOID pManualMapData = 0,pShellCode = 0; char* pStartMapAdd = 0; size_t AllocSize = 0, RETSize; Manual_Mapping_data ManualMapData{ 0 }; if (reinterpret_cast<IMAGE_DOS_HEADER*>(p_dll_memory)->e_magic != 0x5A4D) { status = STATUS_INVALID_PARAMETER; Log("the dll is not valid pe structure\n", true, status); return status; } pNtHeader = (IMAGE_NT_HEADERS*)((ULONG_PTR)p_dll_memory + reinterpret_cast<IMAGE_DOS_HEADER*>(p_dll_memory)->e_lfanew); pFileHeader = &pNtHeader->FileHeader; pOptheader = &pNtHeader->OptionalHeader; if (pFileHeader->Machine != IMAGE_FILE_MACHINE_AMD64) { status = STATUS_INVALID_PARAMETER; Log("the dll is is x86 structure ,not support!\n", true, status); return status; } AllocSize = pOptheader->SizeOfImage; status = ZwAllocateVirtualMemory(NtCurrentProcess(), (PVOID*)&pStartMapAdd, 0, &AllocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) { Log("failed to alloc Memory!", true, status); return status; } RtlSecureZeroMemory(pStartMapAdd, AllocSize); ManualMapData.dwReadson = 0; ManualMapData.pGetProcAddress = (f_GetProcAddress)g_fnGetProcAddress; ManualMapData.pLoadLibraryA = (f_LoadLibraryA)g_fnLoadLibrary; ManualMapData.pRtlAddFunctionTable = (f_RtlAddFunctionTable)g_fnRtlAddFuntion; ManualMapData.pBase = pStartMapAdd; ManualMapData.bContinue = false;//判断是否抹除了pe标识 ManualMapData.bFirst = true; ManualMapData.bStart = false; ManualMapData.DllSize = AllocSize; Process = IoGetCurrentProcess(); status = MmCopyVirtualMemory(Process, p_dll_memory, Process, pStartMapAdd, PAGE_SIZE, KernelMode, &RETSize); if (!NT_SUCCESS(status)) { Log("failed to load pe header!", true, status); return status; } IMAGE_SECTION_HEADER* pSectionHeadr = IMAGE_FIRST_SECTION(pNtHeader); for (size_t i = 0; i < pFileHeader->NumberOfSections; i++, pSectionHeadr++) { if (pSectionHeadr->SizeOfRawData) { status = MmCopyVirtualMemory(Process, p_dll_memory + pSectionHeadr->PointerToRawData, Process, pStartMapAdd + pSectionHeadr->VirtualAddress, pSectionHeadr->SizeOfRawData, KernelMode, &RETSize); if (!NT_SUCCESS(status)) { Log("failed to load pe header!", true, status); return status; } } } AllocSize = PAGE_SIZE; status = ZwAllocateVirtualMemory(NtCurrentProcess(), &pManualMapData, 0, &AllocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) { Log("failed to alloc Memory for maunalmapdata!", true, status); return status; } RtlSecureZeroMemory(pManualMapData, AllocSize); status = MmCopyVirtualMemory(Process, &ManualMapData, Process, pManualMapData, sizeof(ManualMapData), KernelMode, &RETSize); if (!NT_SUCCESS(status)) { Log("failed to write Memory for maunalmapdata!", true, status); return status; } //重定位ShellCode status = ZwAllocateVirtualMemory(NtCurrentProcess(), &pShellCode, 0, &AllocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) { Log("failed to alloc Memory for shellcode!", true, status); return status; } RtlSecureZeroMemory(pShellCode, AllocSize); status = MmCopyVirtualMemory(Process, InstruShellCode, Process, pShellCode, AllocSize, KernelMode, &RETSize); if (!NT_SUCCESS(status)) { Log("failed to write Memory for shellcode!", true, status); return status; } //ShellCode shellcode_t shell_code; memset(&shell_code, 0, sizeof shell_code); memcpy(&shell_code, &g_instcall_shel1code, sizeof shellcode_t); shell_code.manual_data = (UINT64)pManualMapData; shell_code.rip = (UINT64)pShellCode; status = ZwAllocateVirtualMemory(NtCurrentProcess(), inst_callbak_addr, 0, &AllocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) { Log("failed to alloc Memory for instrcall shellcode!", true, status); return status; } RtlSecureZeroMemory(*inst_callbak_addr, AllocSize); status = MmCopyVirtualMemory(Process, &shell_code, Process, *inst_callbak_addr, sizeof shell_code, KernelMode, &RETSize); if (!NT_SUCCESS(status)) { Log("failed to write Memory for instrcall shellcode!", true, status); return status; } *p_manual_data = pManualMapData; return status; } NTSTATUS inst_callback_inject(HANDLE process_id, UNICODE_STRING* us_dll_path) { NTSTATUS status = STATUS_SUCCESS; PEPROCESS pEhtreadyx = NULL; KAPC_STATE apc_state; PUCHAR pDllMem = 0; PVOID InstCallBack = 0, pManualMapData = 0; status = PsLookupProcessByProcessId(process_id, &pEhtreadyx); if (!NT_SUCCESS(status)) { Log("failed to get process!", true, status); status = STATUS_UNSUCCESSFUL; return status; } KeStackAttachProcess(pEhtreadyx, &apc_state); while (true) { pDllMem = inst_callback_get_dll_memory(us_dll_path); if (!pDllMem) { status = STATUS_UNSUCCESSFUL; break; } status = inst_callback_alloc_memory(pDllMem, &InstCallBack, &pManualMapData); if (!NT_SUCCESS(status))break; //设置instrecallback status = inst_callback_set_callback(InstCallBack); break; } if (pManualMapData && MmIsAddressValid(pManualMapData)) { __try { while (1) { if (((Manual_Mapping_data*)pManualMapData)->bStart)break; } } __except (1) { Log("process exit! 1", true, 0); ObDereferenceObject(pEhtreadyx); KeUnstackDetachProcess(&apc_state); return status; } } inst_callback_set_callback(0); if (pManualMapData && MmIsAddressValid(pManualMapData) && PsLookupProcessByProcessId(process_id, &pEhtreadyx) != STATUS_PENDING) { __try { *(PUCHAR)((((Manual_Mapping_data*)pManualMapData))->pBase) = 0; ((Manual_Mapping_data*)pManualMapData)->bContinue = true; } __except (1) { Log("process exit!", true, 0); } } ObDereferenceObject(pEhtreadyx); KeUnstackDetachProcess(&apc_state); if (pDllMem && MmIsAddressValid(pDllMem))ExFreePool(pDllMem); return status; } void __stdcall InstruShellCode(Manual_Mapping_data* pData) { if (!pData->bFirst)return; pData->bFirst = false; pData->bStart = true; char* pBase = pData->pBase; auto* pOptionHeader = &reinterpret_cast<IMAGE_NT_HEADERS*>(pBase + reinterpret_cast<IMAGE_DOS_HEADER*> ((uintptr_t)pBase)->e_lfanew)->OptionalHeader; char* LocationDelta = pBase - pOptionHeader->ImageBase;//差值 if (LocationDelta) { if (pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size) { auto* pRelocData = reinterpret_cast<IMAGE_BASE_RELOCATION*> (pBase + pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); auto* pRelocEnd = reinterpret_cast<IMAGE_BASE_RELOCATION*>(reinterpret_cast<uintptr_t>(pRelocData) + pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size); while (pRelocData< pRelocEnd && pRelocData->SizeOfBlock) { UINT64 AmountOfEntries = (pRelocData->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(short); unsigned short* pRelativeInfo = reinterpret_cast<unsigned short*>(pRelocData + 1); for (UINT64 i = 0; i != AmountOfEntries; ++i, ++pRelativeInfo) { if (RELOC_FLAG(*pRelativeInfo)) { //计算出需要重定位的地址 auto* pPatch = reinterpret_cast<UINT_PTR*>(pBase + pRelocData->VirtualAddress + ((*pRelativeInfo) & 0xFFF)); //手动重定位 *pPatch += reinterpret_cast<UINT_PTR>(LocationDelta); } } pRelocData = reinterpret_cast<IMAGE_BASE_RELOCATION*>(reinterpret_cast<char*>(pRelocData) + pRelocData->SizeOfBlock); } } } if (pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size) { IMAGE_IMPORT_DESCRIPTOR* pImportDescr = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*> (pBase+pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); while (pImportDescr->Name) { HMODULE hDll = pData->pLoadLibraryA(pBase + pImportDescr->Name); //INT IAT //int ULONG_PTR* pInt = (ULONG_PTR*)(pBase + pImportDescr->OriginalFirstThunk); ULONG_PTR* pIat = (ULONG_PTR*)(pBase + pImportDescr->FirstThunk); //IAT if (!pInt)pInt = pIat; for (; *pIat; ++pIat, ++pInt) { if (IMAGE_SNAP_BY_ORDINAL(*pInt)) { *pIat = (ULONG_PTR)pData->pGetProcAddress(hDll, (char*)(*pInt & 0xffff)); } else { IMAGE_IMPORT_BY_NAME* pImport = (IMAGE_IMPORT_BY_NAME*)(pBase + *pInt); *pIat = (ULONG_PTR)pData->pGetProcAddress(hDll, pImport->Name); } } pImportDescr++; } } #define DLL_PROCESS_ATTACH 1 if (pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size) { auto* pTLS = reinterpret_cast<IMAGE_TLS_DIRECTORY*> (pBase + pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress); auto* pCallBack = reinterpret_cast<PIMAGE_TLS_CALLBACK*>(pTLS->AddressOfCallBacks); for (;pCallBack && *pCallBack; ++pCallBack) { (*pCallBack)(pBase, DLL_PROCESS_ATTACH, nullptr); } } auto excep = pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]; if (excep.Size) { pData->pRtlAddFunctionTable((_IMAGE_RUNTIME_FUNCTION_ENTRY*) (pBase + excep.VirtualAddress), excep.Size / sizeof(_IMAGE_RUNTIME_FUNCTION_ENTRY),(DWORD64)pBase); } while (!pData->bContinue); //手动调用dllmain ((f_DLL_ENTRY_POINT)(pBase+pOptionHeader->AddressOfEntryPoint))(pBase, DLL_PROCESS_ATTACH, 0); } 用这些代码注入dll 导致c00005异常
08-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值