Windows核心编程_HOOk SOCKET实现封包拦截

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;
}

 

  • 5
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

17岁boy想当攻城狮

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值