编程控制Win XP SP2的防火墙
编程爱好者
一、Windows XP SP2介绍
Windows XP SP2在安全方面作了重大的调整,安全设计融合到整个操作系统中,防火墙屏障、操作系统补丁和更新病毒库等理念形成一个安全体系,而防火墙是这个安全体系的第一道屏障,它提供了一个强大的保护层,可以阻止恶意用户和程序依靠未经请求的传入流量攻击计算机。
Windows XP SP2防火墙又称ICF(Internet Connection Firewall),已经具备个人防火墙的基本功能,它是一种能够阻截所有传入的未经请求的流量的状态防火墙。这些流量既不是响应计算机请求而发送的流量(请求流量),也不是事先指定允许传入的未经请求的流量(异常流量)。这有助于使计算机更加安全,使您可以更好地控制计算机上的数据。而且这个防火墙默认设置为开启状态,并且支持IPv4和IPv6两种网络协议,可以为我们的电脑提供更多的安全保护。和Windows良好的兼容性及可靠性是其它个人防火墙所不能比拟的。
Windows XP SP2防火墙给我们带来安全的同时也给我们带来了麻烦,假如你的应用程序要监听一个端口就会弹出如图所示的警告窗口,会让人很不放心。为了防止这类事情的发生,今天用编程的方法来控制防火墙,让我们的程序能够连接网络并且不弹出如图的警告窗口。

二、原理介绍
现在大多数第三方防火墙软件提供商如Zone Labs、McAfee和Symantec公司都提供和SP2兼容的防火墙软件。这些新版软件在安装的时候会自动禁用Windows防火墙,而在卸载时又会自动启用Windows防火墙。它们是怎么做到的,是否你也想把你的程序悄无声息的通过防火墙呢?不管那些软件是怎么实现的,今天我们用COM编程来实现,调用Windows API把防火墙作为一个COM对象连接后,你就可以为所欲为了。那么我们有必要了解一下COM编程。
COM组件实际上是一个C++类,而接口都是纯虚类,组件从接口派生而来。COM组件有三个最基本的接口类,分别是IUnknown、IClassFactory、IDispatch。COM规范规定任何组件、任何接口都必须从IUnknown继承,IUnknown包含三个函数,分别是QueryInterface、AddRef、Release;这三个函数是无比重要的,而且它们的排列顺序也是不可改变的。QueryInterface用于查询组件实现的其它接口,说白了也就是看看这个组件的父类中还有哪些接口类,AddRef用于增加引用计数,Release用于减少引用计数。那么怎么使用COM组件?下面我们开始使用VC++6.0应用COM编程来控制Windows防火墙。
三、实现步骤
我们先创建一个网络程序,在某一个端口监听看看防火墙会出现什么动作。
第一步:创建一个网络程序
首先新建一个工程并添加一个C++头文件main.h代码如下:
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib")
再添加一个C++源文件main.cpp代码如下:
#include "main.h"
#include <iostream>
using namespace std;
int main()
{
//初始化网络
WSADATA wsaData;
if (::WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
{
cout<<"加载网络库失败!"<<endl;
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
cout<<"加载网络库失败!"<<endl;
return 0;
}
SOCKET sLink; //套接字
sLink=::socket(AF_INET,SOCK_STREAM,0);
if (SOCKET_ERROR==sLink)
{
cout<<"创建套接字失败!"<<endl;
return 0;
}
SOCKADDR_IN sClient;
sClient.sin_family=AF_INET;
sClient.sin_port=::htons(1080);
sClient.sin_addr.S_un.S_addr=INADDR_ANY;
//绑定端口
if (SOCKET_ERROR==::bind(sLink,(SOCKADDR*)&sClient,sizeof(sClient)))
{
cout<<"绑定端口失败!"<<endl;
::closesocket(sLink);
return 0;
}
//监听
if(SOCKET_ERROR==::listen(sLink,5))
{
cout<<"监听端口失败!"<<endl;
::closesocket(sLink);
return 0;
}
Sleep(10000);
::closesocket(sLink);
//卸载占用的网络资源
WSACleanup();
cout<<"程序退出..."<<endl;
return 0;
}
以上代码只是一个简单的网络连接程序,大家应该可以看得懂吧,笔者就不必作解释了。现在编译运行,看看出现什么情况,正是Windows防火墙的功能所在,弹出如图的警告窗口。
第二步:添加功能函数
下面开始编程让我们的程序通过防火墙并不让防火墙报警,第一步添加下面的函数,作用时初始化COM库并连接防火墙管理器。该函数返回INetFwProfile指针,为防火墙COM实例指针,后面的代码都是使用该指针,熟悉COM编程的应该很明确了。代码如下:
FW_ERROR_CODE WindowsFirewallInitialize(INetFwProfile** fwProfile)
{
FW_ERROR_CODE ret = FW_NOERROR;
HRESULT hr = S_OK;
INetFwMgr* fwMgr = NULL;
INetFwPolicy* fwPolicy = NULL;
_ASSERT(fwProfile != NULL);
*fwProfile = NULL;
try
{
//创建一个防火墙管理器实例
hr = CoCreateInstance(
__uuidof(NetFwMgr),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(INetFwMgr),
(void**)&fwMgr
);
if (FAILED(hr))
{
printf("CoCreateInstance failed: 0x%08lx", hr);
throw FW_ERR_CREATE_SETTING_MANAGER;
}
//返回本地防火墙策略
hr = fwMgr->get_LocalPolicy(&fwPolicy);
if (FAILED(hr))
{
printf("get_LocalPolicy failed: 0x%08lx", hr);
throw FW_ERR_LOCAL_POLICY;
}
//返回当前防火墙配置记录
hr = fwPolicy->get_CurrentProfile(fwProfile);
if (FAILED(hr))
{
printf("get_CurrentProfile failed: 0x%08lx",hr);
throw FW_ERR_PROFILE;
}
}
catch (FW_ERROR_CODE nError)
{
ret = nError;
}
//释放本地防火墙策略
if (fwPolicy != NULL)
{
fwPolicy->Release();
}
//释放防火墙管理器的引用
if (fwMgr != NULL)
{
fwMgr->Release();
}
return ret;
}
其中FW_ERROR_CODE是一个自定义的枚举类型,用来标识出现的错误,定义如下:
enum FW_ERROR_CODE
{
FW_NOERROR = 0,
FW_ERR_INITIALIZED, // 未初始化
FW_ERR_CREATE_SETTING_MANAGER, //不能建立一个Windows防火前实例
FW_ERR_LOCAL_POLICY, // 获取本地防火墙策略失败
FW_ERR_PROFILE, // 获取防火墙配置记录失败
FW_ERR_INVALID_ARG, // 无效参数
FW_ERR_AUTH_APPLICATIONS, // 获取例外应用程序清单失败
FW_ERR_APP_ENABLED, // 获取应用程序是否允许失败
FW_ERR_CREATE_APP_INSTANCE, // 创建一个例外程序实例失败
FW_ERR_SYS_ALLOC_STRING, // 申请BSTR内存失败
FW_ERR_PUT_PROCESS_IMAGE_NAME, // 添加程序映象名到例外列表失败
FW_ERR_PUT_REGISTER_NAME, // Failed to put a registered name
FW_ERR_ADD_TO_COLLECTION, // Failed to add to the Firewall collection
FW_ERR_REMOVE_FROM_COLLECTION, // Failed to remove from the Firewall collection
};
介绍一下代码中的几个重要API函数。函数CoCreateInstance是创建一个指定的类型标识符(CLSID)的未初始化对象,它是COM编程的必须函数,作用就是初始化要连接的COM组件。函数的原型如下:
STDAPI CoCreateInstance(
REFCLSID rclsid, //对象的类型标识符 (CLSID)
LPUNKNOWN pUnkOuter, // IUnknown指针
DWORD dwClsContext, //执行环境
REFIID riid, //接口标识符的引用
LPVOID * ppv //返回接口标识符所指定的接口地址
);
然后就可以调用get_LocalPolicy函数获取本地防火墙策略,函数原型如下:
HRESULT STDMETHODCALLTYPE get_LocalPolicy(
/* [retval][out] */ INetFwPolicy **localPolicy)
再调用get_CurrentProfile函数获取当前防火墙配置记录,函数原型如下:
HRESULT STDMETHODCALLTYPE GetProfileByType(
/* [in] */ NET_FW_PROFILE_TYPE profileType,
/* [retval][out] */ INetFwProfile **profile)
上面的函数是初始化一个防火墙COM实例,下面就可以真正地控制它。
下面函数的功能是把应用程序自己添加到例外程序列表中,fwProcessImageFileName参数是应用程序的路径名,fwName参数为你在防火墙管理器中看见的名字(注册名),需要注意的是Windows底层和COM使用的字符串都是宽字符的,所以你传递的字符串都必须是宽字符。具体代码如下:
FW_ERROR_CODE WindowsFirewallAddApp(INetFwProfile* fwProfile,
const wchar_t* fwProcessImageFileName,
const wchar_t* fwName)
{
FW_ERROR_CODE ret = FW_NOERROR;
HRESULT hr;
BOOL bAppEnable;
BSTR bstrProcessImageFileName = NULL;
BSTR bstrRegisterName = NULL;
INetFwAuthorizedApplication* pFWApp = NULL;
INetFwAuthorizedApplications* pFWApps = NULL;
_ASSERT(fwProfile != NULL);
_ASSERT(fwProcessImageFileName != NULL);
_ASSERT(fwName != NULL);
try
{
//首先检查应用程序是否已经在例外程序列表中
FW_ERROR_CODE nError =WindowsFirewallAppIsEnabled(fwProfile,fwProcessImageFileName,&bAppEnable );
if( nError != FW_NOERROR )
throw nError;
//不在列表中则添加
if( bAppEnable == FALSE )
{
//返回例外程序列表
hr = fwProfile->get_AuthorizedApplications( &pFWApps );
if( FAILED( hr ))
throw FW_ERR_AUTH_APPLICATIONS;
//创建一个例外列表实例
hr = CoCreateInstance( __uuidof(NetFwAuthorizedApplication), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwAuthorizedApplication), (void**)&pFWApp);
if( FAILED( hr ))
throw FW_ERR_CREATE_APP_INSTANCE;
//为这个文件名分配一个BSTR
bstrProcessImageFileName = SysAllocString(fwProcessImageFileName);
if( SysStringLen( bstrProcessImageFileName ) == 0)
throw FW_ERR_SYS_ALLOC_STRING;
//设置程序路径
hr = pFWApp->put_ProcessImageFileName( bstrProcessImageFileName );
if( FAILED( hr ) )
throw FW_ERR_PUT_PROCESS_IMAGE_NAME;
//为列表名称分配一个BSTR
bstrRegisterName = SysAllocString(fwName);
if(0 == SysStringLen(bstrRegisterName))
throw FW_ERR_SYS_ALLOC_STRING;
//设置规则名称
hr = pFWApp->put_Name( bstrRegisterName );
if( FAILED( hr ))
throw FW_ERR_PUT_REGISTER_NAME;
//添加到列表中
hr = pFWApps->Add( pFWApp );
if( FAILED( hr ))
throw FW_ERR_ADD_TO_COLLECTION;
}
}
catch( FW_ERROR_CODE nError )
{
ret = nError;
}
//释放BSTR
SysFreeString( bstrProcessImageFileName );
SysFreeString( bstrRegisterName );
if( pFWApp )
pFWApp->Release();
if( pFWApps )
pFWApps->Release();
return ret;
}
上面的函数先调用WindowsFirewallAppIsEnabled函数来判断要添加的程序在当前配置记录中是否已经存在,参数fwProfile就是你初始化防火墙实例所获取的指针,fwProcessImageFileName为你要检查的路径名,fwAppEnabled返回是否存在,返回TRUE时表示已经在列表中,否则不在列表中(需要注意的是字符串也一样需要宽字符),具体代码如下:
FW_ERROR_CODE WindowsFirewallAppIsEnabled(INetFwProfile* fwProfile,
const wchar_t* fwProcessImageFileName,
BOOL* fwAppEnabled)
{
FW_ERROR_CODE ret = FW_NOERROR;
HRESULT hr;
BSTR bstrFWProcessImageFileName = NULL;
VARIANT_BOOL bFWEnabled;
INetFwAuthorizedApplication* pFWApp = NULL;
INetFwAuthorizedApplications* pFWApps = NULL;
_ASSERT(fwProfile != NULL);
_ASSERT(fwAppEnabled != NULL);
*fwAppEnabled = FALSE;
try
{
if( fwProcessImageFileName == NULL )
throw FW_ERR_INVALID_ARG;
hr = fwProfile->get_AuthorizedApplications( &pFWApps );
if( FAILED( hr ))
throw FW_ERR_AUTH_APPLICATIONS;
hr = fwProfile->get_AuthorizedApplications( &pFWApps );
if( FAILED( hr ))
throw FW_ERR_AUTH_APPLICATIONS;
//为这个文件名分配一个BSTR
bstrFWProcessImageFileName = SysAllocString(fwProcessImageFileName);
if( SysStringLen( bstrFWProcessImageFileName ) == 0)
throw FW_ERR_SYS_ALLOC_STRING;
//尝试查找例外应用程序
hr = pFWApps->Item( bstrFWProcessImageFileName, &pFWApp);
//假如失败则应用程序没有在列表中
if( SUCCEEDED( hr ))
{
//找到例外程序
hr = pFWApp->get_Enabled( &bFWEnabled );
if( FAILED( hr ))
throw FW_ERR_APP_ENABLED;
if( bFWEnabled == VARIANT_TRUE )
*fwAppEnabled = TRUE;
}
}
catch( FW_ERROR_CODE nError )
{
ret = nError;
}
//释放BSTR
SysFreeString( bstrFWProcessImageFileName );
//释放内存资源
if( pFWApp )
pFWApp->Release();
if( pFWApps )
pFWApps->Release();
return ret;
}
上面的代码和API函数在代码中已经注释很清楚了就不再赘述了。读者应该可以看得懂。假如列表中不存在就添加,使用如下函数:
//返回例外程序列表
hr = fwProfile->get_AuthorizedApplications( &pFWApps );
//创建一个例外列表实例
hr = CoCreateInstance( _uuidof(NetFwAuthorizedApplication), NULL, CLSCTX_INPROC_SERVER, _uuidof(INetFwAuthorizedApplication), (void**)&pFWApp);
bstrProcessImageFileName = SysAllocString(fwProcessImageFileName);
//设置程序路径
hr = pFWApp->put_ProcessImageFileName( bstrProcessImageFileName );
//为列表名称分配一个BSTR
bstrRegisterName = SysAllocString(fwName);
//设置名称(注册名)
hr = pFWApp->put_Name( bstrRegisterName );
//添加到列表中
hr = pFWApps->Add( pFWApp );
现在我们的程序就可以创建网络套接字监听进行网络传输了,防火墙已经不会阻挠了。接着假如我们不再进行网络传输了或者程序要退出了我们可以从列表中删除。
现在我们把功能函数写好了,现在把主程序修改一下:
int main()
{
//初始化网络
WSADATA wsaData;
SOCKET sLink; //套接字
INetFwProfile* fwProfile = NULL;
if (::WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
{
cout<<"加载网络库失败!"< return 0; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); cout<<"加载网络库失败!"< return 0; } //得到当前模块名 wchar_t szApplication[MAX_PATH]; ::GetModuleFileNameW(NULL,szApplication,MAX_PATH); try { HRESULT hr = S_OK; //初始化COM ::CoInitialize(NULL); hr=WindowsFirewallInitialize(&fwProfile); if (hr!=FW_NOERROR) { printf("WindowsFirewallInitialize failed: 0x%08lx\n", hr); throw hr; } //把程序自己添加到防火墙配置记录中 hr=WindowsFirewallAddApp(fwProfile,szApplication,L"我的测试"); if (hr!=FW_NOERROR) { printf("WindowsFirewallAddApp failed: 0x%08lx\n", hr); throw hr; } sLink=::socket(AF_INET,SOCK_STREAM,0); if (INVALID_SOCKET==sLink) { cout<<"创建套接字失败!"< throw sLink; } SOCKADDR_IN sClient; sClient.sin_family=AF_INET; sClient.sin_port=::htons(5005); sClient.sin_addr.S_un.S_addr=INADDR_ANY; //::inet_addr("192.168.1.4"); //绑定端口 if (SOCKET_ERROR==::bind(sLink,(SOCKADDR*)&sClient,sizeof(sClient))) { cout<<"绑定端口失败!"< throw sLink; } //监听 if(SOCKET_ERROR==::listen(sLink,5)) { cout<<"监听端口失败!"< throw sLink; } //现在你可以随便进出防火墙了 //暂停5秒 Sleep(5000); ::closesocket(sLink); //卸载占用的网络资源 ::WSACleanup(); //把程序自己从防火墙配置记录中移除 hr=WindowsFirewallRemoveApp(fwProfile,szApplication); if(FAILED(hr)) { printf("WindowsFirewallRemoveApp failed: 0x%08lx", hr); throw hr; } cout<<"程序退出..."< } catch (SOCKET) { if(INVALID_SOCKET!=sLink) ::closesocket(sLink); } catch(...) { } //卸载防火墙的资源 WindowsFirewallCleanup(fwProfile); //卸载COM ::CoUninitialize(); return 0; } 上面的代码需要解释一下:多了两个函数::CoInitialize(NULL) 和::CoUninitialize()。这两个函数就是初始化COM和卸载COM资源。每个使用COM编程的都需要这两个函数。还有得到路径名时用的是GetModuleFileNameW而不是GetModuleFileName或GetModuleFileNameA,因为上面说过我们需要的是宽字符,所以直接调用宽字符函数我们就不需要再作转换。