拦截http协议封包hook哪些
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了拦截http协议封包hook哪些相关的知识,希望对你有一定的参考价值。
文章比较长,得慢点看。转载利用HOOK拦截封包原理 截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。 首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下: HHOOK SetWindowsHookEx( int idHook, // hook type HOOKPROC lpfn, // hook procedure HINSTANCE hMod, // handle to application instance DWORD dwThreadId // thread identifier ); 具体的参数含义可以翻阅msdn,没有msdn可谓寸步难行。 这里Hook本身的功能并不重要,我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook Type我们任选一种即可,只要保证是目标程序肯定会调用到就行,这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll,dwThreadId设为0,表示对所有系统内的线程都挂上这样一个hook,这样我们才能把代码放到别的进程里去。 之后,我们的代码就已经进入了系统内的所有进程空间了。必须注意的是,我们只需要截获我们所关心的目标程序的调用,因此还必须区分一下进程号。我们自己的钩子函数中,第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令,使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用,ws2_32.dll中的send和recv、user32.dll中的GetMessageA。 DWORD dwCurrentPID = 0; HHOOK hOldHook = NULL; DWORD pSend = 0; DWORD pRecv = 0; GETMESSAGE pGetMessage = NULL; BYTE btNewBytes[8] = 0x0B8, 0x0, 0x0, 0x40, 0x0, 0x0FF, 0x0E0, 0 ; DWORD dwOldBytes[3][2]; HANDLE hDebug = INVALID_HANDLE_value; LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam ) DWORD dwSize; DWORD dwPIDWatched; HMODULE hLib; if( dwCurrentPID == 0 ) dwCurrentPID = GetCurrentProcessId(); HWND hwndMainHook; hwndMainHook = ::FindWindow( 0, "MainHook" ); dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100), 0, 0 ); hOldHook = (HHOOK)::SendMessage( hwndMainHook, (WM_USER+101), 0, 0 ); if( dwCurrentPID == dwPIDWatched ) hLib = LoadLibrary( "ws2_32.dll" ); pSend = (DWORD)GetProcAddress( hLib, "send" ); pRecv = (DWORD)GetProcAddress( hLib, "recv" ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize ); *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_send; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize ); *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_recv; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); hLib = LoadLibrary( "user32.dll" ); pGetMessage = (GETMESSAGE)GetProcAddress( hLib, "GetMessageA" ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); hDebug = ::CreateFile( "C:\\\\Trace.log", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); if( hOldHook != NULL ) return CallNextHookEx( hOldHook, nCode, wParam, lParam ); return 0; 上面的钩子函数,只有第一次运行时有用,就是把三个函数的首8字节修改一下(实际上只需要7个)。btNewBytes中的指令实际就是 mov eax, 0x400000 jmp eax 这里的0x400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例: BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) DWORD dwSize; char szTemp[256]; BOOL r = false; //Watch here before it\'s executed. sprintf( szTemp, "Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax 0x%8.8x \\r\\n", hWnd, wMsgFilterMin, wMsgFilterMax ); ::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 ); //Watch over // restore it at first ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); // execute it r = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax ); // hook it again *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); //Watch here after it\'s executed sprintf( szTemp, "Result of GetMessage is %d.\\r\\n", r ); ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); if( r ) sprintf( szTemp, "Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X, lParam 0x%8.8X\\r\\nTime 0x%8.8X, X %d, Y %d\\r\\n", lpMsg->hwnd, lpMsg->message, lpMsg->wParam, lpMsg->lParam, lpMsg->time, lpMsg->pt.x, lpMsg->pt.y ); ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); strcpy( szTemp, "\\r\\n" ); ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); //Watch over return r; 先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。 整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行,以原始套接字的方式 截获流经本机网卡的IP数据包 从事网络安全的技术人员和相当一部分准黑客(指那些使用现成的黑客软件进行攻击而不是根据需要去自己编写代码的人)都一定不会对网络嗅探器(sniffer)感到陌生,网络嗅探器无论是在网络安全还是在黑客攻击方面均扮演了很重要的角色。通过使用网络嗅探器可以把网卡设置于混杂模式,并可实现对网络上传输的数据包的捕获与分析。此分析结果可供网络安全分析之用,但如为黑客所利用也可以为其发动进一步的攻击提供有价值的信息。可见,嗅探器实际是一把双刃剑。 虽然网络嗅探器技术被黑客利用后会对网络安全构成一定的威胁,但嗅探器本身的危害并不是很大,主要是用来为其他黑客软件提供网络情报,真正的攻击主要是由其他黑软来完成的。而在网络安全方面,网络嗅探手段可以有效地探测在网络上传输的数据包信息,通过对这些信息的分析利用是有助于网络安全维护的。权衡利弊,有必要对网络嗅探器的实现原理进行介绍。 文章正文 嗅探器设计原理 嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已到达网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。而网络嗅探器的目的恰恰在于从网卡接收所有经过它的数据包,这些数据包即可以是发给它的也可以是发往别处的。显然,要达到此目的就不能再让网卡按通常的正常模式工作,而必须将其设置为混杂模式。 具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(raw socket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍象流式套接字或数据报套接字那样通过recv()函数来完成。但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有 IP头、 TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。下面先给出结构.数据包的总体结构: 数据包 IP头 TCP头(或其他信息头) 数据 数据在从应用层到达传输层时,将添加TCP数据段头,或是UDP数据段头。其中UDP数据段头比较简单,由一个8字节的头和数据部分组成,具体格式如下: 16位 16位 源端口 目的端口 UDP长度 UDP校验和 而TCP数据头则比较复杂,以20个固定字节开始,在固定头后面还可以有一些长度不固定的可选项,下面给出TCP数据段头的格式组成: 16位 16位 源端口 目的端口 顺序号 确认号 TCP头长 (保留)7位 URG ACK PSH RST SYN FIN 窗口大小 校验和 紧急指针 可选项(0或更多的32位字) 数据(可选项) 对于此TCP数据段头的分析在编程实现中可通过数据结构_TCP来定义: typedef struct _TCP WORD SrcPort; // 源端口 WORD DstPort; // 目的端口 DWORD SeqNum; // 顺序号 DWORD AckNum; // 确认号 BYTE DataOff; // TCP头长 BYTE Flags; // 标志(URG、ACK等) WORD Window; // 窗口大小 WORD Chksum; // 校验和 WORD UrgPtr; // 紧急指针 TCP; typedef TCP *LPTCP; typedef TCP UNALIGNED * ULPTCP; 在网络层,还要给TCP数据包添加一个IP数据段头以组成IP数据报。IP数据头以大端点机次序传送,从左到右,版本字段的高位字节先传输(SPARC是大端点机;Pentium是小端点机)。如果是小端点机,就要在发送和接收时先行转换然后才能进行传输。IP数据段头格式如下: 16位 16位 版本 IHL 服务类型 总长 标识 标志 分段偏移 生命期 协议 头校验和 源地址 目的地址 选项(0或更多) 同样,在实际编程中也需要通过一个数据结构来表示此IP数据段头,下面给出此数据结构的定义: typedef struct _IP union BYTE Version; // 版本 BYTE HdrLen; // IHL ; BYTE ServiceType; // 服务类型 WORD TotalLen; // 总长 WORD ID; // 标识 union WORD Flags; // 标志 WORD FragOff; // 分段偏移 ; BYTE TimeToLive; // 生命期 BYTE Protocol; // 协议WORD HdrChksum; // 头校验和 DWORD SrcAddr; // 源地址 DWORD DstAddr; // 目的地址 BYTE Options; // 选项 IP; typedef IP * LPIP; typedef IP UNALIGNED * ULPIP; 在明确了以上几个数据段头的组成结构后,就可以对捕获到的数据包进行分析了。 嗅探器的具体实现 根据前面的设计思路,不难写出网络嗅探器的实现代码,下面就给出一个简单的示例,该示例可以捕获到所有经过本地网卡的数据包,并可从中分析出协议、IP源地址、IP目标地址、TCP源端口号、TCP目标端口号以及数据包长度等信息。由于前面已经将程序的设计流程讲述的比较清楚了,因此这里就不在赘述了,下面就结合注释对程序的具体是实现进行讲解,同时为程序流程的清晰起见,去掉了错误检查等保护性代码。主要代码实现清单为: // 检查 Winsock 版本号,WSAData为WSADATA结构对象 WSAStartup(MAKEWORD(2, 2), &WSAData); // 创建原始套接字 sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)); // 设置IP头操作选项,其中flag 设置为ture,亲自对IP头进行处理 setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag)); // 获取本机名 gethostname((char*)LocalName, sizeof(LocalName)-1); // 获取本地 IP 地址 pHost = gethostbyname((char*)LocalName)); // 填充SOCKADDR_IN结构 addr_in.sin_addr = *(in_addr *)pHost->h_addr_list[0]; //IP addr_in.sin_family = AF_INET; addr_in.sin_port = htons(57274); // 把原始套接字sock 绑定到本地网卡地址上 bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in)); // dwValue为输入输出参数,为1时执行,0时取消 DWORD dwValue = 1; // 设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL // 的定义为: #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1) ioctlsocket(sock, SIO_RCVALL, &dwValue); 前面的工作基本上都是对原始套接字进行设置,在将原始套接字设置完毕,使其能按预期目的工作时,就可以通过recv()函数从网卡接收数据了,接收到的原始数据包存放在缓存RecvBuf[]中,缓冲区长度BUFFER_SIZE定义为65535。然后就可以根据前面对IP数据段头、TCP数据段头的结构描述而对捕获的数据包进行分析: while (true) // 接收原始数据包信息 int ret = recv(sock, RecvBuf, BUFFER_SIZE, 0); if (ret > 0) // 对数据包进行分析,并输出分析结果 ip = *(IP*)RecvBuf; tcp = *(TCP*)(RecvBuf + ip.HdrLen); TRACE("协议: %s\\r\\n",GetProtocolTxt(ip.Protocol)); TRACE("IP源地址: %s\\r\\n",inet_ntoa(*(in_addr*)&ip.SrcAddr)); TRACE("IP目标地址: %s\\r\\n",inet_ntoa(*(in_addr*)&ip.DstAddr)); TRACE("TCP源端口号: %d\\r\\n",tcp.SrcPort); TRACE("TCP目标端口号:%d\\r\\n",tcp.DstPort); TRACE("数据包长度: %d\\r\\n\\r\\n\\r\\n",ntohs(ip.TotalLen)); 其中,在进行协议分析时,使用了GetProtocolTxt()函数,该函数负责将IP包中的协议(数字标识的)转化为文字输出,该函数实现如下: #define PROTOCOL_STRING_ICMP_TXT "ICMP" #define PROTOCOL_STRING_TCP_TXT "TCP" #define PROTOCOL_STRING_UDP_TXT "UDP" #define PROTOCOL_STRING_SPX_TXT "SPX" #define PROTOCOL_STRING_NCP_TXT "NCP" #define PROTOCOL_STRING_UNKNOW_TXT "UNKNOW" …… CString CSnifferDlg::GetProtocolTxt(int Protocol) switch (Protocol) case IPPROTO_ICMP : //1 /* control message protocol */ return PROTOCOL_STRING_ICMP_TXT; case IPPROTO_TCP : //6 /* tcp */ return PROTOCOL_STRING_TCP_TXT; case IPPROTO_UDP : //17 /* user datagram protocol */ return PROTOCOL_STRING_UDP_TXT; default: return PROTOCOL_STRING_UNKNOW_TXT; 最后,为了使程序能成功编译,需要包含头文件winsock2.h和ws2tcpip.h。在本示例中将分析结果用TRACE()宏进行输出,在调试状态下运行,得到的一个分析结果如下: 协议: UDP IP源地址: 172.168.1.5 IP目标地址: 172.168.1.255 TCP源端口号: 16707 TCP目标端口号:19522 数据包长度: 78 …… 协议: TCP IP源地址: 172.168.1.17 IP目标地址: 172.168.1.1 TCP源端口号: 19714 TCP目标端口号:10 数据包长度: 200 …… 从分析结果可以看出,此程序完全具备了嗅探器的数据捕获以及对数据包的分析等基本功能。 小结 本文介绍的以原始套接字方式对网络数据进行捕获的方法实现起来比较简单,尤其是不需要编写VxD虚拟设备驱动程序就可以实现抓包,使得其编写过程变的非常简便,但由于捕获到的数据包头不包含有帧信息,因此不能接收到与 IP 同属网络层的其它数据包, 如 ARP数据包、RARP数据包等。在前面给出的示例程序中考虑到安全因素,没有对数据包做进一步的分析,而是仅仅给出了对一般信息的分析方法。通过本文的介绍,可对原始套接字的使用方法以及TCP/IP协议结构原理等知识有一个基本的认识。 参考技术A 文章比较长,得慢点看。转载
利用HOOK拦截封包原理 截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。 首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下: HHOOK SetWindowsHookEx( int idHook, // hook type HOOKPROC lpfn, // hook procedure HINSTANCE hMod, // handle to application instance DWORD dwThreadId // thread identifier ); 具体的参数含义可以翻阅msdn,没有msdn可谓寸步难行。 这里Hook本身的功能并不重要,我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook Type我们任选一种即可,只要保证是目标程序肯定会调用到就行,这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll,dwThreadId设为0,表示对所有系统内的线程都挂上这样一个hook,这样我们才能把代码放到别的进程里去。 之后,我们的代码就已经进入了系统内的所有进程空间了。必须注意的是,我们只需要截获我们所关心的目标程序的调用,因此还必须区分一下进程号。我们自己的钩子函数中,第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令,使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用,ws2_32.dll中的send和recv、user32.dll中的GetMessageA。 DWORD dwCurrentPID = 0; HHOOK hOldHook = NULL; DWORD pSend = 0; DWORD pRecv = 0; GETMESSAGE pGetMessage = NULL; BYTE btNewBytes[8] = 0x0B8, 0x0, 0x0, 0x40, 0x0, 0x0FF, 0x0E0, 0 ; DWORD dwOldBytes[3][2]; HANDLE hDebug = INVALID_HANDLE_value; LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam ) DWORD dwSize; DWORD dwPIDWatched; HMODULE hLib; if( dwCurrentPID == 0 ) dwCurrentPID = GetCurrentProcessId(); HWND hwndMainHook; hwndMainHook = ::FindWindow( 0, "MainHook" ); dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100), 0, 0 ); hOldHook = (HHOOK)::SendMessage( hwndMainHook, (WM_USER+101), 0, 0 ); if( dwCurrentPID == dwPIDWatched ) hLib = LoadLibrary( "ws2_32.dll" ); pSend = (DWORD)GetProcAddress( hLib, "send" ); pRecv = (DWORD)GetProcAddress( hLib, "recv" ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize ); *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_send; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize ); *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_recv; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); hLib = LoadLibrary( "user32.dll" ); pGetMessage = (GETMESSAGE)GetProcAddress( hLib, "GetMessageA" ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); hDebug = ::CreateFile( "C:\\Trace.log", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); if( hOldHook ,= NULL ) return CallNextHookEx( hOldHook, nCode, wParam, lParam ); return 0; 上面的钩子函数,只有第一次运行时有用,就是把三个函数的首8字节修改一下(实际上只需要7个)。btNewBytes中的指令实际就是 mov eax, 0x400000 jmp eax 这里的0x400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例: BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) DWORD dwSize; char szTemp[256]; BOOL r = false; //Watch here before it's executed. sprintf( szTemp, "Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax 0x%8.8x \r\n", hWnd, wMsgFilterMin, wMsgFilterMax ); ::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 ); //Watch over // restore it at first ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); // execute it r = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax ); // hook it again *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); //Watch here after it's executed sprintf( szTemp, "Result of GetMessage is %d.\r\n", r ); ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); if( r ) sprintf( szTemp, "Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X, lParam 0x%8.8X\r\nTime 0x%8.8X, X %d, Y %d\r\n", lpMsg->hwnd, lpMsg->message, lpMsg->wParam, lpMsg->lParam, lpMsg->time, lpMsg->pt.x, lpMsg->pt.y ); ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); strcpy( szTemp, "\r\n" ); ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); //Watch over return r; 先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。 整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行,以原始套接字的方式 截获流经本机网卡的IP数据包 从事网络安全的技术人员和相当一部分准黑客(指那些使用现成的黑客软件进行攻击而不是根据需要去自己编写代码的人)都一定不会对网络嗅探器(sniffer)感到陌生,网络嗅探器无论是在网络安全还是在黑客攻击方面均扮演了很重要的角色。通过使用网络嗅探器可以把网卡设置于混杂模式,并可实现对网络上传输的数据包的捕获与分析。此分析结果可供网络安全分析之用,但如为黑客所利用也可以为其发动进一步的攻击提供有价值的信息。可见,嗅探器实际是一把双刃剑。 虽然网络嗅探器技术被黑客利用后会对网络安全构成一定的威胁,但嗅探器本身的危害并不是很大,主要是用来为其他黑客软件提供网络情报,真正的攻击主要是由其他黑软来完成的。而在网络安全方面,网络嗅探手段可以有效地探测在网络上传输的数据包信息,通过对这些信息的分析利用是有助于网络安全维护的。权衡利弊,有必要对网络嗅探器的实现原理进行介绍。 文章正文 嗅探器设计原理 嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已到达网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。而网络嗅探器的目的恰恰在于从网卡接收所有经过它的数据包,这些数据包即可以是发给它的也可以是发往别处的。显然,要达到此目的就不能再让网卡按通常的正常模式工作,而必须将其设置为混杂模式。 具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(raw socket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍象流式套接字或数据报套接字那样通过recv()函数来完成。但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有 IP头、 TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。下面先给出结构.数据包的总体结构: 数据包 IP头 TCP头(或其他信息头) 数据 数据在从应用层到达传输层时,将添加TCP数据段头,或是UDP数据段头。其中UDP数据段头比较简单,由一个8字节的头和数据部分组成,具体格式如下: 16位 16位 源端口 目的端口 UDP长度 UDP校验和 而TCP数据头则比较复杂,以20个固定字节开始,在固定头后面还可以有一些长度不固定的可选项,下面给出TCP数据段头的格式组成: 16位 16位 源端口 目的端口 顺序号 确认号 TCP头长 (保留)7位 URG ACK PSH RST SYN FIN 窗口大小 校验和 紧急指针 可选项(0或更多的32位字) 数据(可选项) 对于此TCP数据段头的分析在编程实现中可通过数据结构_TCP来定义: typedef struct _TCP WORD SrcPort; // 源端口 WORD DstPort; // 目的端口 DWORD SeqNum; // 顺序号 DWORD AckNum; // 确认号 BYTE DataOff; // TCP头长 BYTE Flags; // 标志(URG、ACK等) WORD Window; // 窗口大小 WORD Chksum; // 校验和 WORD UrgPtr; // 紧急指针 TCP; typedef TCP *LPTCP; typedef TCP UNALIGNED * ULPTCP; 在网络层,还要给TCP数据包添加一个IP数据段头以组成IP数据报。IP数据头以大端点机次序传送,从左到右,版本字段的高位字节先传输(SPARC是大端点机;Pentium是小端点机)。如果是小端点机,就要在发送和接收时先行转换然后才能进行传输。IP数据段头格式如下: 16位 16位 版本 IHL 服务类型 总长 标识 标志 分段偏移 生命期 协议 头校验和 源地址 目的地址 选项(0或更多) 同样,在实际编程中也需要通过一个数据结构来表示此IP数据段头,下面给出此数据结构的定义: typedef struct _IP union BYTE Version; // 版本 BYTE HdrLen; // IHL ; BYTE ServiceType; // 服务类型 WORD TotalLen; // 总长 WORD ID; // 标识 union WORD Flags; // 标志 WORD FragOff; // 分段偏移 ; BYTE TimeToLive; // 生命期 BYTE Protocol; // 协议WORD HdrChksum; // 头校验和 DWORD SrcAddr; // 源地址 DWORD DstAddr; // 目的地址 BYTE Options; // 选项 IP; typedef IP * LPIP; typedef IP UNALIGNED * ULPIP; 在明确了以上几个数据段头的组成结构后,就可以对捕获到的数据包进行分析了。 嗅探器的具体实现 根据前面的设计思路,不难写出网络嗅探器的实现代码,下面就给出一个简单的示例,该示例可以捕获到所有经过本地网卡的数据包,并可从中分析出协议、IP源地址、IP目标地址、TCP源端口号、TCP目标端口号以及数据包长度等信息。由于前面已经将程序的设计流程讲述的比较清楚了,因此这里就不在赘述了,下面就结合注释对程序的具体是实现进行讲解,同时为程序流程的清晰起见,去掉了错误检查等保护性代码。主要代码实现清单为: // 检查 Winsock 版本号,WSAData为WSADATA结构对象 WSAStartup(MAKEWORD(2, 2), &WSAData); // 创建原始套接字 sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)); // 设置IP头操作选项,其中flag 设置为ture,亲自对IP头进行处理 setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag)); // 获取本机名 gethostname((char*)LocalName, sizeof(LocalName)-1); // 获取本地 IP 地址 pHost = gethostbyname((char*)LocalName)); // 填充SOCKADDR_IN结构 addr_in.sin_addr = *(in_addr *)pHost->h_addr_list[0]; //IP addr_in.sin_family = AF_INET; addr_in.sin_port = htons(57274); // 把原始套接字sock 绑定到本地网卡地址上 bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in)); // dwValue为输入输出参数,为1时执行,0时取消 DWORD dwValue = 1; // 设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL // 的定义为: #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1) ioctlsocket(sock, SIO_RCVALL, &dwValue); 前面的工作基本上都是对原始套接字进行设置,在将原始套接字设置完毕,使其能按预期目的工作时,就可以通过recv()函数从网卡接收数据了,接收到的原始数据包存放在缓存RecvBuf[]中,缓冲区长度BUFFER_SIZE定义为65535。然后就可以根据前面对IP数据段头、TCP数据段头的结构描述而对捕获的数据包进行分析: while (true) // 接收原始数据包信息 int ret = recv(sock, RecvBuf, BUFFER_SIZE, 0); if (ret > 0) // 对数据包进行分析,并输出分析结果 ip = *(IP*)RecvBuf; tcp = *(TCP*)(RecvBuf + ip.HdrLen); TRACE("协议: %s\r\n",GetProtocolTxt(ip.Protocol)); TRACE("IP源地址: %s\r\n",inet_ntoa(*(in_addr*)&ip.SrcAddr)); TRACE("IP目标地址: %s\r\n",inet_ntoa(*(in_addr*)&ip.DstAddr)); TRACE("TCP源端口号: %d\r\n",tcp.SrcPort); TRACE("TCP目标端口号:%d\r\n",tcp.DstPort); TRACE("数据包长度: %d\r\n\r\n\r\n",ntohs(ip.TotalLen)); 其中,在进行协议分析时,使用了GetProtocolTxt()函数,该函数负责将IP包中的协议(数字标识的)转化为文字输出,该函数实现如下: #define PROTOCOL_STRING_ICMP_TXT "ICMP" #define PROTOCOL_STRING_TCP_TXT "TCP" #define PROTOCOL_STRING_UDP_TXT "UDP" #define PROTOCOL_STRING_SPX_TXT "SPX" #define PROTOCOL_STRING_NCP_TXT "NCP" #define PROTOCOL_STRING_UNKNOW_TXT "UNKNOW" CString CSnifferDlg::GetProtocolTxt(int Protocol) switch (Protocol) case IPPROTO_ICMP : //1 /* control message protocol */ return PROTOCOL_STRING_ICMP_TXT; case IPPROTO_TCP : //6 /* tcp */ return PROTOCOL_STRING_TCP_TXT; case IPPROTO_UDP : //17 /* user datagram protocol */ return PROTOCOL_STRING_UDP_TXT; default: return PROTOCOL_STRING_UNKNOW_TXT; 最后,为了使程序能成功编译,需要包含头文件winsock2.h和ws2tcpip.h。在本示例中将分析结果用TRACE()宏进行输出,在调试状态下运行,得到的一个分析结果如下: 协议: UDP IP源地址: 172.168.1.5 IP目标地址: 172.168.1.255 TCP源端口号: 16707 TCP目标端口号:19522 数据包长度: 78 协议: TCP IP源地址: 172.168.1.17 IP目标地址: 172.168.1.1 TCP源端口号: 19714 TCP目标端口号:10 数据包长度: 200 从分析结果可以看出,此程序完全具备了嗅探器的数据捕获以及对数据包的分析等基本功能。 小结 本文介绍的以原始套接字方式对网络数据进行捕获的方法实现起来比较简单,尤其是不需要编写VxD虚拟设备驱动程序就可以实现抓包,使得其编写过程变的非常简便,但由于捕获到的数据包头不包含有帧信息,因此不能接收到与 IP 同属网络层的其它数据包, 如 ARP数据包、RARP数据包等。在前面给出的示例程序中考虑到安全因素,没有对数据包做进一步的分析,而是仅仅给出了对一般信息的分析方法。通过本文的介绍,可对原始套接字的使用方法以及TCP/IP协议结构原理等知识有一个基本的认识。
通用hook拦截所有API的实现
通用hook拦截所有API的实现
实现一个通用hook,可以对当前进程的所有函数调用进行拦截查看和统计。 类似程序:API Monitor
。
可以对某一进程的所有api调用进行拦截,可以获得的调用函数返回值
,获得详细的参数值
。
现在尝试在windows 32位下实现一个类似API Monitor
的程序。
当要hook一个函数,通常我们会创建一个参数相符的detour fake函数,当然还有跳板函数,但这样有一个问题,程序内部的api几百几千,甚至可能上万,不可能每一个函数都去写一个对应的参数相同调用方式相同的detour函数,那样做太蠢了。
但可以尝试实现一个通用hook,这个通用hook关键在于实现一个通用的detour函数,所有的api函数的跳转都指向这个通用的detour函数,通用的detour函数既能获取到所有参数,也能去调用原函数,同时还能获取到原调用函数的返回值。
话不多说,直接上核心代码。
当任意hook目标的函数执行跳转到这个`通用detour function`函数。
=========通用 detour function================
pushad//pushad压入当前8个寄存器值到栈 EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
//寻找ip 这个ip是call压栈的ip,就是pushad上一条指令,所以栈esp+32
mov eax, dword ptr[esp + 0x20]//0x20 =》32
//开始调用GetCallParameter32
push eax//|GetCallParameter32 参数4
push esp//这时候esp已近加了个8个压栈 所以栈顶是esp +32 |GetCallParameter32 参数3
push 0xff //fun_index |GetCallParameter32 参数2
push 0xff //module_index |GetCallParameter32 参数1
call GetCallParameter32//e8 4b bc fd ff
popad//从栈中还原由pushad压入的8个寄存器,这时候寄存器与原调用函数时候无异
add esp, 4//类似 pop ip,pop ip出去 抵消一个call,直接用原来的push参数
call trampoline//此时再调用跳板函数 执行 原函数
push eax//将原函数return值得到的结果压栈一份
push edx//将原函数return值压栈一份 32位下的64位返回值也要支持,所以需要edx
sub esp, 8
fstp qword ptr[esp] //将浮点数寄存器压栈一份 因为有可能原函数返回浮点数 SetLastCallResult32参数3
push eax//再将得到的结果传参 SetLastCallResult32参数2
push edx//再将得到的结果传参 SetLastCallResult32参数1
call SetLastCallResult32//调用SetLastCallResult32 获取函数执行返回值
mov ecx, eax//SetLastCallResult325返回值是32位跳转地址,存放在ecx
//恢复目标函数的参数返回
sub esp, 8//尝试用原来压栈的浮点数??
fld qword ptr[esp]//取出浮点数
add esp, 8
pop edx //获取压栈的eax
pop eax //获取压栈的eax
push ecx //返回的是ret ip 还原到原来的ret
ret//ret 会跳转到刚存的ecx地址
这个通用的detour函数
在内存中动态创建,这样就能指定fun_index
module_index
trampoline
的任意值,还需要两个c++函数GetCallParameter32
SetLastCallResult32
去辅助完成功能。
通用detour逻辑流程梳理===
pushad
保存当前主要寄存器。寻找ret ip
。
调用GetCallParameter32
这个函数,GetCallParameter32
会去通过esp获取原函数调用的所有参数。
通过popad
恢复寄存器,再恢复栈到参数水平,call 原函数
。
保存原函数返回值
。调用SetLastCallResult32
,SetLastCallResult32
就得到了原函数的返回值了。
再重新设置返回值到寄存器,然后通过ret ip
回到原调用地址。
struct CallContext
{
UINT_PTR ret_ip;
HookEntryInfo* hook_entry = NULL;
};
//使用thread_local是为了存储调用对象,GetCallParameter32开始SetLastCallResult32结束,
//使用stack是为了某些函数在互相调用,用stack可以保证顺序
thread_local std::stack<CallContext*> hook_stack_list;
//在这里获取目标函数的调用信息,可以从module_index和fun_index,确切知道是那个函数在调用,
//可以从_stack这个esp栈指针,获得函数调用的参数信息
void __stdcall GetCallParameter32(uint32_t module_index, uint32_t fun_index, void* _stack, UINT_PTR ret_ip)
{
unsigned char* esp_stack = (unsigned char*)_stack;
ModuleInfo* minfo = module_list[module_index];
HookEntryInfo* einfo = minfo->entry_list[fun_index];
log_printA("GetCallParameter32: \\n module_index [%u] %s fun_index [%u] %s \\n_stack [%08x] ret_ip: 0x%08x \\n",
module_index, minfo->dll_name.c_str(),
fun_index, einfo->func_name.c_str(),
_stack,
ret_ip);
CallContext* tls_hook_context = new CallContext();
tls_hook_context->ret_ip = ret_ip;
tls_hook_context->hook_entry = einfo;
hook_stack_list.push(tls_hook_context);
return;
}
//目标函数执行完会执行这个函数,可以获取函数执行返回值eax,当返回int64edx也有值,当返回浮点数,值就在return_sto
UINT_PTR __stdcall SetLastCallResult32(int edx, int eax, double return_sto)
{
CallContext* tls_hook_context = hook_stack_list.top();
hook_stack_list.pop();
log_printA("SetLastCallResult32: %s eax: %d edx: %d sto: %f",
tls_hook_context->hook_entry->func_name.c_str(),
eax, edx, return_sto);
UINT_PTR ret_ip = tls_hook_context->ret_ip;
delete tls_hook_context;
return ret_ip;
}
不多说,直接上完整的核心代码:=
<此程序只支持32位>
#include "ZEncode.h"//wchar char转换
#include "MinHook.h"
#include <TlHelp32.h>
#include <stack>
#include <unordered_map>
#if defined _M_X64
#elif defined _M_IX86
#pragma comment(lib, "libMinHook.x86.lib")
#endif
void log_printW(const wchar_t* format, ...) {
const int logsize = 2 * 1024;
wchar_t buffer[logsize], * p = buffer;
va_list args;
va_start(args, format);
p += _vsnwprintf_s(buffer, logsize, format, args);
va_end(args);
while (*--p == L\'\\n\') {
}
*++p = L\'\\n\';
*++p = L\'\\0\';
fputws(buffer, stdout);
}
void log_printA(const char* format, ...) {
const int logsize = 2 * 1024;
char buffer[logsize], * p = buffer;
va_list args;
va_start(args, format);
p += _vsnprintf_s(buffer, logsize, format, args);
va_end(args);
while (*--p == L\'\\n\') {
}
*++p = L\'\\n\';
*++p = L\'\\0\';
puts(buffer);
}
class QPCtime {
public:
QPCtime() {
if (!QueryPerformanceFrequency(&Frequency)) {
log_printA("QueryPerformanceFrequency false");
}
}
~QPCtime() {
}
int64_t now_us() {
return now_ns() / 1000;
}
int64_t now_ms() {
return now_ns() / 1000000;
}
int64_t now_ns() {
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
const long long _Whole = (li.QuadPart / Frequency.QuadPart) * 1000000000;
const long long _Part = (li.QuadPart % Frequency.QuadPart) * 1000000000 / Frequency.QuadPart;
return _Whole + _Part;
}
private:
LARGE_INTEGER Frequency;
};
void __stdcall GetCallParameter32(uint32_t module_index, uint32_t fun_index, void* _stack, UINT_PTR ret_ip);
UINT_PTR __stdcall SetLastCallResult32(int edx, int eax, double return_sto);
struct HookAsmCodeSell
{
unsigned char* asm_code = NULL;
int asm_code_size = 0;
void SetValue(uint32_t module_index, uint32_t fun_index, UINT_PTR TrampolineCall)
{
if (asm_code == NULL) {
log_printA("HookAsmCodeSell SetValue hook_shell_code==null");
return;
}
uint32_t _getcallparameter = (uint32_t)&GetCallParameter32;
uint32_t _setlastcallresult32 = (uint32_t)&SetLastCallResult32;
//偏移地址 = 目的地址 - 跳转基地址(的下一条指令的地址)
*(uint32_t*)(asm_code + 8) = fun_index;
*(uint32_t*)(asm_code + 13) = module_index;
*(uint32_t*)(asm_code + 18) =
_getcallparameter - (uint32_t)(asm_code + 18 + 4);//GetCallParameter
*(uint32_t*)(asm_code + 27) =
TrampolineCall - (uint32_t)(asm_code + 27 + 4);;//old fun func_ttttt
*(uint32_t*)(asm_code + 42) =
_setlastcallresult32 - (uint32_t)(asm_code + 42 + 4);//SetLastCallResult32
}
bool AllocShell()
{
unsigned char shellcode_32[] =
{
0x60, // pushad
0x8B, 0x44, 0x24 ,0x20, // mov eax,dword ptr [esp+20h]
0x50, // push eax 这是ret ip
0x54, // push esp
0x68, 0x00, 0x00, 0x00, 0x00, // push fun_index
0x68, 0x00, 0x00, 0x00, 0x00, // push module_index
0xE8, 0x00, 0x00, 0x00, 0x00, // call GetCallParameter (04F2CBAh)
0x61, // popad
//
0x83, 0xC4 ,0x04 , // add esp,4 抵消一个call,直接用原来的push参数
0xE8, 0x00, 0x00, 0x00, 0x00, // call func_ttttt (04F2D55h)
//保存返回值到栈
0x50, // push eax
0x52, // push edx
0x83, 0xEC ,0x08, // sub esp,8
0xDD, 0x1C ,0x24, // fstp qword ptr [esp]
0x50, // push eax
0x52, // push edx
0xE8, 0x00, 0x00, 0x00, 0x00, // call SetLastCallResult32 (04F2D19h)
0x8B, 0xC8 , // mov ecx,eax
//尝试获取原来压栈的8字节浮点数
0x83, 0xEC, 0x08 , // sub esp,8
0xDD, 0x04, 0x24 , // fld qword ptr [esp]
0x83, 0xC4 ,0x08 , // add esp,8
//原来的eax edx返回值
0x5A, // pop edx
0x58, // pop eax
//原来的返回ip
0x51, // push ecx
0xC3, // ret
};
*(uint32_t*)(&shellcode_32[8]) = 0x0;
*(uint32_t*)(&shellcode_32[13]) = 0x0;
*(uint32_t*)(&shellcode_32[18]) = 0xffffffff;//GetCallParameter
*(uint32_t*)(&shellcode_32[27]) = 0xffffffff;//old fun func_ttttt
*(uint32_t*)(&shellcode_32[42]) = 0xffffffff;//SetLastCallResult32
asm_code_size = sizeof(shellcode_32);
asm_code = (unsigned char*)VirtualAlloc(NULL, asm_code_size + 4,
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (asm_code == NULL) {
log_printA("VirtualAlloc shellcode_32 NULL!!");
return false;
}
memcpy(asm_code, shellcode_32, asm_code_size);
return true;
}
void Free()
{
if (asm_code != NULL)
VirtualFree(asm_code, 0, MEM_RELEASE);
asm_code = NULL;
}
};
struct HookEntryInfo
{
int index = 0;
int module_index = 0;
std::string func_name;
UINT_PTR func_addr = 0;
UINT_PTR rva_addr = 0;
UINT_PTR trampoline = 0;
bool enable_hook = false;
HookAsmCodeSell hook_shell;
};
struct ModuleInfo
{
int index = 0;
UINT_PTR base_addr = 0;
std::string dll_name;
std::string dll_name_path;
std::vector<HookEntryInfo*> entry_list;
std::unordered_map<std::string, HookEntryInfo*> entry_list_dic;//存一份dic 加速查找
};
std::vector<ModuleInfo*> module_list;//存储所有module信息
std::unordered_map<std::string, ModuleInfo*> module_list_dic;//为了加速查找的
struct CallContext
{
UINT_PTR ret_ip;
HookEntryInfo* hook_entry = NULL;
int64_t start_time;
int64_t end_time;
};
QPCtime qpc;
//使用thread_local是为了存储调用对象,GetCallParameter32开始SetLastCallResult32结束,
//使用stack是为了某些函数在互相调用,用stack可以保证顺序
thread_local std::stack<CallContext*> hook_stack_list;
//在这里获取目标函数的调用信息,可以从module_index和fun_index,确切知道是那个函数在调用,
//可以从_stack这个esp栈指针,获得函数调用的参数信息
void __stdcall GetCallParameter32(uint32_t module_index, uint32_t fun_index, void* _stack, UINT_PTR ret_ip)
{
unsigned char* esp_stack = (unsigned char*)_stack;
esp_stack = esp_stack + 8 * 4 + 4 + 4;//pushad +push [ret_ip] and call
ModuleInfo* minfo = module_list[module_index];
HookEntryInfo* einfo = minfo->entry_list[fun_index];
log_printA("GetCallParameter32: \\n module_index [%u] %s fun_index [%u] %s \\n_stack [%08x] ret_ip: 0x%08x ",
module_index, minfo->dll_name.c_str(),
fun_index, einfo->func_name.c_str(),
_stack,
ret_ip);
//通过esp获取参数的demo
if (einfo->func_name.compare("sendto") == 0) {
/*int PASCAL FAR sendto (
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags,
_In_reads_bytes_opt_(tolen) const struct sockaddr FAR *to,
_In_ int tolen);*/
//sendto是__stdcall,从右到左压栈,所以,栈顶是最左边的参数,为顺序
char* send_data = *(char**)(esp_stack + 1 * 4);//顺数第2个参数
int send_data_len = *(int*)(esp_stack + 2 * 4);//顺数第3个参数
std::string send_data_str(send_data, send_data_len);
log_printA("sendto parameter: send data:%s data len:%d", send_data_str.c_str(), send_data_len);
}
//通过esp获取参数的demo
if (einfo->func_name.compare("MessageBoxW") == 0) {
/*int
WINAPI
MessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);*/
//MessageBoxW是__stdcall,从右到左压栈,所以,栈顶是最左边的参数,为顺序
wchar_t* lpText = *(wchar_t**)(esp_stack + 1 * 4);//顺数第2个参数
wchar_t* lpCaption = *(wchar_t**)(esp_stack + 2 * 4);//顺数第3个参数
log_printW(L"MessageBoxW parameter: lpText:%s lpCaption:%s", lpText, lpCaption);
lpCaption[0] = L\'A\';
lpCaption[1] = L\'B\';
lpCaption[2] = L\'C\';
lpText[0] = L\'=\';
lpText[1] = L\'=\';
lpText[2] = L\'=\';
}
CallContext* tls_hook_context = new CallContext();
tls_hook_context->ret_ip = ret_ip;
tls_hook_context->hook_entry = einfo;
hook_stack_list.push(tls_hook_context);
tls_hook_context->start_time = qpc.now_ns();
return;
}
//目标函数执行完会执行这个函数,可以获取函数执行返回值eax,当返回int64edx也有值,当返回浮点数,值就在return_sto
UINT_PTR __stdcall SetLastCallResult32(int edx, int eax, double return_sto)
{
CallContext* tls_hook_context = hook_stack_list.top();
hook_stack_list.pop();
tls_hook_context->end_time = qpc.now_ns();
log_printA("SetLastCallResult32: %s eax: %d edx: %d sto: %f use time: %I64d ns",
tls_hook_context->hook_entry->func_name.c_str(),
eax, edx, return_sto, tls_hook_context->end_time - tls_hook_context->start_time);
UINT_PTR ret_ip = tls_hook_context->ret_ip;
delete tls_hook_context;
return ret_ip;
}
//加载模块的所有导出函数
void LoadModuleExportFuncs(ModuleInfo* dll_module)
{
const char* lpImage = (const char*)dll_module->base_addr;
PIMAGE_DOS_HEADER imDH = (PIMAGE_DOS_HEADER)lpImage;
PIMAGE_NT_HEADERS imNH = (PIMAGE_NT_HEADERS)((char*)lpImage + imDH->e_lfanew);
DWORD exportRVA = imNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY imED = (PIMAGE_EXPORT_DIRECTORY)(lpImage + exportRVA);
//long pExportSize = imNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
PWORD lpOrdinals = imED->AddressOfNameOrdinals ? (PWORD)(lpImage + imED->AddressOfNameOrdinals) : 0;
PDWORD lpNames = imED->AddressOfNames ? (PDWORD)(lpImage + imED->AddressOfNames) : 0;
PDWORD lpRvas = (PDWORD)(lpImage + imED->AddressOfFunctions);
PIMAGE_SECTION_HEADER ish = (PIMAGE_SECTION_HEADER)(imNH + 1);
int nsec = imNH->FileHeader.NumberOfSections;
for (DWORD i = 0; i < imED->NumberOfFunctions; ++i)
{
DWORD rvafunc = lpRvas[i];
DWORD oftName = 0;
if (lpNames && lpOrdinals)
{
for (DWORD k = 0; k < imED->NumberOfNames; ++k)
{
if (lpOrdinals[k] == i)
{
oftName = lpNames[k];
break;
}
}
}
std::string fun_name;
UINT_PTR fun_addr = (UINT_PTR)lpImage + rvafunc;
if (oftName) {
fun_name = (char*)(lpImage + oftName);
}
else {
//没名字的不要了
continue;
}
HookEntryInfo* hook_entry = new HookEntryInfo();
hook_entry->func_name = fun_name;
hook_entry->func_addr = fun_addr;
hook_entry->rva_addr = rvafunc;
dll_module->entry_list.push_back(hook_entry);
hook_entry->index = dll_module->entry_list.size() - 1;
hook_entry->module_index = dll_module->index;
dll_module->entry_list_dic[hook_entry->func_name] = hook_entry;
}
}
//加载模块
void LoadModule(UINT_PTR base_addr, WCHAR* dll_name, WCHAR* dll_path)
{
std::wstring w_dll_name = dll_name;
std::wstring w_dll_path = dll_path;
for (auto& str : w_dll_name) {
str = ::towlower(str);
}
for (auto& str : w_dll_path) {
str = ::towlower(str);
}
ModuleInfo* add_module_info = new ModuleInfo();
add_module_info->base_addr = base_addr;
add_module_info->dll_name = WCHAR_TO_ANSI(w_dll_name);
add_module_info->dll_name_path = WCHAR_TO_ANSI(w_dll_path);
module_list.push_back(add_module_info);
add_module_info->index = module_list.size() - 1;
module_list_dic[add_module_info->dll_name] = add_module_info;
LoadModuleExportFuncs(add_module_info);
}
//扫描进程现在所有的模块
void ScanModule()
{
HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
if (hModuleSnap == INVALID_HANDLE_VALUE) {
log_printA("CreateToolhelp32Snapshot failed.");
return;
}
MODULEENTRY32 me32;
me32.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(hModuleSnap, &me32)) {
log_printA("Module32First failed.");
return;
}
do
{
LoadModule((UINT_PTR)me32.hModule, me32.szModule, me32.szExePath);
log_printW(L"0x%08lX %s %s", me32.hModule, me32.szModule, me32.szExePath);
} while (Module32Next(hModuleSnap, &me32));
CloseHandle(hModuleSnap);
}
void MonitorAPI(const char* _dll_name, const char* func_name)
{
std::string dll_name = _dll_name;
for (auto& str : dll_name) {
str = ::tolower(str);
}
auto find_module_iter = module_list_dic.find(dll_name);
if (find_module_iter == module_list_dic.end()) {
log_printA("not find dll %s", dll_name);
return;
}
ModuleInfo* minfo = find_module_iter->second;
auto find_api_iter = minfo->entry_list_dic.find(func_name);
if (find_api_iter == minfo->entry_list_dic.end()) {
log_printA("not find func_name %s", func_name);
return;
}
HookEntryInfo* einfo = find_api_iter->second;
einfo->hook_shell.AllocShell();
if (MH_CreateHook((LPVOID)einfo->func_addr, einfo->hook_shell.asm_code,
reinterpret_cast<LPVOID*>(&einfo->trampoline)) != MH_OK) {
log_printA("MH_CreateHook false");
return;
}
einfo->hook_shell.SetValue(einfo->module_index,
einfo->index,
(UINT_PTR)einfo->trampoline);
einfo->enable_hook = true;
log_printA("MonitorAPI %s success", func_name);
}
//测试用
extern "C" int64_t _declspec(dllexport) int64test(int64_t a, int64_t b)
{
return a + b;
};
extern "C" float _declspec(dllexport) floattest(float a, float b)
{
return a + b;
};
extern "C" double _declspec(dllexport) doubletest(double a, double b)
{
return a + b;
};
int main()
{
ScanModule();
int value = 0;
if (MH_Initialize() != MH_OK) {
log_printA("MH_Initialize false");
return 0;
}
//====随便添加要监控的任意函数============
MonitorAPI("user32.dll", "MessageBoxW");
MonitorAPI("kernel32.dll", "CreateFileA");
MonitorAPI("kernel32.dll", "WriteFile");
MonitorAPI("ws2_32.dll", "socket");
MonitorAPI("ws2_32.dll", "sendto");
MonitorAPI("ws2_32.dll", "closesocket");
//myapp.exe是当前程序名称,这里测试上面那几个导出函数
MonitorAPI("myapp.exe", "int64test");
MonitorAPI("myapp.exe", "floattest");
MonitorAPI("myapp.exe", "doubletest");
//启用所有hook
if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) {
log_printA("MH_EnableHook(MH_ALL_HOOKS) false");
return 1;
}
//==========开始测试,执行下面的函数,都会被拦截监控到===============
wchar_t lpText[50] = L"888--this messagebox text.";
wchar_t lpCaption[30] = L"XXX-caption";
MessageBoxW(NULL, lpText, lpCaption, MB_OK);
HANDLE hFile = CreateFileA("one.txt",
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile != INVALID_HANDLE_VALUE) {
char buff[256] = "this WriteFile context.";
DWORD dwWrite;
WriteFile(hFile, &buff, strlen(buff), &dwWrite, NULL);
CloseHandle(hFile);
}
#pragma comment(lib,"ws2_32.lib")
SOCKET m_sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
char sendstr[256] = "this sendto data.";
sendto(m_sock, sendstr, strlen(sendstr), 0, (struct sockaddr*)&addr, sizeof(addr));
closesocket(m_sock);
int64_t i64v = int64test(UINT32_MAX, 123);
float fv = floattest(1.123, 2.00);
double dv = doubletest(1.123, 2.00);
//=======结束,移除所有hook========
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
}
输出:
0x00520000 myapp.exe D:\\Arma3_Read\\Debug\\myapp.exe
0x77E20000 ntdll.dll C:\\Windows\\SYSTEM32\\ntdll.dll
0x77430000 KERNEL32.DLL C:\\Windows\\System32\\KERNEL32.DLL
0x75CD0000 KERNELBASE.dll C:\\Windows\\System32\\KERNELBASE.dll
0x76CF0000 USER32.dll C:\\Windows\\System32\\USER32.dll
0x77B70000 win32u.dll C:\\Windows\\System32\\win32u.dll
0x77260000 GDI32.dll C:\\Windows\\System32\\GDI32.dll
0x77180000 gdi32full.dll C:\\Windows\\System32\\gdi32full.dll
0x779F0000 msvcp_win.dll C:\\Windows\\System32\\msvcp_win.dll
0x77880000 ucrtbase.dll C:\\Windows\\System32\\ucrtbase.dll
0x77810000 WS2_32.dll C:\\Windows\\System32\\WS2_32.dll
0x77730000 RPCRT4.dll C:\\Windows\\System32\\RPCRT4.dll
0x79890000 MSVCP140D.dll C:\\Windows\\SYSTEM32\\MSVCP140D.dll
0x796F0000 VCRUNTIME140D.dll C:\\Windows\\SYSTEM32\\VCRUNTIME140D.dll
0x79710000 ucrtbased.dll C:\\Windows\\SYSTEM32\\ucrtbased.dll
0x77700000 IMM32.DLL C:\\Windows\\System32\\IMM32.DLL
MonitorAPI MessageBoxW success
MonitorAPI CreateFileA success
MonitorAPI WriteFile success
MonitorAPI socket success
MonitorAPI sendto success
MonitorAPI closesocket success
MonitorAPI int64test success
MonitorAPI floattest success
MonitorAPI doubletest success
GetCallParameter32:
module_index [4] user32.dll fun_index [645] MessageBoxW
_stack [003ef3e4] ret_ip: 0x00553b65
MessageBoxW parameter: lpText:888--this messagebox text. lpCaption:XXX-caption
GetCallParameter32:
module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0683ef3c] ret_ip: 0x101266ae
SetLastCallResult32: WriteFile eax: 1 edx: 109309756 sto: -nan(ind) use time: 407400 ns
GetCallParameter32:
module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0683ef3c] ret_ip: 0x101266ae
SetLastCallResult32: WriteFile eax: 1 edx: 109309756 sto: -nan(ind) use time: 417800 ns
GetCallParameter32:
module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0659ef0c] ret_ip: 0x101266ae
SetLastCallResult32: WriteFile eax: 1 edx: 106557196 sto: -nan(ind) use time: 349500 ns
GetCallParameter32:
module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0659ef0c] ret_ip: 0x101266ae
SetLastCallResult32: WriteFile eax: 1 edx: 106557196 sto: -nan(ind) use time: 139900 ns
SetLastCallResult32: MessageBoxW eax: 1 edx: 10420224 sto: -nan(ind) use time: 3345317900 ns
GetCallParameter32:
module_index [2] kernel32.dll fun_index [200] CreateFileA
_stack [003ef3d8] ret_ip: 0x00553b8b
SetLastCallResult32: CreateFileA eax: -1 edx: 10420224 sto: -nan(ind) use time: 515400 ns
GetCallParameter32:
module_index [10] ws2_32.dll fun_index [22] socket
_stack [003ef3e8] ret_ip: 0x00553c58
SetLastCallResult32: socket eax: -1 edx: 0 sto: -nan(ind) use time: 1633900 ns
GetCallParameter32:
module_index [10] ws2_32.dll fun_index [19] sendto
_stack [003ef3dc] ret_ip: 0x00553cf1
sendto parameter: send data:this sendto data. data len:17
SetLastCallResult32: sendto eax: -1 edx: 0 sto: -nan(ind) use time: 490700 ns
GetCallParameter32:
module_index [10] ws2_32.dll fun_index [2] closesocket
_stack [003ef3f0] ret_ip: 0x00553cfd
SetLastCallResult32: closesocket eax: -1 edx: 0 sto: -nan(ind) use time: 29600 ns
GetCallParameter32:
module_index [0] myapp.exe fun_index [2] int64test
_stack [003ef3e4] ret_ip: 0x00553d0a
SetLastCallResult32: int64test eax: 122 edx: 1 sto: -nan(ind) use time: 13100 ns
GetCallParameter32:
module_index [0] myapp.exe fun_index [1] floattest
_stack [003ef3ec] ret_ip: 0x00553d3a
SetLastCallResult32: floattest eax: 5677115 edx: 1 sto: 3.123000 use time: 12300 ns
GetCallParameter32:
module_index [0] myapp.exe fun_index [0] doubletest
_stack [003ef3e4] ret_ip: 0x00553d68
SetLastCallResult32: doubletest eax: 5677115 edx: 1 sto: 3.123000 use time: 11100 ns
** 很可能你很快发现了问题,[GetCallParameter32 MessageBoxW]之后隔了好几个WriteFile,然后才出现,[SetLastCallResult32: MessageBoxW],这是正常的,因为MessageBoxW内部就在WriteFile,所以就体现了,thread_local std::stack<CallContext*> hook_stack_list;
的std::stack
必要性。 **
函数的参数通过esp栈指针去动态获取。函数的返回值通过eax edx 和浮点数sto去获取。此外我们还可以得到目标函数的调用整个纳秒时间。
关于参数获取
esp_stack = esp_stack + 8 * 4 + 4 + 4;//pushad +push [ret_ip] and call
//通过esp获取参数的demo
if (einfo->func_name.compare("sendto") == 0) {
/*int PASCAL FAR sendto (
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags,
_In_reads_bytes_opt_(tolen) const struct sockaddr FAR *to,
_In_ int tolen);*/
//sendto是__stdcall,从右到左压栈,所以,栈顶是最左边的参数,为顺序
char* send_data = *(char**)(esp_stack + 1 * 4);//顺数第2个参数
int send_data_len = *(int*)(esp_stack + 2 * 4);//顺数第3个参数
std::string send_data_str(send_data, send_data_len);
log_printA("sendto parameter: send data:%s data len:%d", send_data_str.c_str(), send_data_len);
}
参数获取,必须要知道传参的具体类型,上面的sendto函数,每个参数都是4字节,如果遇到int64的参数,那么一个参数就是8字节,就需要注意偏移。
问题来了,如何才能像API Monitor
那样去获取任何api的完整的参数?就需要准备一个函数的资料库,这个资料里面有函数的每个参数类型,参数名称,返回值类型,结构体类型。
API Monitor
有准备好了,参考API Monitor
文件夹里面的API
文件夹,里面的xml文件都是windows主要api的参数和返回值类型,很多类型是别名,需要继续去寻找最根本的类型,这样就能获取到大小了。
下面是我通过自己的程序和API Monitor
的xml文件去配合完成的参数读取结果。
module_name:kernel32.dll
function name:CreateFileW
function addr: 0x6f800558
call addr:0x1039a95
description:文件操作
[return]: 0xe70([FILE_HANDLE])
param:
[0] lpFileName LPCTSTR
value: 0x0753F644 (wstring)\\\\?\\D:\\xxx\\bin_110\\3dmgame\\data\\fonts\\yaheib17-01.paa
[1] dwDesiredAccess [FILE_ACCESS_MASK]
value: 0x80000000 ( GENERIC_READ )
[2] dwShareMode [FILE_SHARE_MODE]
value: 0x1 ( FILE_SHARE_READ )
[3] lpSecurityAttributes LPSECURITY_ATTRIBUTES
value: 0x0753F650 (struct)SECURITY_ATTRIBUTES||[DWORD nLength] 0x3||[PSECURITY_DESCRIPTOR lpSecurityDescriptor] 0x€||[BOOL bInheritHandle] 0x0
[4] dwCreationDisposition [CreationDisposition]
value: 0x3 (OPEN_EXISTING)
[5] dwFlagsAndAttributes [FlagsAndAttributes]
value: 0x60000080 ( FILE_ATTRIBUTE_NORMAL FILE_FLAG_NO_BUFFERING FILE_FLAG_OVERLAPPED )
[6] hTemplateFile HANDLE
value: 0x0 (NULL)
module_name:ws2_32.dll
function name:recv
function addr: 0x6f8001f8
call addr:0x7432c157
description:网络访问
[return]: 0x1([SocketCode-int])
param:
[0] s SOCKET
value: 0x1218(peer:213.192.54.31:443)(local:22.62.0.114:13560)
[1] buf [LPVOID|char*]
value: 0x02E0FCA4
[2] len int
value: 0x1
[3] flags [SendRecvFlags]
value: 0x2 ( MSG_PEEK )
32位的核心代码逻辑已给出了。朋友们可以去自己发挥了。关于64位的是同样的道理,但是64位传参主要都是通过寄存器来的了,所以很多不一样的处理,需要的就自己去研究吧。
<完> 2021年5月26日 qq: base64(MTcxMjgzNjQ0)
以上是关于拦截http协议封包hook哪些的主要内容,如果未能解决你的问题,请参考以下文章
IP封包协议头/TCP协议头/TCP3次握手/TCP4次挥手/UDP协议头/ICMP协议头/HTTP协议(请求报文和响应报文)/IP地址/子网掩码(划分子网)/路由概念/MAC封包格式