当前位置:   article > 正文

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

vbhook拦截封包

Socket的HOOK技术是目前网络拦截程序的基础功能,还有浏览器的抓包工具都是通过拦截Socket函数实现的

浏览器也好,通讯软件也好,他们只是使用的通讯协议不一样,其最底层的全部都是通过封装Socket里的TCP/UDP实现的

如最常用的就是Send函数与Recv函数,一个是发送一个是接收,所以我们只需要通过Hook住Send和Recv函数就可以实现抓包功能

https://blog.csdn.net/bjbz_cxy/article/details/90574824 之前写的这篇文章里很详细的介绍了APIHOOK的原理以及实践,如果你对APIHOOK的理论知识不是很理解,那么建议你看完我写的这篇文章在学习本篇文章。

这里我先创建了一个基本的控制台程序:

  1. #include <stdio.h>
  2. int main(int argc, char* argv[])
  3. {
  4. return 0;
  5. }

在封装一个APIHOOK的类

这篇代码是通过我之前写的:https://blog.csdn.net/bjbz_cxy/article/details/90637158 这篇文章里的代码修改而来,去掉了汇编部分,用更简单的方法实现了

对上一篇文章里的代码进行了重构

  1. class MyHookClass {
  2. public:
  3. MyHookClass()
  4. {
  5. }
  6. ~MyHookClass()
  7. {
  8. }
  9. }

准备工作,申请中间变量,用于保存一些函数信息,以供hook完后的复原操作,声明为保护,防止被开发人员手动修改

  1. private:
  2. PROC m_pfnOld;//原API函数地址
  3. BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
  4. BYTE m_bNewBytes[5];//新的跳转地址

第一步为APIHOOK类编写一个Hook函数

  1. BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/){
  2. }

参数作用:

char* szModuleName HOOk的API属于哪个DLL模块里

char* szFuncName HOOK的APImingzi

PROC pHookFunc 新的函数地址

//第一步获取DLL句柄,Windows下操作任何设备以及应用模块都以句柄为钥匙,同时Windows内核也会记录每一次句柄操作的次数

  1. BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
  2. {
  3. m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
  4. if (!m_pfnOld)
  5. {
  6. return FALSE;
  7. }
  8. }

//第二步保存原API地址

  1. DWORD dwNum = 0;
  2. ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);

//第三步取当前函数的地址 这一步将之前的APIHOOK里的汇编代码修改掉了,所以这里我需要说一下

  1. m_bNewBytes[0] = '\xe9';
  2. *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;

之前的方式是这样的:

实际地址=我们的api地址-拦截的api地址-要写入的数据长度

  1. //地址转换
  2. ULONG Api_MoPtr = JumpApiPtr - (ULONG)&Api;
  3. Api_MoPtr = Api_MoPtr - 5;
  4. //将地址写入到操作码的后四位
  5. char My_FuncPtr[5] = { 0 };
  6. _asm
  7. {
  8. mov eax, Api_MoPtr //获取刚刚获得的地址
  9. mov dword ptr[My_FuncPtr + 1], eax //将算出的地址保存到Arr后面4个字节
  10. //注:一个函数地址占4个字节
  11. }

后来我发现其实这样写更直观和简单一点

  1. m_bNewBytes[0] = '\xe9';
  2. *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;

最后一步写入到cll的地址下就可以啦

 WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);

完整的HOOK函数代码:

  1. BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
  2. {
  3. m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
  4. if (!m_pfnOld)
  5. {
  6. return FALSE;
  7. }
  8. DWORD dwNum = 0;
  9. ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  10. m_bNewBytes[0] = '\xe9';
  11. *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
  12. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  13. return TRUE;
  14. }

除此之外,我们还要写一个复原和复位的函数,用于还原API和重新HOOK的功能

  1. //复原
  2. void UnHook()
  3. {
  4. if (m_pfnOld != nullptr)
  5. {
  6. DWORD dwNum = 0;
  7. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  8. }
  9. }
  10. //重置
  11. bool ReHook()
  12. {
  13. if (m_pfnOld != nullptr)
  14. {
  15. DWORD dwNum = 0;
  16. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  17. return FALSE;
  18. }
  19. return TRUE;
  20. }

这两个函数用在调用里,因为比如是recv函数,如果直接修改跳转到我们的函数里了,那么我们是取不到任何数据的,所以我们需要在函数里调用UnHook函数把函数复原,然后在里面调用一次原生态的Recv获取到数据之后,在使用Rehook将其重置到我们的函数里。

完整代码:

  1. class MyHookClass {
  2. public:
  3. MyHookClass()
  4. {
  5. m_pfnOld = nullptr;
  6. ZeroMemory(m_bNewBytes, 5);
  7. ZeroMemory(m_bOldBytes, 5);
  8. }
  9. ~MyHookClass()
  10. {
  11. UnHook();
  12. }
  13. BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
  14. {
  15. m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
  16. if (!m_pfnOld)
  17. {
  18. return FALSE;
  19. }
  20. DWORD dwNum = 0;
  21. ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  22. m_bNewBytes[0] = '\xe9';
  23. *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
  24. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  25. return TRUE;
  26. }
  27. //复原
  28. void UnHook()
  29. {
  30. if (m_pfnOld != nullptr)
  31. {
  32. DWORD dwNum = 0;
  33. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  34. }
  35. }
  36. //重置
  37. bool ReHook()
  38. {
  39. if (m_pfnOld != nullptr)
  40. {
  41. DWORD dwNum = 0;
  42. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  43. return FALSE;
  44. }
  45. return TRUE;
  46. }
  47. private:
  48. PROC m_pfnOld;//原API函数地址
  49. BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
  50. BYTE m_bNewBytes[5];//新的跳转地址
  51. };

上面介绍的这个方法是最常用的方法,还有其它很多方法,如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) 

我们在写一个客户端/服务器程序

  1. #include <winsock2.h>
  2. #include <WS2tcpip.h>
  3. #include<stdio.h>
  4. #include<iostream>
  5. #include<cstring>
  6. #include <Windows.h>
  7. #pragma comment(lib,"ws2_32.lib")

注意包含头文件的时候,Windows头文件要在Winsock2的下面,不然会出现很多类型错误的问题

其次是要注意一个问题,因为是DLL,如果我们在没调用任何Socket函数的情况下,ws2_32dll是不会载入到程序里的,所以如果我们在Socket之前调用了我们的APIHOOK是不会成功的,所以我们需要当目标程序在调用任何Socket函数后再去使用APIHOOK,因为动态库的特点就是需要时才会加载到内存

我直接把APIHOOK的代码放在一个程序下,也就是说HOOK当前程序下的Recv实现封包拦截功能,如果你想HOOK别人的程序可以参考我前面关于HOOK的文章,里面有详细的方法和代码

这个是客户端代码

  1. int main(int argc, char* argv[])
  2. {
  3. // hook windows api
  4. char szModuleName[MAXBYTE] = { 0 };//"user32.dll";
  5. char szFuncName[MAXBYTE] = { 0 };// "MessageBoxW";
  6. strcpy_s(szModuleName, MAXBYTE, "WS2_32.dll");
  7. strcpy_s(szFuncName, MAXBYTE, "recv");
  8. WORD sockVersion = MAKEWORD(2, 2);
  9. WSADATA data;
  10. if (WSAStartup(sockVersion, &data) != 0)
  11. {
  12. return 0;
  13. }
  14. sockaddr_in serAddr;
  15. serAddr.sin_family = AF_INET;
  16. serAddr.sin_port = htons(8888);
  17. inet_pton(AF_INET, "127.0.0.1", &serAddr.sin_addr);
  18. if (FALSE == g_MsgHook.Hook(szModuleName, szFuncName, (PROC)recv1)) {
  19. printf("error");
  20. }
  21. while (true)
  22. {
  23. SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  24. if (sclient == INVALID_SOCKET)
  25. {
  26. printf("invalid socket!");
  27. return 0;
  28. }
  29. if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
  30. { //连接失败
  31. printf("connect error !");
  32. closesocket(sclient);
  33. return 0;
  34. }
  35. send(sclient, "asd", strlen("asd"), 0);
  36. char recData[255] = {0};
  37. int ret = recv(sclient, recData, 12, 0);
  38. if (ret > 0) {
  39. recData[ret] = 0x00;
  40. printf(recData);
  41. }
  42. closesocket(sclient);
  43. }
  44. WSACleanup();
  45. return 0;
  46. }

recv部分

这里我们复原后然后调用原生态的recv去读数据,这样既没有让原生态的recv失效,我们也能提前拿到数据,非常美哉

因为最开始用户态调用的recv是进入到recv1里了,然后我们在根据传递进来的socket参数(需要注意一点Sock通信中传递进来的数据会根据协议放在对应的缓冲区里,术语上叫做窗口,实际上只是一块内存缓冲区,如TCP窗口等等,如果不去调用recv函数读的话,我们是无法获取这块缓冲区的内容的,必须通过sock提供的接口,理论上来说recv读完缓冲区后会将缓冲区的内容清空)

  1. MyHookClass g_MsgHook;
  2. int recv1(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags) {
  3. //重置recv函数
  4. g_MsgHook.UnHook();
  5. //用recv函数接收服务器消息
  6. recv(s, buf, 255, 0);
  7. //打印
  8. printf("hook:%s",buf);
  9. //重新挂钩
  10. g_MsgHook.ReHook();
  11. return 0;
  12. }

服务器:

  1. // ConsoleApplication2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
  2. //
  3. #include <stdio.h>
  4. #include <winsock2.h>
  5. #pragma comment(lib,"ws2_32.lib")
  6. int main(int argc, char* argv[])
  7. {
  8. //初始化WSA
  9. WORD sockVersion = MAKEWORD(2, 2);
  10. WSADATA wsaData;
  11. if (WSAStartup(sockVersion, &wsaData) != 0)
  12. {
  13. return 0;
  14. }
  15. //创建套接字
  16. SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  17. if (slisten == INVALID_SOCKET)
  18. {
  19. printf("socket error !");
  20. return 0;
  21. }
  22. //绑定IP和端口
  23. sockaddr_in sin;
  24. sin.sin_family = AF_INET;
  25. sin.sin_port = htons(8888);
  26. sin.sin_addr.S_un.S_addr = INADDR_ANY;
  27. if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
  28. {
  29. printf("bind error !");
  30. }
  31. //开始监听
  32. if (listen(slisten, 5) == SOCKET_ERROR)
  33. {
  34. printf("listen error !");
  35. return 0;
  36. }
  37. //循环接收数据
  38. SOCKET sClient;
  39. sockaddr_in remoteAddr;
  40. int nAddrlen = sizeof(remoteAddr);
  41. char revData[1024];
  42. while (true)
  43. {
  44. printf("等待连接...\n");
  45. sClient = accept(slisten, (SOCKADDR*)&remoteAddr, &nAddrlen);
  46. if (sClient == INVALID_SOCKET)
  47. {
  48. printf("accept error !");
  49. continue;
  50. }
  51. //接收数据
  52. int ret = recv(sClient, revData, 255, 0);
  53. if (ret > 0)
  54. {
  55. revData[ret] = 0x00;
  56. printf(revData);
  57. }
  58. //发送数据
  59. const char* sendData = "hello \n";
  60. send(sClient, sendData, strlen(sendData), 0);
  61. }
  62. closesocket(sClient);
  63. closesocket(slisten);
  64. WSACleanup();
  65. return 0;
  66. }

先运行服务器程序,当有连接来的时候返回hello

看下我们的recv函数是否拦截成功:

成功HOOK到了

完整代码:

API HOOK:

  1. class MyHookClass {
  2. public:
  3. MyHookClass()
  4. {
  5. m_pfnOld = nullptr;
  6. ZeroMemory(m_bNewBytes, 5);
  7. ZeroMemory(m_bOldBytes, 5);
  8. }
  9. ~MyHookClass()
  10. {
  11. UnHook();
  12. }
  13. BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
  14. {
  15. m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
  16. if (!m_pfnOld)
  17. {
  18. return FALSE;
  19. }
  20. DWORD dwNum = 0;
  21. ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  22. m_bNewBytes[0] = '\xe9';
  23. *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
  24. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  25. return TRUE;
  26. }
  27. //复原
  28. void UnHook()
  29. {
  30. if (m_pfnOld != nullptr)
  31. {
  32. DWORD dwNum = 0;
  33. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  34. }
  35. }
  36. //重置
  37. bool ReHook()
  38. {
  39. if (m_pfnOld != nullptr)
  40. {
  41. DWORD dwNum = 0;
  42. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  43. return FALSE;
  44. }
  45. return TRUE;
  46. }
  47. private:
  48. PROC m_pfnOld;//原API函数地址
  49. BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
  50. BYTE m_bNewBytes[5];//新的跳转地址
  51. };

 

demo代码:

客户端:

  1. // HookMessage.cpp: 定义控制台应用程序的入口点。
  2. //
  3. #include <stdio.h>
  4. #include <winsock2.h>
  5. #include <WS2tcpip.h>
  6. #include<stdio.h>
  7. #include<iostream>
  8. #include<cstring>
  9. #include <Windows.h>
  10. #pragma comment(lib,"ws2_32.lib")
  11. class MyHookClass {
  12. public:
  13. MyHookClass()
  14. {
  15. m_pfnOld = nullptr;
  16. ZeroMemory(m_bNewBytes, 5);
  17. ZeroMemory(m_bOldBytes, 5);
  18. }
  19. ~MyHookClass()
  20. {
  21. UnHook();
  22. }
  23. BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
  24. {
  25. m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
  26. if (!m_pfnOld)
  27. {
  28. return FALSE;
  29. }
  30. DWORD dwNum = 0;
  31. ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  32. m_bNewBytes[0] = '\xe9';
  33. *(DWORD*)(m_bNewBytes + 1) = (DWORD)pHookFunc - (DWORD)m_pfnOld - 5;
  34. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  35. return TRUE;
  36. }
  37. //复原
  38. void UnHook()
  39. {
  40. if (m_pfnOld != nullptr)
  41. {
  42. DWORD dwNum = 0;
  43. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
  44. }
  45. }
  46. //重置
  47. bool ReHook()
  48. {
  49. if (m_pfnOld != nullptr)
  50. {
  51. DWORD dwNum = 0;
  52. WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
  53. return FALSE;
  54. }
  55. return TRUE;
  56. }
  57. private:
  58. PROC m_pfnOld;//原API函数地址
  59. BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
  60. BYTE m_bNewBytes[5];//新的跳转地址
  61. };
  62. MyHookClass g_MsgHook;
  63. int recv1(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags) {
  64. g_MsgHook.UnHook();
  65. recv(s, buf, 255, 0);
  66. printf("hook:%s",buf);
  67. g_MsgHook.ReHook();
  68. getchar();
  69. return 0;
  70. }
  71. int main(int argc, char* argv[])
  72. {
  73. // hook windows api
  74. char szModuleName[MAXBYTE] = { 0 };//"user32.dll";
  75. char szFuncName[MAXBYTE] = { 0 };// "MessageBoxW";
  76. strcpy_s(szModuleName, MAXBYTE, "WS2_32.dll");
  77. strcpy_s(szFuncName, MAXBYTE, "recv");
  78. WORD sockVersion = MAKEWORD(2, 2);
  79. WSADATA data;
  80. if (WSAStartup(sockVersion, &data) != 0)
  81. {
  82. return 0;
  83. }
  84. sockaddr_in serAddr;
  85. serAddr.sin_family = AF_INET;
  86. serAddr.sin_port = htons(8888);
  87. inet_pton(AF_INET, "127.0.0.1", &serAddr.sin_addr);
  88. if (FALSE == g_MsgHook.Hook(szModuleName, szFuncName, (PROC)recv1)) {
  89. printf("error");
  90. }
  91. while (true)
  92. {
  93. SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  94. if (sclient == INVALID_SOCKET)
  95. {
  96. printf("invalid socket!");
  97. return 0;
  98. }
  99. if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
  100. { //连接失败
  101. printf("connect error !");
  102. closesocket(sclient);
  103. return 0;
  104. }
  105. send(sclient, "asd", strlen("asd"), 0);
  106. char recData[255] = {0};
  107. int ret = recv(sclient, recData, 12, 0);
  108. if (ret > 0) {
  109. recData[ret] = 0x00;
  110. printf(recData);
  111. }
  112. closesocket(sclient);
  113. }
  114. WSACleanup();
  115. return 0;
  116. }

服务器:

  1. // ConsoleApplication2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
  2. //
  3. #include <stdio.h>
  4. #include <winsock2.h>
  5. #pragma comment(lib,"ws2_32.lib")
  6. int main(int argc, char* argv[])
  7. {
  8. //初始化WSA
  9. WORD sockVersion = MAKEWORD(2, 2);
  10. WSADATA wsaData;
  11. if (WSAStartup(sockVersion, &wsaData) != 0)
  12. {
  13. return 0;
  14. }
  15. //创建套接字
  16. SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  17. if (slisten == INVALID_SOCKET)
  18. {
  19. printf("socket error !");
  20. return 0;
  21. }
  22. //绑定IP和端口
  23. sockaddr_in sin;
  24. sin.sin_family = AF_INET;
  25. sin.sin_port = htons(8888);
  26. sin.sin_addr.S_un.S_addr = INADDR_ANY;
  27. if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
  28. {
  29. printf("bind error !");
  30. }
  31. //开始监听
  32. if (listen(slisten, 5) == SOCKET_ERROR)
  33. {
  34. printf("listen error !");
  35. return 0;
  36. }
  37. //循环接收数据
  38. SOCKET sClient;
  39. sockaddr_in remoteAddr;
  40. int nAddrlen = sizeof(remoteAddr);
  41. char revData[1024];
  42. while (true)
  43. {
  44. printf("等待连接...\n");
  45. sClient = accept(slisten, (SOCKADDR*)&remoteAddr, &nAddrlen);
  46. if (sClient == INVALID_SOCKET)
  47. {
  48. printf("accept error !");
  49. continue;
  50. }
  51. //接收数据
  52. int ret = recv(sClient, revData, 255, 0);
  53. if (ret > 0)
  54. {
  55. revData[ret] = 0x00;
  56. printf(revData);
  57. }
  58. //发送数据
  59. const char* sendData = "hello \n";
  60. send(sClient, sendData, strlen(sendData), 0);
  61. }
  62. closesocket(sClient);
  63. closesocket(slisten);
  64. WSACleanup();
  65. return 0;
  66. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/187234
推荐阅读
相关标签
  

闽ICP备14008679号