|
TxaKy Network v0.6
|
||||||||||||||||||||||||
An Adventure: How to implement a Firewall-Hook Driver?
|
||||||||||||||||||||||||
| pData | *pData points to a (struct IPRcvBuf *) structure with packet buffer. |
| RecvInterfaceIndex | Interface where the data is received. |
| pSendInterfaceIndex | Pointer to unsigned int containing the value of the index where data is sent. Although it is a pointer, changing this value doesn't get the packet to be rerouted :(. |
| pDestinationType | Pointer to unsigned int with destination type: local network, remote, broadcast, multicast, etc. |
| pContext | Point to a FIREWALL_CONTEXT_T structure where you can find information about the packet as if the packet is incoming or outgoing packet. |
| ContextLength | Size of buffer pointed by pContext. Its value is always sizeof(FIREWALL_CONTEXT_T). |
| pRcvBuf | *pRcvBuf points always to NULL. |
This information can be changed in future Windows versions because no official documentation is available. I only guarantee that this is the meaning of these fields that I obtained from my tests in Windows 2000 and Windows XP.
For each packet, our function will be called, and depending on the value it returns, the packet will be dropped or will be passed. The values you can return in the filter function are:
| FORWARD | The packet is allowed. |
| DROP | The packet is dropped. |
| ICMP_ON_DROP | The packet is dropped and a ICMP packet is sent to remote machine. |
This information can be changed in future Windows versions because no official documentation is available. I only guarantee that this is the meaning of these fields that I obtained from my tests in Windows 2000 and Windows XP.
In Firewall-Hook filter function, you don’t receive the buffer directly with packet header and packet content as in Filter-Hook driver. After some tests, I got to know the internal structure of the buffer. As I said before, the sent/received packet is passed in the parameter pData. *pData points to a IPRcvBuf structure:
struct IPRcvBuf
// Always 0
{
// Point to the next buffer in the chain
struct IPRcvBuf *ipr_next;
UINT ipr_owner; // Buffer data
UCHAR *ipr_buffer; // Buffer data size
UINT ipr_size; // In my tests always a pointer to NULL.
// Maybe the system could use MDLs instead of IPRcvBuf structures (but
// i never have seen it).
PMDL ipr_pMdl; // Always a pointer to NULL.
UINT *ipr_pClientCnt; // Always a pointer to NULL.
UCHAR *ipr_RcvContext; // Always 0. I suppose this field is a offset into buffer data
// but because I haven't a value different from 0, I can affirm it.
UINT ipr_RcvOffset; // In Windows 2003 DDK the name of this field have changed to flags.
// In my tests I always get 0 value for local traffic and 2 for remote.
// It's the only thing I can tell you about this field.
ULONG ipr_promiscuous;
};
For our purpose, we only have to know the fields ipr_next, ipr_buffer and
ipr_size. The field ipr_buffer contains ipr_size bytes of the packet. However,
the entire packet need not be in one buffer and the system can chain several
buffers. For this reason, the field ipr_next is used. This field points to the
next structure with data of the packet. We have the entire packet when in the
data structure the field ipr_next points to NULL. So, we find in Firewall-Hook
drivers a chained buffer structure as we can see in NDIS drivers. In my tests,
for all received packets, the function receives only one structure with all the
data in its buffer, and for sent packets, I find several chained buffers where
each one contains information about one protocol. I mean, if I send an ICMP
packet, for example, there are three chained buffers: one with IP header, one
with ICMP header, and another with data. However, as we must do with NDIS
drivers, we must not rely on how the system fills the buffers.
In the next figure, you can see an example of how a packet is built in Firewall-Hook driver:

As a simple, you can see in the next code how get a lineal buffer with packet content from the chained buffers:
char *pPacket = NULL;
// First, I calculate the total size of the packet
int iBufferSize;
struct IPRcvBuf *pBuffer = (struct IPRcvBuf *) *pData;
iBufferSize = buffer->ipr_size;
while(pBuffer->ipr_next != NULL)
{
pBuffer = pBuffer->ipr_next;
iBufferSize += pBuffer->ipr_size;
} // Reserve memory to the lineal buffer.
pPacket = (char *) ExAllocatePool(NonPagedPool, iBufferSize);
if(pPacket != NULL)
{
unsigned int iOffset = 0;
pBuffer = (struct IPRcvBuf *) *pData; // we are going to copy each buffer of the chain in the lineal buffer.
memcpy(pPacket, pBuffer->ipr_buffer, pBuffer->ipr_size);
while(pBuffer->ipr_next != NULL)
{
iOffset += pBuffer->ipr_size;
pBuffer = pBbuffer->ipr_next;
memcpy(pPacket + iOffset, pBuffer->ipr_buffer, pBbuffer->ipr_size);
}
}
And for all curious people (and before you ask me about it :P), you can modify data of the packets, on your own risk. There isn't any tool to do this type of software, for doing something like it, I recommend you to implement a NDIS IM driver or a TDI Filter driver. I didn't test it so much but I wouldn't rely very much in the stability of a Firewall Hook driver that change packet content. Why? Because we don't know how IP driver manages these buffers and what risks will modify them. In a few words, my recommendation: see, but not touch.
Well, now we know the syntax of the filter function and we know the format of the packet passed to it. Now, we only have to know how to join these two things to get a packet filtering application. The method I have used is to try to define a filter function similar to the one used in Filter-Hook driver because it's easier to understand. Because the parameters passed to the filter functions in these drivers are different, for Firewall-Hook, I implement an intermediate function (the real Firewall-Hook filter function) that wraps the filter function. I mean, as the Firewall-Hook filter function, I use a function that processes the packet, copy it in a lineal buffer, and pass it to the filter function. With this piece of code, I think you will understand it better:
FORWARD_ACTION cbFilterFunction(VOID **pData,
IPHeader *pIpHeader; // Convert chained buffer to lineal buffer as we see before.
UINT RecvInterfaceIndex,
UINT *pSendInterfaceIndex,
UCHAR *pDestinationType,
VOID *pContext,
UINT ContextLength,
struct IPRcvBuf **pRcvBuf)
{
FORWARD_ACTION result = FORWARD;
char *pPacket = NULL;
int iBufferSize;
struct IPRcvBuf *pBbuffer =(struct IPRcvBuf *) *pData;
PFIREWALL_CONTEXT_T fwContext = (PFIREWALL_CONTEXT_T)pContext;
// This won't be the fastest code but
// will help us to understand better the method. // ........... pIpHeader = (IPHeader *)pPacket; // Call the real filter function and return result
result = FilterPacket(pPacket,
pPacket + (pIpHeader ->headerLength * 4),
iBufferSize - (pIpHeader ->headerLength * 4),
(fwContext != NULL) ? fwContext->Direction: 0,
RecvInterfaceIndex,
(pSendInterfaceIndex != NULL) ? *pSendInterfaceIndex : 0); return result;
}
You can recognize the application of this article quickly. Yes, the GUI is exactly the one I used with my Filter Hook driver. Why? Because I have developed an easy packet filtering application that I use to test all firewalling methods I develop. In this way, I have a common GUI for all of them, that offer the user the same functionality, but at the lower level, work too different. I have different versions of this application (with minimum changes) to test my Filter-Hook driver, Firewall-Hook driver, LSP DLL, TDI filter driver, NDIS drivers... So, I think the methods are easy to understand. Few changes in GUI application, and you want to understand only the new method used.
As in my other article, this application only implements packet filtering. So many people asked me about adding some extra functionality as packet logging, install as service... But I want to follow the idea of offering methods, not solutions. If you want to add some of these extra functionalities, I am enchanted :). You can contact me to ask all questions you need.
Well, once time I have wrote this article, it's yours. I hope you can learn from it so much as I did. Enjoy it!!
Article writen by Jesús O . This Article was published in www.codeproject.com - 28/10/04