Windows核心编程_HOOk SOCKET实现封包拦截
Socket的HOOK技术是目前网络拦截程序的基础功能,还有浏览器的抓包工具都是通过拦截Socket函数实现的浏览器也好,通讯软件也好,他们只是使用的通讯协议不一样,其最底层的全部都是通过封装Socket里的TCP/UDP实现的如最常用的就是Send函数与Recv函数,一个是发送一个是接收,所以我们只需要通过Hook住Send和Recv函数就可以实现抓包功能https://blog.csdn.ne
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;
}
更多推荐
所有评论(0)