
Background
Recently, a notebook that opened 3389 (remote desktop) appeared to be unable to connect. The specific situation is that it can be connected for a while after restarting, and then it cannot be connected after a “random” time, but the notebook is physically checked to be absolutely normal.
After investigation, it was found that the reason why the remote desktop could not be connected was that the VPN wrote a 192.168.0.0/16 routing record to the routing table, which caused all the traffic in the intranet to be sent to this IP address starting with 49. This routing record is a bit too cruel, the entire subnet is all redirected to this 49 machine, no wonder the remote desktop can’t connect.

So writing here, the demand comes out: when find that windows are setting the 192.168.0.0/16 route, refuse to set it. However, after looking around on the Internet, it seems that there is no ready-made software with this function : (
When there is no way, we made a way.
The idea of hijacking the routing table
First of all, we need to figure out how to operate the Windows routing table. It is known that route.exe can view and set the routing table, so we only need to analyze along the function call of route.exe. After a quick trace, it was found that the call path of route.exe to set the routing table is as follows:

In summary, the process is: AddRoute -> AddIpv4Route -> NsiSetAllParameters
The NsiSetAllParameters function is exported by nsi.dll. After reverse it, the call path is as follows:

Continue to improve the call chain: AddRoute -> AddIpv4Route -> NsiSetAllParameters -> NsiSetAllParametersEx -> NsiIoctl -> NsiOpenDevice
From the above call chain, it can be guessed that route.exe finally sends a request to a driver object through NsiIoctl of nsi.dll to achieve the purpose of setting the routing table. Which device is it specifically? Let’s take a look at the reverse code of NsiOpenDevice:

It seems to be the \\.\Nsi device, that is, the \Device\Nsi device. So setting the Windows routing table should be like this: route.exe organizes the parameters obtained after parsing according to a certain format, and then notifies the \Device\Nsi device in the form of ioctl code + buffer. The \Device\Nsi device knows that the function called by the ioctl code is to set the routing table, and then extracts the required fields from the incoming buffer according to the agreed structure, and finally configures the corresponding object in the kernel object routing table.
Looking back at our requirements: when find that Windows are setting the 192.168.0.0/16 route, we refuse to set it. Therefore, we only need to hook the corresponding ioctl code distribution function, and then check whether the corresponding field is 192.168.0.0. If we find that it is the IP address we want to reject, then the entire distribution function returns failure to achieve our goal.
Find the critical data structure
The next critical question is to analyze the position of the aforementioned “corresponding field” in the buffer.
Still going back to the call chain, but this time we start from the beginning. It is known through reverse engineering that the AddRoute function will parse the target IP address (here is 192.168.0.0) passed in by the route.exe command line, and then convert the string type IP address through a certain function. Into in_addr type, and then pass this data as the first parameter to the AddIpv4Route function:

In the AddIpv4Route function, the IP address you want to set is passed to the object a5a[1], and finally a5a is passed as an input parameter to NsiSetAllParameters. NsiSetAllParameters will eventually call the NsiIoctl function to pass the ctrl code and buffer to the device \Device\Nsi. The buffer here is the array a5a:

In fact, the NsiSetAllParameters function just further encapsulates all input parameters according to the agreed data structure. The encapsulated InputBuffer in the above figure is the buffer finally passed to the \device\Nsi device.
In summary:
- The target IP address of the route is an in_addr structure
- The target IP is stored in the following position in the buffer input to the driver:
(char *)buffer + sizeof(void *) * 6 + sizeof(void *) *1
In the above formula: sizeof(void *) * 1 == a5a[1], sizeof(void *) * 6 == InputBuffer[6]
Coding Time
Therefore, you can write the following hijacking driver (32-bit Win7 test passed, part of the data for teaching purposes is fixed):
- #include
- #include
- #define _MAX_PATH 20
- typedef NTSTATUS (*pfnZwQueryInformationProcess)(
- IN HANDLE ProcessHandle,
- IN PROCESSINFOCLASS ProcessInformationClass,
- OUT PVOID ProcessInformation,
- IN ULONG ProcessInformationLength,
- OUT PULONG ReturnLength OPTIONAL
- );
- typedef UCHAR* (*pfnPsGetProcessImageFileName)(PEPROCESS pEprocess);
- PDRIVER_DISPATCH g_oldControlAddress = NULL;
- pfnZwQueryInformationProcess ZwQueryInformationProcess = NULL;
- pfnPsGetProcessImageFileName PsGetProcessImageFileName = NULL;
- VOID DriverUnload(PDRIVER_OBJECT DriverObject)
- {
- InterlockedExchange(
- (PLONG)&DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL],
- (LONG)g_oldControlAddress
- );
- }
- NTSTATUS HookNsiDeviceIOControl( PDEVICE_OBJECT pDeviceObject, PIRP pIrp ) {
- HANDLE hCurrentProcess = NULL;
- PIO_STACK_LOCATION pIrpStackLocation = IoGetCurrentIrpStackLocation(pIrp);
- PEPROCESS pCurrentProcessEprocess= IoGetCurrentProcess();
- switch (pIrpStackLocation->Parameters.DeviceIoControl.IoControlCode) {
- case 0x120013:
- if (pIrpStackLocation->Parameters.Read.Length != 0 && pIrp->UserBuffer) {
- int *userBufferP6 = (int*)(pIrp->UserBuffer) + 6;
- int *ipBufferP1 = (int *)(*userBufferP6) + 1;
- if (*ipBufferP1 == 0xa8c0) { //192.168.0.0
- IoCompleteRequest(pIrp, IO_NO_INCREMENT);
- return STATUS_UNSUCCESSFUL;
- }
- }
- break;
- }
- return ((PDRIVER_DISPATCH)g_oldControlAddress)(pDeviceObject, pIrp);
- }
- NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) {
- NTSTATUS status;
- UNICODE_STRING uniDeviceName, uniRouteName;
- PFILE_OBJECT pFileObject;
- PDEVICE_OBJECT pDeviceObject;
- RtlInitUnicodeString(&uniRouteName, L"ZwQueryInformationProcess");
- ZwQueryInformationProcess = (pfnZwQueryInformationProcess)MmGetSystemRoutineAddress(&uniRouteName);
- RtlInitUnicodeString(&uniRouteName, L"PsGetProcessImageFileName");
- PsGetProcessImageFileName = (pfnPsGetProcessImageFileName)MmGetSystemRoutineAddress(&uniRouteName);
- RtlInitUnicodeString(&uniDeviceName, L"\\Device\\Nsi");
- status = IoGetDeviceObjectPointer(
- &uniDeviceName,
- FILE_READ_DATA,
- &pFileObject,
- &pDeviceObject
- );
- if (NT_SUCCESS(status)) {
- ObDereferenceObject(pFileObject);
- if (!g_oldControlAddress) {
- g_oldControlAddress = pDeviceObject->DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL];
- }
- InterlockedExchange(
- (PLONG)&pDeviceObject->DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL],
- (LONG)HookNsiDeviceIOControl
- );
- pDeviceObject->DriverObject->DriverUnload = (PDRIVER_UNLOAD)DriverUnload;
- }
- return STATUS_SUCCESS;
- }
Effect of the program
After loading the above driver module, the effect is: when using route.exe to add the routing table is as follows, operation will be refused:

You can see that if you want to set the 192.168.0.0 route, it will fail, but setting other routes is normal.
Enjoy hijacking ;)