本帖最后由 JoyChou 于 2014-3-22 21:32 编辑
万地高楼平地起。
夯实基础,复习下基础的东西。
0x1. 输入表
一、什么是输入
可执行文件使用来自于其他dll的代码或数据时,称为输入。
二、函数调用
我们都知道,dll调用有两种方式。分别为隐式和显示。
隐式即PE加载器替我们实现利用LoadLibrary和GetProcAdddress获取API地址,
我们直接调用类似MessageBox 的API即可。
显示即我们自己写LoadLibrary和GetProcAdddress来获取dll中导出的API地址。
三、输入表结构
在NT头偏移0x80处就是IMAGE_IMPORT_DESCRIPTOR结构
编程的可以时候可以通过下面的代码获得IID的指针。
[C++] 纯文本查看 复制代码 PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + \
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
每一个dll对应一个IID结构。其结构如下
[C++] 纯文本查看 复制代码 typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
成员介绍:
OriginalFirstThunk:指向INT(Import Name Tabe)
Name:指向dll的名字
FirstThunk:指向IAT(Impot Address Table)
INT和IAT都是IMAGE_THUNK_DATA结构体数组,这个数组实质就是一个DWORD类型,不同时候有着不同的含义。我们可以把这个结构体直接看成是一个DWORD的值。
[C++] 纯文本查看 复制代码 IMAGE_THUNK_DATA STRUC
union u1
ForwarderString DWORD ? ; 指向一个转向者字符串的RVA
Function DWORD ? ; 被输入的函数的内存地址
Ordinal DWORD ? ; 被输入的API 的序数值
AddressOfData DWORD ? ; 指向 IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS
比较重要的是:
1. 当IMAGE_THUNK_DATA最高位为1时,表示函数以序号方式输入(MFC模块就是这样),低31位被看作是一个函数序号。
2.当IMAGE_THUNK_DATA最高位为0时,调试函数以字符串类型的函数名方式输入,此时,DWORD的值指向一个IMAGE_IMPORT_BY_NAME结构。这样就可以找到函数名了。
[C++] 纯文本查看 复制代码 IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD ?
Name BYTE ?
IMAGE_IMPORT_BY_NAME ENDS
四、IAT
为什么有两个并行的指针数组指向IMAGE_IMPORT_BY_NAME结构呢?
第一个由OriginalFirstThunk指向的INT,不可改写
第二个由FirstThunk指向的IAT指向。
PE装载器首先搜索OriginalFirstThunk找到函数名,调用GetProcAddress函数得到函数入口地址,然后用函数入口地址取代FirstThunk指向的IAT。此时IAT就形成了。
如果OriginalFirstThunk为0,那系统就只能根据FirstThunk进行IAT填充。
下面这图,程序是被装载到内存中或者程序被dump下来的IAT。
如果出现在磁盘上,没有运行的话,IAT和INT是一样的内容,即等价的。
代码遍历输入表: 程序的注释可以帮助更加了解输入表结构
[C++] 纯文本查看 复制代码 #include "stdafx.h"
#include <Windows.h>
int main(int argc, char *argv[])
{
HANDLE hFile = CreateFile(
argv[1],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE | SEC_IMAGE, 0, 0, NULL);
DWORD dwImageBase = (DWORD)MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, NULL, NULL, 0);
// 得到当前基地址
//DWORD dwImageBase = (DWORD)GetModuleHandle(NULL);
// 得到DOS头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;
// 得到NT头
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(dwImageBase + pDosHeader->e_lfanew);
// 得到IID头
PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + \
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
PIMAGE_THUNK_DATA pThunk = NULL;
printf("-----------Import Table----------");
while (pIID->Name)
{
DWORD n = 0;
printf("----------------------------------\n");
printf("DllName: %s\n", pIID->Name + dwImageBase);
// 有些程序的OriginalFirstThunk为0,所以此时要用到FirstThunk
if (pIID->OriginalFirstThunk)
{
pThunk = (PIMAGE_THUNK_DATA)(dwImageBase + pIID->OriginalFirstThunk);
}
else
{
pThunk = (PIMAGE_THUNK_DATA)(dwImageBase + pIID->FirstThunk);
}
while( *(DWORD*)pThunk)
{
// 判断最高位是否为1
// 如果是1,表示函数以序号方式输入
// 此时,低31位被看作是函数序号
if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
{
// ul是联合体,随便哪个,值肯定只有一个,只是表示意义不同
// 像MFC的模块就是以序号输入,只需要和FFFF &运算
// 因为ul.Ordinal的值都是类似80001D56的值,只有第一位为1,第二三四位都为0
// 为了程序的可读性,还是写成7FFFFFFFh
printf("Ordinal = %08X \r\n", pThunk->u1.Ordinal & 0x7FFFFFFF);
}
else
{
PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)(pThunk->u1.Function);
printf("FuncName: %s\n", dwImageBase + pFuncName->Name);
// 得到IAT的地址,这里必须通过ImageBase
printf("Addr = %08X\n", pNtHeader->OptionalHeader.ImageBase + pIID->FirstThunk + n);
}
pThunk++;
n += 4;
}
pIID++;
}
CloseHandle(hFile);
UnmapViewOfFile((LPVOID)dwImageBase);
return 0;
}
0x2. 导出表
关于导出表,可以参考这篇文章:http://bbs.pediy.com/showthread.php?t=122632
关于PE文件导出表看上面的文章已经足够了,《加密与解密》上写的不是很清楚。
自己先写一个DLL,代码如下
[C++] 纯文本查看 复制代码 #include "stdafx.h"
#include <stdio.h>
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
return TRUE;
}
void fnDll1()
{
printf("1\n");
}
void fnDll2()
{
printf("2\n");
}
void fnDll3()
{
printf("3\n");
}
def文件:
[C++] 纯文本查看 复制代码 LIBRARY
EXPORTS
fnDll1 @ 3 NONAME
fnDll2 @ 4
fnDll3 @ 5
解析代码:
[C++] 纯文本查看 复制代码 #include "stdafx.h"
#include <Windows.h>
int main(int argc, char *argv[])
{
HANDLE hFile = CreateFile(
argv[1],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
DWORD dwFileSize = GetFileSize(hFile, NULL);
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE | SEC_IMAGE, 0, dwFileSize, NULL);
if (hMap == NULL)
{
CloseHandle(hFile);
return -1;
}
DWORD dwImageBase = (DWORD)MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, NULL, NULL, dwFileSize);
if (dwImageBase==NULL)
{
CloseHandle(hFile);
CloseHandle(hMap);
return -1;
}
// 得到当前基地址
//DWORD dwImageBase = (DWORD)GetModuleHandle(NULL);
// 得到DOS头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;
// 得到NT头
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(dwImageBase + pDosHeader->e_lfanew);
// 得到IID头
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(dwImageBase + \
pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress);
int nFuncNum = pExportDir->NumberOfFunctions;
int nBase = pExportDir->Base;
int iNameOrdinalsIndex = -1;
// 导出序号表首地址(WORD大小)
WORD *pNameOrdinalsTable = (WORD *)(dwImageBase + pExportDir->AddressOfNameOrdinals);
// 导出函数名称数组首地址(设置为DWORD数组)
DWORD *pNameAddress = (DWORD *)(pExportDir->AddressOfNames + dwImageBase);
// EAT数组首地址
DWORD *pRVAFunc = (DWORD *)(pExportDir->AddressOfFunctions + dwImageBase);
printf("----------------Export Table Start---------------------\n");
printf("\n");
printf("DllName: %s\n", dwImageBase + pExportDir->Name);
for (int i = 0; i < nFuncNum; i++)
{
BOOL bFind = FALSE;
for (int j = 0; j < (int)pExportDir->NumberOfNames; j++)
{
// 导出序号 + base基数
int iIndex = pNameOrdinalsTable[j] + nBase;
// 如果能找到,就表示以名称和序号,否则只是以序号
if (iIndex == i + nBase)
{
iNameOrdinalsIndex++;
bFind = TRUE; // 名称+序号
break;
}
}
if (bFind)
{
// 打印函数名字
printf("序号:%4d\t", i+nBase);
printf("RVA: 0x%08X\t", pRVAFunc);
printf("FuncName: %s\n", pNameAddress[iNameOrdinalsIndex] + dwImageBase);
}
// EAT中RVA为0就不输出
else if(pRVAFunc)
{
printf("序号:%4d\t", i+nBase);
printf("RVA: 0x%08X\t", pRVAFunc);
printf("FuncName: --\n");
}
}
puts("");
printf("----------------Export Table End---------------------\n");
CloseHandle(hFile);
CloseHandle(hMap);
UnmapViewOfFile((LPVOID)dwImageBase);
return 0;
}
自己程序解析的:
lordpe解析的:
peid解析的:
可以看到peid的序号是有问题的。
附件:
Import Table And Export Table.zip
(197.09 KB, 下载次数: 15)
|