注入技術--LSP劫持注入
1.原理
簡單來說,LSP就是一個dll程式. 應用程式通過winsock2進行網路通訊時,會呼叫ws2_32.dll的匯出函式,如connect,accept等.
而後端通過LSP實現這些函式的底層. 簡單來說就是呼叫winsock2提供的函式時會呼叫對應的LSP提供的SPI(服務提供者介面)函式.
例如,mswsock.dll 提供了所有tcp協議api對應的spi函式的實現. 但是如果有多個符合條件的SPI,系統將會呼叫在winsock目錄最前面
的那個. 如果我們註冊一個對應的SPI並調到winsock目錄最前面,這樣就可以替換掉系統預設的了.
一些ring3層的防火牆就是通過這個原理實現的.
詳細介紹:https://en.wikipedia.org/wiki/Layered_Service_Provider
2.實現
涉及到的標頭檔案和庫
#include<WS2spi.h>
#include <RPC.H>
#include <Rpcdce.h>
#include<Sporder.h>
#pragma comment(lib,"Sporder.lib")
#pragma comment(lib, "Rpcrt4.lib") // 實現了UuidCreate函式
(1)列舉winsock目錄協議:
int WSAEnumProtocols(
Parameters
- lpiProtocols
-
[in] Null-terminated array of
iProtocol values. This parameter is optional;
if lpiProtocols is NULL, information on all available protocols is returned. Otherwise, information is retrieved only for those protocols listed in the array.
- lpProtocolBuffer
- [out] Buffer that is filled with WSAPROTOCOL_INFO structures.
- lpdwBufferLength
- [in, out] On input, the count of bytes in the lpProtocolBuffer buffer passed to this function. On output, the minimum buffer size that can be passed to this function to retrieve all the requested information. This routine has no ability to enumerate over multiple calls; the passed-in buffer must be large enough to hold all entries for the routine to succeed. This reduces the complexity of the API and should not pose a problem because the number of protocols loaded on a machine is typically small.
或者:
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; }
1.原理
簡單來說,LSP就是一個dll程式. 應用程式通過winsock2進行網路通訊時,會呼叫ws2_32.dll的匯出函式,如connect,accept等.
而後端通過LSP實現這些函式的底層. 簡單來說就是呼叫winsock2提供的函式時會呼叫對應的LSP提供的SPI(服務提供者介面)函式.
例如,mswsock.dll 提供了所有tcp協議api對應的spi函式的實現. 但是如果有多個符合條件的SPI,系統將會呼叫在winsock目錄最前面
的那個. 如果我們註冊一個對應的SPI並調到winsock目錄最前面,這樣就可以替換掉系統預設的了.
一些ring3層的防火牆就是通過這個原理實現的.
詳細介紹:https://en.wikipedia.org/wiki/Layered_Service_Provider
2.實現
涉及到的標頭檔案和庫
#include<WS2spi.h>
#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);
Parameters
- lpiProtocols
- [in] Null-terminated array of iProtocol values. This parameter is optional; if lpiProtocols is NULL, information on all available protocols is returned. Otherwise, information is retrieved only for those protocols listed in the array.
- lpProtocolBuffer
- [out] Buffer that is filled with WSAPROTOCOL_INFO structures.
- lpdwBufferLength
- [in, out] On input, the count of bytes in the lpProtocolBuffer buffer passed to this function. On output, the minimum buffer size that can be passed to this function to retrieve all the requested information. This routine has no ability to enumerate over multiple calls; the passed-in buffer must be large enough to hold all entries for the routine to succeed. This reduces the complexity of the API and should not pose a problem because the number of protocols loaded on a machine is typically small.
或者:
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; }