查看原文
其他

Ring3注入学习:导入表注入

Ddjsq_2333 看雪学苑 2022-07-01

本文为看雪论坛优秀文章
看雪论坛作者ID:Ddjsq_2333



1


原理


导入表的结构


在编程中常常用到“导入函数”(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等。
 
于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其驻留的DLL 名等。
 
导入表由一系列IMAGE_IMPORT_DESCRIPTOR结构体组成:
 
 
结构的数量取决于程序要使用的DLL文件的数量,每一个结构对应一个DLL文件。
 
该结构体的定义如下:
struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name;//导入模块名的RVA DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;

具体在PE文件中,就像这个样子:
 
 
在IMAGE_IMPORT_DESCRIPTOR的结构体的最后面,附带着许多小结构体,里面记录的信息是该导入表的DLL要导入使用的API。
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1];} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 
以上便是导入表的基础结构。

导入表的注入


 
由《加密与解密》中得知,Windows装载PE文件时,会检查导入表,将导入表所包含要使用的DLL加载进程序中,这也就产生了一个DLL注入的点,如果将恶意DLL写入导入表中,就可以让程序在运行时调用恶意DLL中的代码,达到DLL注入的效果。


2


实现


要实现导入表注入,需要把要注入的DLL构造的结构体塞进导入表里,但是由于导入表默认不是在PE文件的最后,所以导入表原来所在的位置不一定有足够大的空间塞入一个新的结构体。

这时我们就需要把导入表移动到新的足够大的位置,有两种办法,扩大最后一个节和直接新增一个节,比较方便的方法是选择新增一个节,在新增节后将原导入表放进去,如何在最后写入自己的导入表,再新增8字节的INT和8字节IAT,至于为什么要增加8字节的INT表和8字节的IAT表,可以这样解释:
 
 
如图,当我们新写入一个IMAGE_IMPORT_DESCRIPTOR结构,我们就需要建立INT和IAT对于该结构的映射,此时就需要扩充IAT和INT表。
 
具体实现步骤如下:
  1. 找到原导入表
  2. 在程序最后开辟一个新节(也可扩大最后一个节)
  3. 拷贝原来的导入表到新节中
  4. 在新节拷贝的导入表后新增一个导入表
  5. 增加8字节的INT表和8字节的IAT表
  6. 存储要注入的dll的名称
  7. 增加一个_IMAGE_IMPORT_BYNAME结构,并将函数名称存进结构体第一个变量后的内存中
  8. 将_IMAGE_IMPORT_BY_NAME结构的地址的RVA赋值给INT表和IAT表第一项
  9. 将dll名称所在位置的首地址的RVA赋值给新增导入表的Name
  10. 修改IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size



3


代码


DLL代码

// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "pch.h"

extern "C" __declspec(dllexport) void puts(){ MessageBoxA(0, "hi", "hello", 0); return;}
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: puts(); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}


注入程序代码

#include<Windows.h>#include<stdio.h>#define DLLNAMELENGTH 0xE#define FUNCTIONNAMELENGTH 0xF#define FUNCTIONNAME "puts"#define DLLNAME "Dll1.dll"
//获取DOS头PIMAGE_DOS_HEADER GetDosHeader(_In_ char* pBase) { return PIMAGE_DOS_HEADER(pBase);}
//获取NT头PIMAGE_NT_HEADERS GetNtHeader(_In_ char* pBase) { return PIMAGE_NT_HEADERS(GetDosHeader(pBase)->e_lfanew + (SIZE_T)pBase);}
//获取文件头PIMAGE_FILE_HEADER GetFileHeader(_In_ char* pBase) { return &(GetNtHeader(pBase)->FileHeader);}
//获取OPT头PIMAGE_OPTIONAL_HEADER32 GetOptHeader(_In_ char* pBase) { return &(GetNtHeader(pBase)->OptionalHeader);}
PIMAGE_SECTION_HEADER GetSecByName(_In_ char* pBase, _In_ const char* name) { DWORD Secnum = GetFileHeader(pBase)->NumberOfSections; PIMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION(GetNtHeader(pBase)); char buf[10] = { 0 }; for (DWORD i = 0; i < Secnum; i++) { memcpy_s(buf, 8, (char*)Section[i].Name, 8); if (!strcmp(buf, name)) { return Section + i; } } return nullptr;}
//获取最后一个区段PIMAGE_SECTION_HEADER GetLastSec(_In_ char* PeBase) { DWORD SecNum = GetFileHeader(PeBase)->NumberOfSections; PIMAGE_SECTION_HEADER FirstSec = IMAGE_FIRST_SECTION(GetNtHeader(PeBase)); PIMAGE_SECTION_HEADER LastSec = FirstSec + SecNum - 1; return LastSec;}
char* OpenPeFiles(_In_ const char* Path, _Out_opt_ DWORD* nFileSize){ //读文件 HANDLE hFile = CreateFileA(Path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { //printf("打开文件失败"); return NULL; } DWORD PeSize = GetFileSize(hFile, NULL); if (nFileSize) *nFileSize = PeSize; DWORD ReadSize = 0; char* PeBase = new CHAR[PeSize]{ 0 }; ReadFile(hFile, PeBase, PeSize, &ReadSize, NULL);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)PeBase; //检测DOS头和NT头 if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { //printf("不是PE文件\n"); //system("pause"); return NULL; } PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(PeBase + pDosHeader->e_lfanew); if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { //printf("不是PE文件\n"); //system("pause"); return NULL; }
CloseHandle(hFile); return PeBase;}
//粒度对齐处理int AlignMent(_In_ int size, _In_ int alignment) { return (size) % (alignment) == 0 ? (size) : ((size) / alignment + 1) * (alignment);}
//新增节char* AddSection(_In_ char*& PeBase, _In_ DWORD& PeSize, _In_ const char* Section_name, _In_ const int Section_size){ GetFileHeader(PeBase)->NumberOfSections++; PIMAGE_SECTION_HEADER LastPeSection = GetLastSec(PeBase);
memcpy(LastPeSection->Name, Section_name, 8); LastPeSection->Misc.VirtualSize = Section_size; LastPeSection->VirtualAddress = (LastPeSection - 1)->VirtualAddress + AlignMent((LastPeSection - 1)->SizeOfRawData, GetOptHeader(PeBase)->SectionAlignment); LastPeSection->SizeOfRawData = AlignMent(Section_size, GetOptHeader(PeBase)->FileAlignment); LastPeSection->PointerToRawData = AlignMent(PeSize, GetOptHeader(PeBase)->FileAlignment); LastPeSection->Characteristics = 0xc0000040;//节表属性设为该值,意为该节表可读可写且包含已初始化的数据
GetOptHeader(PeBase)->SizeOfImage = LastPeSection->VirtualAddress + LastPeSection->SizeOfRawData;
int NewSize = LastPeSection->PointerToRawData + LastPeSection->SizeOfRawData;
char* NewPeBase = new char [NewSize] {0}; //向新缓冲区录入数据 memcpy(NewPeBase, PeBase, PeSize); //缓存区更替 delete PeBase; PeSize = NewSize; return NewPeBase;}
//保存文件void SaveFile(_In_ const char* path, _In_ const char* data, _In_ int FileSize) { HANDLE hFile = CreateFileA( path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); DWORD Buf = 0; WriteFile(hFile, data, FileSize, &Buf, NULL); CloseHandle(hFile);}
//将RVA的值转换成FOALPVOID RvaToFoa(LPVOID pFileBuffer, LPSTR virtualAddress) { LPSTR sectionAddress = NULL;//记录距离节头的距离 LPSTR fileAddress = NULL;//记录文件中的偏移 PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNTHeader = NULL; PIMAGE_FILE_HEADER pPEHeader = NULL; PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL; PIMAGE_SECTION_HEADER pSectionHeader = NULL;
if (pFileBuffer == NULL) { printf("文件写入内存失败!\n"); return NULL; }
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew); pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4); pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
if ((DWORD)virtualAddress <= pOptionHeader->SizeOfHeaders) { return virtualAddress; }
for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) { if ((DWORD)virtualAddress < pSectionHeader->VirtualAddress) { pSectionHeader--; break; } else if (i == pPEHeader->NumberOfSections) { break; } else { pSectionHeader++; }
}
//距离该节头的距离 sectionAddress = virtualAddress - pSectionHeader->VirtualAddress; fileAddress = pSectionHeader->PointerToRawData + sectionAddress;
return (LPVOID)fileAddress;}
//将FOA的值转换成RVALPVOID FoaToRva(LPVOID pFileBuffer, LPSTR fileaddress) { LPSTR sectionAddress = NULL;//记录距离节头的距离 LPSTR virtualaddress = NULL;//记录内存中的偏移 PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNTHeader = NULL; PIMAGE_FILE_HEADER pPEHeader = NULL; PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL; PIMAGE_SECTION_HEADER pSectionHeader = NULL;
if (pFileBuffer == NULL) { printf("文件写入内存失败!\n"); return NULL; }
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew); pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4); pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
if ((DWORD)fileaddress <= pOptionHeader->SizeOfHeaders) { return fileaddress; }
for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) { if ((DWORD)fileaddress < pSectionHeader->PointerToRawData) { pSectionHeader--; break; } else if (i == pPEHeader->NumberOfSections) { break; } else { pSectionHeader++; }
}
//距离该节头的距离 sectionAddress = fileaddress - pSectionHeader->PointerToRawData; virtualaddress = pSectionHeader->VirtualAddress + sectionAddress;
return (LPVOID)virtualaddress;}
char* inject_dll(_In_ char*& PeBase, _In_ DWORD& PeSize){ PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;//定位表目录 PIMAGE_IMPORT_DESCRIPTOR importTableAddress = NULL;//定位导入表的真正位置 LPVOID returnAddress = NULL;//记录RVAtoFOA的返回值
//定位到新节的位置和导入表的位置 pDataDirectory = (PIMAGE_DATA_DIRECTORY)GetOptHeader(PeBase)->DataDirectory; pDataDirectory += 0x1;
DWORD sectionLength = pDataDirectory->Size + 0x28 + +0x10 + DLLNAMELENGTH + FUNCTIONNAMELENGTH + 0x2; sectionLength = AlignMent(sectionLength, GetOptHeader(PeBase)->FileAlignment);
char SecName[] = ".ddjsq"; char* NewPeBase = AddSection(PeBase, PeSize, SecName, sectionLength);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)GetOptHeader(NewPeBase)->DataDirectory; pDataDirectory += 0x1;
PDWORD pNewSection = (PDWORD)(GetLastSec(NewPeBase)->PointerToRawData + (DWORD)NewPeBase); returnAddress = RvaToFoa(NewPeBase, (LPSTR)pDataDirectory->VirtualAddress); importTableAddress = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)returnAddress + (DWORD)NewPeBase);
//复制原导入表,在原导入表后新增一个导入表 memcpy(pNewSection, importTableAddress, pDataDirectory->Size); importTableAddress = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pNewSection + pDataDirectory->Size - 0x14);
//增加8字节INT表 PIMAGE_THUNK_DATA32 pIntTable = (PIMAGE_THUNK_DATA32)((DWORD)importTableAddress + 0x28);//保留20字节的0 PIMAGE_THUNK_DATA32 repairIntTable = pIntTable; pIntTable++; pIntTable->u1.Ordinal = 0x0; pIntTable++;
//增加8字节IAT表 PIMAGE_THUNK_DATA32 pIatTable = (PIMAGE_THUNK_DATA32)(pIntTable); PIMAGE_THUNK_DATA32 repairIatTable = pIatTable; pIatTable++; pIatTable->u1.Ordinal = 0x0; pIatTable++;
//分配空间存储DLL名称字符串 PDWORD dllNameAddress = (PDWORD)pIatTable; memcpy(dllNameAddress, DLLNAME, DLLNAMELENGTH);
//增加IMAGE_IMPORT_BY_NAME 结构 PIMAGE_IMPORT_BY_NAME functionNameAddress = (PIMAGE_IMPORT_BY_NAME)((DWORD)dllNameAddress + DLLNAMELENGTH); PDWORD pFunctionName = (PDWORD)((DWORD)functionNameAddress + 0x2); memcpy(pFunctionName, FUNCTIONNAME, FUNCTIONNAMELENGTH);
//将IMAGE_IMPORT_BY_NAME结构的RVA赋值给INT和IAT表中的第一项 repairIntTable->u1.AddressOfData = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)functionNameAddress - (DWORD)NewPeBase)); repairIatTable->u1.AddressOfData = repairIntTable->u1.Ordinal;
//修正导入表Name、OriginalFirstThunk、FirstThunk importTableAddress->Name = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)dllNameAddress - (DWORD)NewPeBase)); importTableAddress->OriginalFirstThunk = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)repairIntTable - (DWORD)NewPeBase)); importTableAddress->FirstThunk = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)repairIatTable - (DWORD)NewPeBase));
//修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size pDataDirectory->VirtualAddress = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)pNewSection - (DWORD)NewPeBase)); pDataDirectory->Size += 0x14;
return NewPeBase;}
int main(){ char path[] = "路径"; DWORD pesize; char* PeBase = OpenPeFiles(path, &pesize); if (!PeBase) { printf("wrong"); return 0; } char* NewPeBase = inject_dll(PeBase, pesize); SaveFile("路径", NewPeBase, pesize);}


运行结果


注入前:
 
 
注入后:
 
 
运行时:
 

防范手段


由于是静态注入,直接修改了exe文件,所以可使用校验的方式去检测有无被注入。


参考链接


《32位PE解析、PE修改、导入表注入》(https://bbs.pediy.com/thread-258246.htm)

PE基础之导入表注入(https://bbs.pediy.com/thread-262420.htm)

Ring3注入总结及编程实现(https://bbs.pediy.com/thread-217722.htm)




 


看雪ID:Ddjsq_2333

https://bbs.pediy.com/user-home-910802.htm

*本文由看雪论坛 Ddjsq_2333 原创,转载请注明来自看雪社区






# 往期推荐

1. 新人PWN堆Heap总结

2. 如何利用栈溢出漏洞

3.OD插件 - 支持chm帮助文档

4. Galgame汉化中的逆向:ArmArm64_ELF中汉化字符串超长修改方法

5. FartExt之优化更深主动调用的FART10

6. V8利用初探 2019 StarCTF oob 复现分析



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存