首页
社区
课程
招聘
[原创]iat hook
发表于: 2014-5-25 13:19 13792

[原创]iat hook

2014-5-25 13:19
13792
hook,想来很多小菜和我一样,看到这个词就觉得激动,如果有一天我也学会了hook,我一定会成为大神。回忆起来,一年前我刚刚
熟悉论坛的时候,也是这样的心情。今天这篇文章,专为祭奠这个想法。这篇文章介绍iat hook,也就是修改导入表实现函数调用的hook
(运行时hook本进程)。注:iat(import address table,导入函数地址表,在这里说明一下:导入表是image import table,导入函数地
址表是 image import address table,这是两个不同的pe文件中的表)
        参考文章:http://www.xfocus.net/articles/200403/681.html
        要实现iat hook,首先得清楚iat和导入表的结构。pe文件中,不管是在硬盘上的文件,还是内存中的映像,导入表和iat都会存在,
而且在硬盘上和在映像中的组织方式相同(但是整个pe文件在硬盘上和在内存中的组织方式有很大不同)。pe文件的结构在这
篇文章中不会讨论太多,但是理解
这篇文章需要pe文件结构的知识,请在了解pe文件结构的情况下阅读这篇文章,如果还不了解,可以参考这本书:windows pe权威指南,
写的非常详尽,特别是pe文件头和各种表。
        pe文件中的导入表的起始是一组叫做image_import_descriptor的结构体,每一个结构体20个字节,对应于一个动态链接库(也许关于
这一点我是错的),以一个全0的结构体结束。用汇编定义如下:
IMAGE_IMPORT_DESCRIPTOR STRUCT
  union
    Characteristics dd ?
    OriginalFirstThunk dd ?
  ends
  TimeDateStamp dd ?
  ForwarderChain dd ?
  Name1 dd ? ;我们需要关注这一个成员
  FirstThunk dd ? ;还有这一个成员
IMAGE_IMPORT_DESCRIPTOR ENDS
        这里我们只关心在内存中他们会指向哪里,而不关心在硬盘上的文件,所以我们的hook是在运行时完成的。上面的结构体中,Name1是一
个rva(relative virtual address,相对虚拟地址),也就是说它是一个相对于imagebase(模块基地址)的一个偏移,模块基地址可以用
GetModuleHandle(ModuleName)来获取,如果ModuleName我们传入NULL,可以获取这个函数在被执行时所在的模块。Name1指向的是什么呢?
我们前面说过,每一个IMAGE_IMPORT_DESCRIPTOR对应于一个动态链接库(广义),这个Name1指向的就是这个动态链接库的名字(以0结尾
什么的就不需要我说明了)。
        FirstThunk也是一个rva,它指向Name1对应的动态链接库的所有的被当前程序导入了的函数,实际上它指向的地方就位于iat中,可以
说导入表和iat是你中有我,我中有你。那么iat到底是怎么组织的呢?参考下面:
        链接库A导入函数1的地址
        链接库A导入函数2的地址
        00000000
        链接库B导入函数1的地址
        链接库B导入函数2的地址
        链接库B导入函数3的地址
        00000000
        。。。。。。。
        iat是分成一块一块的,每一块对应于一个被程序导入的动态链接库,每一块都是一个接一个的地址,最后以双字的0表示这一个动态
链接库的函数地址已经全部存储好了。实际上,在硬盘上iat的组织方式不是这样的,但是我们只关心在内存中它是怎样组织的。
        了解了上面的知识后,我们就可以看看当我们在程序中,当调用一个函数的时候到底发生了什么。
        比如下面这句:int pid=GetCurrentProcessId();我们要分解的就是GetCurrentProcessId到底是怎么完成的。
        GetCurrentProcessId()位于kernle32.dll中,如果我们在程序中调用了这个函数,编译链接器就知道,我们的程序在运行的时候需要
知道GetCurrentProcessId在内存的哪个位置,于是就在pe文件中的导入表中加入一个image import descriptor,结构中的Name1指向一个
字符串:kernel32.dll,这样,pe加载器在将我们的程序加载到内存中时,它就知道,我们的程序需要调用kernel32.dll中的函数,它就
把kernel32.dll映射到我们的进程的空间中(要深入说明这个很复杂,我们可以简单的理解为,我们的程序的地址空间中已经把整个的kernel
32.dll映射了)。虽然这个需要的dll已经映射好了,但是我们要的函数的地址放在哪里呢?这里就是iat起作用的地方了,仔细看上面的
iat的组织方式,我们假设链接库A就是kernel32.dll,那pe加载器在加载的时候,就把GetCurrentProcessId的地址放到上面那个结构中
“链接库A导入函数1的地址”那个地方。
        OK,我们假设需要的kernel32.dll已经映射好了,我们要用的函数的地址也找到了并且放到了前面说的那个地方,程序中调用这个函数
的时候到底怎么跳转的呢?
        如果反汇编的话,它就变成下面这个模样:(地址我随意写的,大概这样)
0x00400078        call 0x00403456
                        .
                        .
                        .
                        .
0x00403456        jmp [0x00401234]
        而在0x00401234里面放的是这个东西:0x7c8099b0,那上面的jmp就跳到x07c8099b0开始执行。
        再看看上面这个流程,在0x00400078这个地方,有一个call指令,这个call指令就是代替源程序中的GetCurrentProcessId()这一句。
call到0x00403456后,发现是一个jmp指令,jmp到的地方是0x00401234这个地址中存放的一个地址,也就在是先到0x00401234中取出一个
双字值X,然后跳转到X处开始执行。这个流程有点拐弯抹角,可以仔细看看。
        重要的地方在0x00401234,这个地方存放的就是GetCurrentProcessId()的地址,而0x00401234自己在的地方就是iat中,就是前面的
“链接库A导入函数1的地址”在的地方。
        理解了上面的call流程,我们就可以想想,如果我们把0x00401234这个地址里面的东西换掉,它不是原来是0x7c8099b0吗,也就是
GetCurrentProcessId()函数在内存中的位置吗,我们把它换成0x00400000,或者随便什么东西,0x00000000什么的,如果我们的程序中,
在后面调用GetCurrentProcessId()的时候,它就跑到我们写的那个地方去了。
        上面就是理论,下面看看实践。
#include <iostream>
#include <string>
#include <windows.h>
#include <Dbghelp.h>
using namespace std;
typedef int (*pgetiat)(PVOID,BOOLEAN,USHORT,PULONG);

int test_function()
{
        MessageBox(NULL,"hook_entry function","hook",MB_OK);
        return 0;
}
class iat_hook
{
        private:
                const char *module;
                const char *procname;
                PROC *newaddr;
                PROC pfnHookAPIAddr;
                pgetiat getiat;
                HMODULE h;
        public:
                iat_hook();
                iat_hook(const char *m,const char *p,PROC *n);
                ~iat_hook();
                //~iat_hook(const char *);原来destructor不可以重载啊,为什么呢。。。
                int hook();
};
int iat_hook::hook()
{
        ULONG ulSize;
        PIMAGE_IMPORT_DESCRIPTOR pimportdescriptor =
        (PIMAGE_IMPORT_DESCRIPTOR)getiat(
            GetModuleHandle(NULL),
            TRUE,
            IMAGE_DIRECTORY_ENTRY_IMPORT,
            &ulSize
        );
         while (pimportdescriptor->Name)
    {
                 //cout << (char *)((PBYTE) GetModuleHandle(NULL) + pimportdescriptor->Name) << endl;
        PSTR pszModName = (PSTR)((PBYTE) GetModuleHandle(NULL) + pimportdescriptor->Name);
        if (stricmp(pszModName, module) == 0)
        break;   
        pimportdescriptor++;
    }
         PIMAGE_THUNK_DATA pThunk =
    (PIMAGE_THUNK_DATA)((PBYTE) GetModuleHandle(NULL) + pimportdescriptor->FirstThunk);
          while (pThunk->u1.Function)
    {
        PROC* ppfn = (PROC*) &pThunk->u1.Function;
        BOOL bFound = (*ppfn == pfnHookAPIAddr);

        if (bFound)
        {
            MEMORY_BASIC_INFORMATION mbi;
            VirtualQuery(
                ppfn,
                &mbi,
                sizeof(MEMORY_BASIC_INFORMATION)
            );
            VirtualProtect(
                mbi.BaseAddress,
                mbi.RegionSize,
                PAGE_READWRITE,
                &mbi.Protect
            );

            *ppfn = *newaddr;

            DWORD dwOldProtect;
            VirtualProtect(
                mbi.BaseAddress,
                mbi.RegionSize,
                mbi.Protect,
                &dwOldProtect
            );
            break;
        }
        pThunk++;
    }
}
iat_hook::iat_hook()
{
        cout << "you provied wrong parameters!" << endl;
        ExitProcess(NULL);
}
iat_hook::iat_hook(const char *m,const char *p,PROC *n)
{
        module =m;
        procname = p;
        newaddr = n;
        //cout << "debug :        " << hex << (int)*newaddr << endl; //done
        HMODULE hdbghelp=LoadLibrary("dbghelp.dll");
        getiat = (pgetiat) GetProcAddress (hdbghelp,"ImageDirectoryEntryToData");
        if(getiat == NULL)
        {
                cout << "getprocaddress error" << endl;
                ExitProcess(NULL);
        }
        //cout << "debug:        " << hex << (int)getiat << endl; //done
        h = GetModuleHandle(module);
        pfnHookAPIAddr = GetProcAddress (h,procname);
        //cout << "debug:        " << hex << (int)pfnHookAPIAddr << endl; //done
}
iat_hook::~iat_hook()
{
        cout << "yes you are right,this is destructor" << endl;
}

int main()
{
        PROC stub = (PROC )&test_function;
        iat_hook it("KERNEL32.dll","GetCurrentProcessId",&stub);
        it.hook();
        cout << "current pid :        " << GetCurrentProcessId() << endl;
        return 0;
}
        我用的C++写的。首先可以看到一个test_function,也就是测试用的函数。然后是定义了一个iat_hook类,这个类在初始化的时候
需要提供我们要hook的函数所在的模块的名字M,函数的名字N,和一个函数指针P。我们的类中有一个重要的接口,就是hook(),调用它
就会把M中的函数N重定向到P,也就是如果调用函数N,执行的却是P所指向的函数。
        我并不准备把程序拿来一行一行的讲,程序是拿来读的,读不懂可以慢慢读。我只说说需要注意的地方:
        1.我写程序时本来想直接使用ImageDirectoryEntryToData()的,但是我用的g++编译器老是告诉我找不到函数,我也懒得去添加什么
头文件什么的,就用的动态加载,在程序运行的时候去找,并且我把名字改了,叫getiat,其实这个api函数还有非常多的功能,具体到这里
去参考:http://msdn.microsoft.com/en-us/library/windows/desktop/ms680148(v=vs.85).aspx
        2.我们要修改的内存一般是不可写的,于是需要先用VirtualProtect()函数修改内存页面参数,等我们把要写的东西写好了再给改回
来。
        3.我在这篇文章的开头给出了参考文献,那篇文章中在iat hook那一节给出的程序示例是有问题的,它老是该用程序的基地址的地方
用kernel32.dll的基地址,我改了过来。如果有人发现是由于我的理解有误,请及时指正。
        这篇文章放在论坛上也许是有些令大神见笑了。我也是刚刚学会,现在的我还记得什么都不懂的痛苦和学习的艰辛,等我熟练使用后
忘记了那份痛苦,应该也没有激情来写这么一篇hook入门文章,所以趁着记忆,写下这篇文章。(我还记得我刚刚学会写shellcode的时候
也是激动不已的,可惜写过各种shellcode后,想下笔写shellcode的介绍和教程,却是感觉下不了笔,头脑中的想法就是,这么简单的东西
放上去不是引人笑话么。所以趁着我在hook方面仍是一个小菜,将更多的还在门外的同学带进小菜行列吧)
        最后,既然有了一个hook的方法,希望能有高人能介绍一些hook后比较有趣的api,还有注射代码到其他进程的方法,如果能提供论坛
里一两个讨论这个的帖子的地址就更好了。俺要拿hook这把剑到处去戳一戳。
        书短意长,不尽欲言,且慰同菜。

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (7)
雪    币: 2905
活跃值: (3934)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
书短意长,不尽欲言
2014-5-25 13:37
0
雪    币: 90
活跃值: (92)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
3
写完之后有一点感想,在看别人的iat hook之前,我已经对pe文件的结构了解的比较多了,而从这种hook方法看来,只要对pe理解足够,会汇编,完全可以自己创造出这种hook方法来,但是我并没有这样的创新能力,也许这才是菜鸟和大神之间的区别,大家的知识是一样的,但是对于知识的理解和运用却不同。
2014-5-25 13:49
0
雪    币: 2161
活跃值: (750)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
4
严重受到面向对象的腐蚀,并不是说面向对象不好
而是一个函数就能搞定的事情偏偏写了一个类,不麻烦么?
当然练手是可以的
2014-5-25 19:24
0
雪    币: 90
活跃值: (92)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
5
这个学期刚好学了C++,于是到处用它
2014-5-25 19:40
0
雪    币: 67
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
上传的附件:
2014-5-25 20:22
0
雪    币: 90
活跃值: (92)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
7
[QUOTE=hnllhuihui;1287448][/QUOTE]
  首先我觉得应该说明的是,在网上找到的源代码编译链接或者运行有问题,应该自己解决。不过既然我看到了问题,我也可以解决就解决了。我在帖子的注意事项中也说明了,我用的g++编译器老是说找不到ImageDirectoryEntryToData,所以我采用了动态加载自己找函数地址的方法,而vc的debug版本的程序一般会有很多冗余数据和代码,所以可能会出错。那么可以在vc中就可以修改修改直接使用需要的函数。修改后的代码如下:(vs2010,debug,release均可正确执行)
#include <iostream>
#include <string>
#include <windows.h>
#include <Dbghelp.h>
#pragma comment (lib,"Dbghelp.lib")
using namespace std;
//typedef PVOID (*pgetiat)(PVOID,BOOLEAN,USHORT,PULONG);

int test_function()
{
  MessageBoxA(NULL,"hook_entry function","hook",MB_OK);
  return 0;
}
class iat_hook
{
  private:
    const char *module;
    const char *procname;
    PROC *newaddr;
    PROC pfnHookAPIAddr;
    //pgetiat getiat;
    HMODULE h;
  public:
    iat_hook();
    iat_hook(const char *m,const char *p,PROC *n);
    ~iat_hook();
    //~iat_hook(const char *);原来destructor不可以重载啊,为什么呢。。。
    int hook();
};
int iat_hook::hook()
{
  ULONG ulSize;
  HMODULE hcurrent = GetModuleHandle(NULL);
  PIMAGE_IMPORT_DESCRIPTOR pimportdescriptor =
        (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
            hcurrent,
            TRUE,
            IMAGE_DIRECTORY_ENTRY_IMPORT,
            &ulSize
        );
   while (pimportdescriptor->Name)
    {
     //cout << (char *)((PBYTE) GetModuleHandle(NULL) + pimportdescriptor->Name) << endl;
        PSTR pszModName = (PSTR)((PBYTE) GetModuleHandle(NULL) + pimportdescriptor->Name);
        if (strcmp(pszModName, module) == 0)
        break;   
        pimportdescriptor++;
    }
   PIMAGE_THUNK_DATA pThunk =
    (PIMAGE_THUNK_DATA)((PBYTE) GetModuleHandle(NULL) + pimportdescriptor->FirstThunk);
    while (pThunk->u1.Function)
    {
        PROC* ppfn = (PROC*) &pThunk->u1.Function;
        BOOL bFound = (*ppfn == pfnHookAPIAddr);

        if (bFound)
        {
            MEMORY_BASIC_INFORMATION mbi;
            VirtualQuery(
                ppfn,
                &mbi,
                sizeof(MEMORY_BASIC_INFORMATION)
            );
            VirtualProtect(
                mbi.BaseAddress,
                mbi.RegionSize,
                PAGE_READWRITE,
                &mbi.Protect
            );

            *ppfn = *newaddr;

            DWORD dwOldProtect;
            VirtualProtect(
                mbi.BaseAddress,
                mbi.RegionSize,
                mbi.Protect,
                &dwOldProtect
            );
            break;
        }
        pThunk++;
    }
        return 0;
}
iat_hook::iat_hook()
{
  cout << "you provied wrong parameters!" << endl;
  ExitProcess(NULL);
}
iat_hook::iat_hook(const char *m,const char *p,PROC *n)
{
  module =m;
  procname = p;
  newaddr = n;
  //cout << "debug :  " << hex << (int)*newaddr << endl; //done
  HMODULE hdbghelp=LoadLibraryA("dbghelp.dll");
  /*getiat = (pgetiat) GetProcAddress (hdbghelp,"ImageDirectoryEntryToData");
  if(getiat == NULL)
{
    cout << "getprocaddress error" << endl;
    ExitProcess(NULL);
  }
  cout << "debug:  " << hex << (int)getiat << endl; //done
  */
  h = GetModuleHandleA(module);
  pfnHookAPIAddr = GetProcAddress (h,procname);
  //cout << "debug:  " << hex << (int)pfnHookAPIAddr << endl; //done
}
iat_hook::~iat_hook()
{
  cout << "yes you are right,this is destructor" << endl;
}

int main()
{
  PROC stub = (PROC )&test_function;
  iat_hook it("KERNEL32.dll","GetCurrentProcessId",&stub);
  it.hook();
  cout << "current pid :  " << GetCurrentProcessId() << endl;
  return 0;
}
2014-5-25 21:30
0
雪    币: 44
活跃值: (186)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
小菜进来领教一下
2014-5-25 22:05
0
游客
登录 | 注册 方可回帖
返回
//