By Lang Beeck, Software Design Engineer
Microsoft Corporation
December 2004
Updated: December 2005
The improved security brought by Microsoft Windows XP Service Pack 2 (SP2) also brings a new complication for multiplayer games: the firewall. This article describes the new firewall, why it exists, what it accomplishes, and how it does so. Most importantly, it describes how to configure your application to work well with the firewall.
Contents:
There's something very satisfying about blowing off steam by engaging in virtual mayhem. Hopping into a multiplayer game whether Halo, Doom or the favorite game du jour, it's all good.
Homes are getting more connected. People have multiple PCs which are being networked both in the house and across the internet. Broadband usage is on the rise. Today, everyone knows about the internet. Multiplayer gaming is only going to grow from here - as long as we can keep our customers from getting waxed by malicious hackers. Those people out there want to own your machine. Maybe they want your box for a zombie (to help them with a distributed denial-of-service attack) or maybe they just want to do a little virtual vandalism. People feeling vulnerable just won't play rather than exposing their personal data to these vandals of the internet.
If you haven't been tossed for a loop yourself by one of the viruses out in the wild, there's a good chance your friends have - and you've probably helped them recover from the damage. You know the litany - Blaster, Sobig, Mydoom, and the scores of variants.
After an excellent security talk at Meltdown I passed a group of developers mulling over what they'd heard and realizing just how vulnerable they might be. In a brief internet search, a matter of an hour or two, the speaker had come up with a list of twenty-odd security vulnerabilities that have hit the game industry. There are people who spend most of their time just looking for vulnerabilities for the heck of it. Worse than that, there are those people who search for vulnerabilities so they can exploit them. Gamers need all the protection we can provide.
Multiplayer applications are particularly vulnerable because of the network facing. It's not good enough anymore for a well-behaved client to work with a well-behaved server - people are attacking our games with malicious clients and malicious servers. Enter the first line of defense: The Firewall.
The firewall provides a barrier against network-based intrusions. It blocks unsolicited incoming traffic, and makes the system mostly invisible on the internet by rejecting Internet Control Message Protocol (ICMP) requests. This means that ping and tracert will not work. The firewall also looks and rejects invalid packets.
This barrier prevents opportunistic attacks. Attacks spread by finding many systems with the same vulnerability. The firewall can thwart many attacks by putting up a "do not disturb" sign for those features not currently in use; the attack is ignored and does not strike home. The essential benefit of the new Windows Firewall is that features and applications that are not in use cannot be avenues for attack.
The user configures the system to identify what applications and features are needed and should be open to the network. This happens either when an application is installed or when it tries to open itself to the network.
With the arrival of Windows XP SP2, the Windows Firewall is going to be widely deployed and a wide-open connection between endpoints will be the exception. Asking users to choose between being secure and having fun is bad business.
All multiplayer games are affected to some degree. While clients are generally in good shape, servers, hosts, and peers in peer-to-peer all need the firewall configured to continue working. Specifically, incoming unsolicited traffic is dropped. The firewall intercepts network-traffic filter packets based on the packet contents and recent network activity. The firewall uses the contents and activity to decide to forward or drop a packet. Once the firewall is properly configured, a game will be able to accept inbound unsolicited traffic as before.
Who has inbound unsolicited traffic?
Clients are generally in good shape. Their outgoing Transmission Control Protocol/Internet Protocol (TCP/IP) connections will work fine, as will outgoing User Datagram Protocol (UDP) messages along with timely responses to those messages - the firewall keeps the port open for 90 seconds after each outgoing message in anticipation of a reply.
The Internet Connection Firewall (ICF) in Windows XP and Windows Server 2003 is a stateful packet filter, and handles both Internet Protocol, version 4 (IPv4) and Internet Protocol, version 6 (IPv6). However, it is not on by default and does not support 3rd party network stacks, of which there are a significant number in the world, such as large internet providers including national telephone companies.
For those not on Windows XP SP2, turning on the ICF is highly recommended. See the instructions "Use an Internet Firewall" on http://www.microsoft.com/security/protect as the first step to securing your PC. The Internet Connection Firewall (ICF) provides port mapping to override the packet filter. Essentially, you specify the port to open and it remains opened until you close it. If the user reboots before it is closed, it will remain open until specifically closed. The control of the firewall and the management of the port mappings is done via INetSharingManager (IPv4) and INetFWV6Mgr (IPv6).
The Windows Firewall for Windows XP SP2 is a more comprehensive solution that does support 3rd party network stacks, and handles ports more intelligently: ports are kept open only so long as the application that needs them is still active.
The new Windows Firewall is available on both Windows XP SP2 and Windows Server 2003 Service Pack 1 (SP1). Like the ICF, it is a software firewall that supports both IPv4 and IPv6, but unlike ICF the Windows Firewall:
In the "on with no exceptions" mode, all static holes are closed. API calls to open a static hole are allowed but deferred; that is, they are not applied until the firewall switches back to normal operation. All listen requests by applications will also be ignored. Outbound connections will still succeed.
For new applications, add your application to the "Exceptions List" during installation. After getting the user's agreement, add the application using the INetFWAuthorizedApplications interface, supplying the full path. We'll cover the details in the sample.
Your old applications, including applications installed before the user upgraded to Windows XP SP2, are handled with the Exceptions List popup (see Figure 1). This dialog shows when an application tries to open a port for incoming traffic - either when calling bind() with a non-zero port for UDP, or accept() for TCP/IP protocol. You must be running as an Administrator to "Unblock" applications, while "Ask Me Later" disallows this time around but asks again next time.
This is a non-blocking, system modal dialog box unless the firewall detects a fullscreen Microsoft Direct3D application. When we tested with a blocking version of the dialog, many fullscreen applications crashed when the dialog popped up. To avoid these crashes, the dialog comes in underneath; the user can then handle it when the application exits fullscreen mode.
Figure 1. Exceptions List Popup Dialog
You'll notice that the popup knows the name and publisher of the application. There's no magic here - it's pulled from the executable's version information. This information is an important system administration tool; it is even used for ongoing application compatibility work. Some applications neglect to keep this version information up-to-date.
Users can also add their applications through the user interface (UI) (see Figure 2), but for your new applications it's best to handle it during setup, add the application on install, and remove the application on uninstall. We'll show you how in Managing the Windows Firewall.
Figure 2. Configuring the Firewall
Figure 3. Adding a Program to the Firewall Exceptions List
Windows XP SP2 puts the security choices and settings out front. The goal is to protect by default, and allow the user to make informed choices about what applications are allowed to run on their machine. The best scenario is to make additions and removals from the exception list automated. The best time to perform these additions and removals is during the install and uninstall process.
Again, most games only need to be added to the firewall exception list if they can function as a server or if they implement a peer-to-peer communication protocol. The FirewallInstallHelper.dll can be called from an installer and is provided with source to help you in this process. It can be found here:
Source: | (SDK root)\Samples\C++\Misc\FirewallInstallHelper |
Executable: | (SDK root)\Samples\C++\Misc\Bin\FirewallInstallHelper.dll |
If you are using your own custom installer or InstallShield Script and not using MSI then you call these exported DLL functions directly during the install:
Otherwise if you are using MSI deferred custom actions you can call these exported DLL functions:
The following sections describe specific methods of calling the exported DLL functions from this FirewallInstallHelper from within an InstallShield, Wise, or MSI package. All methods render the same results and it is up to the developer to determine which method to implement.
An alternate method of using the Firewall APIs is to add the function calls to an InstallShield InstallScript. The steps required to integrate are fairly simple:
prototype BOOL FirewallInstallHelper.AddApplicationToExceptionList( WSTRING, WSTRING ); prototype BOOL FirewallInstallHelper.RemoveApplicationFromExceptionList( WSTRING ); function OnEnd() WSTRING path[256]; begin UseDLL( SUPPORTDIR ^ "FirewallInstallHelper.dll" ); path = TARGETDIR ^ "change to relative path to executable from install directory"; if !MAINTENANCE then FirewallInstallHelper.AddApplicationToExceptionList( path, "change to friendly app name" ); endif; if MAINTENANCE && UNINST != "" then FirewallInstallHelper.RemoveApplicationFromExceptionList( path ); endif; UnUseDLL( SUPPORTDIR ^ "FirewallInstallHelper.dll" ); end;
Adding the Firewall APIs to WiseScript is also easy:
At the high level, these steps have to be done. They will be explained in detail below:
The following is the steps need to do this using an MSI editor such as Orca found in the Platform SDK. Note that some editors have use wizards which simplify some of these steps:
Name | Data |
---|---|
FIREWALL | Point it to the FirewallInstallHelper.dll. This file will be embedded in the MSI package so you will have to do this step every time you recompile FirewallInstallHelper.dll. |
Action | Type | Source | Target |
---|---|---|---|
SetMSIFirewallProperties | 65 | FIREWALL | SetMSIFirewallProperties |
AddToExceptionListUsingMSI | 1089 | FIREWALL | AddToExceptionListUsingMSI |
RemoveFromExceptionListUsingMSI | 1089 | FIREWALL | RemoveFromExceptionListUsingMSI |
RollBackAddToFirewall | 1345 | FIREWALL | RemoveFromExceptionListUsingMSI |
Action | Condition | Sequence |
---|---|---|
SetMSIFirewallProperties | 1010 This places soon after CostFinalize | |
AddToExceptionListUsingMSI | NOT Installed | 4010 This places it after the InstallFiles action |
RollBackAddToFirewall | NOT Installed | 4009 This places it directly before the "AddToExceptionListUsingMSI" custom action which indicates this is its rollback action |
RemoveFromExceptionListUsingMSI | Installed | 4020 This places it after the InstallFiles action |
Property | Value |
---|---|
FriendlyNameForFirewall | Needs to be the name the exception list will display. For example, "Example Game" |
RelativePathToExeForFirewall | Needs to be the installed executable of the game. For example, "ExampleGame.exe" |
For more detailed information on how MSI works, see MSI documentation at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/windows_installer_start_page.asp
If you are using a custom installer package it will be necessary to understand what exactly the APIs require and how the APIs function. The following sample will help explain both requirements and function of the APIs and can be found here:
Source: | (SDK root)\Samples\C++\Misc\Firewall |
Executable: | (SDK root)\Samples\C++\Misc\Bin\Firewall.exe |
The sample requires the latest Microsoft Platform Software Development Kit (SDK) to compile because it uses the Windows Firewall application programming interface (API) introduced with Windows XP SP2. The sample demonstrates how to:
The sample hits the two big scenarios: installation (including the uninstall) and multiplayer launch. The sample deals with these two scenarios in OnInstallApplication and in OnLaunchMultiplayer. The complete sample includes error checking left out of these snippets in the interest of brevity.
The install case is crucial (see Figure 4). Once the firewall is properly configured, the firewall takes care of the rest and is invisible to the application.
hr = OnInstallApplication( L"%ProgramFiles%\\Combat Flight Simulator\\COMBATFS.EXE ", L" Combat Flight Simulator" ); if( FAILED(hr) ) printf( "OnInstallApplication failed: 0x%08lx\n", hr );
Figure 4. Install Scenario
We invoke OnInstallApplication passing the complete path to the executable and a "friendly name" that will appear in the firewall exception list (see Figure 4 and Figure 5). OnInstallApplication creates an instance of our FirewallWrapper class, and adds the executable to the firewall's exception list using AddAuthorizedApp.
BOOL OnInstallApplication( IN const wchar_t* szFwProcessImageFileName, IN const wchar_t* szFwFriendlyName ) { FirewallWrapper* pfw = FirewallWrapper::Create(); if( !pfw ) return FALSE; HRESULT hr = pfw->AddAuthorizedApp( szFwProcessImageFileName, szFwFriendlyName ); return SUCCEEDED(hr); }
Figure 5. OnInstallApplication Method
class FirewallWrapper { INetFwProfile* m_pFwProfile; HRESULT InitFirewallProfile(); FirewallWrapper(); public: static FirewallWrapper* Create(); ~FirewallWrapper(); BOOL FirewallPresent() { return m_pFwProfile != NULL; } HRESULT AddAuthorizedApp( IN const wchar_t* szFwProcessImageFileName, IN const wchar_t* szFwFriendlyName ); HRESULT RemoveAuthorizedApp( IN const wchar_t* szFwProcessImageFileName ); BOOL IsAppEnabled( IN const wchar_t* szFwProcessImageFileName ); BOOL AreExceptionsAllowed(); };
Figure 6. The FirewallWrapper class
FirewallWrapper::FirewallWrapper() : m_pFwProfile(NULL) , m_hrComInit(E_FAIL) {} FirewallWrapper* FirewallWrapper::Create() { FirewallWrapper* pfw = new FirewallWrapper; if( pfw ) { pfw->InitFirewallProfile(); if( !pfw->FirewallPresent() ) { // Failed to initialize firewall profile delete pfw; pfw = NULL; } } return pfw; }
Figure 7. Initialize FirewallWrapper
Digging inside the FirewallWrapper class initialization (Figure 7), the first order of business is to initialize a profile. We create an instance of the Firewall Settings Manager (Figure 8), use it to retrieve the local firewall policy, then use the policy to fetch the intended profile. Because all interactions go through the profile, it is stored in a member variable for future use. From now on, the FirewallWrapper will use the profile to check and modify firewall settings.
On older systems that lack the new firewall, no profile will be created and FirewallWrapper::Create will return NULL.
HRESULT FirewallWrapper::InitFirewallProfile() { HRESULT hr = S_OK; INetFwMgr* pFwMgr = NULL; INetFwPolicy* pFwPolicy = NULL; m_pFwProfile = NULL; // Create an instance of the firewall settings manager. hr = CoCreateInstance( __uuidof(NetFwMgr), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwMgr), (void**)&pFwMgr ); _ASSERT( hr != CO_E_NOTINITIALIZED &, & "COM has not been initialized" ); if( FAILED(hr) ) goto cleanup; // Retrieve the local firewall policy. hr = pFwMgr->get_LocalPolicy( &pFwPolicy ); if( FAILED(hr) ) goto cleanup; // Retrieve the firewall profile currently in effect. hr = pFwPolicy->get_CurrentProfile( &m_pFwProfile ); if( FAILED(hr) ) printf( "get_CurrentProfile failed: 0x%08lx\n", hr ); cleanup: if( pFwPolicy != NULL ) pFwPolicy->Release(); if( pFwMgr != NULL ) pFwMgr->Release(); return hr; }
Figure 8. InitFirewallProfile
Adding the authorized application (Figure 9) works through the Authorized Applications list. We create an instance of an authorized application to add to the list, use BSTRs to pass in both the fully-qualified file name and the friendly name, and add the application to the list. Manipulating the firewall is limited to Administrators on the system, so this call will fail when running as a limited user. This does not pose a problem because installs already require Administrator privilege.
HRESULT FirewallWrapper::AddAuthorizedApp( IN const wchar_t* szFwProcessImageFileName, IN const wchar_t* szFwFriendlyName ) { HRESULT hr = S_OK; BSTR fwBstrName = NULL; BSTR fwBstrProcessImageFileName = NULL; INetFwAuthorizedApplication* pFwApp = NULL; . INetFwAuthorizedApplications* pFwApps = NULL; hr = m_pFwProfile->get_AuthorizedApplications( &pFwApps ); // Create an instance of an authorized application. hr = CoCreateInstance( __uuidof(NetFwAuthorizedApplication), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwAuthorizedApplication), (void**)&pFwApp ); // Set the process image file name. fwBstrProcessImageFileName = SysAllocString( szFwProcessImageFileName ); hr = pFwApp->put_ProcessImageFileName( fwBstrProcessImageFileName ); // Set the application friendly name. fwBstrName = SysAllocString( szFwFriendlyName ); hr = pFwApp->put_Name( fwBstrName ); // Add the application to the collection. hr = pFwApps->Add( pFwApp ); cleanup: // Free the BSTRs. SysFreeString( fwBstrName ); SysFreeString( fwBstrProcessImageFileName ); // Release the authorized application instance. if( pFwApp != NULL ) pFwApp->Release(); if( pFwApps != NULL ) pFwApps->Release(); return hr; }
Figure 9. AddAuthorizedApp Method
Removing an authorized application is so similar to adding one we'll omit the details here. The wrapper handles it in RemoveAuthorizedApp (see Figure 10 and the sample application).
BOOL OnUninstallApplication( IN const wchar_t* szFwProcessImageFileName ) { FirewallWrapper* pfw = FirewallWrapper::Create(); if( !pfw ) return FALSE; HRESULT hr = pfw->RemoveAuthorizedApp( szFwProcessImageFileName ); return SUCCEEDED(hr); }
Figure 10. OnUninstallApplication
Now to launch the second basic scenario. The launch case is interesting because connectivity problems are a real headache for gamers (see Figure 11). We want to check the firewall state on launch to proactively detect connection problems, and give better guidance through better error messages.
if( CanHostMultiplayer( L"%ProgramFiles%\\Combat Flight Simulator\\COMBATFS.EXE") ) printf( "Okay to launch multiplayer.\n" );
Figure 11. Launch Scenario
First, we'll check for check for changes since installation - the application may have been disabled or removed from the exceptions list (see Figure 12). We're checking to ensure the work from OnInstallApplication still holds. The second check is even more interesting in its own way, because users can request the special "on with no exceptions" mode at any time. Hosting will not work if either of these checks fails, including the peer-to-peer scenario as discussed earlier.
BOOL CanHostMultiplayer( IN const wchar_t* szFwProcessImageFileName ) { FirewallWrapper* pfw = FirewallWrapper::Create(); if( !pfw ) return TRUE; if( !pfw->IsAppEnabled(szFwProcessImageFileName) ) { // Application is not enabled in the firewall. // You will not be able host games or join a peer-to-peer game. // Depending on the game, you may be able to join. return FALSE; } if( !pfw->AreExceptionsAllowed() ) { // Firewall is on with no exceptions. // You will not be able host games or join a peer-to-peer game. // Depending on the game, you may be able to join. return FALSE; } return TRUE; }
Figure 12. CanHostMultiplayer Method
With the wrapper, it's simple to check if exceptions are allowed as seen in Figure 13. The wrapper uses the profile to call get_ExceptionsNotAllowed, fixes up the negative logic, and returns the result.
BOOL FirewallWrapper::AreExceptionsAllowed() { VARIANT_BOOL vbNotAllowed = VARIANT_FALSE; HRESULT hr = S_OK; hr = m_pFwProfile->get_ExceptionsNotAllowed( &vbNotAllowed ); if( SUCCEEDED(hr) &, & vbNotAllowed != VARIANT_FALSE ) return FALSE; return TRUE; }
Figure 13. AreExceptionsAllowed Method
Discovering if an application is on the enabled list is more involved (see Figure 14). We get the set of authorized applications, and check for our executable image's file name using a BSTR; if present, check to see if it is enabled. Finally, complete the process by cleaning up.
BOOL FirewallWrapper::IsAppEnabled( IN const wchar_t* szFwProcessImageFileName ) { HRESULT hr = S_OK; BSTR fwBstrProcessImageFileName = NULL; VARIANT_BOOL vbFwEnabled; INetFwAuthorizedApplication* pFwApp = NULL; INetFwAuthorizedApplications* pFwApps = NULL; BOOL bFwAppEnabled = FALSE; // Retrieve the collection of authorized applications. hr = m_pFwProfile->get_AuthorizedApplications( &pFwApps ); if( FAILED(hr) ) goto cleanup; fwBstrProcessImageFileName = SysAllocString( szFwProcessImageFileName ); if( SysStringLen( fwBstrProcessImageFileName ) == 0 ) goto cleanup; // Retrieve the authorized application, find if it is enabled hr = pFwApps->Item( fwBstrProcessImageFileName, &pFwApp ); if( SUCCEEDED(hr ) ) { hr = pFwApp->get_Enabled( &vbFwEnabled ); if( SUCCEEDED(hr) &, & vbFwEnabled != VARIANT_FALSE ) bFwAppEnabled = TRUE; } cleanup: SysFreeString( fwBstrProcessImageFileName ); if( pFwApp != NULL ) pFwApp->Release(); if( pFwApps != NULL ) pFwApps->Release(); return bFwAppEnabled; }
Figure 14. IsAppEnabled Method
If you run afoul of the firewall the log can come in handy. We made extensive use of it during our development cycle. You can see the firewall activity in this text log including what packets are dropped and what connections succeed. At the top of the log is a header describing the columns. Turn it on from:
Control Panel / Windows Firewall / Advanced / Security Logging - Settings
Double-click Windows Firewall, select the Advanced tab, and click the Settings button in the Security Logging area. By default, the log file goes to the following location:
C:\windows\pfirewall.log
You may be wondering if it is a security risk that applications can add and remove applications from the exceptions list without a popup, or perhaps you think that the bigger risk is that applications can disable the firewall altogether. To perform these feats, the application must be running in administrator mode. If you have malicious code running in administrator mode on your system, the game is already over. The hacker has already won - your system is "0wned". The hacker's ability to disable the firewall would merit little more than a footnote.
The firewall is here to stay as long as the Internet is under attack. With our customers under attack, don't tell them to disable the firewall. They need a firewall active.
Here's to a safer Internet. Enable your users to be secure and have fun!
There is a sample that accompanies this article. It can be found here:
Source: | (SDK root)\Samples\C++\Misc\Firewall |
Executable: | (SDK root)\Samples\C++\Misc\Bin\x86 or x64\Firewall.exe |
The sample requires the latest Microsoft Platform Software Development Kit (SDK) to compile because it uses the Windows Firewall application programming interface (API) introduced with Windows XP SP2. The sample demonstrates how to: