IAT HOOK
2016-06-02 20:53
141 查看
内存攻击中,最简单的就是IAT Hook,这里,结合前面对PE文件的了解,从我写的内存注入代码中剪切出了IAT Hook的部分进行讲解,因为是部分代码,所以在测试的时候,只Hook了WinEx这个函数,而且函数地址都是直接在代码中写进去的,并没有动态获取,具体的动态获取方法,可以看我接下来的博客<<内存注入只IAT Hook和Inline Hook>>,相关源码和测试程序都已经上传了,资源地址 http://download.csdn.net/detail/enjoy5512/9539443
注:转载请注明来源 enjoy5512的博客 http://blog.csdn.net/enjoy5512
常见的API Hook包括2种, 一种是基于PE文件的导入表(IAT), 还有一种是修改前5个字节直接JMP的inline Hook.
对于基于IAT的方式, 原理是PE文件里有个导入表, 代表该模块调用了哪些外部API,模块被加载到内存后, PE加载器会修改该表,地址改成外部API重定位后的真实地址, 我们只要直接把里面的地址改成我们新函数的地址, 就可以完成对相应API的Hook。
2) 获取调试权限
3) 根据进程PID获取远程进程句柄
4) 申请空间写入我们shellcode会用到的数据
5) 动态修改shellcode中需要重定位的数据
6) 写入shellcode
7) 修改远程进程IAT表,用shellcode的首地址替换需要Hook的函数保存在IAT表中的地址
然后通过前面提供的内存注入完整程序可以获得测试程序的PID和WinExec函数在IAT表中的位置,如果程序运行失败,可以根据这个做修改
输入测试程序的PID,看到注入成功说明IAT Hook成功了
再看测试程序,继续输入1,则先执行我们注入的shellcode,然后继续调用原本要调用的WinExec函数
注:转载请注明来源 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和Inline Hook综合程序
- 示例1.8 转换流
- SQL必知必会(第3版)学习笔记【1-5章】
- 参考小白书上用c++采用结构体重载的大数类模版
- Windows-定制应用程序外观
- vector容器介绍及基本API
- JAVA大数处理(BigInteger,BigDecimal)
- 示例1.7 对字符文件的读写操作
- 回文判断
- 第二个冲刺-项目进展1
- 常见知识点
- mysql ERROR 1045 (28000): Access denied for user解决方法
- 安卓手把手教你实现RecycleView横向和ViewPage的级联滑动
- flume1.6 centos6.x 安装与配置
- 示例1.6 文件拷贝
- Android Camera HAL3中预览preview模式下的控制流
- 解决重新安装sqlserver2008报错Reporting Services目录数据库文件存在的问题
- 示例1.5 字符流输出
- 解决重新安装sqlserver2008报错Reporting Services目录数据库文件存在的问题
- 集群资源管理与调度概要