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