Windows Ring3层注入——修改PE输入表(导入表)注入(一)
PE文件简单介绍
PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任何扩展名。
PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,PE文件头,块表(节表)以及具体的块(节)。
PE文件相关名词解释
- 虚拟空间:PE文件被系统加载器映射到内存中,每个程序都有自己的虚拟空间。
- 虚拟地址(VA):虚拟空间的内存地址。
- 相对虚拟地址(RVA):内存中相对于PE文件载入地址的偏移相对地址(也称偏移量)。
- 模块(Module):PE文件映射到内存中的版本被称为模块(Module)。(GetModuleHandle)
- 模块句柄:映射文件的起始地址。
- 基地址(ImageBase):初始内存起始地址,跟模块句柄一样(Windows CE不成立)。
- 区块间隙:PE文件头中,FileAlignment定义了磁盘区块的对齐值。每一个区块从对齐值的倍数的偏移位置开始,而区块的实际代码或数据的大小不一定刚好是这么多,所以不足的地方一般以00h来填充,就会出现区块间隙。(PE文件的典型对齐值为200h)。
- 内存对齐:PE文件头中,SectionAlignment定义了内存中区块的对齐值。当PE文件被映射到内存中,区块总是从一个页边界处开始。(x86内存页4KB(1000h)排列,x64内存页8KB(2000h)排列)。
File Offset=RVA-△k
File Offset=VA-ImageBase-△k
PE文件磁盘与内存映像结构图
更多PE文件的介绍可以参照《加密与解密》第11章节。
输入表介绍
输入是可执行程序(EXE)被加载到地址空间后,使用来自于其他 Dll 的代码或数据的动作。
输入表是Windows PE文件中的一组数据结构,输入表RVA在上图PE文件结构的PE文件头中的数据目录表中。
输入表就相当于 EXE文件与 DLL文件 沟通的钥匙,形象的可以比喻成两个城市之间交流的高速公路,所有的导入函数信息都会写入输入表中,在 PE 文件映射到内存后,Windows 将相应的 DLL文件装入,EXE 文件通过**“输入表”找到相应的 DLL 中的导入函数**,从而完成程序的正常运行,这一动态连接的过程都是由**“输入表”**参与的。
输入表结构
输入表是以一个IMAGE_IMPORT_DESCRIPTOR(IID) 数组 开始的,每一个被PE文件隐式的 链接进来的DLL 都有一个 IID,IID数组的最后一个单元用NULL表示。
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
_ANONYMOUS_UNION union {
DWORD Characteristics; //
DWORD OriginalFirstThunk; //指向输入名称表(INT)的RVA(相对虚拟地址),INT是一个IMAGE_THUNCK_DATA结构的数组,数组中的每个元素指向一个IMAGE_IMPORT_BY_NAME结构,INT以元素0结束。
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 时间戳,可以忽略
DWORD ForwarderChain; // 如果没有向前引用(Forwarders)的话就是-1
DWORD Name; // 被导入DLL的名字指针,是一个RVA
DWORD FirstThunk; // 指向输入地址表(IAT)的RVA。IAT是一个IMAGE_THUNK_DATA结构的数组
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
- IMAGE_THUNK_DATA 结构:
typedef struct _IMAGE_THUNK_DATA32
{
union
{
DWORD ForwarderString; //指向一个转向者字符串的RVA;
DWORD Function; //被输入的函数的内存地址
DWORD Ordinal; //被输入的API的序数值
DWORD AddressOfData; //指向IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;
- MAGE_IMPORT_BY_NAME结构:
struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // 指出函数在所在的dll的输出表中的序号
BYTE Name[1]; // 指出要输入的函数的函数名
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
- 要两个 IMAGE_THUNK_DATA 数组的原因:
当程序加载时,IAT 会被PE加载器重写,PE加载器先搜索 INT ,PE加载器迭代搜索 INT数组 中的每个指针,找出 INT 所指向的 IMAGE_IMPORT_BY_NAME 结构中的函数在内存中的真正的地址,并把它替代原来 IAT 中的值。当完成后, INT 就没有用了,程序只需要 IAT 就可以正常运行了。看下面图:
这个是 可执行程序在磁盘中 的时候:
这个是 当程序被加载 的时候:
输入表注入原理
首先让我们换一个角度观察一下模块的导入关系。假设有一个可执行程序A.exe,运行之后,其进程空间模块有hid.dll、msvcrt.dll、advapi32.dll,技术模块有ntdll.dll、kernel32.dll,代表其他模块的是other.dll,他们的导入关系如图所示:
- 因为一个进程空间对于同一个dll模块只需要加载一个,所以简化一下模块的导入依赖关系可以看到如图所示。
简化后的模块导入依赖关系图看上去比较清晰和简洁,很像一棵树,树枝代表上面输入表逻辑结构中的IMAGE_IMPORT_DESCRIPTIOR结构。Windows的PE加载器就是根据这种依赖关系来加载模块的。
注入是为了让自己的模块可以加载到目标进程空间中,所以根据上述原理,我们可不可以在上面的导入关系中添加一个是我们自己模块的节点,就可以完成注入。
一般情况,输入表的加载都是通过修改进程、输入依赖树上某个模块的输入表来实现的(树上增加节点),这个修改动作是在进程加载该模块之前完成的(即修改模块文件的输入表)。等到待注入模块被加载到进程控件后,就可以将被修改的模块文件改回来,防止文件修改操作被发现。
输入表加载的原理就是在原有的IMAGE_IMPORT_DESCRIPTION(IDD)数组中增加一条IID记录,所以我们必须考虑原来存放IID的位置是否有空间容纳这个新的IID。为了避免这种意外发生,有两种方法可以采用:
一是在所有的节中寻找一块能容纳心IID数组的文件空隙;
二是新增一个足够大的节来专门存放新的IID数组。
PE输入表注入的两种方法
静态修改PE文件法:
- 1.备份原IID结构
- 2.在原IID区域构造新的OriginalFirstThunk、Name和FirstThunk结构
- 3.填充新输入表项的IID结构
- 4.修正PE文件头的信息
进程创建期修改PE输入表法:
- 1.以挂起方式创建目标进程
- 2.获取目标进程中的PE结构信息
- 3.获取原IID大小,增加一项,搜索可用的节空隙
- 4.构造心的IID及其相关的OriginalFirstThunk、Name和FirstThunk结构
- 5.修正PE映像头
- 6.更新目标进程的内存。
- 7.继续运行主线程
输入表注入核心代码示例
根据上述原理介绍,对代码的流程可以如此梳理:
1.写一个我们自己的输入表IID结构
1.新增一个节(块section)来保存旧的输入表IID结构数组和我们新加的输入表IID结构
2.因为新加了节,我们就需要增加一个节表指向我们新加的节
3.因为新加了节和节表就需要更改PE文件头的信息
//新增输入表
BOOL AddNewImportDescriptor(const char * szPEFilePath,char * szInjectDllName, char *szImportFuncName)
//新增节(块)
BOOL AddNewSection(LPCTSTR lpStrModulePath, DWORD dwNewSectionSize)
BOOL AddNewImportDescriptor(const char * szPEFilePath,char * szInjectDllName, char *szImportFuncName)
{
BOOL bSuccess = FALSE;
LPVOID lpMemModule = NULL;
LPBYTE lpData = NULL;
DWORD dwNewSecFileSize, dwNewSecMemSize;
HANDLE hFile = INVALID_HANDLE_VALUE, hFileMapping = INVALID_HANDLE_VALUE;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_IMPORT_DESCRIPTOR pstImportTable = NULL;
PIMAGE_SECTION_HEADER pstSectionHeader = NULL;
__try
{
//pe文件映射到内存
hFile = CreateFile(
szPEFilePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( INVALID_HANDLE_VALUE == hFile )
{
OutputDebugString("[-] AddNewImportDescriptor CreateFile fail!\n");
goto _EXIT_;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE/* | SEC_IMAGE*/, 0, dwFileSize, "WINSUN_MAPPING_FILE");
if ( NULL == hFileMapping )
{
OutputDebugString("[-] AddNewImportDescriptor CreateFileMapping fail!\n");
goto _EXIT_;
}
lpMemModule = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, dwFileSize);
if ( NULL == lpMemModule )
{
OutputDebugString("[-] AddNewImportDescriptor MapViewOfFile fail!\n");
goto _EXIT_;
}
lpData = (LPBYTE)lpMemModule;
//判断是否是PE
if (((PIMAGE_DOS_HEADER)lpData)->e_magic != IMAGE_DOS_SIGNATURE )
{
OutputDebugString("[-] AddNewImportDescriptor PE Header MZ error!\n");
goto _EXIT_;
}
pNtHeader = (PIMAGE_NT_HEADERS)(lpData + ((PIMAGE_DOS_HEADER)(lpData))->e_lfanew);
if ( pNtHeader->Signature != IMAGE_NT_SIGNATURE )
{
OutputDebugString("[-] AddNewImportDescriptor PE Header PE error!\n");
goto _EXIT_;
}
pstImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(lpData + RVA2Offset(pNtHeader,pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
BOOL bBoundImport = FALSE;
if (pstImportTable->Characteristics == 0 && pstImportTable->FirstThunk != 0)
{
bBoundImport = TRUE;
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;
}
pstSectionHeader = (PIMAGE_SECTION_HEADER)(pNtHeader+1)+pNtHeader->FileHeader.NumberOfSections-1;
PBYTE pbNewSection = pstSectionHeader->PointerToRawData + lpData;
int i = 0;
while(pstImportTable->FirstThunk != 0)
{
memcpy(pbNewSection, pstImportTable, sizeof(IMAGE_IMPORT_DESCRIPTOR));
pstImportTable++;
pbNewSection += sizeof(IMAGE_IMPORT_DESCRIPTOR);
i++;
}
memcpy(pbNewSection, (pbNewSection-sizeof(IMAGE_IMPORT_DESCRIPTOR)), sizeof(IMAGE_IMPORT_DESCRIPTOR));
DWORD dwDelt = pstSectionHeader->VirtualAddress - pstSectionHeader->PointerToRawData;
//avoid import not need table
PIMAGE_THUNK_DATA pImgThunkData = (PIMAGE_THUNK_DATA)(pbNewSection + sizeof(IMAGE_IMPORT_DESCRIPTOR)*2);
//import dll name
PBYTE pszDllNamePosition = (PBYTE)(pImgThunkData + 2);
memcpy(pszDllNamePosition, szInjectDllName, strlen(szInjectDllName));
pszDllNamePosition[strlen(szInjectDllName)] = 0;
//确定IMAGE_IMPORT_BY_NAM的位置
PIMAGE_IMPORT_BY_NAME pImgImportByName = (PIMAGE_IMPORT_BY_NAME)(pszDllNamePosition + strlen(szInjectDllName) + 1);
//init IMAGE_THUNK_DATA
pImgThunkData->u1.Ordinal = dwDelt + (DWORD)pImgImportByName - (DWORD)lpData ;
//init IMAGE_IMPORT_BY_NAME
pImgImportByName->Hint = 1;
memcpy(pImgImportByName->Name, szImportFuncName, strlen(szImportFuncName)); //== dwDelt + (DWORD)pszFuncNamePosition - (DWORD)lpData ;
pImgImportByName->Name[strlen(szImportFuncName)] = 0;
//init OriginalFirstThunk
if (bBoundImport)
{
((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->OriginalFirstThunk = 0;
}
else
((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->OriginalFirstThunk = dwDelt + (DWORD)pImgThunkData - (DWORD)lpData;
//init FirstThunk
((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->FirstThunk = dwDelt + (DWORD)pImgThunkData - (DWORD)lpData;
//init Name
((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->Name = dwDelt + (DWORD)pszDllNamePosition-(DWORD)lpData;
//改变导入表指向顺序
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = pstSectionHeader->VirtualAddress;
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = (i+1)*sizeof(IMAGE_IMPORT_DESCRIPTOR);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
OutputDebugString("[-] AddNewImportDescriptor Exception!\n");
return false;
}
_EXIT_:
if ( hFile )
{
CloseHandle(hFile);
}
if ( lpMemModule)
{
UnmapViewOfFile(lpMemModule);
}
if ( hFileMapping )
{
CloseHandle(hFileMapping);
}
return true;
}
BOOL AddNewSection(LPCTSTR lpStrModulePath, DWORD dwNewSectionSize)
{
bool bSuccess = false;
LPVOID lpMemModule = NULL;
LPBYTE lpData = NULL;
DWORD dwNewSecFileSize, dwNewSecMemSize;
HANDLE hFile = INVALID_HANDLE_VALUE, hFileMapping = INVALID_HANDLE_VALUE;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_SECTION_HEADER pNewSection = NULL, pLastSection = NULL;
OutputDebugString("[!] AddNewSection Enter!\n");
//TODO:可能还涉及关闭windows文件保护
__try
{
//pe文件映射到内存
hFile = CreateFile(
lpStrModulePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( INVALID_HANDLE_VALUE == hFile )
{
OutputDebugString("[-] AddNewSection CreateFile fail!\n");
goto _EXIT_;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE/* | SEC_IMAGE*/, 0, dwFileSize, "WINSUN_MAPPING_FILE");
if ( NULL == hFileMapping )
{
OutputDebugString("[-] AddNewSection CreateFileMapping fail!\n");
goto _EXIT_;
}
lpMemModule = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, dwFileSize);
if ( NULL == lpMemModule )
{
OutputDebugString("[-] AddNewSection MapViewOfFile fail!\n");
goto _EXIT_;
}
lpData = (LPBYTE)lpMemModule;
//判断是否是PE文件
if (((PIMAGE_DOS_HEADER)lpData)->e_magic != IMAGE_DOS_SIGNATURE )
{
OutputDebugString("[-] AddNewSection PE Header MZ error!\n");
goto _EXIT_;
}
pNtHeader = (PIMAGE_NT_HEADERS)(lpData + ((PIMAGE_DOS_HEADER)(lpData))->e_lfanew);
if ( pNtHeader->Signature != IMAGE_NT_SIGNATURE )
{
OutputDebugString("[-] AddNewSection PE Header PE error!\n");
goto _EXIT_;
}
//判断是否可以增加一个新节
if ( ((pNtHeader->FileHeader.NumberOfSections + 1) * sizeof(IMAGE_SECTION_HEADER)) > (pNtHeader->OptionalHeader.SizeOfHeaders) )
{
OutputDebugString("[-] AddNewSection cannot add a new section!\n");
goto _EXIT_;
}
pNewSection = (PIMAGE_SECTION_HEADER)(pNtHeader+1) + pNtHeader->FileHeader.NumberOfSections;
pLastSection = pNewSection - 1;
DWORD rsize,vsize,roffset,voffset;
//对齐偏移和RVA
rsize=PEAlign(dwNewSectionSize,
pNtHeader->OptionalHeader.FileAlignment);
roffset=PEAlign(pLastSection->PointerToRawData+pLastSection->SizeOfRawData,
pNtHeader->OptionalHeader.FileAlignment);
vsize=PEAlign(dwNewSectionSize,
pNtHeader->OptionalHeader.SectionAlignment);
voffset=PEAlign(pLastSection->VirtualAddress+pLastSection->Misc.VirtualSize,
pNtHeader->OptionalHeader.SectionAlignment);
//填充新节表
memcpy(pNewSection->Name, "WINSUN", strlen("WINSUN"));
pNewSection->VirtualAddress = voffset;
pNewSection->PointerToRawData = roffset;
pNewSection->Misc.VirtualSize = vsize;
pNewSection->SizeOfRawData = rsize;
pNewSection->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
//修改IMAGE_NT_HEADERS,增加新节表
pNtHeader->FileHeader.NumberOfSections++;
pNtHeader->OptionalHeader.SizeOfImage += vsize;
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;
//增加新节到文件尾部
DWORD dwWriteBytes;
SetFilePointer(hFile,0,0,FILE_END);
PBYTE pbNewSectionContent = new BYTE[rsize];
ZeroMemory(pbNewSectionContent, rsize);
bSuccess = WriteFile(hFile, pbNewSectionContent, rsize, &dwWriteBytes, NULL);
if (!bSuccess)
{
MessageBox(NULL,"新增节失败","error",MB_OK);
goto _EXIT_;
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
OutputDebugString("[-] AddImportTableItem Exception!\n");
return false;
}
OutputDebugString("[!] AddNewSection Exit!\n");
bSuccess = true;
_EXIT_:
if ( hFile )
{
CloseHandle(hFile);
}
if ( lpMemModule)
{
UnmapViewOfFile(lpMemModule);
}
if ( hFileMapping )
{
CloseHandle(hFileMapping);
}
return true;
}
来源:CSDN
作者:手拿肉的美女剑豪
链接:https://blog.csdn.net/qq_38493448/article/details/104005689