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

Windows核心编程_HOOk SOCKET实现封包拦截相关推荐

  1. Windows核心编程_HOOK(续)_APIHOOK

    啰嗦啰嗦: 开始之前还是要啰嗦几句,看到自己博客粉丝增加,访问量也越来越多,感到非常开心,而且好评也是不少,指错也非常感谢,从错误中发现了很多问题,非常感谢,也高兴自己的文章能帮助到其它人. 就比如之 ...

  2. C++Windows核心编程读书笔记(转)

    http://www.makaidong.com/(马开东博客) 这篇笔记是我在读<windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的 ...

  3. [C++]《Windows核心编程》读书笔记

    这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...

  4. 《windows核心编程系列》二谈谈ANSI和Unicode字符集

    第二章:字符和字符串处理 使用vc编程时项目-->属性-->常规栏下我们可以设置项目字符集合,它可以是ANSI(多字节)字符集,也可以是unicode字符集.一般情况下说Unicode都是 ...

  5. 《windows核心编程系列》十八谈谈windows钩子

    windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...

  6. [笔记]Windows核心编程《二十》DLL的高级操作技术

    系列文章目录 [笔记]Windows核心编程<一>错误处理.字符编码 [笔记]Windows核心编程<二>内核对象 [笔记]Windows核心编程<三>进程 [笔记 ...

  7. [笔记]Windows核心编程《十六》线程栈

    系列文章目录 [笔记]Windows核心编程<一>错误处理.字符编码 [笔记]Windows核心编程<二>内核对象 [笔记]Windows核心编程<三>进程 [笔记 ...

  8. [笔记]Windows核心编程《十九》DLL基础

    系列文章目录 [笔记]Windows核心编程<一>错误处理.字符编码 [笔记]Windows核心编程<二>内核对象 [笔记]Windows核心编程<三>进程 [笔记 ...

  9. Windows核心编程 - API HOOK应用

    #Windows核心编程 - API HOOK应用 如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 目录 文章目录 #Windows核 ...

最新文章

  1. python每隔2s执行一次hello world!
  2. spring三: 装配bean( 在xml中进行显式配置, 在java中进行显式配置)
  3. java基础(八) 深入解析常量池与装拆箱机制
  4. echarts地图在ie浏览器上不显示
  5. zhajinhuagame为了迎接新版
  6. Niginx笔记-Linux上源码安装Niginx
  7. js生成一周内的日期+周几
  8. 从客户端(Content=p666/p)中检测到有潜在危险的 Request.Form 值。
  9. d3 v4 api transitions
  10. Python numpy基础知识
  11. shapenet各类数据(转载)
  12. java中$和 的区别详解_MyBatis中#{}和${}的区别详解
  13. tomcat使用中出现的问题及其解决之道
  14. Win10安装Ubuntu16.04 双系统
  15. 职业学校计算机课评课,信息技术课评课范文
  16. 实验01 使用网络协议分析仪Wireshark分析数据链路层帧结构实验报告
  17. 用Python获取好看听书网中的《星期五有鬼》有声小说
  18. K-th Closest Distance (主席树)
  19. prometheus如何评估告警策略以及如何推送告警消息到alertmanager?
  20. 如何把microsoft store里面的软件添加到桌面

热门文章

  1. shedlock 重启系统报错问题_闲谈ShedLock解决分布式定时任务重复执行问题
  2. atm系统的用例模型_ATM银行系统用例图.doc
  3. eclipse代码量统计插件_Android Studio 代码行数统计插件Statistic的简单使用
  4. 施耐德 m340 编程手册_施耐德PLC漏洞历险记
  5. python中高阶函数和装饰器_Python高阶函数与装饰器函数的深入讲解
  6. c语言函数可变长参数,一种使用变长参数为C程序构造灵活回调函数的方法
  7. 苹果CMS V10 播放记录_苹果cms采集后无法播放怎么解决?
  8. docker创建mysql实例_使用docker创建mysql实例
  9. 如何隐藏地址栏中的真实地址_代理IP如何隐藏真实IP
  10. java 接口开发时间_Java开发中的日期和时间使用