1.原理html
简单来讲,LSP就是一个dll程序. 应用程序经过winsock2进行网络通讯时,会调用ws2_32.dll的导出函数,如connect,accept等.shell
然后端经过LSP实现这些函数的底层. 简单来讲就是调用winsock2提供的函数时会调用对应的LSP提供的SPI(服务提供者接口)函数.windows
例如,mswsock.dll 提供了全部tcp协议api对应的spi函数的实现. 可是若是有多个符合条件的SPI,系统将会调用在winsock目录最前面后端
的那个. 若是咱们注册一个对应的SPI并调到winsock目录最前面,这样就能够替换掉系统默认的了.api
一些ring3层的防火墙就是经过这个原理实现的.数组
详细介绍:https://en.wikipedia.org/wiki/Layered_Service_Provider网络
2.实现socket
涉及到的头文件和库tcp
#include<WS2spi.h>ide
#include <RPC.H>
#include <Rpcdce.h>
#include<Sporder.h>
#pragma comment(lib,"Sporder.lib")
#pragma comment(lib, "Rpcrt4.lib") // 实现了UuidCreate函数
(1)枚举winsock目录协议:
int WSAEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, ILPDWORD lpdwBufferLength);
或者:
int WSCEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFOW lpProtocolBuffer, LPDWORD lpdwBufferLength, LPINT lpErrno );
typedef struct _WSAPROTOCOL_INFOW { DWORD dwServiceFlags1; DWORD dwServiceFlags2;//0 DWORD dwServiceFlags3;//0 DWORD dwServiceFlags4;//0 DWORD dwProviderFlags; GUID ProviderId; //guid DWORD dwCatalogEntryId; //winsock目录id WSAPROTOCOLCHAIN ProtocolChain; //协议属性 int iVersion; int iAddressFamily; int iMaxSockAddr; int iMinSockAddr; int iSocketType; int iProtocol; int iProtocolMaxOffset; int iNetworkByteOrder; int iSecurityScheme; DWORD dwMessageSize; DWORD dwProviderReserved; WCHAR szProtocol[WSAPROTOCOL_LEN+1]; //协议名字,可任意填写 } WSAPROTOCOL_INFOW, FAR * LPWSAPROTOCOL_INFOW;
安装协议提供者函数
int WSCInstallProvider( const LPGUID lpProviderId, const LPWSTR lpszProviderDllPath, const LPWSAPROTOCOL_INFOW lpProtocolInfoList, DWORD dwNumberOfEntries, LPINT lpErrno );
排列提供者顺序函数
int WSCWriteProviderOrder( LPDWORD lpwdCatalogEntryId, DWORD dwNumberOfEntries);
简单测试代码:
// spi.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<Windows.h> #include<locale.h> #include<stdio.h> #include<malloc.h> #pragma comment(lib,"ws2_32.lib") GUID layerGuid; #define layerName L"freesec" DWORD findGuid() { //枚举winsock目录中的协议 LPWSAPROTOCOL_INFOW info;//指向winsock目录中协议 DWORD size = 0; //大小 DWORD num; //数量 WSCEnumProtocols(0, 0, &size, 0); info = (LPWSAPROTOCOL_INFOW)malloc(size); num = WSCEnumProtocols(0, info, &size, 0); if (num == SOCKET_ERROR) { free(info); return 0; } int i; for ( i= 0; i < num; i++) { if (lstrcmpW(info[i].szProtocol,layerName)==0) { memcpy(&layerGuid, &info[i].ProviderId, sizeof(GUID)); break; } } free(info); if (i==num)//没找到 { return 0; } return 1; } DWORD lspInject() { //枚举winsock目录中的协议 LPWSAPROTOCOL_INFOW info;//指向winsock目录中协议 DWORD size = 0; //大小 DWORD num; //数量 WSCEnumProtocols(0, 0, &size, 0); info = (LPWSAPROTOCOL_INFOW)malloc(size); num = WSCEnumProtocols(0, info, &size, 0); DWORD trueId; //存储被安装的提供者的目录id if (num == SOCKET_ERROR) { free(info); return 0; } WCHAR supplier[] = layerName; WCHAR dllpath[] = L"E:\\0day\\shellcode\\Debug\\freesec.dll";//指定你的dll文件 DWORD myId; int proto = IPPROTO_TCP; //目标协议 WSAPROTOCOL_INFOW save = { 0 }; //用于存储指定协议的正常的提供者,最后用来做为分层协议和协议链的模板for (int i = 0; i < num; i++) {//找符合条件的提供者,但不能是分层协议 if (info[i].iAddressFamily == AF_INET&&info[i].iProtocol == proto&&info[i].ProtocolChain.ChainLen!=0) { memcpy(&save, &info[i], sizeof(WSAPROTOCOL_INFOW)); //将原来的基础协议信息保存 save.dwServiceFlags1 &= ~XP1_IFS_HANDLES; //去掉XP1_IFS_HANDLES标志 trueId = info[i].dwCatalogEntryId; break; } } //安装分层协议 WSAPROTOCOL_INFOW Lpi = { 0 }; //新的分层协议 memcpy(&Lpi, &save, sizeof(WSAPROTOCOL_INFOW)); //以这个保存的系统已有协议做为模板 lstrcpyW(Lpi.szProtocol, supplier); //协议名,其实就是一个代号而已,能够随意起名 Lpi.ProtocolChain.ChainLen = LAYERED_PROTOCOL; //设置为分层协议 Lpi.dwProviderFlags |= PFL_HIDDEN; //? GUID pguid; //分层协议的guid UuidCreate(&pguid); memcpy(&layerGuid,&pguid,sizeof(GUID)); if (WSCInstallProvider(&pguid, dllpath, &Lpi, 1, 0) == SOCKET_ERROR) //安装该分层协议 { free(info); return 0; } //从新枚举协议以获取分层协议的目录id free(info); //由于添加了一个分层协议,因此须要从新分配内存 DWORD layerId; //保存分层协议目录id WSCEnumProtocols(0, 0, &size, 0); info = (LPWSAPROTOCOL_INFOW)malloc(size); num = WSCEnumProtocols(0, info, &size, 0); if (num == SOCKET_ERROR) { free(info); return 0; } for (int i = 0; i < num; i++) //遍历协议,直到找到刚才新增的分层协议 { if (memcmp(&info[i].ProviderId, &pguid, sizeof(GUID)) == 0) { layerId = info[i].dwCatalogEntryId; //获取分层协议目录id } } //安装协议链 WCHAR chainName[WSAPROTOCOL_LEN + 1]; //其实就是一个名字代号,和分层协议的名字同样 wsprintf(chainName, L"%ls over %ls", supplier, save.szProtocol); lstrcpyW(save.szProtocol, chainName); //更名字1 if (save.ProtocolChain.ChainLen == 1) //若是目标协议的正常提供者是基础协议则将其目录id放在协议链的第2个位置 { save.ProtocolChain.ChainEntries[1] = trueId; //将id写入到该协议链的ChainEntries数组中,这个数组只有当它是协议链时才有意义 } else //不然就是协议链提供者 { for (int i = save.ProtocolChain.ChainLen; i > 0; i--)//若是是协议链则将该协议链中其余协议日后移, //以便将本身的分层协议插入到链首.可是这个数组最大存7个,因此若是原来就占满了,理论上会挤掉最后一个 { save.ProtocolChain.ChainEntries[i] = save.ProtocolChain.ChainEntries[i - 1]; } } save.ProtocolChain.ChainEntries[0] = layerId; save.ProtocolChain.ChainLen++; //获取guid,安装协议链 GUID providerChainGuid; UuidCreate(&providerChainGuid); if (WSCInstallProvider(&providerChainGuid, dllpath, &save, 1, 0) == SOCKET_ERROR) { free(info); return 0; } //从新枚举协议 free(info); WSCEnumProtocols(0, 0, &size, 0); info = (LPWSAPROTOCOL_INFOW)malloc(size); num = WSCEnumProtocols(0, info, &size, 0); if (num == SOCKET_ERROR) { free(info); return 0; } //遍历获取咱们的协议链的目录id DWORD* chainId = (DWORD*)malloc(num * sizeof(DWORD)); //这个是协议链的目录id数组,把咱们的协议链id //放在最前面,系统原来的按顺序放后面 DWORD cindex = 0; for (int i = 0; i < num; i++) { if ((info[i].ProtocolChain.ChainLen > 1) && (info[i].ProtocolChain.ChainEntries[0] == layerId)) { chainId[cindex] = info[i].dwCatalogEntryId; cindex++; } } for (int i = 0; i < num; i++) { if ((info[i].ProtocolChain.ChainLen <= 1) || (info[i].ProtocolChain.ChainEntries[0] != layerId)) { chainId[cindex] = info[i].dwCatalogEntryId; cindex++; } } if (WSCWriteProviderOrder(chainId, cindex) != 0) { free(info); free(chainId); return 0; } free(info); free(chainId); return 1; } DWORD uninstall() { if(findGuid()==0) { return 0; } //枚举winsock目录中的协议 LPWSAPROTOCOL_INFOW info;//指向winsock目录中协议 DWORD size = 0; //大小 DWORD num; //数量 DWORD Id; DWORD result; int cc; //做为错误码,下面2个函数的错误码地址必须提供,不然会调用失败 WSCEnumProtocols(0, 0, &size, 0); info = (LPWSAPROTOCOL_INFOW)malloc(size); num = WSCEnumProtocols(0, info, &size, 0); if (num == SOCKET_ERROR) { free(info); return 0; } int i = 0; for (i=0; i < num; i++) { if (memcmp(&layerGuid,&info[i].ProviderId,sizeof(GUID))==0) { Id = info[i].dwCatalogEntryId; } } if (i<=num) { for (i = 0; i < num; i++) { if ((info[i].ProtocolChain.ChainLen>1)&&(info[i].ProtocolChain.ChainEntries[0]==Id)) { if((result=WSCDeinstallProvider(&info[i].ProviderId, &cc))==SOCKET_ERROR) { free(info); return 0; } break; } } free(info); if((result=WSCDeinstallProvider(&layerGuid, &cc))==SOCKET_ERROR) {return 0; } } else {
free(info); return 0; }return 1; } int main(int argc, char** argv) { setlocale(LC_ALL, "chs"); int result; if (argc!=2) { printf("usage:%s install or uninstall\n", argv[0]); return 0; } if (strcmp(argv[1],"install")==0) { if (lspInject()) { printf("install success\n"); } else { printf("install error code is %d\n", GetLastError()); } } else if(strcmp(argv[1], "uninstall") == 0) { if (uninstall()) { printf("uninstall success\n"); } else { printf("uninstall error code is %d\n", GetLastError()); } } return 1; }
dll文件的测试代码:
// freesec.dll.cpp : 定义 DLL 应用程序的入口点。 // #include "stdafx.h" WCHAR exepath[MAX_PATH] = { 0 }; WSPPROC_TABLE trueTable = { 0 }; int GetProvider(LPWSAPROTOCOL_INFOW &pProtoInfo) { // 首次调用,pProtoInfo传入NULL,取得须要的缓冲区长度 DWORD dwSize = 0; int nError = 0; if (WSCEnumProtocols(NULL, NULL, &dwSize, &nError) == SOCKET_ERROR) { if (nError != WSAENOBUFS) { return 0; } } // 申请足够缓冲区内存。 pProtoInfo = (LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR, dwSize); if (pProtoInfo == NULL) { return 0; } //再次调用WSCEnumProtocols函数 return WSCEnumProtocols(NULL, pProtoInfo, &dwSize, &nError); } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: GetModuleFileNameW(0, exepath, MAX_PATH * sizeof(wchar_t)); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } int WSPConnect(SOCKET s, const struct sockaddr FAR* name, int namelen, LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS, LPINT lpErrno) { SOCKADDR_IN addr = *(SOCKADDR_IN*)name; if (addr.sin_port==htons(80)) { MessageBoxW(0, L"有程序访问外网80端口", L"拒绝访问", 0); return SOCKET_ERROR; } return trueTable.lpWSPConnect(s, name, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS, lpErrno); } int WSPAPI WSPStartup( WORD wVersionRequested, LPWSPDATA lpWSPData, LPWSAPROTOCOL_INFOW lpProtocolInfo, WSPUPCALLTABLE UpcallTable, LPWSPPROC_TABLE lpProcTable ) /* 当应用程序经过SOCKET建立socket时会调用系统根据Winsock目录和程序的须要来将对应的传输服务提供者,即 一个dll加载到目标进程中. 而后调用该dll提供的WSPStartup函数来初始化.初始化的 目的就是为了经过调用这个函数来获取该此次操做socket的API函数对应的SPI 这就是windows上写socket时以前必须经过WSAStartup来进行socket初始化的缘由 该函数的lpProcTable 参数是个结构体,保存了全部的SPI函数.也就是能够从这个参数来获取SPI 因此只需导出这个函数,而后将其余的SPI填写到lpProcTable中,最后返回给程序 以上都是正常状况下的调用过程. 若是咱们让系统加载咱们给它提供的dll就能够导出该函数,并 hook掉lpProcTable中的成员进行监控. 可是咱们hook该函数后容许的话应该最后要调用正常的SPI, 这时参数lpProtocolInfo就能派上用场. 经过该参数能够获取原来的协议的目录id,而后遍历winsock 目录找到对应的协议的传输服务提供者即一个dll路径,经过加载该dll并调用其中的WSPStartup便可获取 真正的SPI,而后调用它.最终能够实现监控,修改,拦截等功能 */ { //咱们编写的DLL用于协议链中,因此若是是基础协议或分层协议使用则直接返回错误 if (lpProtocolInfo->ProtocolChain.ChainLen <= 1) { return WSAEPROVIDERFAILEDINIT; } WCHAR exename[100] = { 0 }; wsprintf(exename, L"应用程序: %ls 正在联网,是否容许?", exepath); if (MessageBoxW(0,exename,L"舒适提示",MB_YESNO|MB_ICONWARNING)==IDNO) { MessageBoxW(0, L"已拦截", L"提示", 0); return WSAEPROVIDERFAILEDINIT; } // 枚举协议,找到下层协议的WSAPROTOCOL_INFOW结构 WSAPROTOCOL_INFOW trueProtocolInfo; //保存真正的协议结构 LPWSAPROTOCOL_INFOW pProtoInfo = NULL; int allproto = GetProvider(pProtoInfo); DWORD trueId = lpProtocolInfo->ProtocolChain.ChainEntries[1];//获取真正的协议目录id int i; //遍历查找真正的协议结构 for (i = 0; i < allproto; i++) { if (pProtoInfo[i].dwCatalogEntryId==trueId) { memcpy(&trueProtocolInfo, &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW)); break; } } //没找到就返回失败 if (i>=allproto) { return WSAEPROVIDERFAILEDINIT; } int nError; wchar_t szBaseProviderDll[MAX_PATH];//保存真正dll路径 int nLen = MAX_PATH; // 取得下层提供程序DLL路径 if (WSCGetProviderPath(&trueProtocolInfo.ProviderId, szBaseProviderDll, &nLen, &nError) == SOCKET_ERROR) { return WSAEPROVIDERFAILEDINIT; } //上面的函数执行后路径中会存在环境变量,经过下面展开环境变量 if (!ExpandEnvironmentStringsW(szBaseProviderDll, szBaseProviderDll, MAX_PATH)) { return WSAEPROVIDERFAILEDINIT; } // 加载真正dll HMODULE hModule = LoadLibraryW(szBaseProviderDll); if (hModule == NULL) { return WSAEPROVIDERFAILEDINIT; } // 导入真正dll的WSPStartup函数 LPWSPSTARTUP pfnWSPStartup = NULL; pfnWSPStartup = (LPWSPSTARTUP)GetProcAddress(hModule, "WSPStartup"); if (pfnWSPStartup == NULL) { return WSAEPROVIDERFAILEDINIT; } // 调用下层提供程序的WSPStartup函数以填充SPI地址表 LPWSAPROTOCOL_INFOW pInfo = lpProtocolInfo; // if (trueProtocolInfo.ProtocolChain.ChainLen == BASE_PROTOCOL) { pInfo = &trueProtocolInfo; } else { for (int j = 0; j<lpProtocolInfo->ProtocolChain.ChainLen; j++) { lpProtocolInfo->ProtocolChain.ChainEntries[j] = lpProtocolInfo->ProtocolChain.ChainEntries[j + 1]; } lpProtocolInfo->ProtocolChain.ChainLen--; } //调用真正的WSPStartup, 注意参数,协议结构参数必须是原来咱们想劫持的那个协议结构 int nRet = pfnWSPStartup(wVersionRequested, lpWSPData, pInfo, UpcallTable, lpProcTable); if (nRet != ERROR_SUCCESS) { return nRet; } memcpy(&trueTable, lpProcTable, sizeof(WSPPROC_TABLE)); //保存到trueTable中以便调用 //进行api替换 lpProcTable->lpWSPConnect = (LPWSPCONNECT)WSPConnect; }