⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 漫谈兼容内核之二十一windows进程的用户空间.txt

📁 漫谈系统内核内幕 收集得很辛苦 呵呵 大家快下在吧
💻 TXT
📖 第 1 页 / 共 4 页
字号:
> RtlRosCreateUserThread() > RtlRosCreateStack()]

NTSTATUS NTAPI RtlRosCreateStack(IN HANDLE ProcessHandle,
              OUT PINITIAL_TEB InitialTeb, IN LONG StackZeroBits,
              IN OUT PULONG StackReserve OPTIONAL,
              IN OUT PULONG StackCommit OPTIONAL)
{
/* FIXME: read the defaults from the executable image */
ULONG_PTR nStackReserve = 0x100000;
/* FIXME: when we finally have exception handling, make this PAGE_SIZE */
ULONG_PTR nStackCommit = 0x100000;
NTSTATUS nErrCode;

if(StackReserve == NULL)  StackReserve = &nStackReserve;
else  *StackReserve = ROUNDUP(*StackReserve, PAGE_SIZE);

if(StackCommit == NULL)  StackCommit = &nStackCommit;
else  *StackCommit = ROUNDUP(*StackCommit, PAGE_SIZE);

/* FIXME: no SEH, no guard pages */
*StackCommit = *StackReserve;

/* FIXME: this code assumes a stack growing downwards */
/* fixed stack */
if(*StackCommit == *StackReserve)
{
   InitialTeb->StackCommit = NULL;
   InitialTeb->StackCommitMax = NULL;
   InitialTeb->StackReserved = NULL;
   InitialTeb->StackLimit = NULL;
   /* allocate the stack */
   nErrCode = NtAllocateVirtualMemory(ProcessHandle, &(InitialTeb->StackLimit),
               StackZeroBits, StackReserve, MEM_RESERVE | MEM_COMMIT,
               PAGE_READWRITE);

   /* store the highest (first) address of the stack */
   InitialTeb->StackBase = (PUCHAR)(InitialTeb->StackLimit) + *StackReserve;
   *StackCommit = *StackReserve;
}
/* expandable stack */
else
{
   ULONG_PTR nGuardSize = PAGE_SIZE;
   PVOID pGuardBase;
  
   InitialTeb->StackBase = NULL;
   InitialTeb->StackLimit =  NULL;
   InitialTeb->StackReserved = NULL;
   /* reserve the stack */
   nErrCode = NtAllocateVirtualMemory(ProcessHandle,
                          &(InitialTeb->StackReserved), StackZeroBits,
                          StackReserve, MEM_RESERVE, PAGE_READWRITE);
   /* expandable stack base - the highest address of the stack */
   InitialTeb->StackCommit = (PUCHAR)(InitialTeb->StackReserved) + *StackReserve;
   /* expandable stack limit - the lowest committed address of the stack */
   InitialTeb->StackCommitMax = (PUCHAR)(InitialTeb->StackCommit) - *StackCommit;
   /* commit as much stack as requested */
   nErrCode = NtAllocateVirtualMemory(ProcessHandle, &(InitialTeb->StackCommitMax),
                             0, StackCommit, MEM_COMMIT, PAGE_READWRITE);
   pGuardBase = (PUCHAR)(InitialTeb->StackCommitMax) - PAGE_SIZE;
   /* set up the guard page */
   nErrCode = NtAllocateVirtualMemory(ProcessHandle, &pGuardBase, 0,
               &nGuardSize, MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD);
}
/* success */
return STATUS_SUCCESS;
}

    代码的主体是一个if语句,判定的条件是指针StackCommit和StackReserve所指向的数值是否相等。这两个指针是通过调用参数传下来的,它们所指向的数值决定了堆栈的大小,如果是空指针就采用默认值0x100000,就是1MB。如果两个指针所指的值相同就表示要建立大小固定的堆栈;否则就表示要建立可以随时扩充的堆栈,此时需要为限制堆栈的增长设置一个隔离页面。
    我们看它else部分的代码,因为这相对而言要复杂一点。
    先看对NtAllocateVirtualMemory()的第一次调用,其目的是要保留用于堆栈的区间。注意调用参数InitialTeb->StackReserved的值已预先设置为NULL;StackZeroBits的值是从上面一路传下来的,实际上也是0。所以,这个堆栈的位置也是自由分配的。根据前面分配使用用户空间的历史,我们可以得出结论:堆栈的默认位置是0x30000至0x12ffff,共1MB。实际分配的起始地址和大小则通过InitialTeb->StackReserved和参数StackReserve返回。
    然后,对NtAllocateVirtualMemory()的第二次调用则要求“交割”目前所要求的大小,即StackCommit所指向的数值。注意内存管理分配的空间是由下往上伸展的,而堆栈是由上向下伸展的,因此首先交割的范围是在这区间的上部,所以这里要进行一些计算。注意这里InitialTeb->StackCommitMax所指向的倒反而是地址较低的地方。
    第三次调用NtAllocateVirtualMemory()的目的在于在堆栈区间的下部(堆栈的顶部)设置一个保护页面。这里的nGuardSize是个局部量,预先设置成PAGE_SIZE,所以隔离区的大小就是一个页面。当然,这个页面也在前面保留的1MB区间之中。
    这个过程中所涉及或改变的一些数值都是应该记录在目标线程的TEB中的,但是此时目标线程的TEB尚未建立,所以上层传下一个指针InitialTeb,指向一个“初始TEB”,可以先用起来,以后再把它的内容复制到目标线程的TEB中。初始TEB的数据结构与实际的TEB数据结构略有不同。

回到RtlRosCreateUserThread()的代码,下一步的操作是RtlRosInitializeContext(),这是要为新建的线程虚构出一个上下文。

[CreateProcessW() > KlCreateFirstThread() > RtlRosCreateUserThreadVa()
> RtlRosInitializeContext()]

NTSTATUS NTAPI
RtlRosInitializeContext(IN HANDLE ProcessHandle, OUT PCONTEXT Context,
                     IN PVOID StartAddress, IN PINITIAL_TEB InitialTeb,
                     IN ULONG ParameterCount, IN ULONG_PTR * Parameters)
{
static PVOID s_pRetAddr = (PVOID)0xDEADBEEF;
. . . . . .

/* Intel x86: linear top-down stack, all parameters passed on the stack */
/* get the stack base and limit */
nErrCode = RtlpRosGetStackLimits(InitialTeb, &pStackBase, &pStackLimit);
/* validate the stack */
nErrCode = RtlpRosValidateTopDownUserStack(pStackBase, pStackLimit);

memset(Context, 0, sizeof(CONTEXT));
/* initialize the context */
. . . . . .
Context->Eip = (ULONG_PTR)StartAddress;
Context->SegGs = USER_DS;
Context->SegFs = TEB_SELECTOR;
. . . . . .
Context->Esp = (ULONG_PTR)pStackBase - (nParamsSize + sizeof(ULONG_PTR));
Context->EFlags = ((ULONG_PTR)1 << 1) | ((ULONG_PTR)1 << 9);

/* write the parameters */
nErrCode = NtWriteVirtualMemory(ProcessHandle, ((PUCHAR)pStackBase) - nParamsSize,
                                Parameters, nParamsSize, &nDummy);

/* write the return address */
return NtWriteVirtualMemory(ProcessHandle,
                ((PUCHAR)pStackBase) - (nParamsSize + sizeof(ULONG_PTR)),
                &s_pRetAddr, sizeof(s_pRetAddr), &nDummy);
}

    这里涉及对新建进程用户空间的两次写操作。一次是把上面传下来的指针数组Parameters复制到用户空间的堆栈上,另一次是把s_pRetAddr的值0xDEADBEEF也复制到堆栈上,这样就在用户空间的堆栈上虚构出一个函数调用框架。这个框架的返回地址是0xDEADBEEF,但是实际上不会被用到,只是摆个样子、充个数。而调用参数则是由上层传下来的,下面读者就会看到一共有两个参数,其一是函数指针,其二则是PEB所在的地址。
    这里指针pStackBase的值来自前面的InitialTeb->StackCommit,注意这个地址比InitialTeb->StackCommitMax更高,因为堆栈是由上向下伸展的。
    注意这里所说的参数是为新建线程在用户空间的程序入口提供的,不要把它们跟前面所说的进程参数块相混淆。实际上,进程的第一个线程正式进入用户空间以后首先执行的是RtlBaseProcessStart(),这里所说的参数就是对于这个函数的调用参数。为了搞清这两个参数到底是什么,我们往上回溯到KlCreateFirstThread()对RtlRosCreateUserThreadVa()的调用:

HANDLE STDCALL KlCreateFirstThread(…)
{
  ……
  nErrCode = RtlRosCreateUserThreadVa(ProcessHandle, &oaThreadAttribs,
          dwCreationFlags & CREATE_SUSPENDED, 0,
          &(Sii->StackReserve), &(Sii->StackCommit),
          pTrueStartAddress, &hThread, &cidClientId,
          2, (ULONG_PTR)lpStartAddress, (ULONG_PTR)PEB_BASE);
  ……
}

    显然,这里往下传的是2个参数。一个是lpStartAddress,这是目标线程的入口地址;另一个是PEB_BASE,就是进程环境块所在的地址。注意这里的lpStartAddress和pTrueStartAddress是不同的两个地址。读者在以前的漫谈中看到过,pTrueStartAddress的值来自RtlBaseProcessStartRoutine,所指向的是RtlBaseProcessStart()。而lpStartAddress所指向的才是来自目标映像的程序入口。

    又回到RtlRosCreateUserThread(),下一步是系统调用NtCreateThread()。这当然又是个比较大的操作,但是涉及用户空间格局的只是PsCreateTeb()、即TEB的建立(此外还涉及内核空间堆栈的建立,但那与用户空间无关)。我们分段阅读PsCreateTeb()的代码:

[CreateProcessW() > KlCreateFirstThread() > RtlRosCreateUserThreadVa()
> NtCreateThread() > PsCreateTeb()]

static NTSTATUS PsCreateTeb(HANDLE ProcessHandle, PTEB *TebPtr,
                           PETHREAD Thread, PINITIAL_TEB InitialTeb)
{
   . . . . . .

   TebSize = PAGE_SIZE;
   if (NULL == Thread->ThreadsProcess)
   {
       /* We'll be allocating a 64k block here and only use 4k of it, but this
          path should almost never be taken. Actually, I never saw it was taken,
          so maybe we should just ASSERT(NULL != Thread->ThreadsProcess) and
          move on */
       TebBase = NULL;
       Status = ZwAllocateVirtualMemory(ProcessHandle, &TebBase, 0, &TebSize,
                          MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
                          PAGE_READWRITE);
       . . . . . .
   }
   else
   {
       Process = Thread->ThreadsProcess;
       ExAcquireFastMutex(&Process->TebLock);
       if (NULL == Process->TebBlock || Process->TebBlock == Process->TebLastAllocated)
       {
         Process->TebBlock = NULL;
         RegionSize = MM_VIRTMEM_GRANULARITY;
         Status = ZwAllocateVirtualMemory(ProcessHandle,
                                        &Process->TebBlock, 0, &RegionSize,
                                        MEM_RESERVE | MEM_TOP_DOWN,
                                        PAGE_READWRITE);
         . . . . . .
         Process->TebLastAllocated = (PVOID) ((char *) Process->TebBlock + RegionSize);
       }
       TebBase = (PVOID) ((char *) Process->TebLastAllocated - PAGE_SIZE);
       Status = ZwAllocateVirtualMemory(ProcessHandle, &TebBase, 0, &TebSize,
                                        MEM_COMMIT, PAGE_READWRITE);
       . . . . . .
       Process->TebLastAllocated = TebBase;
       ExReleaseFastMutex(&Process->TebLock);
   }
   . . . . . .

    这部分代码基本上就是一个if语句。判定的条件是Thread->ThreadsProcess为0,表示目标线程并不属于任何一个进程,但是这在正常的情况下实际上是不该发生的,所以代码的作者在注释中也说“也许我们只要放上ASSERT(NULL != Thread->ThreadsProcess)”就行。在正常的情况下,Thread->ThreadsProcess指向所属进程的EPROCESS数据结构,此时又分两种情况。一种是Process->TebBlock为0或者与Process->TebLastAllocated相同,前者说明尚未为TEB保留空间,后者说明为TEB保留的空间已经用完。实际上,Process->TebBlock是在PsCreatePeb()中设置的(见前面的代码),应该指向PEB所在64KB区间的下部边界,所以不应该是0,而Process->TebLastAllocated则随着TEB的创建而逐次下移,每次下移一个页面,当它指向这个区间的下部边界时,就说明这个区间已经耗尽了。这时候就在原有区间的下面再分配一个区间(但是这段代码好像有点问题)。
    在绝大多数的情况下,TEB区间已经保留,其顶部的一个页面用于PEB,并且Process->TebLastAllocated也已作了相应调整,现在则在它下方再获取一个页面用作目标线程的TEB,并对Process->TebLastAllocated作相应的调整。
    为目标线程的TEB分配了一个页面以后,下面就要把有关的数据写入TEB了。具体的数据主要来自前面的“初始TEB”,参数InitialTeb就是指向初始TEB结构的指针。我们继续往下看:

[CreateProcessW() > KlCreateFirstThread() > RtlRosCreateUserThreadVa()
> NtCreateThread() > PsCreateTeb()]

   RtlZeroMemory(&Teb, sizeof(TEB));
   /* set all pointers to and from the TEB */
   Teb.Tib.Self = TebBase;
   if (Thread->ThreadsProcess)
   {
      Teb.Peb = Thread->ThreadsProcess->Peb; /* No PEB yet!! */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -