<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

文章分类

导航

订阅

10.12 随笔一则

下面的随笔是我昨天夜读源代码的笔记,算是一个自我小结。本随笔粗粒度跟踪了 Windows NT5.2 的启动过程(源代码级 / 及 Win32 兼容内核 / 本人初学者,水平有限,您多包涵),正好也应应 Raymond 老师将要出版的新书 —— 《软件调试》。Raymond 老师在新书的某一章节重点关注了 KdInitSystem() —— 内核调试器的初始化过程,我自然就没理由狗尾续貂了。所以,本随笔重点关注一下 KeInitSystem() —— 执行体的初始化过程,特别是 MSR 寄存器的初始化、进 SSDT 之前 Trap 的相关过程等 —— 我想对于刚接触内核的人来说,第一个吸引眼球的东西多半就是 SSDT (及相关)了。


为了不给 Advdbg.org 和自己带来不必要的麻烦,我最终还是将这里列出的代码选为 ReactOS 的代码,关于其许可和版权信息您可以参见 ReactOS 的 LICENSE。反正它们基本上是二进制兼容的,况且“市面上”的 2000 代码没有 SYSENTER 相关信息。 PS : 看见 MJ001 前辈说 XP SP3 已经到手,可怜的我咋什么都没有呢?


那么我们从什么说起呢?一直往前追踪可就没边了,虽然原来我读过一些 Award BIOS 的源代码,但是这的确是很痛苦的事情,咱以后再聊。这次就从 Ntldr 开始吧。


首先从 Ntldr 中提一个 PE 出来 —— osloader.exe。用IDA来分析osloader.exe,当IDA的分析停止时,你就可以看到代码的入口点了(上面这句话不是我说的,这是《NTLDR Reverse Engineering》文中的话) —— NtProcessStartup()  [NtProcessStartup@..\ntoskrnl\ke\i386\boot.S]

========================
[1] : NtProcessStartup()
========================

对于老版本的 ReactOS  Alex Ionescu 是这么说的:
Remove all the remaining code in boot.S and make KiRosPrepareForSystemStartup fastcall. Now NtProcessStartup just does a jmp to KiRosPrepareForSystemStartup without any other code.

代码 / 代码片断如下:           [NtProcessStartup@..\ntoskrnl\ke\i386\boot.S    Old]
 /*
  * FILE:            ntoskrnl/ke/i386/boot.S
  * COPYRIGHT:       See COPYING in the top level directory
  * PURPOSE:         Kernel Bootstrap Code
  * PURPOSE:         FreeLDR Wrapper Bootstrap Code
  * PROGRAMMER:      Alex Ionescu (alex at relsoft.net)
  */

 
@@ -30,16 +30,6 @@
 .text
 .func NtProcessStartup
 _NtProcessStartup:
 
     /* Load the initial kernel stack */
     lea eax, _P0BootStack
     sub eax, (NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH + KTRAP_FRAME_ALIGN)
     mov esp, eax
 
    
/* Save initial CR0 state */
     push CR0_EM + CR0_TS + CR0_MP
 
    
/* Call the main kernel initialization */
     push edx
     call _KiRosPrepareForSystemStartup@4
     jmp @KiRosPrepareForSystemStartup@8
 .endfunc


关于 NtProcessStartup() 最近的一次代码更新日志是这样的:
Add two more lines in boot.S which detect boot-by-NTLDR and jump into KiSystemService (I thought FreeLdr didn't use the entrypoint, but it looks like it does, so this hack is needed).

代码 / 代码片断如下:           [NtProcessStartup@..\ntoskrnl\ke\i386\boot.S]
.text
.func NtProcessStartup
_NtProcessStartup:

    /* NTLDR Boot: Call the main kernel initialization */
    test dword ptr [esp+4], 0x80000000
    jnz _KiSystemStartup@ 4

    /* FREELDR Boot: Cal the FreeLDR wrapper */
    jmp @KiRosPrepareForSystemStartup@8
.endfunc



其实在 ReactOS 里 NtProcessStartup() 说白了就是一个无条件的转移...



====================================
[2] : KiRosPrepareForSystemStartup()
====================================
嗯,看来新版的 NtProcessStartup() 还支持 Ntldr 启动了。不过我们还是追寻 ReactOS 自己的主线 —— KiRosPrepareForSystemStartup()。

代码 / 代码片断如下:           [KiRosPrepareForSystemStartup@..\ntoskrnl\ke\freeldr.c]
VOID
FASTCALL
KiRosPrepareForSystemStartup(IN ULONG Dummy,
                             IN PROS_LOADER_PARAMETER_BLOCK LoaderBlock)
{
    PLOADER_PARAMETER_BLOCK NtLoaderBlock;
    .........

    /* Load the GDT and IDT */
    Ke386SetGlobalDescriptorTable(*(PKDESCRIPTOR)&KiGdtDescriptor.Limit);
    Ke386SetInterruptDescriptorTable(*(PKDESCRIPTOR)&KiIdtDescriptor.Limit);

    /* Initialize the boot TSS */
    Tss = &KiBootTss;
    TssEntry = &KiBootGdt[KGDT_TSS / sizeof(KGDTENTRY)];
    TssEntry->HighWord.Bits.Type = I386_TSS;
    TssEntry->HighWord.Bits.Pres = 1;
    TssEntry->HighWord.Bits.Dpl = 0;
    TssEntry->BaseLow = (USHORT)((ULONG_PTR)Tss & 0xFFFF);
    TssEntry->HighWord.Bytes.BaseMid = (UCHAR)((ULONG_PTR)Tss >> 16);
    TssEntry->HighWord.Bytes.BaseHi = (UCHAR)((ULONG_PTR)Tss >> 24);
    .........

    /* Do general System Startup */
    KiSystemStartup(NtLoaderBlock);
}


简要解释一下,KiSystemStartup() 函数是内核镜像 Ntoskrnl.exe 的 KERNEL_ENTRY_POINT,这个分水岭也是算做 Win32 初始化的第二大阶段。另外,CPU(CPUs) 的 GDT、IDT 和 TSS 等数据结构也需要在此调用前后完成初始化。



=======================
[3] : KiSystemStartup()
=======================
下面开始 KiSystemStartup() 的“展示”,我认为如此核心的代码是非常值得一读的~

代码 / 代码片断如下:          
[KiSystemStartup@..\ntoskrnl\ke\i386\kiinit.c]
VOID
NTAPI
KiSystemStartup(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
    ULONG Cpu;
    PKTHREAD InitialThread;
    ULONG InitialStack;
    PKGDTENTRY Gdt;
    PKIDTENTRY Idt;
    PKTSS Tss;
    PKIPCR Pcr;

    /* Save the loader block and get the current CPU */
    KeLoaderBlock = LoaderBlock;
    Cpu = KeNumberProcessors;
    .........

    /* Initialize the machine type */
    KiInitializeMachineType();
    .........

    /* Get GDT, IDT, PCR and TSS pointers */
    KiGetMachineBootPointers(&Gdt, &Idt, &Pcr, &Tss);

    /* Setup the TSS descriptors and entries */
    Ki386InitializeTss(Tss, Idt, Gdt);

    /* Initialize the PCR */
    RtlZeroMemory(Pcr, PAGE_SIZE);
    KiInitializePcr(Cpu,
                    Pcr,
                    Idt,
                    Gdt,
                    Tss,
                    InitialThread,
                    KiDoubleFaultStack);

    .........

    /* Clear DR6/7 to cleanup bootloader debugging */
    __writefsdword(KPCR_TEB, 0);
    __writefsdword(KPCR_DR6, 0);
    __writefsdword(KPCR_DR7, 0);

    /* Setup the IDT */
    KeInitExceptions();

    /* Load Ring 3 selectors for DS/ES */
    Ke386SetDs(KGDT_R3_DATA | RPL_MASK);
    Ke386SetEs(KGDT_R3_DATA | RPL_MASK);
    .........

    /* Setup CPU-related fields */
    __writefsdword(KPCR_NUMBER, Cpu);
    __writefsdword(KPCR_SET_MEMBER, 1 << Cpu);
    __writefsdword(KPCR_SET_MEMBER_COPY, 1 << Cpu);
    __writefsdword(KPCR_PRCB_SET_MEMBER, 1 << Cpu);

    /* Initialize the Processor with HAL */
    HalInitializeProcessor(Cpu, KeLoaderBlock);
    .........

    /* Check if this is the boot CPU */
    if (!Cpu)
    {
        /* Initialize debugging system */
        KdInitSystem(0, KeLoaderBlock);

        /* Check for break-in */
        if (KdPollBreakIn()) DbgBreakPointWithStatus(1);
    }

    /* Raise to HIGH_LEVEL */
    KfRaiseIrql(HIGH_LEVEL);
    .........

    /* Switch to new kernel stack and start kernel bootstrapping */
    KiSetupStackAndInitializeKernel(&KiInitialProcess.Pcb,
                                    InitialThread,
                                    (PVOID)InitialStack,
                                    (PKPRCB)__readfsdword(KPCR_PRCB),
                                    (CCHAR)Cpu,
                                    KeLoaderBlock);

}


这代码片断的份量够重,其实根本无需我做太多解释,细看看函数的名字就可以看出它们的意义 —— 初始化 Processors、TSS、Exceptions(IDT)、KdInitSystem (内核调试引擎 ^_^),提升IRQL、调用 KiInitializeKernel()、然后再降IRQL,直到 KiIdleLoop()执行完,KiSystemStartup() 函数最终退化为 Idle 进程,这大致就是它的生命周期了。



=======================================
[4] : KiSetupStackAndInitializeKernel()
=======================================

刚才提到的 KiInitializeKernel() 关键函数、以及 KiSystemStartup() 函数功成名就后退化为 Idle 进程的实现秘密全在函数 KiSetupStackAndInitializeKernel()里,这个不瞧瞧怎么行。 ; )

代码 / 代码片断如下:          
[KiSetupStackAndInitializeKernel@..\ntoskrnl\ke\i386\boot.S]
.globl _KiSetupStackAndInitializeKernel@ 24
.func KiSetupStackAndInitializeKernel@ 24
_KiSetupStackAndInitializeKernel@ 24:

    ...........

    /* Copy all parameters to the new stack */
    push [esi+24]
    push [esi+20]
    push [esi+16]
    push [esi+12]
    push [esi+8]
    push [esi+4]
    xor ebp, ebp
    call _KiInitializeKernel@ 24

    /* Set the priority of this thread to 0 */
    mov ebx, PCR[KPCR_CURRENT_THREAD]
    mov byte ptr [ebx+KTHREAD_PRIORITY], 0

    /* Force interrupts enabled and lower IRQL back to DISPATCH_LEVEL */
    sti
    mov ecx, DISPATCH_LEVEL
    call @KfLowerIrql@4

    /* Set the right wait IRQL */
    mov byte ptr [ebx+KTHREAD_WAIT_IRQL], DISPATCH_LEVEL;

    /* Jump into the idle loop */
    jmp @KiIdleLoop@0
.endfunc



很好,流程很清晰! 下面的继续~



========================
[5] KiInitializeKernel()
========================

从上面 KiSetupStackAndInitializeKernel() 汇编函数里的 6 个压栈,我们就能把 KiInitializeKernel() 函数参数猜个一二了。但是 KiInitializeKernel() 是一个非常庞大的大家伙,Idel 进程就是由这个函数创建的,同时它也调用了执行体的初始化函数入口 —— ExpInitializeExecutive()。此时,各个函数的重要性已经不分彼此了...


代码 / 代码片断如下:           [KiInitializeKernel@..\ntoskrnl\ke\i386\kiinit.c]
VOID
NTAPI
KiInitializeKernel(IN PKPROCESS InitProcess,
                   IN PKTHREAD InitThread,
                   IN PVOID IdleStack,
                   IN PKPRCB Prcb,
                   IN CCHAR Number,
                   IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{

    /* Initialize portable parts of the OS */
    KiInitSystem();

    /* Initialize the Idle Process and the Process Listhead */
    InitializeListHead(&KiProcessListHead);
    PageDirectory.QuadPart = 0;
    KeInitializeProcess(InitProcess,
                        0,
                        0xFFFFFFFF,
                        &PageDirectory,
                        FALSE);
  
  ..........

    /* Setup the Idle Thread */
    KeInitializeThread(InitProcess,
                       InitThread,
                       NULL,
                       NULL,
                       NULL,
                       NULL,
                       NULL,
                       IdleStack);
    InitThread->NextProcessor = Number;
    InitThread->Priority = HIGH_PRIORITY;
    InitThread->State = Running;
    InitThread->Affinity = 1 << Number;
    InitThread->WaitIrql = DISPATCH_LEVEL;
    InitProcess->ActiveProcessors = 1 << Number;
    ..........

    /* Set basic CPU Features that user mode can read */
    SharedUserData->ProcessorFeatures[PF_MMX_INSTRUCTIONS_AVAILABLE] =
        (KeFeatureBits & KF_MMX) ? TRUE: FALSE;
    SharedUserData->ProcessorFeatures[PF_COMPARE_EXCHANGE_DOUBLE] =
        (KeFeatureBits & KF_CMPXCHG8B) ? TRUE: FALSE;
    SharedUserData->ProcessorFeatures[PF_XMMI_INSTRUCTIONS_AVAILABLE] =
        ((KeFeatureBits & KF_FXSR) && (KeFeatureBits & KF_XMMI)) ? TRUE: FALSE;
    SharedUserData->ProcessorFeatures[PF_XMMI64_INSTRUCTIONS_AVAILABLE] =
        ((KeFeatureBits & KF_FXSR) && (KeFeatureBits & KF_XMMI64)) ? TRUE: FALSE;
    SharedUserData->ProcessorFeatures[PF_3DNOW_INSTRUCTIONS_AVAILABLE] =
        (KeFeatureBits & KF_3DNOW) ? TRUE: FALSE;
    SharedUserData->ProcessorFeatures[PF_RDTSC_INSTRUCTION_AVAILABLE] =
        (KeFeatureBits & KF_RDTSC) ? TRUE: FALSE;

    /* Set up the thread-related fields in the PRCB */
    Prcb->CurrentThread = InitThread;
    Prcb->NextThread = NULL;
    Prcb->IdleThread = InitThread;

    /* Initialize the Kernel Executive */
    ExpInitializeExecutive(Number, LoaderBlock);
    ..........
}



简单的列了一些代码,类似于 Idle 进程、线程创建与初始化;SharedUserData 那个数据结构(后面 SYSENTER 要打下交道,所以这里列一下);还有就是关键的 ExpInitializeExecutive() 函数调用。



============================
[6] ExpInitializeExecutive()
============================

如果上面的 KiSetupStackAndInitializeKernel() 函数还是 Stub 的话,那么 ExpInitializeExecutive() 函数则是正式的执行体初始化过程,代码里可以很清晰的看到所谓的 phase 0 和 phase 1 两个步骤。而 phase 1 步骤就是 Windows XP 启动时有 logo 闪动,时间废的最长的那个。话不多言,接代码!

代码 / 代码片断如下:           [ExpInitializeExecutive@..\ntoskrnl\ex\init.c]
VOID
NTAPI
ExpInitializeExecutive(IN ULONG Cpu,
                       IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
    ...........

    /* Set phase to 0 */
    ExpInitializationPhase = 0;
    ...........

    /* Setup NT System Root Path */
    sprintf(Buffer, "C:%s", LoaderBlock->NtBootPathName);
    ...........

    /* Initialize the executive at phase 0 */
    if (!ExInitSystem()) KEBUGCHECK(PHASE0_INITIALIZATION_FAILED);

    /* Initialize the memory manager at phase 0 */
    if (!MmInitSystem(0, LoaderBlock)) KeBugCheck(PHASE0_INITIALIZATION_FAILED);
    ...........

    /* Create the Basic Object Manager Types to allow new Object Types */
    if (!ObInit()) KEBUGCHECK(OBJECT_INITIALIZATION_FAILED);

    /* Load basic Security for other Managers */
    if (!SeInit()) KEBUGCHECK(SECURITY_INITIALIZATION_FAILED);

    /* Initialize the Process Manager */
    if (!PsInitSystem(LoaderBlock)) KEBUGCHECK(PROCESS_INITIALIZATION_FAILED);

    /* Initialize the PnP Manager */
    if (!PpInitSystem()) KEBUGCHECK(PP0_INITIALIZATION_FAILED);

    /* Initialize the User-Mode Debugging Subsystem */
    DbgkInitialize();
    ...........
}


.: 1 :.  随便挑了一些代码片断,这种代码要是不去读一读,我想睡觉都会睡不踏实的~ 我现在很感兴趣的是 Device Tree 的 PnP 视图模式,驱网上 Tiamo 前辈提出的“总线是如何枚举驱动的”的确是一件很有意思的事情 —— 我想答案可以在上面代码的这里找找 —— PpInitSystem()。 ^_^


.: 2 :.  然后看看代码这里:
#if DBG
    /* On checked builds, allocate the system call count table */
    KeServiceDescriptorTable[0].Count =
        ExAllocatePoolWithTag(NonPagedPool,
                              KiServiceLimit * sizeof(ULONG),
                              TAG('C', 'a', 'l', 'l'));

    /* Use it for the shadow table too */
    KeServiceDescriptorTableShadow[0].Count = KeServiceDescriptorTable[0].Count;

    /* Make sure allocation succeeded */
    if (KeServiceDescriptorTable[0].Count)
    {
        /* Zero the call counts to 0 */
        RtlZeroMemory(KeServiceDescriptorTable[0].Count,
                      KiServiceLimit * sizeof(ULONG));
    }
#endif

它解释了一般的 Free Build 系统上 Count 域为 0 的原因 —— #if DBG
kd> dd KeServiceDescriptorTableShadow
8055a640  804e36a8 00000000 0000011c 80513eb8     ; KeServiceDescriptorTableShadow
8055a650  bf999280 00000000 0000029b bf999f90     ; W32pServiceTable
8055a660  00000000 00000000 00000000 00000000
8055a670  00000000 00000000 00000000 00000000
8055a680  804e36a8 00000000 0000011c 80513eb8     ; KeServiceDescriptorTable
8055a690  00000000 00000000 00000000 00000000
8055a6a0  00000000 00000000 00000000 00000000
8055a6b0  00000000 00000000 00000000 00000000


.: 3 :.  最后值得一说的是这里:      -*> NOTE <*-
/* Set system ranges */
SharedUserData->Reserved1 = (ULONG_PTR)MmHighestUserAddress;
SharedUserData->Reserved3 = (ULONG_PTR)MmSystemRangeStart;

各位看到了吗? Alex Ionescu 连 _KUSER_SHARED_DATA 数据结构的保留位都知道怎么初始化,这实力强悍的直让我想说脏话...  PS : Alex Ionescu 貌似比我小 2 岁,1986 年生人!差距太可怕...



==================
[7] PsInitSystem()
==================

还是先回到正道上来,看看 PsInitSystem() 这个函数。您可能会好奇,为什么接着往下追踪是 Ps 系列的函数,原因是 Ntoskrnl 在完成 Idle 进程之后,接下去创建了 system 进线程,phase 1 初始化过程就是在这里被调度的(当然,这里有降 IRQL 的问题)。 

代码 / 代码片断如下:           [PsInitSystem@..\ntoskrnl\ps\psmgr.c]
BOOLEAN
NTAPI
PsInitSystem(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
    /* Check the initialization phase */
    switch (ExpInitializationPhase)
    {
    case 0:
        /* Do Phase 0 */
        return PspInitPhase0(LoaderBlock);

    case 1:
        /* Do Phase 1 */
        return PspInitPhase1();

    default:
        /* Don't know any other phase! Bugcheck! */
        KeBugCheckEx(UNEXPECTED_INITIALIZATION_CALL,
                     1,
                     ExpInitializationPhase,
                     0,
                     0);
        return FALSE;
    }
}


Good!非常清晰的两个阶段,请注意这里特指的是 PS 部分的初始化,其中 ExpInitializationPhase 常量是全局的。



===================
[7] PspInitPhase0()
===================

下面让我们细看看进程初始化的第 0 阶段 —— PspInitPhase0()。

代码 / 代码片断如下:           [PspInitPhase0@..\ntoskrnl\ps\psmgr.c]
BOOLEAN
NTAPI
PspInitPhase0(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
    .........

    /* Setup callbacks */
    for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++)
    {
        ExInitializeCallBack(&PspThreadNotifyRoutine[i]);
    }
    for (i = 0; i < PSP_MAX_CREATE_PROCESS_NOTIFY; i++)
    {
        ExInitializeCallBack(&PspProcessNotifyRoutine[i]);
    }
    for (i = 0; i < PSP_MAX_LOAD_IMAGE_NOTIFY; i++)
    {
        ExInitializeCallBack(&PspLoadImageNotifyRoutine[i]);
    }
    .........

    /* Create the Initial System Process */
    Status = PspCreateProcess(&PspInitialSystemProcessHandle,
                              PROCESS_ALL_ACCESS,
                              &ObjectAttributes,
                              0,
                              FALSE,
                              0,
                              0,
                              0,
                              FALSE);
    if (!NT_SUCCESS(Status)) return FALSE;
    .........

    /* Copy the process names */
    strcpy(PsIdleProcess->ImageFileName, "Idle");
    strcpy(PsInitialSystemProcess->ImageFileName, "System");
    .........

    /* Setup the system initialization thread */
    Status = PsCreateSystemThread(&SysThreadHandle,
                                  THREAD_ALL_ACCESS,
                                  &ObjectAttributes,
                                  0,
                                  NULL,
                                  Phase1Initialization,
                                  LoaderBlock);

    if (!NT_SUCCESS(Status)) return FALSE;
    .........
}


那些 NotifyRoutine 的回调的意义就不多说了 ; ) ,主要是 System Process 和 Thread 的创建是在这里完成的。真正的关键点在上面那个红色的 Phase1Initialization,这意味着函数 Phase1Initialization() 是线程 system 的入口。不过这个线程目前是得不到时间片的 —— 回忆一下前面的代码 —— KiSystemStartup() 函数是将中断级提升到 HIGH_LEVEL 后再调用 KiInitializeKernel() 函数的。想得到调度的前提必须是等 KiInitializeKernel() 执行完,IRQL 降到 DISPATCH_LEVEL 才行。



==========================
[8] Phase1Initialization()
==========================









【未完,1/2都不到,作者吃饭中】

【累死我了,写随笔居然比看代码、调试还累】








posted on 2007年10月12日 11:07 由 WANGyu

# re: 10.12 随笔一则 @ 2007年10月13日 22:51

启动过程的确是很值得研究的一个过程。我在微软组织的Webcast的三讲中有一讲讲了Windows启动过程。但是觉得很多地方还没有深入,后来在单位又给同事讲了一次,两个小时。关于这方面一直也想写篇文章。但是很难写,WANGyu加油吧!

Raymond

# re: 10.12 随笔一则 @ 2007年10月14日 13:02

这则随笔还没有写完,周末我这里网络不是很方便,应该是要拖到下周一了。

仔细看过 Raymond 老师在“微软 - 深入研究Windows内部原理系列课程之七 —— 开机引导过程”上的演讲以及新书里的相关内容,其实 Raymond 老师讲的已经很详细了,不过要是自己没有跟踪过或是阅读过相关部分的源代码是很难产生共鸣的。

我这个随笔充其量也就是阅读源代码的流水帐,说白了意义不大,还要向各位老师多请教。

启动过程的重要性无需多言。正所谓“一切答案都在内存里”,如果研究内存像是“远距离观察一颗行星”,那么,研究内核数据结构就是“深入行星内部观察行星上的物种及它们之间的生态关系”。而这一切都是在系统初始化的时候确定下来的。我想 Russinovish 之所以那么强大很大一部分原因和他在 NuMega 对系统启动的跟踪、分析直接相关。 ^_^

WANGyu

# re: 10.12 随笔一则 @ 2007年10月16日 17:38

这篇随笔估计又要拖几天了,这两天忙,而且也不想草草收场。

正好跟踪到自己感兴趣的一个地方了,就是所谓的“Configuration Management”,为了看懂 Hive,特别是 Ntldr 操作 SYSTEM Hive 的来龙去脉,所以得多花些时间了。

看到 MJ001 前辈的想法 —— Hook HvpGetCellMapped,以及 另外的一种 KOH 的方法,有些感触,定要实践一把~

WANGyu

# re: 10.12 随笔一则 @ 2007年12月27日 10:51

Hook HvpGetCellMapped 这玩意表面上的东西已经搞定了,只是深层次的 CM 数据结构需要一段时间去挖掘。

按时间上来说,MJ0011前辈第一个爆料,zzzevazzz前辈第一个放代码,法国人ivanlef0u第一个写解释(可惜是法语...)

原理很简单,英文的描述是这样的:
So, think a moment...
Knowing the prototype of HvpGetCellMapped /* Add : HvpGetCellPaged()
*/, if we find a way to gain a HHIVE structure and to change the
GetCellRoutine function's pointer with a function parsing the return
of HvpGetCellMapped; it should be possible to return a value saying
that an arbitrary SubKey was not found.

需要注意的是:
So, when HvpGetCellMapped will return the pointer to the CM_KEY_NODE we want to hide, we will go to read the ParentNode to obtain the last SubKey of the list and return this one instead.
Later, when the HvpGetCellMapped function return the LastKeyNode, we return NULL instead.

WANGyu

# re: 10.12 随笔一则 @ 2007年12月27日 10:56

kd> !object \
Object: e10011d8 Type: (867eb108) Directory
ObjectHeader: e10011c0
HandleCount: 0 PointerCount: 40
Directory Object: 00000000 Name: \
...
>> HashBucket[ 03 ]: e10006f8 Key '\REGISTRY'
...


kd> dt nt!_CM_KEY_BODY e10006f8
nt!_CM_KEY_BODY
+0x000 Type : 0x6b793032
>> +0x004 KeyControlBlock : 0xe1033008 _CM_KEY_CONTROL_BLOCK
+0x008 NotifyBlock : (null)
+0x00c ProcessID : 0x00000004
+0x010 Callers : 0
+0x014 CallerAddress : [10] 0x05000000
+0x03c KeyBodyList : _LIST_ENTRY [ 0xe1033038 - 0xe1033038 ]


kd> dt nt!_CM_KEY_CONTROL_BLOCK 0xe1033008
+0x000 RefCount : 3
+0x002 Flags : 0x2c
+0x004 ExtFlags : 0y00000000 (0)
+0x004 PrivateAlloc : 0y1
+0x004 Delete : 0y0
+0x004 DelayedCloseIndex : 0y100000000000 (0x800)
+0x004 TotalLevels : 0y0000000001 (0x1)
+0x008 KeyHash : _CM_KEY_HASH
+0x008 ConvKey : 0xd936a631
+0x00c NextHash : (null)
>> +0x010 KeyHive : 0xe102d008 _HHIVE
+0x014 KeyCell : 0x20
+0x018 ParentKcb : (null)
>> +0x01c NameBlock : 0xe100b4f8 _CM_NAME_CONTROL_BLOCK
+0x020 CachedSecurity : 0xe1010438 _CM_KEY_SECURITY_CACHE
+0x024 ValueCache : _CACHED_CHILD_LIST
+0x02c IndexHint : 0x00000002 _CM_INDEX_HINT_BLOCK
+0x02c HashKey : 2
+0x02c SubKeyCount : 2
+0x030 KeyBodyListHead : _LIST_ENTRY [ 0xe1000734 - 0xe1000734 ]
+0x030 FreeListEntry : _LIST_ENTRY [ 0xe1000734 - 0xe1000734 ]
+0x038 KcbLastWriteTime : _LARGE_INTEGER 0x1c8468f`fafe8af4
+0x040 KcbMaxNameLen : 0xe
+0x042 KcbMaxValueNameLen : 0
+0x044 KcbMaxValueDataLen : 0


kd> dt nt!_CM_NAME_CONTROL_BLOCK 0xe100b4f8
+0x000 Compressed : 0x1 ''
+0x002 RefCount : 1
+0x004 NameHash : _CM_NAME_HASH
+0x004 ConvKey : 0xd936a631
+0x008 NextHash : (null)
+0x00c NameLength : 8
>> +0x00e Name : [1] 0x4552


kd> db e100b506 l 8
e100b506 52 45 47 49 53 54 52 59 REGISTRY


kd> dt nt!_HHIVE 0xe102d008
+0x000 Signature : 0xbee0bee0
>> +0x004 GetCellRoutine : 0x8056f5f7 _CELL_DATA* nt!HvpGetCellPaged+0
+0x008 ReleaseCellRoutine : (null)
+0x00c Allocate : 0x8058c3b8 void* nt!CmpAllocate+0
+0x010 Free : 0x8058c3ff void nt!CmpFree+0
+0x014 FileSetSize : 0x8058cf0d unsigned char nt!CmpFileSetSize+0
+0x018 FileWrite : 0x80596a69 unsigned char nt!CmpFileWrite+0
+0x01c FileRead : 0x805ae696 unsigned char nt!CmpFileRead+0
+0x020 FileFlush : 0x8059695b unsigned char nt!CmpFileFlush+0
+0x024 BaseBlock : 0xe102e000 _HBASE_BLOCK
+0x028 DirtyVector : _RTL_BITMAP
+0x030 DirtyCount : 0
+0x034 DirtyAlloc : 0
+0x038 RealWrites : 0 ''
+0x03c Cluster : 1
+0x040 Flat : 0 ''
+0x041 ReadOnly : 0 ''
+0x042 Log : 0 ''
+0x044 HiveFlags : 1
+0x048 LogSize : 0
+0x04c RefreshCount : 0
+0x050 StorageTypeCount : 2
+0x054 Version : 3
+0x058 Storage : [2] _DUAL


kd> ln HvpGetCellMapped
(8056f28b) nt!HvpGetCellMapped | (8056f39e) nt!CmpTouchView


kd> ln HvpGetCellPaged
(8056f5f7) nt!HvpGetCellPaged | (8056f65c) nt!HvGetCellSize

WANGyu

Powered by Community Server Powered by CnForums.Net