IAT HOOK

Posted enjoy5512

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IAT HOOK相关的知识,希望对你有一定的参考价值。

  内存攻击中,最简单的就是IAT Hook,这里,结合前面对PE文件的了解,从我写的内存注入代码中剪切出了IAT Hook的部分进行讲解,因为是部分代码,所以在测试的时候,只Hook了WinEx这个函数,而且函数地址都是直接在代码中写进去的,并没有动态获取,具体的动态获取方法,可以看我接下来的博客<<内存注入只IAT Hook和Inline Hook>>,相关源码和测试程序都已经上传了,资源地址 http://download.csdn.net/detail/enjoy5512/9539443
  注:转载请注明来源 enjoy5512的博客 http://blog.csdn.net/enjoy5512


IAT Hook原理


  在windows系统下编程,应该会接触到api函数的使用,常用的api函数大概有2000个左右。今天随着控件,stl等高效编程技术的出现,api的使用概率在普通的用户程序上就变得越来越小了。当诸如控件这些现成的手段不能实现的功能时,我们还需要借助api。最初有些人对某些api函数的功能不太满意,就产生了如何修改这些api,使之更好的服务于程序的想法,这样api hook就自然而然的出现了。我们可以通过api hook,改变一个系统api的原有功能。基本的方法就是通过hook“接触”到需要修改的api函数入口点,改变它的地址指向新的自定义的函数。api hook并不属于msdn上介绍的13类hook中的任何一种。所以说,api hook并不是什么特别不同的hook,它也需要通过基本的hook提高自己的权限,跨越不同进程间访问的限制,达到修改api函数地址的目的。对于自身进程空间下使用到的api函数地址的修改,是不需要用到api hook技术就可以实现的。
常见的API Hook包括2种, 一种是基于PE文件的导入表(IAT), 还有一种是修改前5个字节直接JMP的inline Hook.
  对于基于IAT的方式, 原理是PE文件里有个导入表, 代表该模块调用了哪些外部API,模块被加载到内存后, PE加载器会修改该表,地址改成外部API重定位后的真实地址, 我们只要直接把里面的地址改成我们新函数的地址, 就可以完成对相应API的Hook。


IAT Hook基本思路


1) 编写shellcode,shellcode用于Hook相应函数后,需要执行的代码,还用于执行完我们的代码后恢复原函数的调用
2) 获取调试权限
3) 根据进程PID获取远程进程句柄
4) 申请空间写入我们shellcode会用到的数据
5) 动态修改shellcode中需要重定位的数据
6) 写入shellcode
7) 修改远程进程IAT表,用shellcode的首地址替换需要Hook的函数保存在IAT表中的地址


代码实现 :


/////////////////////////////////////////////////////////////////////////////
//  文件名 : test.c
//  工程 : test
//  作者 : enjoy5512   修改者 : enjoy5512   最后优化注释者 : enjoy5512
//  个人技术博客 : blog.csdn.net/enjoy5512
//  个人GitHub   : github.com/whu-enjoy
//  csdn code    : code.csdn.net/enjoy5512
//  描述 : 对测试进程的WinExec函数进行IAT Hook
//  编译环境 : Windows XP SP3 + vc6.0
//  主要函数 :
//  版本 : 最终确定版  完成日期 : 2016年6月2日 20:29:56
//  修改 :
/////////////////////////////////////////////////////////////////////////////

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


    //函数说明开始
    //==================================================================================
    //  功能 : 获取进程的调试权限
    //  参数 : const char *name
    //  (入口)  name : 指向权限名称,我们这里用到SE_DEBUG_NAME
    //    #define          SE_BACKUP_NAME           TEXT("SeBackupPrivilege")
    //    #define          SE_RESTORE_NAME          TEXT("SeRestorePrivilege")
    //    #define          SE_SHUTDOWN_NAME         TEXT("SeShutdownPrivilege")
    //    #define          SE_DEBUG_NAME            TEXT("SeDebugPrivilege")
    //  返回 : -1表示获取权限失败, 0表示获取权限成功
    //  主要思路 : 先打开进程令牌环,然后获得本地进程name所代表的权限类型的局部唯一ID
    //             最后调整进程权限
    //  调用举例 : EnableDebugPriv(SE_DEBUG_NAME)
    //  日期 : 2016年6月1日 19:08:22(注释日期)
    //==================================================================================
    //函数说明结束
int EnableDebugPriv(const char *name)
{
    HANDLE hToken;        //进程令牌句柄
    TOKEN_PRIVILEGES tp;  //TOKEN_PRIVILEGES结构体,其中包含一个【类型+操作】的权限数组
    LUID luid;            //上述结构体中的类型值

    //打开进程令牌环
    //GetCurrentProcess()获取当前进程的伪句柄,只会指向当前进程或者线程句柄,随时变化
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken))
    {
       fprintf(stderr,"OpenProcessToken error\\n");
       return -1;
    }

    //获得本地进程name所代表的权限类型的局部唯一ID
    if (!LookupPrivilegeValue(NULL, name, &luid))
    {
       fprintf(stderr,"LookupPrivilegeValue error\\n");
    }

    tp.PrivilegeCount = 1;                               //权限数组中只有一个“元素”
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;  //权限操作
    tp.Privileges[0].Luid = luid;                        //权限类型

    //调整进程权限
    if (!AdjustTokenPrivileges(hToken, 0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
    {
       fprintf(stderr,"AdjustTokenPrivileges error!\\n");
       return -1;
    }

    return 0;

}

int main()
{
    char addr[5] = {0};            //保存四字节地址信息

    HANDLE hProcess;               //进程句柄
    DWORD dwHasWrite;              //实际读取的字节数
    LPVOID lpRemoteBuf;            //新申请的内存空间指针

    int temp = 0;                  //临时变量
    int pid = 0;                   //需要Hook的进程PID

    //数据
    char data[] = "\\x74\\x65\\x73\\x74\\x00\\xCC\\xCC\\xCC"
        "\\xD7\\xE9\\xB3\\xA4\\x20\\x3A\\x20\\xBA"
        "\\xCE\\xC4\\xDC\\xB1\\xF3\\x20\\x32\\x30"
        "\\x31\\x33\\x33\\x30\\x32\\x35\\x33\\x30"
        "\\x30\\x32\\x30\\x0A\\xD7\\xE9\\xD4\\xB1"
        "\\x20\\x3A\\x20\\xCD\\xF5\\x20\\x20\\xEC"
        "\\xB3\\x20\\x32\\x30\\x31\\x33\\x33\\x30"
        "\\x32\\x35\\x33\\x30\\x30\\x30\\x35\\x0A"
        "\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\xB5"
        "\\xCB\\xB9\\xE3\\xF6\\xCE\\x20\\x32\\x30"
        "\\x31\\x33\\x33\\x30\\x32\\x35\\x33\\x30"
        "\\x30\\x31\\x34\\x0A\\x20\\x20\\x20\\x20"
        "\\x20\\x20\\x20\\xB9\\xA8\\xD3\\xF1\\xB7"
        "\\xEF\\x20\\x32\\x30\\x31\\x33\\x33\\x30"
        "\\x32\\x35\\x33\\x30\\x30\\x32\\x31\\x00";

    //shellcode
    //pushfd
    //push eax
    //push ecx
    //push edx
    //push ebx
    //push ebp
    //push esi
    //push edi
    //push 0
    //push offset ptr "test"
    //push offset ptr "内容"
    //push 0
    //mov eax,user32.MessageBox
    //call eax
    //pop edi
    //pop esi
    //pop ebp
    //pop ebx
    //pop edx
    //pop ecx
    //pop eax
    //popfd
    //mov eax,kernel32.WinExec
    //jmp eax
    char shellcode[] =
        "\\x9C\\x50\\x51\\x52\\x53\\x55\\x56\\x57"
        "\\x6A\\x00\\x68\\x00\\x10\\x40\\x00\\x68"
        "\\x00\\x10\\x40\\x00\\x6A\\x00\\xB8\\xEA"
        "\\x07\\xD5\\x77\\xFF\\xD0\\x5F\\x5E\\x5D"
        "\\x5B\\x5A\\x59\\x58\\x9D\\xB8\\xEA\\x07"
        "\\xD5\\x7C\\xFF\\xE0";

    if (EnableDebugPriv(SE_DEBUG_NAME))   //获取调试权限
    {
        fprintf(stderr,"Add Privilege error\\n");

        return -1;
    }

    printf("请输入需要Hook的进程PID :");
    scanf("%d", &pid);                   //输入需要hook的函数的程序PID

    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); //获取进程句柄
    if(hProcess == NULL) 
    { 
        fprintf(stderr,"\\n获取进程句柄错误%d",GetLastError()); 
        return -1; 
    }

    //申请120字节的数据空间,并写入我们需要的数据
    lpRemoteBuf = VirtualAllocEx(hProcess, NULL, 120, MEM_COMMIT, PAGE_READWRITE);
    if(WriteProcessMemory(hProcess, lpRemoteBuf, data, 120, &dwHasWrite)) 
    { 
        if(dwHasWrite != 120) 
        { 
            VirtualFreeEx(hProcess,lpRemoteBuf,120,MEM_COMMIT); 
            CloseHandle(hProcess); 
            return -1; 
        } 

    }else 
    { 
        printf("\\n写入远程进程内存空间出错%d。",GetLastError()); 
        CloseHandle(hProcess); 
        return -1; 
    }

    temp = (int)lpRemoteBuf;   //数据所在首地址
    addr[0] = temp&0xff;
    addr[1] = temp>>8&0xff;
    addr[2] = temp>>16&0xff;
    addr[3] = temp>>24&0xff;

    shellcode[11] = addr[0];  //"test" 的地址
    shellcode[12] = addr[1];
    shellcode[13] = addr[2];
    shellcode[14] = addr[3];

    shellcode[16] = addr[0]+8;//"所要显示的字符串首地址"
    shellcode[17] = addr[1];
    shellcode[18] = addr[2];
    shellcode[19] = addr[3];

    temp = MessageBoxA;      //MessageBoxA的地址
    addr[0] = temp&0xff;
    addr[1] = temp>>8&0xff;
    addr[2] = temp>>16&0xff;
    addr[3] = temp>>24&0xff;
    shellcode[23] = addr[0];
    shellcode[24] = addr[1];
    shellcode[25] = addr[2];
    shellcode[26] = addr[3];

    temp = WinExec;         //原函数的地址,用于jmp回原来的函数,我的程序只Hook了WinExec,可以做相应的调整
    addr[0] = temp&0xff;
    addr[1] = temp>>8&0xff;
    addr[2] = temp>>16&0xff;
    addr[3] = temp>>24&0xff;
    shellcode[38] = addr[0];
    shellcode[39] = addr[1];
    shellcode[40] = addr[2];
    shellcode[41] = addr[3];

    //申请44字节的可读可写可执行的shellcode空间,并写入shellcode
    lpRemoteBuf = VirtualAllocEx(hProcess, NULL, 44, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if(WriteProcessMemory(hProcess, lpRemoteBuf, shellcode, 44, &dwHasWrite)) 
    { 
        if(dwHasWrite != 44) 
        { 
            VirtualFreeEx(hProcess,lpRemoteBuf,44,MEM_COMMIT); 
            CloseHandle(hProcess); 
            return -1; 
        } 

    }else 
    { 
        printf("\\n写入远程进程内存空间出错%d。",GetLastError()); 
        CloseHandle(hProcess); 
        return -1; 
    }

    temp = (int)lpRemoteBuf;  //获取shellcode的首地址,并替换IAT表中相应的函数地址
    addr[0] = temp&0xff;
    addr[1] = temp>>8&0xff;
    addr[2] = temp>>16&0xff;
    addr[3] = temp>>24&0xff;
    //0x42f1b8这个地址是远程进程保存WinExec的地址内存块的地址
    //地址可以通过我上传的完整版内存注入程序获取
    if(WriteProcessMemory(hProcess, 0x42f1b8, addr, 4, &dwHasWrite)) 
    { 
        printf("注入成功!!\\n");
        return 0;
    }
    else
    {
        printf("\\n写入远程进程内存空间出错%d。",GetLastError());
    }

    CloseHandle(hProcess); 
    return -1;
}

测试程序源码


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

int main(void)
{
    unsigned short flag = 0;
    MessageBox(NULL,"导入MessageBox成功!!\\n本程序会循环调用计算器","说明",NULL);

    do
    {
        WinExec("calc",SW_SHOW);
        printf("请输入一个数字(输入0或者数字之外的字符程序将退出) : ");
        scanf("%x",&flag);
    }while(0 != flag);

    system("pause");
    return 0;
}

程序运行截图:


原测试程序本来输入一次数字就打开一次计算器
这里写图片描述


然后通过前面提供的内存注入完整程序可以获得测试程序的PID和WinExec函数在IAT表中的位置,如果程序运行失败,可以根据这个做修改
这里写图片描述


输入测试程序的PID,看到注入成功说明IAT Hook成功了
这里写图片描述


再看测试程序,继续输入1,则先执行我们注入的shellcode,然后继续调用原本要调用的WinExec函数
这里写图片描述
这里写图片描述

以上是关于IAT HOOK的主要内容,如果未能解决你的问题,请参考以下文章

IAT HOOK 简单实现

IAT HOOK DEMO win32/win64

IAT HOOK DEMO win32/win64

PE基础6_远程线程注入-HOOK(消息-InLine-IAT)

rootkit检测之检测hook——iat hookinline hookeat hookidt hookirp hookssdt

Inline Hook