赞
踩
Socket的HOOK技术是目前网络拦截程序的基础功能,还有浏览器的抓包工具都是通过拦截Socket函数实现的
浏览器也好,通讯软件也好,他们只是使用的通讯协议不一样,其最底层的全部都是通过封装Socket里的TCP/UDP实现的
如最常用的就是Send函数与Recv函数,一个是发送一个是接收,所以我们只需要通过Hook住Send和Recv函数就可以实现抓包功能
https://blog.csdn.net/bjbz_cxy/article/details/90574824 之前写的这篇文章里很详细的介绍了APIHOOK的原理以及实践,如果你对APIHOOK的理论知识不是很理解,那么建议你看完我写的这篇文章在学习本篇文章。
这里我先创建了一个基本的控制台程序:
- #include <stdio.h>
-
- int main(int argc, char* argv[])
- {
- return 0;
- }
在封装一个APIHOOK的类
这篇代码是通过我之前写的:https://blog.csdn.net/bjbz_cxy/article/details/90637158 这篇文章里的代码修改而来,去掉了汇编部分,用更简单的方法实现了
对上一篇文章里的代码进行了重构
- class MyHookClass {
- public:
- MyHookClass()
- {
-
- }
- ~MyHookClass()
- {
-
- }
-
- }
准备工作,申请中间变量,用于保存一些函数信息,以供hook完后的复原操作,声明为保护,防止被开发人员手动修改
- private:
- PROC m_pfnOld;//原API函数地址
- BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
- BYTE m_bNewBytes[5];//新的跳转地址
第一步为APIHOOK类编写一个Hook函数
- BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/){
-
- }
参数作用:
char* szModuleName HOOk的API属于哪个DLL模块里
char* szFuncName HOOK的APImingzi
PROC pHookFunc 新的函数地址
//第一步获取DLL句柄,Windows下操作任何设备以及应用模块都以句柄为钥匙,同时Windows内核也会记录每一次句柄操作的次数
- BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
- {
- m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
- if (!m_pfnOld)
- {
- return FALSE;
- }
- }
//第二步保存原API地址
- DWORD dwNum = 0;
- ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
//第三步取当前函数的地址 这一步将之前的APIHOOK里的汇编代码修改掉了,所以这里我需要说一下
- m_bNewBytes[0] = '\xe9';
-
- *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
之前的方式是这样的:
实际地址=我们的api地址-拦截的api地址-要写入的数据长度
- //地址转换
- ULONG Api_MoPtr = JumpApiPtr - (ULONG)&Api;
- Api_MoPtr = Api_MoPtr - 5;
- //将地址写入到操作码的后四位
- char My_FuncPtr[5] = { 0 };
-
- _asm
- {
- mov eax, Api_MoPtr //获取刚刚获得的地址
- mov dword ptr[My_FuncPtr + 1], eax //将算出的地址保存到Arr后面4个字节
- //注:一个函数地址占4个字节
- }
后来我发现其实这样写更直观和简单一点
- m_bNewBytes[0] = '\xe9';
-
- *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
最后一步写入到cll的地址下就可以啦
WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
完整的HOOK函数代码:
- BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
- {
-
-
-
- m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
- if (!m_pfnOld)
- {
- return FALSE;
- }
-
-
- DWORD dwNum = 0;
- ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
-
- m_bNewBytes[0] = '\xe9';
-
- *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
-
-
- return TRUE;
- }
除此之外,我们还要写一个复原和复位的函数,用于还原API和重新HOOK的功能
- //复原
- void UnHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
- }
- }
- //重置
- bool ReHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
- return FALSE;
- }
- return TRUE;
- }
这两个函数用在调用里,因为比如是recv函数,如果直接修改跳转到我们的函数里了,那么我们是取不到任何数据的,所以我们需要在函数里调用UnHook函数把函数复原,然后在里面调用一次原生态的Recv获取到数据之后,在使用Rehook将其重置到我们的函数里。
完整代码:
- class MyHookClass {
- public:
- MyHookClass()
- {
- m_pfnOld = nullptr;
- ZeroMemory(m_bNewBytes, 5);
- ZeroMemory(m_bOldBytes, 5);
- }
- ~MyHookClass()
- {
- UnHook();
- }
-
- BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
- {
-
-
-
- m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
- if (!m_pfnOld)
- {
- return FALSE;
- }
-
-
- DWORD dwNum = 0;
- ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
-
- m_bNewBytes[0] = '\xe9';
-
- *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
-
-
- return TRUE;
- }
- //复原
- void UnHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
- }
- }
- //重置
- bool ReHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
- return FALSE;
- }
- return TRUE;
- }
- private:
- PROC m_pfnOld;//原API函数地址
- BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
- BYTE m_bNewBytes[5];//新的跳转地址
- };
上面介绍的这个方法是最常用的方法,还有其它很多方法,如PE方式的HOOk
这个方法难度不是很大,技术点就在于APIHOOK的原理还有逻辑地址转换https://blog.csdn.net/bjbz_cxy/article/details/90574824 我在这篇文章中有很详细的介绍API HOOK
如果你想拦截其它程序的封包数据需要使用DLL注入的方式,注入到目标进程下就可以了,原因是因为内存保护,用户态下不能随便越过自己的内存区的。
实战:
这个是Recv函数的原型
int recv(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags) ;
根据原型写一个我们自己的函数:
int recv1(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags)
我们在写一个客户端/服务器程序
- #include <winsock2.h>
- #include <WS2tcpip.h>
- #include<stdio.h>
- #include<iostream>
- #include<cstring>
- #include <Windows.h>
-
- #pragma comment(lib,"ws2_32.lib")
注意包含头文件的时候,Windows头文件要在Winsock2的下面,不然会出现很多类型错误的问题
其次是要注意一个问题,因为是DLL,如果我们在没调用任何Socket函数的情况下,ws2_32dll是不会载入到程序里的,所以如果我们在Socket之前调用了我们的APIHOOK是不会成功的,所以我们需要当目标程序在调用任何Socket函数后再去使用APIHOOK,因为动态库的特点就是需要时才会加载到内存
我直接把APIHOOK的代码放在一个程序下,也就是说HOOK当前程序下的Recv实现封包拦截功能,如果你想HOOK别人的程序可以参考我前面关于HOOK的文章,里面有详细的方法和代码
这个是客户端代码
-
- int main(int argc, char* argv[])
- {
- // hook windows api
- char szModuleName[MAXBYTE] = { 0 };//"user32.dll";
- char szFuncName[MAXBYTE] = { 0 };// "MessageBoxW";
- strcpy_s(szModuleName, MAXBYTE, "WS2_32.dll");
- strcpy_s(szFuncName, MAXBYTE, "recv");
- WORD sockVersion = MAKEWORD(2, 2);
- WSADATA data;
- if (WSAStartup(sockVersion, &data) != 0)
- {
- return 0;
- }
- sockaddr_in serAddr;
- serAddr.sin_family = AF_INET;
- serAddr.sin_port = htons(8888);
- inet_pton(AF_INET, "127.0.0.1", &serAddr.sin_addr);
- if (FALSE == g_MsgHook.Hook(szModuleName, szFuncName, (PROC)recv1)) {
- printf("error");
- }
- while (true)
- {
- SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (sclient == INVALID_SOCKET)
- {
- printf("invalid socket!");
- return 0;
- }
- if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
- { //连接失败
- printf("connect error !");
- closesocket(sclient);
- return 0;
- }
- send(sclient, "asd", strlen("asd"), 0);
- char recData[255] = {0};
- int ret = recv(sclient, recData, 12, 0);
- if (ret > 0) {
- recData[ret] = 0x00;
- printf(recData);
- }
- closesocket(sclient);
- }
- WSACleanup();
-
-
- return 0;
- }
-
recv部分
这里我们复原后然后调用原生态的recv去读数据,这样既没有让原生态的recv失效,我们也能提前拿到数据,非常美哉
因为最开始用户态调用的recv是进入到recv1里了,然后我们在根据传递进来的socket参数(需要注意一点Sock通信中传递进来的数据会根据协议放在对应的缓冲区里,术语上叫做窗口,实际上只是一块内存缓冲区,如TCP窗口等等,如果不去调用recv函数读的话,我们是无法获取这块缓冲区的内容的,必须通过sock提供的接口,理论上来说recv读完缓冲区后会将缓冲区的内容清空)
-
- MyHookClass g_MsgHook;
-
- int recv1(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags) {
- //重置recv函数
- g_MsgHook.UnHook();
- //用recv函数接收服务器消息
- recv(s, buf, 255, 0);
- //打印
- printf("hook:%s",buf);
- //重新挂钩
- g_MsgHook.ReHook();
- return 0;
- }
服务器:
- // ConsoleApplication2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
- //
-
- #include <stdio.h>
- #include <winsock2.h>
-
- #pragma comment(lib,"ws2_32.lib")
-
- int main(int argc, char* argv[])
- {
- //初始化WSA
- WORD sockVersion = MAKEWORD(2, 2);
- WSADATA wsaData;
- if (WSAStartup(sockVersion, &wsaData) != 0)
- {
- return 0;
- }
- //创建套接字
- SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (slisten == INVALID_SOCKET)
- {
- printf("socket error !");
- return 0;
- }
- //绑定IP和端口
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(8888);
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
- {
- printf("bind error !");
- }
- //开始监听
- if (listen(slisten, 5) == SOCKET_ERROR)
- {
- printf("listen error !");
- return 0;
- }
- //循环接收数据
- SOCKET sClient;
- sockaddr_in remoteAddr;
- int nAddrlen = sizeof(remoteAddr);
- char revData[1024];
- while (true)
- {
- printf("等待连接...\n");
- sClient = accept(slisten, (SOCKADDR*)&remoteAddr, &nAddrlen);
- if (sClient == INVALID_SOCKET)
- {
- printf("accept error !");
- continue;
- }
- //接收数据
- int ret = recv(sClient, revData, 255, 0);
- if (ret > 0)
- {
- revData[ret] = 0x00;
- printf(revData);
- }
- //发送数据
- const char* sendData = "hello \n";
- send(sClient, sendData, strlen(sendData), 0);
- }
- closesocket(sClient);
- closesocket(slisten);
- WSACleanup();
- return 0;
- }
-
先运行服务器程序,当有连接来的时候返回hello
看下我们的recv函数是否拦截成功:
成功HOOK到了
完整代码:
API HOOK:
- class MyHookClass {
- public:
- MyHookClass()
- {
- m_pfnOld = nullptr;
- ZeroMemory(m_bNewBytes, 5);
- ZeroMemory(m_bOldBytes, 5);
- }
- ~MyHookClass()
- {
- UnHook();
- }
-
- BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
- {
-
-
-
- m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
- if (!m_pfnOld)
- {
- return FALSE;
- }
-
-
- DWORD dwNum = 0;
- ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
-
- m_bNewBytes[0] = '\xe9';
-
- *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
-
-
- return TRUE;
- }
- //复原
- void UnHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
- }
- }
- //重置
- bool ReHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
- return FALSE;
- }
- return TRUE;
- }
- private:
- PROC m_pfnOld;//原API函数地址
- BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
- BYTE m_bNewBytes[5];//新的跳转地址
- };
demo代码:
客户端:
- // HookMessage.cpp: 定义控制台应用程序的入口点。
- //
-
-
-
- #include <stdio.h>
-
-
-
- #include <winsock2.h>
- #include <WS2tcpip.h>
- #include<stdio.h>
- #include<iostream>
- #include<cstring>
-
- #include <Windows.h>
- #pragma comment(lib,"ws2_32.lib")
- class MyHookClass {
- public:
- MyHookClass()
- {
- m_pfnOld = nullptr;
- ZeroMemory(m_bNewBytes, 5);
- ZeroMemory(m_bOldBytes, 5);
- }
- ~MyHookClass()
- {
- UnHook();
- }
-
- BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
- {
-
-
-
- m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
- if (!m_pfnOld)
- {
- return FALSE;
- }
-
-
- DWORD dwNum = 0;
- ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
-
- m_bNewBytes[0] = '\xe9';
-
- *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
-
-
- return TRUE;
- }
- //复原
- void UnHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
- }
- }
- //重置
- bool ReHook()
- {
- if (m_pfnOld != nullptr)
- {
- DWORD dwNum = 0;
-
- WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
- return FALSE;
- }
- return TRUE;
- }
- private:
- PROC m_pfnOld;//原API函数地址
- BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
- BYTE m_bNewBytes[5];//新的跳转地址
- };
-
- MyHookClass g_MsgHook;
-
- int recv1(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags) {
- g_MsgHook.UnHook();
- recv(s, buf, 255, 0);
- printf("hook:%s",buf);
- g_MsgHook.ReHook();
- getchar();
- return 0;
- }
- int main(int argc, char* argv[])
- {
- // hook windows api
- char szModuleName[MAXBYTE] = { 0 };//"user32.dll";
- char szFuncName[MAXBYTE] = { 0 };// "MessageBoxW";
- strcpy_s(szModuleName, MAXBYTE, "WS2_32.dll");
- strcpy_s(szFuncName, MAXBYTE, "recv");
- WORD sockVersion = MAKEWORD(2, 2);
- WSADATA data;
- if (WSAStartup(sockVersion, &data) != 0)
- {
- return 0;
- }
- sockaddr_in serAddr;
- serAddr.sin_family = AF_INET;
- serAddr.sin_port = htons(8888);
- inet_pton(AF_INET, "127.0.0.1", &serAddr.sin_addr);
- if (FALSE == g_MsgHook.Hook(szModuleName, szFuncName, (PROC)recv1)) {
- printf("error");
- }
- while (true)
- {
- SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (sclient == INVALID_SOCKET)
- {
- printf("invalid socket!");
- return 0;
- }
- if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
- { //连接失败
- printf("connect error !");
- closesocket(sclient);
- return 0;
- }
- send(sclient, "asd", strlen("asd"), 0);
- char recData[255] = {0};
- int ret = recv(sclient, recData, 12, 0);
- if (ret > 0) {
- recData[ret] = 0x00;
- printf(recData);
- }
- closesocket(sclient);
- }
- WSACleanup();
-
-
- return 0;
- }
-
服务器:
- // ConsoleApplication2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
- //
-
- #include <stdio.h>
- #include <winsock2.h>
-
- #pragma comment(lib,"ws2_32.lib")
-
- int main(int argc, char* argv[])
- {
- //初始化WSA
- WORD sockVersion = MAKEWORD(2, 2);
- WSADATA wsaData;
- if (WSAStartup(sockVersion, &wsaData) != 0)
- {
- return 0;
- }
- //创建套接字
- SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (slisten == INVALID_SOCKET)
- {
- printf("socket error !");
- return 0;
- }
- //绑定IP和端口
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(8888);
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
- {
- printf("bind error !");
- }
- //开始监听
- if (listen(slisten, 5) == SOCKET_ERROR)
- {
- printf("listen error !");
- return 0;
- }
- //循环接收数据
- SOCKET sClient;
- sockaddr_in remoteAddr;
- int nAddrlen = sizeof(remoteAddr);
- char revData[1024];
- while (true)
- {
- printf("等待连接...\n");
- sClient = accept(slisten, (SOCKADDR*)&remoteAddr, &nAddrlen);
- if (sClient == INVALID_SOCKET)
- {
- printf("accept error !");
- continue;
- }
- //接收数据
- int ret = recv(sClient, revData, 255, 0);
- if (ret > 0)
- {
- revData[ret] = 0x00;
- printf(revData);
- }
- //发送数据
- const char* sendData = "hello \n";
- send(sClient, sendData, strlen(sendData), 0);
- }
- closesocket(sClient);
- closesocket(slisten);
- WSACleanup();
- return 0;
- }
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。