NDIS Hook is an interception technology used by professional firewalls. The focus of NDIS Hook is on how to obtain the ndis_protocol_block pointer corresponding to a specific protocol, next, you can replace the receiving and receiving functions registered with the protocol to intercept network data.
The method to obtain the ndis_protocol_block pointer is generally to use ndisregisterprotocol to register a new protocol. The obtained protocol handle is actually a ndis_protocol_block pointer which traverses the ndis_protocol_block linked list, you can find the ndis_protocol_block corresponding to the protocol you want to hook up. this can be done because every time a protocol is registered, the system places the ndis_protocol_block corresponding to the Protocol at the beginning of the Protocol linked list. Each element of the Protocol linked list is of the ndis_protocol_block type, indicates a registered protocol.
In fact, all we need is the ndis_protocol_block pointer of the TCPIP protocol family. After all, almost all the protocols we are interested in are implemented in the tcpip. sys protocol driver. If we only need the ndis_protocol_block corresponding to the TCPIP protocol, the above method is a bit cumbersome. We can try to find a simpler method to obtain the ndis_protocol_block of the TCPIP protocol.
So I configured tcpip. the sys Driver implements disassembly and finds that the ndis_protocol_block pointer is stored in a global variable named _ arphandle. Therefore, if the _ arphandle address can be found, we will succeed, we can use the offset of the global variable as a constant, but here we just want to broaden the idea, I will introduce another method to find the global variable.
Tcpip. sys has an export function called ipdelayedndisreenumeratebindings. This function once contains the _ arphandle address. Why does it appear? Because the function calls the ndisreenumerateprotocolbindings function internally, we should know that, before calling a function using the Call Command, the push command must be used to compress the function parameters into the stack. Unfortunately, the ndisreenumerateprotocolbindings function has only one parameter, this parameter is exactly the ndis_protocol_block pointer type. Here, _ arphandle is actually passed as a parameter.
Ndisreenumerateprotocolbindings, so the _ arphandle address must follow the push command. Specifically, the four bytes that follow the push command are the _ arphandle address.
So the specific idea is as follows: first find the IP address of the ipdelayedndisreenumeratebindings function, and then search for the signature of the push command from the address of the function. After searching, return the four bytes that follow the push command as the pointer to the ndis_protocol_block pointer.
Some may ask, if the ipdelayedndisreenumeratebindings function contains multiple push commands, it is not necessary to find the incorrect address. In fact, although all commands are called Push commands, however, the machine code levels are different. There are more than a dozen machine codes for the push command, which are used to distinguish different addressing methods. When calling ndisreenumerateprotocolbindings, the byte sequence of the push command is 0xff35, this push command indicates that the four bytes followed by it are a memory address, rather than an immediate number or register. With this knowledge, we can understand that the uniqueness of 0xff35 can be satisfied in a limited address range. According to my observations, ipdelayedndisreenumeratebindings is a very short function on Win2000, WINXP, and win2003. The 0xff35 command does appear only once, so this method is very reliable.
The idea has come out. I will post the detailed code below. To understand the code, you need to understand the Windows PE format. If you don't want to understand it, you can use the Code directly.
The following is a general routine I wrote to get an export function address of the kernel module. This is mainly used to obtain the export function ipdelayedndisreenumeratebindings of the tcpip. SYS module.
Void * getroutineaddress (char * modulename, char * routinename)
{
Pimage_dos_header dos_hdr;
Pimage_nt_headers nt_hdr;
Pimage_export_directory export_dir;
Ulong * fn_name, * fn_addr, I;
Char * base;
Base = (char *) findmodule (modulename); // This function is used to obtain the base address of the kernel module.
If (! Base)
Return NULL;
Dbuplint ("TCPIP address: % P", base );
Dos_hdr = (pimage_dos_header) base;
If (dos_hdr-> e_magic! = Image_dos_signature)
Return NULL;
Nt_hdr = (pimage_nt_headers) (base + dos_hdr-> e_lfanew );
Export_dir = (pimage_export_directory) (base + nt_hdr-> optionalheader. datadirectory [image_directory_entry_export]. virtualaddress );
Fn_name = (ulong *) (base + export_dir-> addressofnames );
Fn_addr = (ulong *) (base + export_dir-> addressoffunctions );
For (I = 0; I <export_dir-> numberofnames; I ++, fn_name ++, fn_addr ++)
{
If (strcmp (routinename, base + * fn_name) = 0)
{
Return base + * fn_addr;
}
}
Return NULL;
}
The findmodule function is implemented as follows:
Void *
Findmodule (char * name)
{
Ulong I, n, * q;
Psystem_module_information P;
Void * base;
Zwquerysysteminformation (systemmoduleinformation, & N, 0, & N );
Q = (ulong *) exallocatepool (pagedpool, N );
Zwquerysysteminformation (systemmoduleinformation, Q, N * sizeof (* q), 0 );
P = (psystem_module_information) (q + 1 );
Base = NULL;
For (I = 0; I <* q; I ++ ){
If (_ stricmp (P [I]. imagename + P [I]. modulenameoffset, name) = 0 ){
Base = P [I]. base;
Break;
}
}
Exfreepool (Q );
Return base;
}
The following is a function to obtain the ndis_protocol_block pointer of the TCPIP protocol.
Void * getprotocolblock ()
{
Char * base;
Char bytes [] = {0xff, 0x35 };
Base = getroutineaddress ("tcpip. sys", "ipdelayedndisreenumeratebindings ");
While (rtlcomparememory (base, bytes, 2 )! = 2)
{
Base ++;
}
Return ** (void ***) (base + 2 ));
}
The previous section describes how to implement the NDIS Hook by obtaining ndis_protocol_block. The second method is the inline Hook method. Inline hook is nothing new. It is nothing more than embedding a JMP machine command in the first part of a function. Before the function executes valid code, it jumps to our proxy function, after necessary processing is done in our proxy function, we will jump back to the original function and then execute the command of the original function.
Since tcpip. sys is a standard NDIS protocol driver, so the package receiving function should obviously be in tcpip. as implemented internally by sys, we can find the two package receiving functions directly, and then can we just hook them to the inline? After reverse analysis, I found these two functions. I installed two XP systems, one of which exported the two functions, but the other was not exported, therefore, we still need to use the signature to search for these two functions. The declaration of these two functions is as follows:
Ndis_status
Arprcv (ndis_handle bindcontext,
Ndis_handle maccontext,
Uchar * headbuffer,
Ulong headsize,
Uchar * buffer,
Ulong buffersize,
Ulong packetsize );
Int
Arprcvpacket (ndis_handle bindcontext,
Pndis_packet packet );
The code for searching these two function addresses is as follows:
// Address of the following global variables to save the two functions
Void * arprcv = NULL;
Void * arprcvpacket = NULL;
Void searchprotocolroutine ()
{
// The following are the signatures of the two functions.
Uchar arprcvbytes [] = {0x8b, 0xff, 0x55, 0x8b, 0xec, 0x56, 0x8b, 0x75, 0x08,0x33 };
Uchar arprcvpacketbytes [] = {0x8b, 0xff, 0x55, 0x8b, 0xec, 0x51,0x53,0x56,0x57, 0x8b };
// Obtain the base address of the tcpip. SYS module. This function is provided in the previous section.
Char * base = findmodule ("tcpip. sys ");
While (arprcv = NULL | arprcvpacket = NULL)
{
If (arprcv = NULL &&
Rtlcomparememory (arprcvbytes, base, 10) = 10)
{
Arprcv = base;
}
Else if (arprcvpacket = NULL &&
Rtlcomparememory (arprcvpacketbytes, base, 10) = 10)
{
Arprcvpacket = base;
}
Base ++;
}
}
The first few commands of the functions compiled by various compilers are almost the same and used to create stack frames. These commands are called the function's preface.
Three bytes in Win2000
Push EBP
MoV EBP, ESP
In WINXP and later systems, it is changed to five bytes.
MoV EDI, EDI
Push EBP
MoV EBP, ESP
However, a near-jump command is exactly five bytes, And it just overwrites the function's preface on XP, so it is easier to hook up on XP. Here we will explain how to hook arprcv, we insert a JMP Command inside arprcv, which will jump to the arprcvprox function. This function is a bare function and is implemented as follows:
_ Declspec (naked) arprcvprox () // springboard Function
{
_ ASM
{
MoV EDI, EDI
Push EBP
MoV EBP, ESP
// Seven parameters start to press the stack
Push [EBP + 20 H]
Push [EBP + 1ch]
Push [EBP + 18 h]
Push [EBP + 14 H]
Push [EBP + 10 h]
Push [EBP + 0ch]
Push [EBP + 8]
Call newarprcv // call the newarprcv Function
CMP eax, 0x10003 // determine whether the function return value is ndis_status_not_accepted
JZ end // if it is ndis_status_not_accepted, directly end this function
// Returns the arprcv function without skipping.
MoV eax, arprcv // If ndis_status_not_accepted is not returned
// Execute this command to load the address of the arprcv function into eax
Add eax, 5 // Add the arprcv address value to 5 and save it to eax, indicating the address to be redirected
JMP eax // start to jump back to arprcv
End:
Pop EBP
Retn 1ch
}
}
Inside the function, the newarprcv function is called. The prototype is consistent with that of arprcv and must be implemented by ourselves:
Ndis_status
Newarprcv (
In ndis_handle protocolbindingcontext,
In ndis_handle macreceivecontext,
In pvoid headerbuffer,
In uint headerbuffersize,
In pvoid lookaheadbuffer,
In uint lookaheadbuffersize,
In uint packetsize
)
{
/*
Add your judgment logic code here to determine whether to intercept the data
If interception is required, ndis_status_not_accepted is returned.
Otherwise, ndis_status_success is returned and the data is handed over to arprcv for processing.
*/
Return ndis_status_success;
}
In the same principle, we insert the JMP command in arprcvpacket to jump to the arprcvpacketprox bare function, which is implemented as follows:
_ Declspec (naked) arprcvpacketprox ()
{
_ ASM
{
MoV EDI, EDI
Push EBP
MoV EBP, ESP
// Two parameters start to press the stack
Push [EBP + 0ch]
Push [EBP + 8]
Call newarprcvpacket // call newarprcvpacket
CMP eax, 0 // If 0 is returned, this packet is rejected
JZ end // return this function directly
MoV eax, arprcvpacket
Add eax, 5
JMP eax // The sixth byte of the arprcvpacket function.
End: Pop EBP
Retn 8
}
}
Inside the function, newarprcvpacket is called. The function implementation is as follows:
Int
Newarprcvpacket (ndis_handle bindcontext, pndis_packet ndispacket)
{
/*
Add your judgment logic here to determine whether to intercept the data. If you want to intercept the data, 0 is returned,
Otherwise, a non-0 value is returned.
*/
Dbuplint ("rcvpacket ");
Return 1;
}
Please read the comments of the above Code carefully. Next, we must provide a function to install and uninstall the hook.
Void patcharprcv (Boolean ispatch) // If ispatch is set to true, the hook is installed. If it is set to false, the hook is uninstalled.
{
/* The following five bytes will be used to overwrite the first five bytes of the arprcv function.
These five bytes are the machine code of the jmp xxxx command.
Further computation, so temporarily use zero fill
*/
Uchar patchbytes [5] = {0xe9, 0x00,0x00,0x00,0x00 };
// Cover the first five bytes of the arprcvpacket function with the following five Bytes:
Uchar patchbytes2 [5] = {0xe9, 0x00,0x00,0x00,0x00 };
// Save the first five bytes of the original function to facilitate future hook recovery
Uchar restorebytes [5] = {0x8b, 0xff, 0x55, 0x8b, 0xec };
/*
The following two lines of code calculate the jump offset
*/
Int offset = (char *) arprcvprox-(char *) ARPRcv-5;
Int offset2 = (char *) arprcvpacketprox-(char *) ARPRcvPacket-5;
// Modify the relative address in patchbytes and patchbytes2
Memcpy (patchbytes + 1, & offset, 4 );
Memcpy (patchbytes2 + 1, & offset2, 4 );
If (ispatch)
{
Disablewriteprotect (); // disable write Protection
Memcpy (arprcv, patchbytes, 5 );
Memcpy (arprcvpacket, patchbytes2, 5 );
Enablewriteprotect (); // Enable write Protection
}
Else
{
Disablewriteprotect ();
Memcpy (arprcv, restorebytes, 5 );
Memcpy (arprcvpacket, restorebytes, 5 );
Enablewriteprotect ();
}
}
Because the arprcv and arprcvpacket functions are on a read-only page, you must disable write protection before you can insert code into it. Disable write protection and enable write Protection Code as follows:
Void disablewriteprotect ()
{
_ ASM
{
CLI
MoV eax, Cr0
And eax, 0 fffeffffh
MoV Cr0, eax
}
}
Void enablewriteprotect ()
{
_ ASM
{
MoV eax, Cr0
Or eax, not 0 fffeffffh
MoV Cr0, eax
STI
}
}