Watch & Learn

Debugwar Blog

Step in or Step over, this is a problem ...

Route表添加劫持

2021-03-01 @ UTC+0

背景说明


近期有一台开了3389(远程桌面)的笔记本出现无法链接的情况。其具体状况是重启之后一段时间内可以链接,然后“随机”时间后便无法链接,但是物理查看一下笔记本处于开机+联网状态。

经过再三的排查,发现导致远程桌面无法链接的原因是VPN向路由表中写入了一条192.168.0.0/16的路由记录,这导致内网中所有的流量均被发往了49开头的这个ip地址,这条路由记录有点太狠了,整个子网全部被重定向到了49这台机器,难怪远程桌面连不上。


所以写到此处,需求就出来了:当发现在设置192.168.0.0/16这条路由时拒绝设置。然而网上找了一圈,似乎没有现成的软件具备这个功能:(,木的办法了,自己动手丰衣足食。

劫持路由表的思路


首先我们要搞清楚,如何操作windows的路由表。而已知route.exe可以查看并设置路由表,那么我们只需要顺着route.exe的函数调用分析即可。经过快速的跟踪,发现route.exe设置路由表的调用路径如下:


总结一下就是:AddRoute -> AddIpv4Route -> NsiSetAllParameters

NsiSetAllParameters函数由nsi.dll导出, 经过逆向,调用路径如下图:


继续完善调用链: AddRoute -> AddIpv4Route -> NsiSetAllParameters -> NsiSetAllParametersEx -> NsiIoctl -> NsiOpenDevice

由以上调用链路可以猜测,route.exe最终是通过nsi.dll的NsiIoctl向某个驱动对象发送请求,从而达到设置路由表的目的的。具体是哪个设备呢?看一下NsiOpenDevice的逆向代码:


看来是\\.\Nsi这个设备,即\Device\Nsi这个设备。所以设置windows路由表应该是这样的:
route.exe将解析后得到的各个参数按照一定的格式组织起来,然后通过ioctl code + buffer的形式通知\Device\Nsi设备,\Device\Nsi设备通过ioctl code知道调用的功能是设置路由表,然后按照约定的结构从传入的buffer中取出需要的字段,最终配置内核对象中路由表对应的对象。

回头看一下我们的需求:当发现在设置192.168.0.0/16这条路由时拒绝设置。因此,我们只需要Hook住对应的ioctl code分发函数,然后检查对应字段是否是192.168.0.0,如果发现是我们想要拒绝的ip地址,则整个分发函数返回失败即可达成我们的目的。

寻找关键数据结构


接下来关键问题就是分析出上述的“对应字段”在buffer的什么位置。

依然回到调用链,但是这次我们从头开始找。通过逆向得知AddRoute函数会解析route.exe命令行传入的目标ip地址(此处是192.168.0.0),然后通过某函数将string类型的IP地址,转换成in_addr类型,随后将该数据以第一个参数传递给AddIpv4Route函数:


而在AddIpv4Route函数中,将想要设置的ip地址传递给了a5a[1]这个对象,最后a5a作为输入参数传递给NsiSetAllParameters,而NsiSetAllParameters最终会调用NsiIoctl函数给设备\Device\Nsi传递ctrl code和buffer,此处的buffer即a5a这个数组:


而NsiSetAllParameters函数其实只是进一步将所有的输入参数,按照约定的数据结构进行封装,上图中封装后的InputBuffer即为最终传递给\device\Nsi设备的buffer。

总结一下:
  • 路由的目标ip地址是一个in_addr结构
  • 目标ip被存放在输入给驱动的buffer的如下位置:
(char *)buffer + sizeof(void *) * 6 + sizeof(void *) *1
上述公式, sizeof(void *) * 1 == a5a[1], sizeof(void *) * 6 == InputBuffer[6]

Coding Time


因此可以写出如下劫持用驱动(32位Win7测试通过,教学目的部分数据大小写死):

  1. #include   
  2. #include   
  3.   
  4. #define _MAX_PATH 20  
  5.   
  6. typedef NTSTATUS (*pfnZwQueryInformationProcess)(  
  7.         IN HANDLE ProcessHandle,  
  8.         IN PROCESSINFOCLASS ProcessInformationClass,  
  9.         OUT PVOID ProcessInformation,  
  10.         IN ULONG ProcessInformationLength,  
  11.         OUT PULONG ReturnLength OPTIONAL  
  12. );  
  13. typedef UCHAR* (*pfnPsGetProcessImageFileName)(PEPROCESS pEprocess);  
  14.   
  15. PDRIVER_DISPATCH g_oldControlAddress = NULL;  
  16. pfnZwQueryInformationProcess ZwQueryInformationProcess = NULL;  
  17. pfnPsGetProcessImageFileName PsGetProcessImageFileName = NULL;  
  18.   
  19. VOID DriverUnload(PDRIVER_OBJECT DriverObject)  
  20. {  
  21.     InterlockedExchange(  
  22.         (PLONG)&DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL],  
  23.         (LONG)g_oldControlAddress  
  24.     );  
  25. }  
  26.   
  27. NTSTATUS HookNsiDeviceIOControl( PDEVICE_OBJECT pDeviceObject, PIRP pIrp ) {  
  28.     HANDLE hCurrentProcess = NULL;  
  29.     PIO_STACK_LOCATION pIrpStackLocation = IoGetCurrentIrpStackLocation(pIrp);  
  30.     PEPROCESS pCurrentProcessEprocess= IoGetCurrentProcess();  
  31.   
  32.     switch (pIrpStackLocation->Parameters.DeviceIoControl.IoControlCode) {  
  33.         case 0x120013:  
  34.             if (pIrpStackLocation->Parameters.Read.Length != 0 && pIrp->UserBuffer) {  
  35.                 int *userBufferP6 = (int*)(pIrp->UserBuffer) + 6;  
  36.                 int *ipBufferP1 = (int *)(*userBufferP6) + 1;  
  37.                 if (*ipBufferP1 == 0xa8c0) { //192.168.0.0  
  38.                     IoCompleteRequest(pIrp, IO_NO_INCREMENT);  
  39.                     return STATUS_UNSUCCESSFUL;  
  40.                 }  
  41.             }  
  42.             break;  
  43.     }  
  44.     return ((PDRIVER_DISPATCH)g_oldControlAddress)(pDeviceObject, pIrp);  
  45. }  
  46.   
  47. NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath  ) {  
  48.     NTSTATUS status;  
  49.     UNICODE_STRING uniDeviceName, uniRouteName;  
  50.     PFILE_OBJECT pFileObject;  
  51.     PDEVICE_OBJECT pDeviceObject;  
  52.   
  53.     RtlInitUnicodeString(&uniRouteName, L"ZwQueryInformationProcess");  
  54.     ZwQueryInformationProcess = (pfnZwQueryInformationProcess)MmGetSystemRoutineAddress(&uniRouteName);  
  55.     RtlInitUnicodeString(&uniRouteName, L"PsGetProcessImageFileName");  
  56.     PsGetProcessImageFileName = (pfnPsGetProcessImageFileName)MmGetSystemRoutineAddress(&uniRouteName);  
  57.   
  58.     RtlInitUnicodeString(&uniDeviceName, L"\\Device\\Nsi");  
  59.     status = IoGetDeviceObjectPointer(  
  60.         &uniDeviceName,   
  61.         FILE_READ_DATA,  
  62.         &pFileObject,  
  63.         &pDeviceObject  
  64.     );  
  65.     if (NT_SUCCESS(status)) {  
  66.         ObDereferenceObject(pFileObject);  
  67.         if (!g_oldControlAddress) {  
  68.             g_oldControlAddress = pDeviceObject->DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL];  
  69.         }  
  70.         InterlockedExchange(  
  71.             (PLONG)&pDeviceObject->DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL],  
  72.             (LONG)HookNsiDeviceIOControl  
  73.         );  
  74.         pDeviceObject->DriverObject->DriverUnload = (PDRIVER_UNLOAD)DriverUnload;  
  75.     }  
  76.   
  77.     return STATUS_SUCCESS;  
  78. }  

效果展示


将上述驱动模块加载后,使用route.exe添加路由表时效果如下:


可以看到如果要设置192.168.0.0这条路由会失败,但是设置其他路由正常。

打完收工~
目录
背景说明
劫持路由表的思路
寻找关键数据结构
Coding Time
效果展示

版权所有 (c) 2020 - 2025 Debugwar.com

由 Hacksign 设计