挂钩APIHooking API)一般分为运行前挂钩和运行时挂钩。运行前挂钩是修改我们想要挂钩函数来自的物理模块(大多数时候是exe或者dll文件)。运行时挂钩则是直接修改进程的内存空间。本文的方法属于运行时挂钩。

 

  如果是修改本进程的IAT来实现挂钩,可以直接使用imagehlp.dll里的ImageDirectoryEntryToData函数很容易地找到本进程的IAT。具体方法参考这篇文章:http://www.xfocus.net/articles/200403/681.html

 

  如果要修改其他进程的IAT,则首先要定位到内存中该进程的IAT,然后在其进程空间中搜索需要挂钩的dllAPI函数。

 

  读写其他进程的内存空间可以使用ReadProcessMemoryWriteProcessMemory这两个函数。

 

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_READONLYPAGE_READWRITEPAGE_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字段为0IMAGE_IMPORT_DESCRIPTOR结束。

 

  其中的Name字段是一个RVA,指向此结构所对应的dll的文件名,文件名是以NULL结束的字符串。在PE文件中,OriginalFirstThunkFirstThunk都是RVA,分别指向两个内容完全相同的IMAGE_THUNK_DATA结构的数组,每个结构对应一个引入的函数,整个数组以一个内容为0IMAGE_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来测试最高位是否为1IMAGE_IMPORT_BY_NAME结构定义如下:

 

typedef struct _IMAGE_IMPORT_BY_NAME {

    WORD Hint;

    BYTE Name[1];

} IMAGE_IMPORT_BY_NAME;

 

  其中Hint字段的内容是可选的,如果它不是0,则它也表示函数的序号,我们编程是不必考虑它。虽然上面的定义中Name数组只包含一个元素,但其实它是一个变长数组,保存的是一个以NULL结尾的字符串,也就是函数名。

 

  需要注意的是,在PE文件中OriginalFirstThunkFirstThunk指向的内容完全一样,但是当文件被装入内存后,差别就出现了:OriginalFirstThunk的内容不会变,但FirstThunk里数据却会变成与其相对应的函数的入口地址。因此,在进程的内存空间中读取函数名的时候要使用OriginalFirstThunkAddressOfData偏移,而修改函数地址的时候要修改FirstThunkFunction地址。如果弄错了就可能读取不到正确的函数名,或者修改了错误的地址。

 

       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);