R3获取SSDT的原始地址及SDT、SST、KiServiceTbale的关系

在内核下SSDT地址,可能被ssdt hook或者是inline hook,如果我们的ssdt被别人hook了,怎么改会原来的ssdt地址呢?我们知道在内核文件中是保留了一份原始的ssdt表,所以我们只要通过解析内核文件获取到原始的ssdt地址即可。

首先我们得确认我们当前系统使用的内核文件是ntoskrnl.exe、ntkrnlmp.exe还是ntkrnlpa.exe?这个问题好解决,ZwQuerySystemInformation传入SystemModuleInformation(11)得到系统模块列表,第一个模块就是系统当前使用的内核模块了。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NtQuerySystemInformation = (long(__stdcall*)(DWORD,PVOID,DWORD,DWORD))GetProcAddress( GetModuleHandle( "ntdll.dll" ),"NtQuerySystemInformation" );  
//通过NtQuerySystemInformation取得系统内核文件,判断为是ntoskrnl.exe ntkrnlmp.exe ntkrnlpa.exe
rc=NtQuerySystemInformation(SystemModuleInformation,pModules,4,(ULONG)&dwNeededSize);
if (rc==STATUS_INFO_LENGTH_MISMATCH) //如果内存不够
{
    pModules=(PMODULES)GlobalAlloc(GPTR,dwNeededSize) ; //重新分配内存
    rc=NtQuerySystemInformation(SystemModuleInformation,pModules,dwNeededSize,NULL); //系统内核文件是总是在第一个,枚举1次
}  
 
if (!NT_SUCCESS(rc))
{
    cout << "NtQuerySystemInformation() Failed !\n"; //NtQuerySystemInformation执行失败,检查当前进程权限
    return 0;
}
 
dwKernelBase=(DWORD)pModules->smi.Base;   // imagebase
pKernelName=pModules->smi.ModuleNameOffset+pModules->smi.ImageName;

这样我们就获得了系统当前使用内核文件的名字,然后我们手动加载这个内核文件,找到KeServiceDescriptorTable的地址,然后通过重定位信息找到KiServiceTable的地址,而KiServiceTable保存的就是我们所要找的ssdt数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
hKernel = LoadLibraryExA(pKernelName,0,DONT_RESOLVE_DLL_REFERENCES);   //加载NTOSKREL 的基址
if(!hKernel)
    return;

//在内核文件中查找KeServiceDescriptorTable地址
//这里得到的是相对虚拟地址
if(!(dwKSDT = (DWORD)GetProcAddress(hKernel,"KeServiceDescriptorTable")))
    return;

dwKSDT -= (DWORD)hKernel;

//获取KiServiceTable地址RVA
if(!(dwKiServiceTable = FindKiServiceTable(hKernel,dwKSDT)))
    return;

GetHeader((PCHAR)hKernel,&pfh,&poh,&psh);
dwServices = 0;

for(pService = (PDWORD)((DWORD)hKernel + dwKiServiceTable);*pService-poh->ImageBase < poh->SizeOfImage;pService++,dwServices++)
{
    ((pSSDTSaveTable)((ULONG)pSSDTST + dwServices * sizeof(SSDTSaveTable)))->ulOriginalFunctionAddress = \
        *pService-poh->ImageBase+dwKernelBase;
}

FreeLibrary(hKernel);

整个流程差不多就是这样,下面我们来详细的讲解一下。

首先既然找到了KeServiceDescriptorTable的地址,为什么还要去找KiServiceTable的地址?

我们先来弄清系统服务描述表(SDT,Service Descriptor Table)、系统服务表(SST,System Service Table)、系统服务地址表(KiServiceTable)之间的关系。

在WindowsNT系列操作系统中,有两种类型的系统服务,一种实现在内核文件中,是常用的系统服务;另一种实现在win32k.sys中,是一些与图形显示及用户界面相关的系统服务。这些系统服务在系统执行期间常驻于系统内存区中,并且他们的入口地址保存在两个系统服务地址表KiServiceTable和Win32pServiceTable中。而每个系统服务的入口参数所用的总字节数则分别保存在另外两个系统服务参数表(ArgumentTable)中。

系统服务地址表和系统参数表是一一对应的,每个系统服务表(一下简称SST)都指向一个地址表和一个参数表。在Windows 2000/xp/7系统中,只有两个SST。一个SST指向了KiServiceTable,而另一个SST则指向了Win32pServiceTable.

所有的SST都保存在系统服务描述表(SDT)中。系统中一共有两个SDT,一个是ServiceDescriptorTable,另一个是ServiceDescriptorTableShadow。ServiceDescriptor中只有指向KiServiceTable的SST,而ServiceDescriptorTableShadow则包含了所有的两个SST。SSDT是可以访问的,而SSDTShadow是不公开的。

windows内核文件导出了一个公开的变量KeServiceDecriptorTable,它指向了SSDT。在内核程序中可以直接使用这个变量,通过数据结构之间的关系,找到KiServiceTable,然后从KiServiceTable中查找任何一个系统服务的入口地址。

下面是关于这些数据结构的示意图:

image

我想在大家看完上面这段解释之后,应该有了比较清晰的认识!!!

所以我们虽然得到了KeServiceDescriptorTable的地址,但在文件中这个服务函数的入口地址是由KiServiceTable来保存的。

那么我们应该如何获取KiServiceTable的地址呢?

我们在Windows内核的源码中可以看到KiInitSystem这样的一个函数,这个函数初始化了一些比较重要也比较常见的内核数据结构,包括SSDT的初始化。所以我们可以从这里入手!!!先来看看KiInitSystem函数的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
VOID KiInitSystem (VOID)
{
  ULONG Index;

  // 初始化调度队列链表头,每一个优先级都有一个独立的进程链表
  for (Index = 0; Index < MAXIMUM_PRIORITY; Index += 1) {
    InitializeListHead(&KiDispatcherReadyListHead[Index]);
  }

  // 初始化BugCheck回调函数链表,及其旋转锁
  InitializeListHead(&KeBugCheckCallbackListHead);
  KeInitializeSpinLock(&KeBugCheckCallbackLock);

  // 初始化定时器过期的DPC对象
  KeInitializeDpc(&KiTimerExpireDpc,
    (PKDEFERRED_ROUTINE)KiTimerExpiration, NIL);

  // 初始化profile链表,及其旋转锁
  KeInitializeSpinLock(&KiProfileLock);
  InitializeListHead(&KiProfileListHead);

  // 初始化当前活动的profile链表
  InitializeListHead(&KiProfileSourceListHead);

  // 初始化定时器链表
  for (Index = 0; Index < TIMER_TABLE_SIZE; Index += 1) {
    InitializeListHead(&KiTimerTableListHead[Index]);
  }

  // 初始化swap通知事件
  KeInitializeEvent(&KiSwapEvent,SynchronizationEvent,FALSE);

  InitializeListHead(&KiProcessInSwapListHead);
  InitializeListHead(&KiProcessOutSwapListHead);
  InitializeListHead(&KiStackInSwapListHead);
  InitializeListHead(&KiWaitInListHead);
  InitializeListHead(&KiWaitOutListHead);

  // 初始化SSDT
  KeServiceDescriptorTable[0].Base = &KiServiceTable[0];
  KeServiceDescriptorTable[0].Count = NULL;
  KeServiceDescriptorTable[0].Limit = KiServiceLimit;
#if defined(_IA64_)
  KeServiceDescriptorTable[0].TableBaseGpOffset =
    (LONG)(*(KiServiceTable-1) - (ULONG_PTR)KiServiceTable);
#endif
  KeServiceDescriptorTable[0].Number = &KiArgumentTable[0];
  for (Index = 1; Index < NUMBER_SERVICE_TABLES; Index += 1) {
    KeServiceDescriptorTable[Index].Limit = 0;
  }

  // 拷贝SSDT到Shadow服务表
  RtlCopyMemory(KeServiceDescriptorTableShadow,
    KeServiceDescriptorTable,
    sizeof(KeServiceDescriptorTable));

  // ……
  return;
}

上面我们注意到 

KeServiceDescriptorTable[0].Base = &KiServiceTable[0];

这句。因此我们可以根据重定位信息和特征码来找到这句的地址,同时获取KiServiceTable的地址!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
DWORD FindKiServiceTable(HMODULE hModule,DWORD dwKSDT)
{
    PIMAGE_FILE_HEADER    pfh;
    PIMAGE_OPTIONAL_HEADER    poh;
    PIMAGE_SECTION_HEADER    psh;
    PIMAGE_BASE_RELOCATION    pbr;
    PIMAGE_FIXUP_ENTRY    pfe;

    DWORD    dwFixups=0,i,dwPointerRva,dwPointsToRva,dwKiServiceTable;
    BOOL    bFirstChunk;

    GetHeader((PCHAR)hModule,&pfh,&poh,&psh);
    TRACE("hModule:%X",hModule);
    if ((poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress) &&
        (!((pfh->Characteristics)&IMAGE_FILE_RELOCS_STRIPPED))) {              //存在重定位信息

            pbr=(PIMAGE_BASE_RELOCATION)RVATOVA(poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress,hModule);

            bFirstChunk=TRUE;

            while (bFirstChunk || pbr->VirtualAddress) {
                bFirstChunk=FALSE;

                pfe=(PIMAGE_FIXUP_ENTRY)((DWORD)pbr+sizeof(IMAGE_BASE_RELOCATION));

                //遍历重定位项
                for (i=0;i<(pbr->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))>>1;i++,pfe++) {
                    if (pfe->type==IMAGE_REL_BASED_HIGHLOW) {     //重定位地址指向的双字的32位都需要被修正
                        dwFixups++;
                        //指向需要重定位地址的相对虚拟地址指针
                        dwPointerRva=pbr->VirtualAddress+pfe->offset;

                        //需要修正的重定位项(RVA)
                        dwPointsToRva=*(PDWORD)((DWORD)hModule+dwPointerRva)-(DWORD)poh->ImageBase;

                        if (dwPointsToRva==dwKSDT) {        //KeServiceDescriptorTable的重定位信息

                            if (*(PWORD)((DWORD)hModule+dwPointerRva-2)==0x05c7) {

                                dwKiServiceTable=*(PDWORD)((DWORD)hModule+dwPointerRva+4) - poh->ImageBase;
                                return dwKiServiceTable;
                            }
                        }

                    }

                }
                *(PDWORD)&pbr+=pbr->SizeOfBlock;
            }
    }    

    return 0;
}

等一下。。。上面的特征码0x05c7是怎么来的???

我们可以使用IDA对内核文件ntoskrnl.exe进行反反汇编,看看KiInitSystem这个函数的反汇编代码。首先我们找到这个函数所在的地方:

image

然后我们继续往下看。。。

image

nice,,,这里我们看到了对SSDT表进行初始化的反汇编代码,同时也看到了0x05c7,KeServiceDescriptorTable的地址和KiServiceTable的地址。

为了验证一下KiServiceTable里面是不是保存的服务函数的入口地址,我们跟进看一下。

image

果不其然,和我们预期的一样。。。

到这里,我相信大家对SSDT的认识又更加深刻了一些。

下面是我在win7 32位下面获取ssdt当前地址和原始地址的效果图:

wps_clip_image-23024

本文链接:http://www.alonemonkey.com/get-original-ssdt.html

一条评论

  1. 沉疴

    博主,您好,请问您说的SDT和SSDT是同一个概念吧?

Comments are closed.