导入表的结构如下
xxxxxxxxxx
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
需要关注的三个字段为:
OriginalFirstThunk
是指向INT
表的RVA
,FirstThunk
是指向IAT
表的RVA
INT表和IAT表在文件中是完全一样的(前提是没有绑定导入)
IMAGE_THUNK_DATA
是一个4字节的数据,如果最高位是1,那么低31位就是函数的导出序号;如果最高位是0,那么它的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME
结构
IMAGE_THUNK_DATA结构(不需要把它当成struct)
xxxxxxxxxx
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString;
DWORD Function;
DWORD Ordinal;
DWORD AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;
IMAGE_IMPORT_BY_NAME结构
xxxxxxxxxx
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
其中低地址的Hint
是导出序号,然而这个值可能并不准确,有些编译器会把它设置成0,我们只需要关注 Name
,这个是一个以0结尾的字符串,表示函数名
如果没有提前绑定函数地址,IAT表和INT表在文件中存储的数据是完全一样的
在PE加载前后IAT
表变化
参考之前的文章,拿到导出表的FOA
和RVA
其中IMAGE_DIRECTORY_ENTRY_IMPORT
是宏定义的1,导入表位于数据目录第二个
xxxxxxxxxx
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
return;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 0x04);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
pDataDirectoryImportTable = &pDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (!pDataDirectoryImportTable->VirtualAddress)
{
printf("这个程序不存在导入表\n");
return;
}
printf("Import Table RVA: %#010x\n", pDataDirectoryImportTable->VirtualAddress);
DWORD FoaImportTable = RvaToFileOffset(pFileBuffer, pDataDirectoryImportTable->VirtualAddress);
printf("Import Table FOA: %#010x\n", FoaImportTable);
回顾PE的加载过程:
循环所有导出表(如果名字为空认为读取结束)
xxxxxxxxxx
pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + FoaImportTable);
while (pImportTable->Name != 0)
{
// ...
pImportTable++;
}
拿到DLL名称,注意转为FOA
xxxxxxxxxx
DWORD FoaDllName = RvaToFileOffset(pFileBuffer, pImportTable->Name);
PDWORD pFoaDllName = (PDWORD)((DWORD)pFileBuffer + FoaDllName);
printf("%s\n", (LPSTR)pFoaDllName);
拿到OriginalFirstThunk
结构体进行遍历
xxxxxxxxxx
DWORD FoaOriginalFirstThunk = RvaToFileOffset(pFileBuffer, pImportTable->OriginalFirstThunk);
PDWORD pFoaOriginalFirstThunk = (PDWORD)((DWORD)pFileBuffer + FoaOriginalFirstThunk);
pOriginalFirstThunk = (PIMAGE_THUNK_DATA)pFoaOriginalFirstThunk;
xxxxxxxxxx
while (*(PDWORD)pOriginalFirstThunk)
{
// ...
pOriginalFirstThunk++;
}
参考开头部分的内容:IMAGE_THUNK_DATA
是一个4字节的数据,如果最高位是1,那么低31位就是函数的导出序号;如果最高位是0,那么它的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME
结构
拿到这个值的最高位(其中IMAGE_ORDINAL_FLAG32是最高位为1的DWORD)
xxxxxxxxxx
DWORD value = *(PDWORD)pOriginalFirstThunk;
DWORD cntf = (value & IMAGE_ORDINAL_FLAG32) >> 31;
最高位为1时输出,为0时转FOA
后计算偏移转IMAGE_IMPORT_BY_NAME
结构体拿到名称
xxxxxxxxxx
if (cntf)
{
value -= IMAGE_ORDINAL_FLAG32;
printf("Import Ordinal --> %#010x\n", value);
}
else
{
DWORD FoaImportByName = RvaToFileOffset(pFileBuffer, value);
PDWORD pFoaImportByName = (PDWORD)((DWORD)pFileBuffer + FoaImportByName);
pImportByName = (PIMAGE_IMPORT_BY_NAME)pFoaImportByName;
printf("Import Hint Name --> %#-028s Import Address --> %#010x\n", pImportByName->Name, pImportByName->Hint);
}
打印FirstThunk
部分代码逻辑类似,整体代码如下
xxxxxxxxxx
VOID LogImportTable(IN PVOID pFileBuffer)
{
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;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectoryImportTable = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImportTable = NULL;
PIMAGE_IMPORT_BY_NAME pImportByName = NULL;
PIMAGE_THUNK_DATA pOriginalFirstThunk = NULL;
PIMAGE_THUNK_DATA pFirstThunk = NULL;
if (pFileBuffer == NULL)
{
return;
}
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
return;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
return;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 0x04);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
pDataDirectoryImportTable = &pDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (!pDataDirectoryImportTable->VirtualAddress)
{
printf("这个程序不存在导入表\n");
return;
}
printf("Import Table RVA: %#010x\n", pDataDirectoryImportTable->VirtualAddress);
DWORD FoaImportTable = RvaToFileOffset(pFileBuffer, pDataDirectoryImportTable->VirtualAddress);
printf("Import Table FOA: %#010x\n", FoaImportTable);
pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + FoaImportTable);
while (pImportTable->Name != 0)
{
DWORD FoaDllName = RvaToFileOffset(pFileBuffer, pImportTable->Name);
PDWORD pFoaDllName = (PDWORD)((DWORD)pFileBuffer + FoaDllName);
printf("%s\n", (LPSTR)pFoaDllName);
DWORD FoaOriginalFirstThunk = RvaToFileOffset(pFileBuffer, pImportTable->OriginalFirstThunk);
PDWORD pFoaOriginalFirstThunk = (PDWORD)((DWORD)pFileBuffer + FoaOriginalFirstThunk);
pOriginalFirstThunk = (PIMAGE_THUNK_DATA)pFoaOriginalFirstThunk;
while (*(PDWORD)pOriginalFirstThunk)
{
DWORD value = *(PDWORD)pOriginalFirstThunk;
DWORD cntf = (value & IMAGE_ORDINAL_FLAG32) >> 31;
if (cntf)
{
value -= IMAGE_ORDINAL_FLAG32;
printf("Import Ordinal --> %#010x\n", value);
}
else
{
DWORD FoaImportByName = RvaToFileOffset(pFileBuffer, value);
PDWORD pFoaImportByName = (PDWORD)((DWORD)pFileBuffer + FoaImportByName);
pImportByName = (PIMAGE_IMPORT_BY_NAME)pFoaImportByName;
printf("Import Hint Name --> %#-028s Import Address --> %#010x\n", pImportByName->Name, pImportByName->Hint);
}
pOriginalFirstThunk++;
}
DWORD FoaFirstThunk = RvaToFileOffset(pFileBuffer, pImportTable->FirstThunk);
PDWORD pFoaFirstThunk = (PDWORD)((DWORD)pDosHeader + FoaFirstThunk);
pFirstThunk = (PIMAGE_THUNK_DATA)pFoaFirstThunk;
while (*(PDWORD)pFirstThunk)
{
DWORD value = *(PDWORD)pFirstThunk;
DWORD cntf = (value & IMAGE_ORDINAL_FLAG32) >> 31;
if (cntf)
{
value -= IMAGE_ORDINAL_FLAG32;
printf("Import Ordinal --> %#010x\n", value);
}
else
{
DWORD FoaImportByName = RvaToFileOffset(pFileBuffer, value);
PDWORD pFoaImportByName = (PDWORD)((DWORD)pFileBuffer + FoaImportByName);
pImportByName = (PIMAGE_IMPORT_BY_NAME)pFoaImportByName;
printf("Import Hint Name --> %#-028s Import Address --> %#010x\n", pImportByName->Name, pImportByName->Hint);
}
pFirstThunk++;
}
printf("\n");
pImportTable++;
}
}
某非WINDOWS
自带EXE截图
NOTEPAD
截图(为什么这里的FirstThunk
内的Name
为空,参考下篇文章)