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