首页
论坛
课程
招聘
[原创]shadow ssdt学习笔记(一)(二)
2007-12-22 22:01 71441

[原创]shadow ssdt学习笔记(一)(二)

zhuwg 活跃值
11
2007-12-22 22:01
71441
1。取得shadow ssdt真实地址
系统只提供了KeServiceDescriptorTable导出
KeServiceDescriptorTableShadow是个未导出结构

定义
Copy code

    typedef struct _SYSTEM_SERVICE_TABLE
    {
          PNTPROC  ServiceTable;  // array of entry points
          PDWORD  CounterTable;  // array of usage counters
          DWORD  ServiceLimit;    // number of table entries
          PBYTE    ArgumentTable;  // array of byte counts
    }
    SYSTEM_SERVICE_TABLE,
    *PSYSTEM_SERVICE_TABLE,
    **PPSYSTEM_SERVICE_TABLE;
    //-----------------------------------------------------------------------------------------------------------
    typedef struct _SERVICE_DESCRIPTOR_TABLE
    {
          SYSTEM_SERVICE_TABLE ntoskrnl;  // ntoskrnl.exe ( native api )
        SYSTEM_SERVICE_TABLE win32k;    // win32k.sys (gdi/user support)
          SYSTEM_SERVICE_TABLE Table3;    // not used
          SYSTEM_SERVICE_TABLE Table4;    // not used
    }
    SYSTEM_DESCRIPTOR_TABLE,
    *PSYSTEM_DESCRIPTOR_TABLE,
    **PPSYSTEM_DESCRIPTOR_TABLE;

其实 KeServiceDescriptorTableShadow包含4个子结构,其中第一个就是ntoskrnl.exe ( native api ),和KeServiceDescriptorTable指向一样 我们真正需要获得的是第二个win32k.sys (gdi/user support),第三个和第四个一般不使用

定位方法
1。硬编码
Copy code

    //for xp
    if(gKernelVersion==WINXP)
      KeServiceDescriptorTableShadow=KeServiceDescriptorTable-0x40;
    //for 2k
    if(gKernelVersion==WIN2K)
      KeServiceDescriptorTableShadow=KeServiceDescriptorTable+0xE0;

2。搜索KeAddSystemServiceTable
反汇编代码可以看出
Copy code

    lkd> u KeAddSystemServiceTable l 40
    nt!KeAddSystemServiceTable:
    805ba589 8bff            mov    edi,edi
    805ba58b 55              push    ebp
    805ba58c 8bec            mov    ebp,esp
    805ba58e 837d1803        cmp    dword ptr [ebp+18h],3
    805ba592 774e            ja      nt!KeAddSystemServiceTable+0x6b (805ba5e2)
    805ba594 8b4518          mov    eax,dword ptr [ebp+18h]
    805ba597 c1e004          shl    eax,4
    805ba59a 83b880a6558000  cmp    dword ptr nt!KeServiceDescriptorTable (8055a680)[eax],0
    805ba5a1 753f            jne    nt!KeAddSystemServiceTable+0x6b (805ba5e2)
    805ba5a3 8d8840a65580    lea    ecx,nt!KeServiceDescriptorTableShadow (8055a640)[eax]
    805ba5a9 833900          cmp    dword ptr [ecx],0
    805ba5ac 7534            jne    nt!KeAddSystemServiceTable+0x6b (805ba5e2)
    805ba5ae 837d1801        cmp    dword ptr [ebp+18h],1
    805ba5b2 8b5508          mov    edx,dword ptr [ebp+8]
    805ba5b5 56              push    esi
    805ba5b6 8b7510          mov    esi,dword ptr [ebp+10h]
    805ba5b9 57              push    edi
    805ba5ba 8b7d14          mov    edi,dword ptr [ebp+14h]
    805ba5bd 8911            mov    dword ptr [ecx],edx
    805ba5bf 8b4d0c          mov    ecx,dword ptr [ebp+0Ch]
    805ba5c2 898844a65580    mov    dword ptr nt!KeServiceDescriptorTableShadow+0x4 (8055a644)[eax],ecx
    805ba5c8 89b048a65580    mov    dword ptr nt!KeServiceDescriptorTableShadow+0x8 (8055a648)[eax],esi
    805ba5ce 89b84ca65580    mov    dword ptr nt!KeServiceDescriptorTableShadow+0xc (8055a64c)[eax],edi
    805ba5d4 0f855a3e0300    jne    nt!KeAddSystemServiceTable+0x4d (805ee434)
    805ba5da 5f              pop    edi
    805ba5db b001            mov    al,1
    805ba5dd 5e              pop    esi
    805ba5de 5d              pop    ebp
    805ba5df c21400          ret    14h
    805ba5e2 32c0            xor    al,al
    805ba5e4 ebf8            jmp    nt!KeAddSystemServiceTable+0x6d (805ba5de)
    805ba5e6 90              nop
    805ba5e7 90              nop
    805ba5e8 90              nop
    805ba5e9 90              nop
    805ba5ea 90              nop

搜索办法很简单
就是找到
805ba5a3 8d8840a65580    lea    ecx,nt!KeServiceDescriptorTableShadow (8055a640)[eax]
的8d88 40a65580    lea    ecx, nt!KeServiceDescriptorTableShadow
代码如下
Copy code

    void GetKeServiceDescriptorTableShadow()
    {
        PUCHAR cPtr, pOpcode;
        ULONG Length;
     
        for (cPtr = (PUCHAR)KeAddSystemServiceTable;
            cPtr < (PUCHAR)KeAddSystemServiceTable + PAGE_SIZE;
            cPtr += Length)
        {
            if (!MmIsAddressValid(cPtr)) break;

            Length = SizeOfCode(cPtr, &pOpcode);

            if (!Length || (Length == 1 && *pOpcode == 0xC3)) break;
         
            if (*(PUSHORT)pOpcode == 0x888D)
            {
                KeServiceDescriptorTableShadow = *(PVOID *)(pOpcode + 2);
                break;
            }
        }
    }

3。从KTHREAD.ServiceTable里面搜索
如果是GUI线程 那么就是指向ServiceDescriptorTableShadow
其实个人更加倾向这个办法 稳定安全可靠,上面那个代码也许会受到以后系统代码变化影响 但是这个肯定不会

Copy code

    GetServiceDescriptorTableShadowAddress proc uses esi edi ebx

    local dwThreadId:DWORD

        xor ebx, ebx                ; = NULL. Assume ServiceDescriptorTableShadow will be not found

        mov eax, KeServiceDescriptorTable
        mov esi, [eax]

        ; Find KTHREAD.ServiceTable field
        ; For non-GUI threads this field == KeServiceDescriptorTable
        ; and it points to ServiceDescriptorTable
        ; For GUI threads
        ; ServiceDescriptorTableShadow

        invoke KeGetCurrentThread
        mov edi, 200h-4
        .while edi
            .break .if dword ptr [eax][edi] == esi
            dec edi
        .endw

        .if edi != 0
            ; edi = offset to ServiceTable field in KTHREAD structure
            mov dwThreadId, 080h
            .while dwThreadId < 400h
                push eax                    ; reserve DWORD on stack
                invoke PsLookupThreadByThreadId, dwThreadId, esp
                pop ecx                        ; -> ETHREAD/KTHREAD
                .if eax == STATUS_SUCCESS
                    push dword ptr [ecx][edi]
                    fastcall ObfDereferenceObject, ecx
                    pop eax
                    .if eax != esi
                        mov edx, MmSystemRangeStart
                        mov edx, [edx]
                        mov edx, [edx]
                        .if eax > edx        ; some stupid error checking
                            mov ebx, eax
                            invoke DbgPrint, $CTA0("FindShadowTable: Found in thread with ID: %X\n"), dwThreadId
                            .break
                        .endif
                    .endif
                .endif
                add dwThreadId, 4
            .endw
        .endif

        mov eax, ebx
        ret

    GetServiceDescriptorTableShadowAddress endp

4.mj0011所说的搜索有效内存地址的办法
Copy code

    /*
    define structure for the system service table
    */
    struct SYS_SERVICE_TABLE {
    void **ServiceTable;
    unsigned long CounterTable;
    unsigned long ServiceLimit;
    void **ArgumentsTable;
    };

    SYSTEM_DESCRIPTOR_TABLE KeServiceDescriptorTableShadow;

    /*
    Define KeServiceDescriptorTable based on the SST structure
    */
    extern struct SYS_SERVICE_TABLE *KeServiceDescriptorTable;

    /*
    Declare function GetServiceDescriptorShadowTableAddress()
    */
    //struct SYS_SERVICE_TABLE * GetServiceDescriptorShadowTableAddress ();
    /*
    Declare the KeAddSystemServiceTable. This is just a
    handle to the call function, it will be used by the function
    above to obtain the correct address of the KeServiceDescriptorShadowTable
    */
    __declspec(dllimport) KeAddSystemServiceTable (ULONG, ULONG, ULONG, ULONG, ULONG);

    struct SYS_SERVICE_TABLE * GetServiceDescriptorShadowTableAddress ()
    {
    // First, obtain a pointer to KeAddSystemServiceTable
    unsigned char *check = (unsigned char*)KeAddSystemServiceTable;
    int i;
      //Initialize an instance of System Service Table, will be used to
    //obtain an address from KeAddSystemServiceTable
    struct SYS_SERVICE_TABLE *rc=0;
    // Make 100 attempts to match a valid address with that of KeServiceDescriptorTable
    for (i=0; i<=99; i++) {
      __try {
      // try to obtain an address from  KeAddSystemServiceTable
      rc = *(struct SYS_SERVICE_TABLE**)check;
      // if this address is NOT valid OR it itself is the address of
      //KeServiceDescriptorTable OR its first entry is NOT equal
      //to the first entry of KeServiceDescriptorTable
      if (!MmIsAddressValid (rc) || (rc == KeServiceDescriptorTable)
        || (memcmp (rc, KeServiceDescriptorTable, sizeof (*rc)) != 0)) {
        // Proceed with the next address
        check++;
        // don't forget to reset the old address
        rc = 0;
      }
      } __except (EXCEPTION_EXECUTE_HANDLER) { rc = 0; }
      // when the loop is completed, check if it produced a valid address
      if (rc)
      // because if it didn't, we failed to find the address of KeServiceDescriptorTableShadow
      break;
    }
    // otherwise, there is a valid address! So return it!
    return rc;
    }

二。函数名定位
这个似乎没有多少好办法,解析pdb可以是可以 但是很麻烦
不过还好的是 同一个版本的系统 调用号一样
所以只需要3套 2k xp 2k3的调用号就可以完成hook
pdb办法
SymInitialize初始化
SymSetSearchPath “srv**symbols*http://msdl.microsoft.com/”
SymLoadModule
SymGetSymFromName
老v曾经发出1个获取shadow地址和函数名称的工具 使用他可以很方便的获取
具体代码可以F5查看
以上读取pdb的方法测试使用的可以的
但是实际使用很麻烦

经过分析 我找到了1个比较好的定位办法
我们以SetWindowsHookExA为例子
Copy code

    .text:77D311D1 ; HHOOK __stdcall SetWindowsHookExA(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId)
    .text:77D311D1                public _SetWindowsHookExA@16
    .text:77D311D1 _SetWindowsHookExA@16 proc near
    .text:77D311D1
    .text:77D311D1 idHook          = dword ptr  8
    .text:77D311D1 lpfn            = dword ptr  0Ch
    .text:77D311D1 hModule        = dword ptr  10h
    .text:77D311D1 dwThreadId      = dword ptr  14h
    .text:77D311D1
    .text:77D311D1                mov    edi, edi
    .text:77D311D3                push    ebp
    .text:77D311D4                mov    ebp, esp
    .text:77D311D6                push    2              ; int
    .text:77D311D8                push    [ebp+dwThreadId] ; int
    .text:77D311DB                push    [ebp+hModule]  ; hModule
    .text:77D311DE                push    [ebp+lpfn]      ; int
    .text:77D311E1                push    [ebp+idHook]    ; int
    .text:77D311E4                call    _SetWindowsHookExAW@20 ; SetWindowsHookExAW(x,x,x,x,x)
    .text:77D311E9                pop    ebp
    .text:77D311EA                retn    10h
    .text:77D311EA _SetWindowsHookExA@16 endp

    .text:77D2DCFD ; int __stdcall SetWindowsHookExAW(int, int, HMODULE hModule, int, int)
    .text:77D2DCFD _SetWindowsHookExAW@20 proc near        ; CODE XREF: SetWindowsHookExW(x,x,x,x)+13p
    .text:77D2DCFD                                        ; SetWindowsHookExA(x,x,x,x)+13p
    .text:77D2DCFD
    .text:77D2DCFD Filename        = word ptr -20Ch
    .text:77D2DCFD var_4          = dword ptr -4
    .text:77D2DCFD arg_0          = dword ptr  8
    .text:77D2DCFD arg_4          = dword ptr  0Ch
    .text:77D2DCFD hModule        = dword ptr  10h
    .text:77D2DCFD arg_C          = dword ptr  14h
    .text:77D2DCFD arg_10          = dword ptr  18h
    .text:77D2DCFD
    .text:77D2DCFD                mov    edi, edi
    .text:77D2DCFF                push    ebp
    .text:77D2DD00                mov    ebp, esp
    .text:77D2DD02                sub    esp, 20Ch
    .text:77D2DD08                mov    eax, ___security_cookie
    .text:77D2DD0D                push    esi
    .text:77D2DD0E                mov    esi, [ebp+hModule]
    .text:77D2DD11                test    esi, esi
    .text:77D2DD13                push    edi
    .text:77D2DD14                mov    edi, [ebp+arg_4]
    .text:77D2DD17                mov    [ebp+var_4], eax
    .text:77D2DD1A                jz      short loc_77D2DD33
    .text:77D2DD1C                push    104h            ; nSize
    .text:77D2DD21                lea    eax, [ebp+Filename]
    .text:77D2DD27                push    eax            ; lpFilename
    .text:77D2DD28                push    esi            ; hModule
    .text:77D2DD29                call    ds:__imp__GetModuleFileNameW@12 ; GetModuleFileNameW(x,x,x)
    .text:77D2DD2F                test    eax, eax
    .text:77D2DD31                jz      short loc_77D2DD52
    .text:77D2DD33
    .text:77D2DD33 loc_77D2DD33:                          ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+1Dj
    .text:77D2DD33                push    [ebp+arg_10]
    .text:77D2DD36                mov    eax, esi
    .text:77D2DD38                push    edi
    .text:77D2DD39                push    [ebp+arg_0]
    .text:77D2DD3C                neg    eax
    .text:77D2DD3E                push    [ebp+arg_C]
    .text:77D2DD41                sbb    eax, eax
    .text:77D2DD43                lea    ecx, [ebp+Filename]
    .text:77D2DD49                and    eax, ecx
    .text:77D2DD4B                push    eax
    .text:77D2DD4C                push    esi
    .text:77D2DD4D                call    __SetWindowsHookEx@24 ; _SetWindowsHookEx(x,x,x,x,x,x)
    .text:77D2DD52
    .text:77D2DD52 loc_77D2DD52:                          ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+34j
    .text:77D2DD52                mov    ecx, [ebp+var_4]
    .text:77D2DD55                pop    edi
    .text:77D2DD56                pop    esi
    .text:77D2DD57                call    @__security_check_cookie@4 ; __security_check_cookie(x)
    .text:77D2DD5C                leave
    .text:77D2DD5D                retn    14h
    .text:77D2DD5D _SetWindowsHookExAW@20 endp
    .text:77D2DD5D

    .text:77D2DD65 ; __stdcall _SetWindowsHookEx(x, x, x, x, x, x)
    .text:77D2DD65 __SetWindowsHookEx@24 proc near        ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+50p
    .text:77D2DD65
    .text:77D2DD65 var_10          = byte ptr -10h
    .text:77D2DD65 var_8          = dword ptr -8
    .text:77D2DD65 var_4          = dword ptr -4
    .text:77D2DD65 arg_0          = dword ptr  8
    .text:77D2DD65 arg_4          = dword ptr  0Ch
    .text:77D2DD65 arg_8          = dword ptr  10h
    .text:77D2DD65 arg_C          = dword ptr  14h
    .text:77D2DD65 arg_10          = dword ptr  18h
    .text:77D2DD65 arg_14          = dword ptr  1Ch
    .text:77D2DD65
    .text:77D2DD65                mov    edi, edi
    .text:77D2DD67                push    ebp
    .text:77D2DD68                mov    ebp, esp
    .text:77D2DD6A                sub    esp, 10h
    .text:77D2DD6D                push    [ebp+arg_4]
    .text:77D2DD70                and    [ebp+var_4], 0
    .text:77D2DD74                lea    eax, [ebp+var_10]
    .text:77D2DD77                push    eax
    .text:77D2DD78                mov    [ebp+var_8], eax
    .text:77D2DD7B                call    ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
    .text:77D2DD81                push    [ebp+arg_14]
    .text:77D2DD84                push    [ebp+arg_10]
    .text:77D2DD87                push    [ebp+arg_C]
    .text:77D2DD8A                push    [ebp+arg_8]
    .text:77D2DD8D                push    [ebp+var_8]
    .text:77D2DD90                push    [ebp+arg_0]
    .text:77D2DD93                call    _NtUserSetWindowsHookEx@24 ; NtUserSetWindowsHookEx(x,x,x,x,x,x)
    .text:77D2DD98                leave
    .text:77D2DD99                retn    18h
    .text:77D2DD99 __SetWindowsHookEx@24 endp
    .text:77D2DD99

    .text:77D2DDA1 ; __stdcall NtUserSetWindowsHookEx(x, x, x, x, x, x)
    .text:77D2DDA1 _NtUserSetWindowsHookEx@24 proc near    ; CODE XREF: _SetWindowsHookEx(x,x,x,x,x,x)+2Ep
    .text:77D2DDA1                mov    eax, 1225h
    .text:77D2DDA6                mov    edx, 7FFE0300h
    .text:77D2DDAB                call    dword ptr [edx]
    .text:77D2DDAD                retn    18h
    .text:77D2DDAD _NtUserSetWindowsHookEx@24 endp

我们看这里 .text:77D2DDA1                mov    eax, 1225h
里面有和ssdt一样的调用号 但是有个问题 NtUserSetWindowsHookEx在user32里面没有导出,我们需要多次搜索才行
Copy code

    HHOOK STDCALL    NtUserSetWindowsHookEx (HINSTANCE Mod, PUNICODE_STRING UnsafeModuleName, DWORD ThreadId, int HookId, HOOKPROC HookProc, BOOL Ansi)

这个就是原型 不过搜索起来确实是比较麻烦的,不知道还有无其他好办法了

看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

收藏
点赞1
打赏
分享
最新回复 (37)
雪    币: 308
活跃值: 活跃值 (76)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
zhuwg 活跃值 11 2007-12-22 22:02
2
0
三。如何hook
似乎这个问题并不大,shadow ssdt和ssdt本质上都是1个地址表,最为简单的方法是把你的函数替换地址表的对应项,具体hook代码甚至可以完全照抄ssdt的,这里只说1下几个偶遇到的小问题
1。win32k.sys不是常在内存的,如果不是GUI线程,shadow ssdt地址无效
解决办法:
1。在driverdispatch中hokk
driverdispatch是位于执行driveriocontrol的线程上下文的
我们使用1个GUI线程去driveriocontrol

2。attachtoprocess
通常 我们使用cerss.exe
Copy code

    HANDLE GetCsrPid()
    {
        HANDLE                        Process, hObject;
        HANDLE                        CsrId = (HANDLE)0;
        OBJECT_ATTRIBUTES            obj;
        CLIENT_ID                    cid;
        UCHAR                        Buff[0x100];
        POBJECT_NAME_INFORMATION      ObjName = (PVOID)&Buff;
        PSYSTEM_HANDLE_INFORMATION_EX Handles;
        ULONG                        r;

        Handles = GetInfoTable(SystemHandleInformation);

        if (!Handles) return CsrId;

        for (r = 0; r < Handles->NumberOfHandles; r++)
        {
            if (Handles->Information[r].ObjectTypeNumber == 21) //Port object
            {
                InitializeObjectAttributes(&obj, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
            
                cid.UniqueProcess = (HANDLE)Handles->Information[r].ProcessId;
                cid.UniqueThread  = 0;

                if (NT_SUCCESS(NtOpenProcess(&Process, PROCESS_DUP_HANDLE, &obj, &cid)))
                {
                    if (NT_SUCCESS(ZwDuplicateObject(Process,
                                                    (HANDLE)Handles->Information[r].Handle,
                                                    NtCurrentProcess(),
                                                    &hObject,
                                                      0, 0, DUPLICATE_SAME_ACCESS)))
                    {
                        if (NT_SUCCESS(ZwQueryObject(hObject,
                                                    ObjectNameInformation,
                                                        ObjName,
                                                    0x100, NULL)))
                        {
                            if (ObjName->Name.Buffer &&
                                !wcsncmp(L"\\Windows\\ApiPort", ObjName->Name.Buffer, 20))
                            {
                                CsrId = (HANDLE)Handles->Information[r].ProcessId;
                            }
                        }

                        ZwClose(hObject);
                    }

                    ZwClose(Process);
                }
            }
        }

        ExFreePool(Handles);

        return CsrId;
    }

然后我们KeAttachProcess
Copy code

      ntStatus = PsLookupProcessByProcessId(GetCsrPid(), &EProcess);

            if (!NT_SUCCESS( ntStatus ))
      {

      DbgPrint("PsLookupProcessByProcessId()\n");
                  return ntStatus;
      }

            KeAttachProcess(EProcess);

3.使用MDL映射一块不分页内存,设置成可以写入
不分页内存  ExAllocatePool(NonPagedPool
即为不会被切换到pagefile的内存,是常驻在物理内存的,比较希缺,使用完毕以后记住释放掉
参考regmon的代码
Copy code

    PVOID *
    RegmonMapServiceTable(
      SERVICE_HOOK_DESCRIPTOR **ServiceIsHooked
      )
    {
      *ServiceIsHooked = (SERVICE_HOOK_DESCRIPTOR*)
                  ExAllocatePool(NonPagedPool, KeServiceDescriptorTable->TableSize);

      if( *ServiceIsHooked )
      {
        RtlZeroMemory(*ServiceIsHooked, KeServiceDescriptorTable->TableSize);

        KeServiceTableMdl = MmCreateMdl(0, KeServiceDescriptorTable->ServiceTable, KeServiceDescriptorTable->TableSize << 2);

        if( KeServiceTableMdl )
        {
            MmBuildMdlForNonPagedPool(KeServiceTableMdl);

            KeServiceTableMdl->MdlFlags |= 1;

            return (PVOID*)MmMapLockedPages(KeServiceTableMdl, KernelMode);
        }
      }

      return NULL;
    };

看起来shadowssdt何hook本身并没有什么难度
只要正确定位了地址,hook起来和ssdt完全一样
unhook不在重复 完全抄ssdt的代码即可

四。checkhook
hook用的人多了 难免出现重复hook的情况 为了实现和别人的东西和平共处,最好hook之前做个检测
hook的方法是固定的,就那么2种 改地址表 或者inline hook,我们分别来对待
1。修改地址表的hook
  这个对付起来几乎不用特别对待,只是你保存的OldFunc是其他的程序hook函数所在的地址,判断方法很简单,看看这个地址在不在win32k的模块里面,和平共处的办法是你的myfunc里面call别人的hook函数,当然 如果你不想要别人的hook了,直接搜索到原始地址call之也是可行的
另外说1下 在别人的函数里面搜索原始地址 用MmIsAddressValid+ismodulewin32k
可以判断(这个办法好像是在mj文章里面看到的 记不清了)
2。inlinehook
这个比较麻烦了 如果是修改前面5个字节还好,直接替换处理都可以不改变
麻烦是函数中间地方被hook,盲目修改可能直接BSOD了,这个需要1个反汇编引擎来帮助分析代码字节长度,海风大大的hooklib很不错 可以完成这个功能

五,简单说1下restore
Copy code

    pWin32k = GetModuleHandle("win32k.sys");
    SdtRVA = &KeServiceDescriptorTable->win32k.ServiceTable - pWin32k ;
    w32kCopy = LoadPeFile("win32k.sys");
    ProcessRelocs(w32kCopy, pWin32k);
    memcpy(&KeServiceDescriptorTable->win32k.ServiceTable, w32kCopy + SdtRVA, SdtSize);

不过 其实可以不用ProcessRelocs处理
因为你可以使用lordpe查看1下 win32k.sys的默认基址就是系统加载的基址
可以不需要ProcessRelocs,不过为了更加方便和通用,这么做也可以的

休息去咯---
雪    币: 162
活跃值: 活跃值 (65)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
wyfe 活跃值 2007-12-22 23:36
3
0
坐下来,好好学习zhuwg的文章
雪    币: 212
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fool 活跃值 2007-12-23 13:40
4
0
收藏先,慢慢学习
雪    币: 482
活跃值: 活跃值 (938)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
sudami 活跃值 25 2007-12-23 14:14
5
0
LZ在EST发的这些曲高和寡啊~

好文。学习~~~
雪    币: 142
活跃值: 活跃值 (37)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
小楼 活跃值 1 2007-12-23 14:54
6
0
ssdt在哪里?在ntoskrnl.exe中。内存中ntoskrnl.exe位置在80000000以上,直接读写不方便。

所以我以前做的时候,先读取硬盘上的ntoskrnl.exe文件,根据export table定位原始的SSDT,再用物理内存与虚拟内存映射的办法转换,得到系统中SSDT的位置,对此进行处理(rootkit检测,hook/unhook)
雪    币: 308
活跃值: 活跃值 (76)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
zhuwg 活跃值 11 2007-12-23 21:03
7
0
大牛隐藏在这里 被偶抓住了 嘿嘿
EST得大牛都很忙 没人理偶啊
雪    币: 1298
活跃值: 活跃值 (63)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
yijun8354 活跃值 12 2007-12-24 21:03
8
0
先收藏再学习~~~
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
robar 活跃值 2007-12-28 16:16
9
0
lkd> u KeAddSystemServiceTable l 40
在我的机器上执行后,并没有看到KeServiceDescriptorTableShadow的相关汇编指令啊?只有KeServiceDescriptorTable的相关汇编指令...请问楼主需要什么前提条件吗?
雪    币: 205
活跃值: 活跃值 (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
okdodo 活跃值 2 2008-1-3 17:27
10
0
二。函数名定位
这个似乎没有多少好办法   
雪    币: 2071
活跃值: 活跃值 (14)
能力值: (RANK:170 )
在线值:
发帖
回帖
粉丝
Aker 活跃值 4 2008-1-4 00:47
11
0
学习,是不是页表转换的方法,支持大虾写个文章出来,呵呵呵
雪    币: 101
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sailmer 活跃值 2008-1-4 09:00
12
0

好问,跟SSDT的相关操作没有本质的区别
雪    币: 101
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sailmer 活跃值 2008-1-4 09:02
13
0
SSDTRestore 0.2就是这样做的
你找下相关的代码看下
雪    币: 109
活跃值: 活跃值 (197)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
combojiang 活跃值 26 2008-1-18 09:11
14
0
lkd>  u KeAddSystemServiceTable l 40
nt!KeAddSystemServiceTable:
80596a56 8bff             mov     edi,edi
80596a58 55               push    ebp
80596a59 8bec             mov     ebp,esp
80596a5b 837d1803         cmp     dword ptr [ebp+0x18],0x3
80596a5f 7760             ja      nt!KeAddSystemServiceTable+0x6b (80596ac1)
80596a61 8b4518           mov     eax,[ebp+0x18]
80596a64 c1e004           shl     eax,0x4
80596a67 83b88035558000   cmp     dword ptr [eax+0x80553580],0x0
80596a6e 7551             jnz     nt!KeAddSystemServiceTable+0x6b (80596ac1)
80596a70 8d8840355580     lea     ecx,[eax+0x80553540]
80596a76 833900           cmp     dword ptr [ecx],0x0
80596a79 7546             jnz     nt!KeAddSystemServiceTable+0x6b (80596ac1)
80596a7b 837d1801         cmp     dword ptr [ebp+0x18],0x1
80596a7f 8b5508           mov     edx,[ebp+0x8]
80596a82 56               push    esi
80596a83 8b7510           mov     esi,[ebp+0x10]
80596a86 57               push    edi
80596a87 8b7d14           mov     edi,[ebp+0x14]
80596a8a 8911             mov     [ecx],edx
80596a8c 8b4d0c           mov     ecx,[ebp+0xc]
80596a8f 898844355580     mov     [eax+0x80553544],ecx
80596a95 89b048355580     mov     [eax+0x80553548],esi
80596a9b 89b84c355580     mov     [eax+0x8055354c],edi
80596aa1 7418             jz      nt!KeAddSystemServiceTable+0x65 (80596abb)
80596aa3 899080355580     mov     [eax+0x80553580],edx
80596aa9 898884355580     mov     [eax+0x80553584],ecx
80596aaf 89b088355580     mov     [eax+0x80553588],esi
80596ab5 89b88c355580     mov     [eax+0x8055358c],edi
80596abb 5f               pop     edi
80596abc b001             mov     al,0x1
80596abe 5e               pop     esi
80596abf eb02             jmp     nt!KeAddSystemServiceTable+0x6d (80596ac3)
80596ac1 32c0             xor     al,al
80596ac3 5d               pop     ebp
80596ac4 c21400           ret     0x14
80596ac7 cc               int     3
80596ac8 cc               int     3
80596ac9 cc               int     3
80596aca cc               int     3
80596acb cc               int     3
上面是我机器里 看到的KeAddSystemServiceTable反编译。它对应的码是
8d8840355580     lea     ecx,[eax+0x80553540]
机器码跟楼主的机器码不同。
雪    币: 109
活跃值: 活跃值 (197)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
combojiang 活跃值 26 2008-1-18 13:57
15
0
原创】shadow ssdt学习笔记(二)中
Handles = GetInfoTable(SystemHandleInformation);
GetInfoTable这个函数的实现没有帖出来.实际上还是用ZwQuerySystemInformation来实现的.
雪    币: 1068
活跃值: 活跃值 (20)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
DiKeN 活跃值 5 2008-1-20 19:09
16
0
方法三是有问题的,HOOK SSDT还是没问题,HOOK SSDT Shadow是有问题,不知道你尝试过没有。

mjxxx的方法可以参见卡巴的FindSystemServiceDescriptionTableShadow基本一致,
卡巴的相对安全一些:)

我HOOK Shadow使用的是修改ServerPointer. 指向我自己的区域。然后转跳到原始的里面去
雪    币: 308
活跃值: 活跃值 (76)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
zhuwg 活跃值 11 2008-1-20 19:33
17
0
combojiang:
你仔细看1下 是地址不同 操作一样 都是lea     ecx

DiKeN“
感谢大牛对偶小文的关注 确实存在问题
我想应该是GUI线程的问题

参考
我们所说的线程,实际上分为核心态和用户态两部分。Win32下这两者基本上是1对1的关系,其他平台如Solaris或2.4以前的Linux则使用不同的映射模型。而Win32系统中核心态的线程,实际上也分为两类:非GUI和GUI线程。前者是建立核心线程的缺省类型,后者在线程第一次使用 Win32k.sys系统服务时自动转换,或者使用PsConvertToGuiThread函数(ntos\ps\psquery.c:3247)显式转换。两者之间的区别主要在于使用的资源缺省大小不同,以及使用的系统服务描述表不同。这也是为什么系统服务描述表要分为KeServiceDescriptorTable和KeServiceDescriptorTableShadow的原因之一,后者包括前者没有的对GDI服务的入口函数地址,一般在Win32k.sys中实现。核心线程对象的ETHREAD::KTHREAD:: ServiceTable字段保存了此线程适用的系统调用服务表地址,此字段也被PsConvertToGuiThread函数用于判断线程类型。功能与Windows XP/2003提供的IsGUIThread函数类型。
使用上我们可以创建一个线程,此线程不做任何实际工作,只是根据我们要取哪个系统服务描述表来决定是否调用GDI函数
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
creatorwm 活跃值 2008-1-27 16:00
18
0
哦呵呵
             偶来好好的学习哈
    先谢谢楼主了
雪    币: 148
活跃值: 活跃值 (130)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
likecrack 活跃值 2008-4-21 15:49
19
0
我取SSDT Shadow的地址。很简单。用KeServiceDescriptorTable+16就得到了
取函数地址。只要知道函序在表中导出的序号,跟取KeServiceDescriptorTable的一样.
可能在不同的系统里面不一样。不通用吧
雪    币: 173
活跃值: 活跃值 (10)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
Maggle 活跃值 1 2008-4-22 16:46
20
0
又是hook,hook、hook......
能换点新鲜的不?
雪    币: 302
活跃值: 活跃值 (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
烽火sheng 活跃值 2008-5-2 20:47
21
0
学习中.期待中.!
雪    币: 202
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
avensun 活跃值 2008-5-5 22:47
22
0
好东西,值得收藏
雪    币: 204
活跃值: 活跃值 (68)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
菜菜小J 活跃值 2008-5-8 14:28
23
0
收藏了,留待学习:-) 感谢。
雪    币: 203
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
黄哈哈 活跃值 2008-9-29 23:13
24
0
创建时间:2008-03-13
文章属性:不是我的!!^_^好东西大家分享(不知怎么发帖子^_^!原创作者请原谅!对不起了原创作者!)
文章提交:秘密(对不起了原创作者)

/*
标 题: 【原创】“机器狗”病毒驱动部分逆向分析注释(C代码)
作 者: dream2fly
时 间: 2008.03.13 于深圳科技园
版 本: 1.0

【软件名称】: 机器狗(病毒)
【下载地址】: 自己搜索下载
【加壳方式】: 未知壳
【编写语言】: MASM
【使用工具】: IDA
【操作平台】: win2003
【软件介绍】: 穿透冰点型带驱动病毒
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!

*/
#include <ntddk.h>          // various NT definitions

#define IOCTL_MYDEV_BASE             0xF000
#define IOCTL_MYDEV_Fun_0xF01        CTL_CODE(IOCTL_MYDEV_BASE, 0xF01, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define DR0_DEVICE_NAME                        "\\Device\\Harddisk0\\DR0"
#define NT_DEVICE_NAME                        "\\Device\\PhysicalHardDisk0"
#define DOS_DEVICE_NAME                        "\\DosDevices\\PhysicalHardDisk0"

PDEVICE_OBJECT g_DR0_DeviceObject;
PDEVICE_OBJECT g_OldAttachedDeviceOfDR0;
VOID*   g_ResData;
SIZE_T  g_ResDataSize;

typedef struct _idtr
{
    //定义中断描述符表的限制,长度两字节;
    short        IDTLimit;
    //定义中断描述服表的基址,长度四字节;
    unsigned int    IDTBase;
}IDTR,*PIDTR;

typedef struct _idtentry
{
    //中断执行代码偏移量的底16位;
    unsigned short    OffsetLow;
    //选择器,也就是寄存器;
    unsigned short    Selector;
    //保留位,始终为零;
    unsigned char        Reserved;
    //IDT中的门的类型:包括中断门,陷阱门和任务门;
    unsigned char        Type:4;
    //段标识位;
    unsigned char        SegmentFlag:1;
    //中断门的权限等级,0表示内核级,3表示用户级;
    unsigned char        DPL:2;
    //呈现标志位;
    unsigned char        Present:1;
    //中断执行代码偏移量的高16位;
    unsigned short    OffsetHigh;
}IDTENTRY,*PIDTENTRY;

#define HOOKINTID_09 9                //NPX Segment Overrun
#define HOOKINTID_0E 0x0E        //Page Fault

VOID CheckIdt()//用SIDT指令得到中断向量啊,然后修改中断向量入口地址
{
        int INT_09_Address_High8;
        int INT_0E_Address_High8;
        unsigned long OldISR_09;
        unsigned long OldISR_0E;

        //保存IDT入口的基地址和限制信息的数据结构;
    IDTR        idtr;//store   interrupt   descript   table   register. to  idtr

            //记录IDT数组的指针,通过它可以查找到我们需要Hook中断号对应的中断门;
    PIDTENTRY    IdtEntry;

    //汇编指令sidt,获取IDT入口信息;
    __asm sidt    idtr

    //赋予IDT基地址值;
    IdtEntry = (PIDTENTRY)idtr.IDTBase;

    //保存中断号HOOKINTID对应中断门所指向的执行代码偏移量,以备执行中断处理或恢复时使用
        OldISR_09 = ((unsigned int)IdtEntry[HOOKINTID_09].OffsetHigh << 16) | (IdtEntry[HOOKINTID_09].OffsetLow);

        INT_09_Address_High8 = OldISR_09&0x0FF000000;

        /*
        这两句汇编代码什么意思?eax相减应该总是0,那么 jz不总是跳转返回了???
        有知道的大侠告诉我dream2fly(QQ:838468959)
        sub     eax, eax
        jz      short FunctionExit
        难道是?
        if (INT_09_Address_High8 == 0)
                return;
        */

        //保存中断号HOOKINTID对应中断门所指向的执行代码偏移量,以备执行中断处理或恢复时使用;
        OldISR_0E = ((unsigned int)IdtEntry[HOOKINTID_0E].OffsetHigh << 16) | (IdtEntry[HOOKINTID_0E].OffsetLow);

        INT_0E_Address_High8 = OldISR_0E&0x0FF000000;

        if (INT_09_Address_High8 != INT_0E_Address_High8)//检查0E是不是被HOOK
        {                 
                //关中断
                __asm cli
               
                IdtEntry[HOOKINTID_0E].OffsetHigh = 0;// 作者此处没关中断,难道不bosd?
        
                //开中断
                __asm sti
        }
}

/*
通过搜索地址来查找自己的加载地址
查找驱动文件的资源中的1000/1000,并复制到一个全局缓冲区中
*/
VOID* SearchSelf()
{
        VOID* pSelfImage = NULL;
        VOID* pCurAddr = NULL;
        VOID* pTmpAddr = NULL;

//         loc_40045F:这个取当前地址用C怎么写?
//028 lea     ebx, loc_40045F
//028 and     ebx, 0FFFFFC00h

        //pSelfImage如何取?

        while(MmIsAddressValid(pSelfImage))
        {
                if ((unsigned long)pSelfImage <= 0x80000000)
                        return NULL;

                if (RtlEqualMemory(pSelfImage, "MZ", 2))
                {
                        pCurAddr = pSelfImage;
                        pTmpAddr = (VOID*)((unsigned long)pSelfImage+0x3C);
                        (unsigned long)pCurAddr += (unsigned long)(&pTmpAddr);

                        if (!MmIsAddressValid(pCurAddr))
                                return NULL;

                        if (RtlEqualMemory(pCurAddr, "PE", 2))
                                return pSelfImage;
                }

                (unsigned long)pSelfImage -= 0x400;//-1024K
        }
        
        return NULL;
}

SIZE_T ResLookupDataInDirectoryById(void* pSysBaseAddr, int id1, int id2, CHAR* pResDatas)
{
        // 有空再补上:)

        return 0;
}
//
// Device driver routine declarations.
//

NTSTATUS
DriverEntry(
    IN OUT PDRIVER_OBJECT   DriverObject,
    IN PUNICODE_STRING      RegistryPath
    );

NTSTATUS
CommonDispatch(
    IN PDEVICE_OBJECT       DeviceObject,
    IN PIRP                 Irp
    );

VOID
Unload(
    IN PDRIVER_OBJECT       DriverObject
    );

NTSTATUS
DriverEntry(
    IN OUT PDRIVER_OBJECT   DriverObject,
    IN PUNICODE_STRING      RegistryPath
    )
{
    NTSTATUS        ntStatus;
        CHAR*                pResData = NULL;
        ANSI_STRING                SourceString;
        PDEVICE_OBJECT  DeviceObject = NULL;    // ptr to device object
    UNICODE_STRING  SymbolicLinkName;   
        UNICODE_STRING  DeviceName;   
        VOID* pSelfImage;
        PDEVICE_OBJECT cur_device_object;
        PDEVICE_OBJECT next_device_object;

        CheckIdt();

        pSelfImage = SearchSelf();
        if (pSelfImage == NULL)
                return -1;

        g_ResDataSize = ResLookupDataInDirectoryById(pSelfImage, 1000, 1000, pResData);
        if (g_ResDataSize == 0)
        {
                return -1;
        }

        g_ResData = ExAllocatePool(NonPagedPool, g_ResDataSize);
        // 跳转到下条指令,延时 jmp short $+2

        RtlCopyMemory(g_ResData, pResData, g_ResDataSize);

        DriverObject->DriverUnload = Unload;
    DriverObject->MajorFunction[IRP_MJ_CREATE] =
    DriverObject->MajorFunction[IRP_MJ_CLOSE] =
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = CommonDispatch;

        // 为什么不用RtlInitUnicodeString( &ntUnicodeString, NT_DEVICE_NAME );代替
        RtlInitAnsiString(&SourceString, NT_DEVICE_NAME);
        RtlAnsiStringToUnicodeString(&DeviceName, &SourceString, TRUE);

        RtlInitAnsiString(&SourceString, DOS_DEVICE_NAME);
        RtlAnsiStringToUnicodeString(&SymbolicLinkName, &SourceString, TRUE);
   
    ntStatus = IoCreateDevice(
        DriverObject,                   // Our Driver Object
        0,                              // We don't use a device extension
        &DeviceName,                                       
        FILE_DEVICE_NULL,            // Device type
        0,     // Device characteristics //此处应该用FILE_DEVICE_SECURE_OPEN吧?
        FALSE,                          // Not an exclusive device
        &DeviceObject );                // Returned ptr to Device Object

        if ( !NT_SUCCESS( ntStatus ) )
        {
                goto End;
        }

        ntStatus = IoCreateSymbolicLink( &SymbolicLinkName, &DeviceName );

        if ( !NT_SUCCESS( ntStatus ) )
        {
                cur_device_object = DriverObject->DeviceObject;

                while (cur_device_object)
                {
                        next_device_object = DeviceObject->NextDevice;
                        IoDeleteDevice(cur_device_object);
                        cur_device_object = next_device_object;
                }
        }

End:
        RtlFreeUnicodeString(&DeviceName);
        RtlFreeUnicodeString(&SymbolicLinkName);

        return STATUS_SUCCESS;
}

VOID
Unload(
   IN PDRIVER_OBJECT DriverObject
    )
{
        ANSI_STRING                SourceString;
        PDEVICE_OBJECT  DeviceObject = NULL;    // ptr to device object
    UNICODE_STRING  SymbolicLinkName;   
        PDEVICE_OBJECT cur_device_object;
        PDEVICE_OBJECT next_device_object;

        if (g_ResData)
        {
                ExFreePool(g_ResData);
        }

        if (DriverObject)
        {
                RtlInitAnsiString(&SourceString, DOS_DEVICE_NAME);
                RtlAnsiStringToUnicodeString(&SymbolicLinkName, &SourceString, TRUE);

                IoDeleteSymbolicLink(&SymbolicLinkName);
                RtlFreeUnicodeString(&SymbolicLinkName);

                cur_device_object = DriverObject->DeviceObject;

                while (cur_device_object)
                {
                        next_device_object = DeviceObject->NextDevice;
                        IoDeleteDevice(cur_device_object);
                        cur_device_object = next_device_object;
                }
        }
}

NTSTATUS
CommonDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
        PDEVICE_OBJECT  DRO_DeviceObject = NULL;    // ptr to device object
        PFILE_OBJECT        DRO_FileObject;
        ANSI_STRING                SourceString;
    UNICODE_STRING  DRO_DeviceName;   

    PIO_STACK_LOCATION  irpSp;// Pointer to current stack location
    NTSTATUS            ntStatus = STATUS_SUCCESS;// Assume success
    ULONG               inBufLength; // Input buffer length
    ULONG               outBufLength; // Output buffer length

        Irp->IoStatus.Status = STATUS_SUCCESS;
        Irp->IoStatus.Information = 0;

    irpSp = IoGetCurrentIrpStackLocation( Irp );
    inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
    outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

    if(!inBufLength || !outBufLength)
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        goto End;
    }

        switch ( irpSp->MajorFunction )
        {
        case IRP_MJ_CREATE:
                RtlInitAnsiString(&SourceString, DR0_DEVICE_NAME);
                RtlAnsiStringToUnicodeString(&DRO_DeviceName, &SourceString, TRUE);

                IoGetDeviceObjectPointer(&DRO_DeviceName, 0x80,&DRO_FileObject, &DRO_DeviceObject);
                g_DR0_DeviceObject = DRO_FileObject->DeviceObject;

                //保存DR0上的附加设备,然后断开附加,等IRP_MJ_CLOSE时恢复附加
                if (DRO_FileObject->DeviceObject->AttachedDevice)
                {
                        g_OldAttachedDeviceOfDR0 = DRO_FileObject->DeviceObject->AttachedDevice;
                        DRO_FileObject->DeviceObject->AttachedDevice= NULL;
                }

                ObDereferenceObject(DRO_FileObject);

                RtlFreeUnicodeString(&DRO_DeviceName);

                break;
        case IRP_MJ_CLOSE:
                if (g_DR0_DeviceObject)
                {
                        if (g_OldAttachedDeviceOfDR0)
                        {
                                g_DR0_DeviceObject->AttachedDevice = g_OldAttachedDeviceOfDR0;
                        }
                }

                break;
        case IRP_MJ_DEVICE_CONTROL:
                if ( irpSp->Parameters.DeviceIoControl.IoControlCode == 0x0F0003C04)
                {
                        if (outBufLength < g_ResDataSize)
                                goto End;

                        // 此处就是提取驱动里的资源解码返回给ap层,很简单,不再反汇编了,此处省略
                        // 唯一不理解的是既然是双缓冲应该用IRP.AssociatedIrp.SystemBuffer返回给ap才是
                        // 难道此时Irp->AssociatedIrp.SystemBuffer和Irp->UserBuffer地址相同??
                        RtlCopyMemory(Irp->UserBuffer, g_ResData, g_ResDataSize);

                        Irp->IoStatus.Information = g_ResDataSize;
                }
                else
                {
                        ntStatus = STATUS_INVALID_DEVICE_REQUEST;
                }

                break;

        default:

                //
        // The specified I/O control code is unrecognized by this driver.
        //

        ntStatus = STATUS_INVALID_DEVICE_REQUEST;
        break;
    }

End:
    //
    // Finish the I/O operation by simply completing the packet and returning
    // the same status as in the packet itself.
    //

    Irp->IoStatus.Status = ntStatus;

    IoCompleteRequest( Irp, IO_NO_INCREMENT );

    return ntStatus;
}
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sinojiang 活跃值 2008-11-2 21:06
25
0
请问哪儿能查到SSDT Shadow表中的函数定义和参数说明啊?
游客
登录 | 注册 方可回帖
返回