phoenix防火牆原理
應用程式層原理
Winsock 2 服務提供者介面(SPI)為應用程式提供服務。
SPI的體繫結構為:
(協議層次)
Winsock 2 API -> WS2_32.DLL -> SPI -> Transport ,Layered Protocol, SPI -> BaseProtocol
(Winsock 2 結構)
Winsock 2 Application -> Winsock 2 API -> Winsock 2 DLL -> Winsock 2 Transport Function SPI -> Transport Service Provider
分層服務提供者將自己安裝到Winsock目錄中的基礎提供者上面,截取來自應用程式的 Winsock API 呼叫。
使用者建立通訊端時,通訊端建立函數會在Winsock目錄中尋找合適的協議,然後調用此協議的提供者匯出的函數完成各種功能。
安裝LSP 的過程是:
1、先安裝一個分層協議,然後安裝協議鏈; WSCInstallProvider
2、重新為目錄排序 WSCWriteProviderOrder
移除LSP 使用 WSCDeinstallProvider
具體編寫 LSP 的過程是,匯出 WSPStartup函數,在這個函數裡面修改 WSPPROC_TABLE 指向的 Winsock 2 API 函數的地址,指向自己的函數.
驅動層原理
NDIS 網路驅動程式為網路驅動抽象了網路硬體,為上層驅動抽象了管理硬體的下層驅動。
Transport Driver Interface (TDI)
|
NDIS Interface
|
NetCard
中介層驅動位於微連接埠驅動和傳輸驅動之間,是基於鏈路層和網路層之間的驅動。
中介層驅動的主要用途之一是過濾網路封包,它能夠截獲所有的網路資料包。
PassThru 是 DDK 內建的中介層例子,
在 PassThru中,有 4 處封包是可以截獲的。
MPSend
MPSendPackets
PTReceive
PTReceivePacket
擷取網路資料包的函數是 NdisQueryBufferSafe ,網路資料包在系統內部使用 NDIS_BUFFER 類型的鏈表格儲存體,取出資料包後根據過濾規則檢查,允許的就推進到下一層,不允許的直接丟棄。
-----------------------------------------------
程式碼分析:
phoenixFW 一共有 3 個模組 :
phoenixFW.exe phoenixLSP.dll passthru.sys
phoenixLSP.dll匯出WSPStartup,WSPStartup儲存下層提供者的函數表,修改Winsock API 函數的地址
g_NextProcTable = *lpProcTable;
lpProcTable->lpWSPSocket = WSPSocket;
lpProcTable->lpWSPCloseSocket = WSPCloseSocket;
lpProcTable->lpWSPBind = WSPBind;
lpProcTable->lpWSPAccept = WSPAccept;
lpProcTable->lpWSPConnect = WSPConnect;
lpProcTable->lpWSPSendTo = WSPSendTo;
lpProcTable->lpWSPRecvFrom = WSPRecvFrom;
TCP向外發起串連使用 connect函數,connect使用WSPConnect,
WSPConnect
{
//根據規則檢查是否允許串連到遠程主機
//如果允許通過,直接調用原來的下層函數,否則直接返回SOCKET_ERROR
return g_NextProcTable.lpWSPConnect
}
接受串連使用 accept,accept調用WSPAccept;
UDP 向外發包使用 sendto,sendto調用WSPSendTo
接收UDP封包的函數 recvfrom調用WSPRecvFrom
phoenixLSP.dll使用 #pragma指令設定共用記憶體,用來和主模組 phoenixFW.exe進行通訊
#pragma data_seg(".initdata")
HWND g_hPhoenixWnd = NULL; // 主視窗控制代碼
int g_nWorkMode = PF_PASS_ALL; // 工作模式
#pragma data_seg()
#pragma bss_seg(".uinitdata")
RULE_ITEM g_Rule[MAX_RULE_COUNT]; // 應用程式層規則
ULONG g_RuleCount;
QUERY_SESSION g_QuerySession[MAX_QUERY_SESSION]; // 向主程式發送會話詢問時使用
SESSION g_SessionBuffer[MAX_SESSION_BUFFER]; // 向主程式發送會話資訊時使用
TCHAR g_szPhoenixFW[MAX_PATH]; // 記錄主程式路徑
#pragma bss_seg()
extern TCHAR g_szCurrentApp[MAX_PATH];
每當一個應用程式調用 Winsock 2 API,就會自動載入phoenixLSP.dll,此時記錄下這個進程的路徑
case DLL_PROCESS_ATTACH:
{
// 取得主模組的名稱
::GetModuleFileName(NULL, g_szCurrentApp, MAX_PATH);
}
data_seg 和 bss_seg 段,無論多少進程載入這個DLL ,都只有1份,也就是說只要1個模組修改這些內容,其他模組都會改變。
phoenixLSP.dll 匯出一個函數,用來修改 data_seg 和 bss_seg 段
__declspec(dllexport) int __stdcall PLSPIoControl(struct LSP_IO_CONTROL *pIoControl, int nType);
int __stdcall PLSPIoControl(struct LSP_IO_CONTROL *pIoControl, int nType)
{
switch(nType)
{
case IO_CONTROL_SET_RULE_FILE: // 設定應用程式層規則
break;
case IO_CONTROL_SET_WORK_MODE: // 設定工作模式
break;
case IO_CONTROL_GET_WORK_MODE: // 擷取工作模式
break;
case IO_CONTROL_SET_PHOENIX_INSTANCE: // 設定主模組資訊
break;
case IO_CONTROL_GET_SESSION: // 擷取一個會話
break;
case IO_CONTROL_SET_QUERY_SESSION: // 返回DLL詢問的結果
break;
case IO_CONTROL_GET_QUERY_SESSION: // 擷取發出詢問的會話
break;
}
return 0;
}
phoenixLSP.dll檢查應用程式的許可權後,根據規則,如果找到規則,直接按規則處理,否則 PostMessage 到phoenixFW.exe 詢問該如何操作;
phoenixFW.exe 使用 class CPIOControl 來和phoenixLSP.dll通訊,用來改變其中的data_seg 和 bss_seg 段。
passthru.sys 使用鏈表來儲存規則內容
// 過濾規則
typedef struct _PassthruFilter
{
USHORT protocol; // 使用的協議
//驅動程式中不方便操作 TCHAR 類型的字串,所以改為 ULONG 類型
ULONG SourceIpFrom;
ULONG SourceIpTo;
ULONG DestIpFrom;
ULONG DestIpTo;
USHORT sourcePort; // 源連接埠號碼
USHORT destinationPort; // 目的連接埠號碼
BOOLEAN bDrop; // 是否丟棄此封包
}PassthruFilter, *PPassthruFilter;
// 過濾規則列表
typedef struct _PassthruFilterList
{
PassthruFilter filter;
struct _PassthruFilterList *pNext;
} PassthruFilterList, *PPassthruFilterList;
phoenixFW.exe 使用CreateFile開啟到驅動程式所控制裝置的控制代碼 ,枚舉所有的適配器, 開啟每個控制裝置物件控制代碼,使用DeviceIoControl函數添加過濾規則
passthru 添加我們自己的派遣函數入口
DispatchTable[IRP_MJ_DEVICE_CONTROL] = DevIoControl;
DevIoControl
{
switch(uIoControlCode)
{
case IOCTL_PTUSERIO_ENUMERATE: //枚舉適配器
break;
case IOCTL_PTUSERIO_OPEN_ADAPTER: //開啟適配器
break;
case IOCTL_PTUSERIO_QUERY_OID: //擷取OID
case IOCTL_PTUSERIO_SET_OID: //設定OID
break;
default:
return FltDevIoControl(pDeviceObject, pIrp); //處理其他控制命令
}
}
FltDevIoControl
{
switch(uIoControlCode)
{
case IOCTL_PTUSERIO_QUERY_STATISTICS: // 擷取網路活動狀態
break;
case IOCTL_PTUSERIO_RESET_STATISTICS: // 重設網路活動狀態
break;
case IOCTL_PTUSERIO_ADD_FILTER: // 添加一個過濾規則
break;
case IOCTL_PTUSERIO_CLEAR_FILTER: // 清除過濾規則
break;
}
}
對於發送單個封包的函數
MPSend
{
if(!FltFilterSendPacket(pAdapt,Packet,TRUE))
{
//
// 如果拒絕的話,就欺騙上層,說已經發送成功了(雖然並沒有真正地發送)
//
return NDIS_STATUS_SUCCESS;
}
}
FltFilterSendPacket
{
FltReadPacketData
bPass =FltCheckFilterRules
return bPass;
}
FltReadPacketData
{
//使用NdisQueryBufferSafe 擷取網路封包鏈表中的資料 Ethheader + IPheader + TCP/UDP header +淨額
}
FltCheckFilterRules
{
//根據過濾規則檢查這個網路封包的源IP,目的IP,源連接埠,目的連接埠
//因為這裡得到的IP都是網路位元組序,所以要轉換成本機位元組序,來判斷是否在我們指定的IP範圍內,如192.168.1.2-192.168.1.254
#define NTOHL(val,x,y,m,n) (((val) & (x)) << 24)| /
((((val) & (y))>> 8) << 16)| /
((((val) & (m))>> 16) << 8)| /
(((val) & (n)) >> 24)
int x,y,m,n;
x=0xff;
y=0xff << 8;
m=0xff << 16;
n=0xff << 24;
ipSource=pIpHdr->ipSource;
ipSource=NTOHL(ipSource,x,y,m,n);
//因為在phoenixFW.exe 中設定的源IP 目的IP 預設是發送封包時的順序,所以當接收封包時,要把規則中的源IP 和封包中的目的IP 進行比較
return !pFilterList->filter.bDrop;
}
MPSendPackets
PtReceive
{
bPass = FltFilterReceive
if(!bPass)
{
// 拒絕這個封包
Status = NDIS_STATUS_SUCCESS;
break;
}
}
FltFilterReceive
{
bPass = FltCheckFilterRules
return bPass;
}