Introduction
Very often, when I'm developing, doing some research activity, or bug fixing, I use utilities like FileMon and RegMon from SysInternals. The thing that I like the best about these utilities is the fact that, when you need them, you can simply launch an .EXE file and instantly get real-time activity logs about the disk or the registry on a system, without any previous installation or reboot. This is simply invaluable, because when you are stuck in a problem, the last thing you want to do is to install a complex application or to reboot the machine on which you are working.
I always had the need for a utility similar to FileMon or RegMon, but oriented more to network and the real-time logging of network packets and activities. So I developed NDIS Monitor. NDIS Monitor consists of three components: a C# WinForms application (folder "NdisMonitor"), a C++ layer that calls the kernel driver (folder "NdisHook") and the kernel driver itself. NDIS Monitor doesn't require any previous installation or reboot of the hosting machine. Simply unzip the file and launch it to start logging network traffic.
NDIS Monitor's sources provided with this article demonstrate several advanced .NET features at work, used in real-world applications: Multithreading, Message Filtering, Reflection, CodeDom, WinForm UserControls, PInvoke, Marshaling from the unmanaged space, Synchronization through Windows Messages etc. The source code is fully commented.
The NDIS Monitor's kernel driver (whose sources are provided here for download) directly hooks several NDIS miniport APIs to capture the network traffic. This is not the standard and "documented" way to do these kind of things, but it is by far the most flexible, allowing instant interception of network data.
This hooking method is the result of an extensive research and reverse engineering activity I conducted some time ago. However, I discovered that several commercial and shareware products (like firewalls) actually use this same method to insert themselves into the flow of packet data between an arbitrary NDIS Miniport driver and its bound NDIS Protocol module, as NDIS Monitor does. Specific information about the implementation of the kernel driver can be found in the NDIS Monitor homepage.
I want to put emphasis on the fact that NDIS Monitor does not have any external dependency and doesn't require any third party software or library to run, contrary to many similar programs.
How to start using NDIS Monitor
Simply unzip the file and start the .EXE file.
When first launched, NDIS Monitor tries to enumerate all the extensions that will be made available to the user later. This is accomplished by looking in the "bin" subdirectory, using Reflection to inspect and load the assemblies found. The list of all the extensions loaded and the one that is currently displayed to the user are shown in a drop-down list in the application toolbar, as you can see in the screenshot above. An "extension" is simply a .NET WinForms User Control, that is hosted in the client area of the application's main window. If you want to write your own custom extension for NDIS Monitor, simply create a WinForms User Control and implement the INmExtension interface. Full instructions to do this are provided in the NDIS Monitor homepage, on my website.
The first step is to select the extension with which you want to work, in the aforementioned drop-down list. The "Standard" extension (implemented in the NmStandardExtension.dll file) is a User Control that displays (in a list control) all the packets sent and received through the specified interfaces. For each packet, the extension displays the direction (sent or received), a number, the corresponding log time, MAC and IP addresses and a short description, if available. If you double-click on a packet, the extension opens the "Packet Viewer" window, displaying the contents of the selected packet:
Another extension (with the name "User Example" and implemented in the NmExampleExtension.dll file) is provided for reference for people who want to implement their own extensions.
After you have selected the extension, the "Open Adapters" window pops up. This window (through a tree control) displays a list of all the NDIS Protocols and the corresponding open NDIS Adapters. Then the user has to check all the NDIS Adapters whose traffic he intends to intercept (take a look at the first screenshot at the top of this page). All the packets that are sent and received through the selected adapters are passed to the current User Control (i.e. the extension) for processing. Typically you should select an NIC card under the "TCPIP" protocol. Selecting an adapter under the "TCPIP_WANARP" protocol may be useful if you want to intercept the packets sent and received from WAN Adapters (like ADSL modems).
How to script NDIS Monitor
NDIS Monitor allows in-place scripting, using the objects in the System.CodeDom.Compiler namespace:
To open the "C# Code Window", simply select "Code Window" from the Extension menu. A "Code Wizard" is provided here to automatically generate filtering code based on common criteria (such as the presence of a particular byte sequence in the packet). Essentially, by scripting NDIS Monitor, you can tell the program what to do when it receives a new packet from the kernel driver. As soon as you have finished writing your script, you can compile it and, eventually, fix it directly from the program's user interface:
An optional console can be open and used to interact with the user. Your script can easily receive commands and/or log events in this console:
What you have to do in the "C# Code Window" is implement the ProcessNextPacket
method, that is the method called by NDIS Monitor whenever a new packet is sent to or received from the currently selected NDIS Adapters. When you are done writing your NDIS filter in the "C# Code Window", simply press ALT+RETURN to compile your code and to see it in action. This is the signature of the ProcessNextPacket
method:
public bool ProcessNextPacket( ProcessNextPacketFn impl, LogFn log,
RawPacket rp, NdisHookStubs.NEXT_PACKET np, int ord, DateTime tm )
{
return impl( rp, np, ord, tm );
}
As you can see in the code above, the default implementation of ProcessNextPacket
simply calls the impl
delegate, which points to the corresponding ProcessNextPacket
method of the currently selected extension (i.e. the WinForms User Control described in the above paragraphs). If the "Standard" extension is selected in the drop-down list, this simply means that the packet is added to the list control. Instead, if you return false here, the packet is discarded and not shown to the user.
The other parameters are as follows: the log
delegate can be used to send strings to the log console (see the screenshot above). Typically, if you want to display statistical information about the intercepted traffic and the rp
and np
objects describe the characteristics of the single packet just sent or received. The parameter rp
of type RawPacket
represents the network packet in a strongly object oriented way, thus allowing, among other things, to easily test the type of the instance with the is
C# operator. This is an example of a test that can be done on the intercepted packet:
public bool ProcessNextPacket( ProcessNextPacketFn impl, LogFn log,
RawPacket rp, NdisHookStubs.NEXT_PACKET np, int ord, DateTime tm )
{
if ( rp is EthernetPacket && ((
EthernetPacket)rp)._tranHeader is TranProt_TCP )
{
TranProt_TCP tcp = (TranProt_TCP) (
(EthernetPacket)rp)._tranHeader;
if ( tcp._srcPort == 80 && np._bDirection == 0 )
return impl( rp, np, ord, tm );
}
return false;
}
If you script NDIS Monitor with this code, the program will display in the list control only the packets of type TCP, and received from the port 80. A complete description of the RawPacket class hierarchy can be found in the NDIS Monitor homepage.
A more complex scripting example with NDIS Monitor
Just to demonstrate the potential of the scripting features of NDIS Monitor, I have prepared a small example. What the following code does is to intercept all the incoming TCP packets directed to the port 80, and then extract the request URL and the host from them, if present. Then, the output is logged in the console window (menu "View" -> "Output Window"), as the list of all the URLs of all the resources downloaded from any internet or lan web server through the port 80 on that system.
To try this code, you should open the "Code Window" (menu "Extension" -> "Code Window"), replace the current implementation of the ProcessNextPacket
method with the code below, recompile the script and close the code window (closing the code window is important because it is a modal window and NDIS Monitor accepts the new script ONLY when the user closes it):
public bool ProcessNextPacket(ProcessNextPacketFn impl, LogFn log,
RawPacket rp, NdisHookStubs.NEXT_PACKET np, int ord, DateTime tm)
{
if (rp is EthernetPacket && (
(EthernetPacket)rp)._tranHeader is TranProt_TCP)
{
TranProt_TCP tcp = (TranProt_TCP)((EthernetPacket)rp)._tranHeader;
if (tcp._dstPort == 80 && np._bDirection != 0 )
{
try
{
if (rp._data[54] == 'G' && rp._data[55] == 'E' &&
rp._data[56] == 'T')
{
int i;
System.Text.ASCIIEncoding ascii =
new System.Text.ASCIIEncoding();
string host = "";
for (i = 58; i < rp._data.Length - 4; i++)
if (ascii.GetString(
rp._data, i, 5).ToLower() == "host:")
{
i += 5;
while (rp._data[i++] == ' ') ;
int hostStart = i - 1;
while (rp._data[i++] != 0xD) ;
host = ascii.GetString(rp._data, hostStart,
i - hostStart - 1);
break;
}
i = 58;
while (rp._data[i++] != ' ') ;
string url = ascii.GetString(rp._data, 58, i - 58 - 1);
log("http://" + host + url);
return impl(rp, np, ord, tm);
}
}
catch
{ }
}
}
return false;
}
Then open the console window and try to navigate to the Microsoft home page. This is what should happen:
This is only a small example of what you can do with NDIS Monitor. You can write very complex script code, using all the classes of the .NET FCL, without even exiting the program. This includes, for example, the classes for reading and writing files to the disk. Also consider that the UserHook
class instance (i.e. the class that you can edit in the code window and that contains the definition of the ProcessNextPacket
method) is persistent throughout all the interception process, after its compilation. This means that you can declare instance data in your script code that you may use to store information of any kind between different ProcessNextPacket
calls.
More information about NDIS Monitor
More information about NDIS Monitor (including an explanation of how the kernel driver works) can be found in the NDIS Monitor homepage, on my website.