如果是修改本进程的IAT来实现挂钩,可以直接使用imagehlp.dll里的ImageDirectoryEntryToData函数很容易地找到本进程的IAT。具体方法参考这篇文章:http://www.xfocus.net/articles/200403/681.html。
如果要修改其他进程的IAT,则首先要定位到内存中该进程的IAT,然后在其进程空间中搜索需要挂钩的dll和API函数。
读写其他进程的内存空间可以使用ReadProcessMemory和WriteProcessMemory这两个函数。
BOOL ReadProcessMemory(
HANDLE hProcess, // handle to the process whose memory is read
LPCVOID lpBaseAddress,
// address to start reading
LPVOID lpBuffer, // address of buffer to place read data
DWORD nSize, // number of bytes to read
LPDWORD lpNumberOfBytesRead
// address of number of bytes read
);
该函数用于读取进程内存空间的数据。其中,hProcess表示要读取的进程的句柄;lpBaseAddress表示读取的起始地址;lpBuffer表示用于保存读取数据的缓冲;nSize表示缓冲区大小;lpNumberOfBytesRead表示实际读取的字节数。如果函数调用成功返回TRUE。
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process whose memory is written to
LPVOID lpBaseAddress,
// address to start writing to
LPVOID lpBuffer, // pointer to buffer to write data to
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten
// actual number of bytes written
);
该函数用于向进程内存空间写入数据。其中,hProcess表示要写入的进程的句柄;lpBaseAddress表示写入的起始地址;lpBuffer表示用于写入数据的缓冲;nSize表示缓冲区大小;lpNumberOfBytesWritten表示实际写入的字节数。如果函数调用成功返回TRUE。
修改调用API函数代码其实是修改被调用API函数在输入地址表中IAT项内容。由于Windows系统对应用程序指令代码地址空间的严密保护机制,使得修改程序指令代码非常困难。修改进程内存需要调用几个Windows核心API函数:
DWORD VirtualQueryEx(
HANDLE hProcess, // handle to process
LPCVOID lpAddress, // address of region
PMEMORY_BASIC_INFORMATION lpBuffer,
// address of information buffer
DWORD dwLength // size of buffer
);
该函数用于查询关于进程内虚拟地址页的信息。其中,hProcess表示要查询的进程句柄;lpAddress表示被查询页的区域地址;lpBuffer表示用于保存查询页信息的缓冲;dwLength表示缓冲区大小。返回值为实际缓冲大小。
BOOL VirtualProtectEx(
HANDLE hProcess, // handle to process
LPVOID lpAddress, // address of region of committed pages
DWORD dwSize, // size of region
DWORD flNewProtect, // desired access protection
PDWORD lpflOldProtect
// address of variable to get old protection
);
该函数用于改变进程内虚拟地址页的保护属性。其中,hProcess表示要查询的进程句柄;lpAddress表示被改变保护属性页区域地址;dwSize表示页区域大小;flNewProtect表示新的保护属性,可取值为PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE等;lpflOldProtect表示用于保存改变前的保护属性。如果函数调用成功返回TRUE。
有了这几个API函数,我们就可以读取和修改进程内存了。首先,调用VirtualQueryEx()函数查询被修改内存的页信息,再根据此信息调用VirtualProtectEx()函数改变这些页的保护属性为PAGE_READWRITE,有了这个权限就可以使用WriteProcessMemory函数修改进程内存数据了。
进程的首地址就是进程的主模块句柄,可以通过PSAPI中的EnumProcessModules等函数获得。进程首地址即为IMAGE_DOS_HEADER结构,根据其成员e_lfanew可以计算出IMAGE_OPTIONAL_HEADER结构的地址,由它的成员DataDirectory数组即可定位到IMAGE_IMPORT_DESCRIPTOR结构。
PE文件的输入表记录了一个Win32程序隐式加载的所有dll的文件名及从中引入的API的函数名,通过PE文件头的数据目录中的第二个IMAGE_DATA_DIRECTORY,我们可以获得输入表的位置和大小。实际上,输入表是一个由IMAGE_IMPORT_DESCRIPTOR结构组成的数组,每个结构对应一个需要隐式加载的dll文件,整个输入表以一个Characteristics字段为0的IMAGE_IMPORT_DESCRIPTOR结束。
其中的Name字段是一个RVA,指向此结构所对应的dll的文件名,文件名是以NULL结束的字符串。在PE文件中,OriginalFirstThunk和FirstThunk都是RVA,分别指向两个内容完全相同的IMAGE_THUNK_DATA结构的数组,每个结构对应一个引入的函数,整个数组以一个内容为0的IMAGE_THUNK_DATA结构作为结束标志。IMAGE_THUNK_DATA结构定义如下:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA;
从上面的定义可以看出,完全能够把IMAGE_THUNK_DATA结构当作一个DWORD使用。当这个DWORD的最高为是1时,表示函数是以序号的形式引入的;否则函数是以函数名的形式引入的,且此DWORD是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构。我们可以使用在WINNT.H中预定义的常量IMAGE_ORDINAL_FLAG来测试最高位是否为1。IMAGE_IMPORT_BY_NAME结构定义如下:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME;
其中Hint字段的内容是可选的,如果它不是0,则它也表示函数的序号,我们编程是不必考虑它。虽然上面的定义中Name数组只包含一个元素,但其实它是一个变长数组,保存的是一个以NULL结尾的字符串,也就是函数名。
需要注意的是,在PE文件中OriginalFirstThunk和FirstThunk指向的内容完全一样,但是当文件被装入内存后,差别就出现了:OriginalFirstThunk的内容不会变,但FirstThunk里数据却会变成与其相对应的函数的入口地址。因此,在进程的内存空间中读取函数名的时候要使用OriginalFirstThunk的AddressOfData偏移,而修改函数地址的时候要修改FirstThunk的Function地址。如果弄错了就可能读取不到正确的函数名,或者修改了错误的地址。
int i;
HMODULE hModule;
DWORD dwOffset, dwRet, cbneeded;
DWORD dwPid = 1600; // PID of process to be hooked
TCHAR szModName[MAX_PATH];
TCHAR szHookModName[MAX_PATH] = {"kernel32.dll"};
TCHAR szFuncName[MAX_PATH] = {"Sleep"};
IMAGE_DOS_HEADER DOSHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
IMAGE_DATA_DIRECTORY DataDirectory;
IMAGE_IMPORT_DESCRIPTOR ImportDesc;
IMAGE_THUNK_DATA OrigThunkData;
IMAGE_THUNK_DATA RealThunkData;
IMAGE_IMPORT_BY_NAME ImportByName;
MEMORY_BASIC_INFORMATION mbi;
DWORD dwOldProtect;
LPVOID lpBaseAddress;
PROC pfnNew = (PROC)0x12345678; // address of new function
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, dwPid);
if (hProcess == NULL)
{
printf("Fail to open process!"n");
return;
}
if(!EnumProcessModules(hProcess, &hModule, sizeof(hModule), &cbneeded))
{
printf("Fail to enum process modules!"n");
return;
}
if (!ReadProcessMemory(hProcess, hModule, (void *)&DOSHeader, sizeof(IMAGE_DOS_HEADER), &dwRet))
{
printf("Fail to read memory in remote process!"n");
return;
}
dwOffset = DOSHeader.e_lfanew + SIZE_OF_NT_SIGNATURE + sizeof(IMAGE_FILE_HEADER);
if (!ReadProcessMemory(hProcess, (PBYTE)hModule + dwOffset, (void *)&OptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER), &dwRet))
{
printf("Fail to read memory in remote process!"n");
return;
}
DataDirectory = OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
i = 0;
do
{
dwOffset = DataDirectory.VirtualAddress + sizeof(IMAGE_IMPORT_DESCRIPTOR) * i;
if (!ReadProcessMemory(hProcess, (PBYTE)hModule + dwOffset, (void *)&ImportDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR), &dwRet))
{
printf("Fail to read memory in remote process!"n");
return;
}
if (!ReadProcessMemory(hProcess, (PBYTE)hModule + ImportDesc.Name, (void *)szModName, MAX_PATH, &dwRet))
{
printf("Fail to read memory in remote process!"n");
return;
}
if (stricmp(szModName, szHookModName) == 0)
{
break;
}
i++;
} while (ImportDesc.Name);
i = 0;
do
{
lpBaseAddress = (PBYTE)hModule + ImportDesc.OriginalFirstThunk + sizeof(IMAGE_THUNK_DATA) * i;
if (!ReadProcessMemory(hProcess, lpBaseAddress, (void *)&OrigThunkData, sizeof(IMAGE_THUNK_DATA), &dwRet))
{
printf("Fail to read memory in remote process!"n");
return;
}
lpBaseAddress = (PBYTE)hModule + ImportDesc.FirstThunk + sizeof(IMAGE_THUNK_DATA) * i;
if (!ReadProcessMemory(hProcess, lpBaseAddress, (void *)&RealThunkData, sizeof(IMAGE_THUNK_DATA), &dwRet))
{
printf("Fail to read memory in remote process!"n");
return;
}
if ((OrigThunkData.u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{
if (!ReadProcessMemory(hProcess, (PBYTE)hModule + OrigThunkData.u1.AddressOfData, (void *)&ImportByName, sizeof(WORD) + strlen(szFuncName) + 1, &dwRet))
{
if (GetLastError() == ERROR_PARTIAL_COPY)
{
i++;
continue;
}
else
{
printf("Fail to read memory in remote process!"n");
return;
}
}
if(ImportByName.Name[0] == '"0')
{
printf("Function not located!"n");
break;
}
if (strcmpi((char*)ImportByName.Name, szFuncName) == 0)
{
VirtualQueryEx(hProcess, lpBaseAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect);
if (!WriteProcessMemory(hProcess, lpBaseAddress, &pfnNew, sizeof(pfnNew), &dwRet))
{
printf("Fail to write memory in remote process!"n");
return;
}
printf("Hook Success!"n");
VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect);
break;
}
}
i++;
} while (OrigThunkData.u1.Function);