1. 程式人生 > >網路程式設計之編寫LSP進行Winsock API監控攔截或LSP注入

網路程式設計之編寫LSP進行Winsock API監控攔截或LSP注入

【1】工具介紹:

用到的工具:VS2015
語言:C/C++
需要系統提供的動態連結庫:1、 sporder.dll    //很多系統不自帶著個dll,導致編譯時缺少dll無法編譯. (釋出時必須將此dll放到程式目錄)
本人只提供:   WIN7 64位的sporder.dll :http://download.csdn.net/download/aaron133/10153240
               其他系統自行網上下載.

安裝、移除LSP、編寫分層提供者DLL、測試程式的原始碼:(申明:本人只在Win7 32/64位 和 Win10 64測試過)
http://download.csdn.net/download/aaron133/10152873 
(除了文章中的原始碼之外,包含了測試程式的原始碼)

【2】編寫LSP分層服務提供者需知的概念:

1、先看我寫的SPI介面的概念:http://blog.csdn.net/aaron133/article/details/78005779

2、本章就是介紹安裝SPI的分層協議提供者(LSP),即第三方系統網路元件。

3、當Windows程式想要使用網路時,必須載入SPI的基礎提供者(TCP、UDP、原始)才能進行網路通訊。

4、安裝LSP分層服務提供者就是寫一個DLL,讓網路程式先載入進去呼叫,然後再我們的DLL內,再呼叫基礎服務提供者,進行網路通訊,所以在這過程中,我們可以對系統上所有使用特定協議的網路程式,在使用者模式下進行Winsock API呼叫監控,HOOK攔截,甚至利用LSP注入DLL。

5、LSP一般是對網路進行更高階的通訊服務管理、過濾,黑客常用它來進行瀏覽器劫持、監控使用者資訊等等.

6、360所謂的修復LSP或CMD的netsh winsock reset命令,就是清除第三方的LSP提供者,並清除它的DLL,留下系統的基礎服務提供者.

【3】不能攔截的Winsock API函式:

1、htonl,htons僅在ws2_32.dll中實現.
2、inet_addr,inet_ntoa,gethostname,WSACreateEvent,WSACloseEvent等等都不在SPI中.

3、如果程式直接使用傳輸驅動介面(TDI)進行TCP/IP傳送資料包,那麼攔截不了.

4、所以在使用者模式下,使用LSP過濾網路封包是一個很好的選擇.


【4】LSP分層服務提供者的編寫:(DLL)

一、簡述:

1、編寫LSP提供者就是寫一個DLL.

2、WSPStartup是LSP必須匯出的函式.

3、載入下層提供者的DLL,並呼叫它的初始化WSPStartup是LSP必須做的事情.

4、攔截API函式就是將下層提供者(基礎協議提供者)的函式地址記錄下來,將我們自定義的函式地址替換上去,執行到如send時就會先呼叫我們的自定義函式,再由我們的自定義函式,考慮要不要呼叫真正的send.

二、開始編寫LSP分層服務提供者的DLL:

【開始編寫】

1、步驟 :建立Win32程式     -->     DLL開發

2、新建一個.def檔案:

EXPORTS
WSPStartup      @2

【程式碼步驟分析】

1、網路程式載入LSP分層服務提供者的DLL,並呼叫了DLL裡的WSPStartup初始化函式.

2、在LSP的WSPStartup函式中,載入它的下層服務提供者的DLL,並呼叫它的WSPStartup初始化函式.

3、對下層提供者的函式表地址進行修改,修改感興趣的網路函式指向我們的自定義函式,進行攔截、監視Winsock API.

4、下面的例子中攔截了connect函式、sendto函式.

標頭檔案:       //在講解SPI篇的時候,用到的函式,用於遍歷系統所有協議,包括分層協議

#include <WinSock2.h>
#include <WS2spi.h>
#include <tchar.h>
LPWSAPROTOCOL_INFOW GetProvider(LPINT lpnTotalProtocols)
{//遍歷所有協議
int nError = 0;
DWORD dwSize = 0;
LPWSAPROTOCOL_INFOW pProtoInfo = NULL;
if (WSCEnumProtocols(NULL, pProtoInfo, &dwSize, &nError) == SOCKET_ERROR)
{
if (nError != WSAENOBUFS)
return NULL;
}
pProtoInfo = (LPWSAPROTOCOL_INFOW)new WSAPROTOCOL_INFOW[dwSize / sizeof(WSAPROTOCOL_INFOW)];
if (!pProtoInfo)
return NULL;
ZeroMemory(pProtoInfo, dwSize);
*lpnTotalProtocols = WSAEnumProtocols(NULL, pProtoInfo, &dwSize);
return pProtoInfo;
}
void FreeProvider(LPWSAPROTOCOL_INFOW pProtoInfo)
{
delete[] pProtoInfo;
}

原始檔:

WSPPROC_TABLE g_NextProcTable;  //下層提供者的函式表        全域性
//LSP的初始化函式(唯一的匯出函式)
int WSPAPI WSPStartup(
WORD wVersionRequested,                          //使用者程式載入套接字型檔的版本號(in)
LPWSPDATA lpWSPData,                               //用於取得Winsock服務的詳細資訊
LPWSAPROTOCOL_INFO lpProtocolInfo,   //指定想得到的協議的特徵
WSPUPCALLTABLE UpcallTable,                 //Ws2_32.dll向上呼叫轉發的函式表
LPWSPPROC_TABLE lpProTable                 //下層提供者的函式表(一般為基礎協議,共30個服務函式)
)
{   //如果協議位分層協議或基礎協議,那麼返回錯誤
if (lpProtocolInfo->ProtocolChain.ChainLen <= 1)
{   //無法載入或初始化請求的服務提供程式
return WSAEPROVIDERFAILEDINIT;
}

//找到下層協議的WSAPROTOCOL_INFOW結構體
WSAPROTOCOL_INFOW NextProtocolInfo;
int nTotalProtols;
LPWSAPROTOCOL_INFOW pProtoInfo = GetProvider(&nTotalProtols);
//下層提供者的入口ID
DWORD dwBaseEntryId = lpProtocolInfo->ProtocolChain.ChainEntries[1];
//遍歷所有協議
int i = 0;
for (; i < nTotalProtols; i++)
{//找到下層提供者協議
if (pProtoInfo[i].dwCatalogEntryId == dwBaseEntryId)
{
memcpy(&NextProtocolInfo, &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
break;
}
}
//如果沒找到
if (i >= nTotalProtols)
return WSAEPROVIDERFAILEDINIT;
//載入下層協議的Dll
int nError = 0;
TCHAR szBaseProviderDll[MAX_PATH];
int nLen = MAX_PATH;
//取得下層提供者的DLL路徑(可能包含壞境變數)
if(WSCGetProviderPath(&NextProtocolInfo.ProviderId, szBaseProviderDll, &nLen, &nError) == SOCKET_ERROR)
return WSAEPROVIDERFAILEDINIT;
//壞境變數轉換字串
if(!ExpandEnvironmentStrings(szBaseProviderDll, szBaseProviderDll, MAX_PATH))
return WSAEPROVIDERFAILEDINIT;
//載入dll
HMODULE hModdule = LoadLibrary(szBaseProviderDll);
if(hModdule == NULL)
return WSAEPROVIDERFAILEDINIT;
//取出下層提供者的WSPStartup函式
LPWSPSTARTUP pfnWSPStartup = (LPWSPSTARTUP)GetProcAddress(hModdule, "WSPStartup");
if(NULL == pfnWSPStartup )
return WSAEPROVIDERFAILEDINIT;
LPWSAPROTOCOL_INFOW pInfo = lpProtocolInfo;
if (NextProtocolInfo.ProtocolChain.ChainLen == BASE_PROTOCOL)//如果下層提供者是基礎協議
pInfo = &NextProtocolInfo;                               //賦給pInfo指標
//呼叫下層提供者的初始化函式
int nRet = pfnWSPStartup(wVersionRequested, lpWSPData, lpProtocolInfo, UpcallTable, lpProTable);
//初始化失敗
if (nRet != ERROR_SUCCESS)
return nRet;

//初始化完成後,複製下層提供者(基礎協議)的整個函式表
g_NextProcTable = *lpProTable;
//將基礎協議的SendTo函式指標,指向我們的WSPSendTo函式,在我們的函式內,再確定要不要呼叫回基礎協議的Sendto函式
lpProTable->lpWSPSendTo = WSPSendTo; 
lpProTable->lpWSPConnect = WSPConnect;
FreeProvider(pProtoInfo, nTotalProtols);
return nRet;
}

//下面對sendto、connect函式的8888埠進行攔截:

int WSPAPI WSPConnect(       //自定義的WSPConnect函式
SOCKET s,
const struct sockaddr FAR * name,
int namelen,
LPWSABUF lpCallerData,
LPWSABUF lpCalleeData,
LPQOS lpSQOS,
LPQOS lpGQOS,
LPINT lpErrno
)
{
sockaddr_in* info = (sockaddr_in*)name;
USHORT port = ntohs(info->sin_port);
if (port == 8888)   //如果是8888埠,那麼攔截
{
int nError = 0;
               //因為整個dll已經載入程序序裡,這裡對我的控制檯程式進行測試
SetConsoleTitle(_T("sorry,we shutdown you tcp protocol port<8888>!")); 
g_NextProcTable.lpWSPShutdown(s, SD_BOTH, &nError);
//設定錯誤資訊
*lpErrno = WSAECONNABORTED;   
return SOCKET_ERROR; 
}
       //如果不是,呼叫下層提供者的函式表中的WSPConnect函式
return g_NextProcTable.lpWSPConnect(s, name, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS, lpErrno);
}
int WSPAPI WSPSendTo         //自定義的WSPSendTo函式
(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
const struct sockaddr FAR * lpTo,
int iTolen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
LPWSATHREADID lpThreadId,
LPINT lpErrno
)
{
sockaddr_in* info = (sockaddr_in*)lpTo;
USHORT port = ntohs(info->sin_port);
if (port == 8888)    //如果是8888埠,那麼攔截
{
int nError = 0;
SetConsoleTitle(_T("sorry,we shutdown you udp protocol port<8888>!"));
g_NextProcTable.lpWSPShutdown(s, SD_BOTH, &nError);
//設定錯誤資訊
*lpErrno = WSAECONNABORTED;
return SOCKET_ERROR;
}
        //如果不是,呼叫下層提供者的函式表中的WSPSendTo函式
return g_NextProcTable.lpWSPSendTo(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags,
lpTo, iTolen, lpOverlapped, lpCompletionRoutine, lpThreadId, lpErrno);
}

【5】LSP的DLL編寫完成後,編寫安裝與解除安裝LSP的程式:

一、簡述:1、安裝、解除安裝LSP必須是管理員使用者組的許可權才能使用.2、下面的例子中,我一共安裝了1個分層協議(DLL),3個協議鏈用於搶在TCP、UDP、原始套接字提供者前執行)3、http://blog.csdn.net/aaron133/article/details/78005779篇中的     “網路程式是如何呼叫Winsock2 服務提供者進行網路通訊    呼叫網路通訊的機制,所以要將新安裝的協議鏈,排在遍歷函式的最前面,網路程式先找到適合的協議,就會用那個協議,如果排在後面,就可能載入別的相同型別的協議的提供者,而不使用我們的分層提供者.二、開始編寫安裝LSP程式:【編寫步驟分析】一、遍歷所有協議,將UDP、TCP、原始的Winsock目錄入口(結構體)各複製一份出來.二、隨便找一個下層協議(基礎服務提供者)Winsock目錄入口結構體作為模板,用於安裝LSP時作為它的Winsock目錄入口(結構體).1、必須修改協議名稱(szProtocol成員).2、dwServiceFlags1成員必須將XP1_IFS_HANDLES標誌去掉.3、提供者的型別:ProtocolChain成員ChainLen變數 = LAYERED_PROTOCOL(0) //0暗示為分層協議提供者    不懂這個概念的先看我上一篇講解SPI文章.//地址:http://blog.csdn.net/aaron133/article/details/780057794、表示方式:dwProviderFlags成員 = PFL_HIDDEN; //由提供者自己設定的Winsock目錄入口.5、安裝分層協議提供者.三、安裝3個協議鏈(協議鏈,排在第一位的就是我們新安裝的分層提供者)1、為什麼有3個協議鏈,因為它們各對應一個基礎協議提供者,分別是TCP、UDP、原始,當網路程式使用TCP、UDP、原始,會先載入我們的分層服務提供者LSP的DLL。四、重新排序Winsock目錄1、因為新安裝的提供者,都會排在最後,這樣如果前面有網路程式適合的提供者時,就會直接載入它的DLL,而不載入我們LSP的DLL.標頭檔案:
#include <WS2spi.h>
#include <winsock2.h>
#include <process.h>
#include <ws2tcpip.h>
#include <mstcpip.h>
#include <Windows.h>
#include <iostream>
#include <tchar.h>
using namespace std;
#pragma warning(disable:4996)
#pragma comment(lib,"Sporder.lib")
#pragma comment(lib, "Ws2_32.lib")
#include <sporder.h>
//安裝LSP
class installLSP
{
public:
installLSP()
{
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
CoCreateGuid(&this->Layered_guid);
CoCreateGuid(&this->AgreementChain_guid);
}
~installLSP()
{
WSACleanup();
}
public:
//安裝LSP,並安裝3個協議鏈
BOOL InstallProvider(WCHAR* wszDllPath)  //引數:LSP的DLL的地址
{
WCHAR wszLSPName[] = _T("AaronLSP");
LPWSAPROTOCOL_INFOW pProtoInfo = NULL;
int nProtocols = 0; //分層協議     取出來的模板
WSAPROTOCOL_INFOW OriginalProtocolInfo[3]; //陣列成員為TCP、UDP、原始的目錄入口資訊
DWORD dwOrigCatalogId[3]; //記錄入口ID號
int nArrayCount = 0;      //陣列個數索引
DWORD dwLayeredCatalogId; //分層協議的入口ID號
int nError;
pProtoInfo = GetProvider(&nProtocols);
if (nProtocols < 1 || pProtoInfo == NULL)
return FALSE;
BOOL bFindUdp = FALSE;
BOOL bFindTcp = FALSE;
BOOL bFindRaw = FALSE;
for (int i = 0; i < nProtocols; i++)
{   //查詢地址族為AF_INET的協議
if (pProtoInfo[i].iAddressFamily == AF_INET)
{
if (!bFindUdp && pProtoInfo[i].iProtocol == IPPROTO_UDP)
{
memcpy(&OriginalProtocolInfo[nArrayCount], &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
//去除XP1_IFS_HANDLES標誌,防止提供者返回的控制代碼是真正的作業系統控制代碼
OriginalProtocolInfo[nArrayCount].dwServiceFlags1 &= (~XP1_IFS_HANDLES);
//記錄目錄入口ID
dwOrigCatalogId[nArrayCount++] = pProtoInfo[i].dwCatalogEntryId; 
bFindUdp = TRUE;
}
if (!bFindTcp && pProtoInfo[i].iProtocol == IPPROTO_TCP)
{
memcpy(&OriginalProtocolInfo[nArrayCount], &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
//去除XP1_IFS_HANDLES標誌,防止提供者返回的控制代碼是真正的作業系統控制代碼
OriginalProtocolInfo[nArrayCount].dwServiceFlags1 &= (~XP1_IFS_HANDLES);
//記錄目錄入口ID
dwOrigCatalogId[nArrayCount++] = pProtoInfo[i].dwCatalogEntryId;
bFindTcp = TRUE;
}
if (!bFindRaw && pProtoInfo[i].iProtocol == IPPROTO_IP)
{
memcpy(&OriginalProtocolInfo[nArrayCount], &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
//去除XP1_IFS_HANDLES標誌,防止提供者返回的控制代碼是真正的作業系統控制代碼
OriginalProtocolInfo[nArrayCount].dwServiceFlags1 &= (~XP1_IFS_HANDLES);
//記錄目錄入口ID
dwOrigCatalogId[nArrayCount++] = pProtoInfo[i].dwCatalogEntryId;
bFindRaw = TRUE;
}
}
}
if (nArrayCount == 0)
{
FreeProvider(pProtoInfo);
return FALSE;
}
//安裝LSP分層協議
WSAPROTOCOL_INFOW LayeredProtocolInfo;


memcpy(&LayeredProtocolInfo, &OriginalProtocolInfo[0], sizeof(WSAPROTOCOL_INFOW));
//修改協議名稱的字串
wcscpy(LayeredProtocolInfo.szProtocol, wszLSPName);
//表示分層協議
LayeredProtocolInfo.ProtocolChain.ChainLen = LAYERED_PROTOCOL;//0
//表示方式為由提供者自己設定
LayeredProtocolInfo.dwProviderFlags = PFL_HIDDEN;
//安裝分層協議
if (SOCKET_ERROR == WSCInstallProvider(&Layered_guid, wszDllPath, &LayeredProtocolInfo, 1, &nError))
{
FreeProvider(pProtoInfo);
return FALSE;
}
FreeProvider(pProtoInfo);
//重新遍歷協議,獲取分層協議的目錄ID號
pProtoInfo = GetProvider(&nProtocols);
if (nProtocols < 1 || pProtoInfo == NULL)
return FALSE;
for (int i = 0; i < nProtocols; i++)//一般安裝新入口後,會排在最低部
{
if (memcmp(&pProtoInfo[i].ProviderId, &Layered_guid, sizeof(GUID)) == 0)
{
//取出分層協議的目錄入口ID
dwLayeredCatalogId = pProtoInfo[i].dwCatalogEntryId;
break;
}
}
//安裝協議鏈                 256
WCHAR wszChainName[WSAPROTOCOL_LEN + 1];//新分層協議的名稱  over   取出來的入口模板的名稱
for (int i = 0; i < nArrayCount; i++)
{
swprintf(wszChainName, _T("%s over %s"), wszLSPName, OriginalProtocolInfo[i].szProtocol);
wcscpy(OriginalProtocolInfo[i].szProtocol, wszChainName);  //將這個模板的名稱改成新名稱↑
if (OriginalProtocolInfo[i].ProtocolChain.ChainLen == 1)//這是基礎協議的模板
{   //修改基礎協議模板的協議鏈, 在協議鏈[1]寫入真正UDP[基礎協議]的入口ID
OriginalProtocolInfo[i].ProtocolChain.ChainEntries[1] = dwOrigCatalogId[i];
}
else
{//如果大於1,相當於是個協議鏈,表示:將協議鏈中的入口ID,全部向後退一格,留出[0]
for (int j = OriginalProtocolInfo[i].ProtocolChain.ChainLen; j > 0; j--)
OriginalProtocolInfo[i].ProtocolChain.ChainEntries[j] = OriginalProtocolInfo[i].ProtocolChain.ChainEntries[j - 1];
}
//讓新分層協議排在基礎協議的前面(如果為協議鏈排就排在開頭了)
OriginalProtocolInfo[i].ProtocolChain.ChainLen++;
OriginalProtocolInfo[i].ProtocolChain.ChainEntries[0] = dwLayeredCatalogId;
}
//一次安裝3個協議鏈
if (SOCKET_ERROR == WSCInstallProvider(&AgreementChain_guid, wszDllPath, OriginalProtocolInfo, nArrayCount, &nError))
{
FreeProvider(pProtoInfo);
return FALSE;
}
//第三步:將所有3種協議進行重新排序,以讓系統先呼叫我們的協議(讓協議鏈排第一,協議鏈中[0]是新分層協議,[1]基礎UDP協議)
//重新遍歷所有協議
FreeProvider(pProtoInfo);
pProtoInfo = GetProvider(&nProtocols);
if (nProtocols < 1 || pProtoInfo == NULL)
return FALSE;
DWORD dwIds[20];
int nIndex = 0;
//新增我們的協議鏈
for (int i = 0; i < nProtocols; i++)
{//如果是我們新建立的協議鏈
if (pProtoInfo[i].ProtocolChain.ChainLen > 1 && pProtoInfo[i].ProtocolChain.ChainEntries[0] == dwLayeredCatalogId)
dwIds[nIndex++] = pProtoInfo[i].dwCatalogEntryId;//將3個協議鏈排在前3
}
//新增其他協議
for (int i = 0; i < nProtocols; i++)
{//如果是基礎協議,分層協議(不包括我們的協議鏈,但包括我們的分層協議)
if (pProtoInfo[i].ProtocolChain.ChainLen <= 1 || pProtoInfo[i].ProtocolChain.ChainEntries[0] != dwLayeredCatalogId)
dwIds[nIndex++] = pProtoInfo[i].dwCatalogEntryId;
}
//重新排序Winsock目錄
if (WSCWriteProviderOrder(dwIds, nIndex) != ERROR_SUCCESS)
return FALSE;
FreeProvider(pProtoInfo);
return TRUE;
}
        //解除安裝LSP
void RemoveProvider()
{
LPWSAPROTOCOL_INFOW pProtoInfo = NULL;
int nProtocols = 0;
DWORD dwLayeredCatalogId = 0; //分層協議提供者的入口ID號
 //遍歷出所有協議
pProtoInfo = GetProvider(&nProtocols);
if (nProtocols < 1 || pProtoInfo == NULL)
return;
int nError = 0;
int i = 0;
for (i = 0; i < nProtocols; i++)
{ //查詢分層協議提供者
if (memcmp(&Layered_guid, &pProtoInfo[i].ProviderId, sizeof(GUID)) == 0)
{
dwLayeredCatalogId = pProtoInfo[i].dwCatalogEntryId;
break;
}
}
if (i < nProtocols)
{
for (i = 0; i < nProtocols; i++)
{//查詢協議鏈(這個協議鏈的[0]為分層協議提供者)
if (pProtoInfo[i].ProtocolChain.ChainLen > 1 && pProtoInfo[i].ProtocolChain.ChainEntries[0] == dwLayeredCatalogId)
{//先解除安裝協議鏈
WSCDeinstallProvider(&pProtoInfo[i].ProviderId, &nError);
break;
}
}
WSCDeinstallProvider(&Layered_guid, &nError);
}
}
private:
       //這兩個函式是遍歷所有協議函式,在編寫DLL時,已經把原始碼放出來了,這裡就不放出來了.
       LPWSAPROTOCOL_INFOW GetProvider(LPINT lpnTotalProtocols);
       void FreeProvider(LPWSAPROTOCOL_INFOW pProtoInfo); 
private:
GUID Layered_guid;        //分層協議GUID
GUID AgreementChain_guid; //協議鏈GUID
};
原始檔:
#include "Hello.h"
#define PATH _T("C:\\Users\\Administrator\\Desktop\\例項\\網路實驗\\安裝LSP服務提供程式\\LSPDll\\Debug\\LSPDll.dll")
int main(int argc,char** argv)
{
system("color 4e");
SetConsoleTitle(_T("安裝LSP提供者程式實驗"));
ProtocolTraversestheExperiment2 s;
printf("安裝LSP前的所有協議:\r\n");
s.ShowAllProtocol();
installLSP LSP;
LSP.InstallProvider(PATH);
printf("安裝LSP後的所有協議:\r\n");
s.ShowAllProtocol();
getchar();
LSP.RemoveProvider();
printf("清除LSP完成\r\n");
getchar();
return 0;
}

【測試】1、安裝了一個LSP分層服務提供者(相當於HOOK了TCP、UDP、原始套接字),三個協議鏈
2、當有程式使用8888埠進行TCP連線或8888埠使用UDP傳送資料時,就會攔截禁止:
附加說明:1、Sporder.dll在編寫LSP程式時,32位放在WOWSys64資料夾、64位放在system32資料夾.2、釋出時必須攜帶著Sporder.dll在程式目錄存放.3、編寫64位LSP程式的程式時,不要包含#pragma comment(lib,"Sporder.lib")否則程式出錯!