Windows Internals第6章的内容(1)

来源:互联网 发布:淘宝网双肩女包卡帝包 编辑:程序博客网 时间:2024/04/29 22:50

这是Windows Internals第6章的内容。发现描述的还算详细。读了总比不读强。于是纪录之,老鸟飘过----

                                                         <一> 进程相关

EPROCESS算是进程的代表,它还保留着与之相关的其他信息。进程是死的,其中的线程才是活的。进程就好比一个容器,装着很多活跃的线程而已[没有线程的进程注定要死亡,因为它没有活力]。对了,还有PEB、TEB[这2个结构存在于用户进程空间中,其他的都在系统的高2GB地址范围里] 。见图:

① 明确KPCR、KPRCB、ETHREAD、KTHREAD、EPROCESS、KPROCESS、TEB、PEB ----

KPCR(Kernel's Processor Control Region,内核进程控制区域)是一个不会随WINDOWS版本变动而改变的固定结构体,在它的末尾[偏移0x120]指向KPRCB结构。

nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : Ptr32 _KPCR
   +0x020 Prcb             : Ptr32 _KPRCB
   +0x024 Irql             : UChar
   +0x028 IRR              : Uint4B
   +0x02c IrrActive        : Uint4B
   +0x030 IDR              : Uint4B
   +0x034 KdVersionBlock   : Ptr32 Void
   +0x038 IDT              : Ptr32 _KIDTENTRY
   +0x03c GDT              : Ptr32 _KGDTENTRY
   +0x040 TSS              : Ptr32 _KTSS
   ...// 省略
   +0x120 PrcbData         : _KPRCB

KPRCB同样是一个不会随WINDOWS版本变动而改变的固定结构体。它包含有指向当前KTHREAD的指针,偏移值0x004。其实也就是知道了当前的ETHREAD基地址。[因为ETHREAD的第一项便是KTHREAD,ETHREAD在后面讨论,现在讨论进程相关] [通过 KeGetCurrentPrcb() 函数即可得到PKPRCB,具体参见WRK]

展开KTHREAD,其中的_KAPC_STATE结构中包含当前KPROCESS的地址

nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   ...
   +0x034 ApcState         : _KAPC_STATE

+0x034 ApcState         : struct _KAPC_STATE, 5 elements, 0x18 bytes
      +0x000 ApcListHead      : [2] struct _LIST_ENTRY, 2 elements, 0x8 bytes
      +0x010 Process          : Ptr32 to struct _KPROCESS, 29 elements, 0x6c bytes
      +0x014 KernelApcInProgress : UChar
      +0x015 KernelApcPending : UChar
      +0x016 UserApcPending   : UChar

而EPROCESS的第一项正是KPROCESS。联想我们熟悉的断EPROCESS链表隐藏进程的手法。通过PsGetCurrentProcess得到的其实是当前KPROCESS的地址,而KPROCESS就是EPROCESS结构体的第一项,这样就得到了当前的EPROCESS。然后遍历整个链表。。。

---->>大致流程:PsGetCurrentProcess()函数---->_PsGetCurrentProcess()宏----->KeGetCurrentThread()函数

---->>具体细节:
#define _PsGetCurrentProcess() (CONTAINING_RECORD(((KeGetCurrentThread())->ApcState.Process),EPROCESS,Pcb))
// 很明显,KeGetCurrentThread()得到KTHREAD结构体,KTHREAD偏移0x034处的
// ApcState中process即为EPROCESS的第一项KPROCESS的地址。CONTAINING_RECORD宏
// 将此地址减去它在EPROCESS中的偏移值,得到当前EPROCESS的实际地址

FORCEINLINE
struct _KTHREAD *
NTAPI KeGetCurrentThread (VOID)
{
#if (_MSC_FULL_VER >= 13012035)
    return (struct _KTHREAD *) (ULONG_PTR) __readfsdword (FIELD_OFFSET (KPCR, PrcbData.CurrentThread));
#else
    __asm { mov eax, fs:[0] KPCR.PrcbData.CurrentThread }
#endif
}
// fs在用户模式下指向TEB结构,在内核模式下指向KPCR[前面第一个描述的结构体]

呵呵,偶画个图更直观些,也更方便记忆

关于KPROCESS。里面保存了一些有用的信息,我们来简单的瞅下。

nt!_KPROCESS
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 ProfileListHead : _LIST_ENTRY
   +0x018 DirectoryTableBase : [2] Uint4B    // 进程的页目录PDT [涉及内存管理知识]
   +0x020 LdtDescriptor    : _KGDTENTRY    // GDT的入口
   +0x028 Int21Descriptor : _KIDTENTRY     // IDT的入口
   +0x030 IopmOffset       : Uint2B
   +0x032 Iopl             : UChar
   +0x033 Unused           : UChar
   +0x034 ActiveProcessors : Uint4B
   +0x038 KernelTime       : Uint4B
   +0x03c UserTime         : Uint4B
   +0x040 ReadyListHead    : _LIST_ENTRY
   +0x048 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x04c VdmTrapcHandler : Ptr32 Void
   +0x050 ThreadListHead   : _LIST_ENTRY // 指向KTHREAD链
   +0x058 ProcessLock      : Uint4B
   +0x05c Affinity         : Uint4B
   +0x060 StackCount       : Uint2B
   +0x062 BasePriority     : Char
   +0x063 ThreadQuantum    : Char
   +0x064 AutoAlignment    : UChar
   +0x065 State            : UChar
   +0x066 ThreadSeed       : UChar
   +0x067 DisableBoost     : UChar
   +0x068 PowerState       : UChar
   +0x069 DisableQuantum   : UChar
   +0x06a IdealNode        : UChar
   +0x06b Flags            : _KEXECUTE_OPTIONS
   +0x06b ExecuteOptions   : UChar

PEB是很有用的东西,写shellcode、定位EPROCESS等都可以用到它。PEB在EPROCESS偏移0x1b0处

nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   ...
   +0x084 UniqueProcessId : Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY
   ...
   +0x160 PhysicalVadList : _LIST_ENTRY
   +0x168 PageDirectoryPte : _HARDWARE_PTE
   ...
   +0x1b0 Peb              : Ptr32 _PEB
   ...

---->>获得PEB的地址是非常简单的。可以通过EPROCESS的偏移,也可以用硬编码实现[不同进程的PEB高位都是一样的]
xor esi, esi                     ;   FS寄存器 -> TEB结构,TEB+0x30 -> PEB结构
mov esi, fs:[esi + 30H]   ;   而PEB中包含有_PEB_LDR_DATA。通过一系列的
mov eax, esi                 ;   偏移可以定位到Kernel32.dll基地址。。。
ret                           ;   呵呵,参看gz1X大虾的文章:WIN下获取kernel基址的shellcode探讨

当然可以直接用Windbg来查看当前的PEB的结构 lkd>dt _peb

lkd> !peb
PEB at 7ffdc000    //高位都是7ffd
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
    ImageBaseAddress:         01000000
    Ldr                       00191e90
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 00191f28 . 00193330
    Ldr.InLoadOrderModuleList:           00191ec0 . 00193320
    Ldr.InMemoryOrderModuleList:         00191ec8 . 00193328
    ...// 省略

② 与进程相关的一些内核变量、计数、函数 ----

关于PspCidTable参见gz1X大虾的:基于pspCidTable的进程检测技术

这些变量的申明保存在WRK的Psinit.c中。先看下PspCreateProcess的参数
PspCreateProcess(
    OUT PHANDLE ProcessHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ParentProcess OPTIONAL, //如果没有指定,表明此进程没有父进程。是系统进程
    IN ULONG Flags,
    IN HANDLE SectionHandle OPTIONAL,
    IN HANDLE DebugPort OPTIONAL,
    IN HANDLE ExceptionPort OPTIONAL,
    IN ULONG JobMemberLevel
    )


系统启动时,PspInitPhase0() 函数会做很多事情,这里只是例举与上图全局变量相关的细节:

初始化Queue header:InitializeListHead(&PsActiveProcessHead);
初始化PsIdleProcess[这个是系统进程的EPROCESS]

创建系统进程,并且把系统进程的EPROCESS保存在PsInitialSystemProcess中。见代码:

InitializeObjectAttributes (&ObjectAttributes,
                                NULL,
                                0,
                                NULL,
                                NULL);

    if (!NT_SUCCESS (PspCreateProcess (&PspInitialSystemProcessHandle,
                                       PROCESS_ALL_ACCESS,
                                       &ObjectAttributes,
                                       NULL, // 注意这里。没有指定,所以创建的是系统进程
                                       0,
                                       NULL,
                                       NULL,
                                       NULL,
                                       0))) {
        return FALSE;
    }

    if (!NT_SUCCESS (ObReferenceObjectByHandle (PspInitialSystemProcessHandle,
                                                0L,
                                                PsProcessType,
                                                KernelMode,
                                                &PsInitialSystemProcess, // 系统的EPROCESS保存于此
                                                NULL))) {

        return FALSE;
    }

    strcpy((char *) &PsIdleProcess->ImageFileName[0], "Idle");
    strcpy((char *) &PsInitialSystemProcess->ImageFileName[0], "System");

// EPROCESS OFFEST+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
    PsInitialSystemProcess->SeAuditProcessCreationInfo.ImageFileName =
        ExAllocatePoolWithTag (PagedPool,
                               sizeof(OBJECT_NAME_INFORMATION),
                               'aPeS');

    if (PsInitialSystemProcess->SeAuditProcessCreationInfo.ImageFileName != NULL) {
        RtlZeroMemory (PsInitialSystemProcess->SeAuditProcessCreationInfo.ImageFileName,
                       sizeof (OBJECT_NAME_INFORMATION));
    } else {
        return FALSE;
    }


下面几个全局变量定义在WRK的Psp.h文件中:
ULONG PspCreateProcessNotifyRoutineCount;
EX_CALLBACK PspCreateProcessNotifyRoutine[8];
ULONG PspLoadImageNotifyRoutineCount;
EX_CALLBACK PspLoadImageNotifyRoutine[8];
extern PHANDLE_TABLE PspCidTable;

相关的函数就更多了,具体请参见原版WINDWOS INTERNALS 6[附件里有]

③ 一个进程的诞生,CreateProcess的步骤 ----

用户程序可以调用CreateProcess、CreateProcessAsUser、CreateProcessWithTokenWor、CreateProcessWithLogonW创建进程。而进程的创建主要由3部分参合进来完成的:Kernel32.dll、the Windows executive、子系统进程CSRSS.exe。

CreateProcess创建进程的大致步骤:
1.打开文件[.exe]。
2.创建进程内核对象
3.在进程中创建线程[堆栈、线程上下文、线程内核对象]
4.通知WINDOWS子系统已经创建了一个新进程,便于它进一步的初始化
5.如果标致不是CREATE_ SUSPENDED,那么就开始执行进程中的线程
6.在进程和线程的context里,完成地址空间的初始化[eg.加载需要的DLL],开始执行程序

CreateProcess在打开文件运行之前,会检查参数中的flags,决定如何设置新进程的优先级。

---->> stage 1: 打开并运行文件<<----

如果是PE文件,直接运行;如果不是PE格式,系统根据相应的格式选择相应的加载措施:
    POSIX的程序                 -- 调用 Posix.exe
    OS/2 1.x的程序              -- 调用 Os2.exe
    .bat/.cmd的程序             -- 调用 Cmd.exe
    16位/MS-DOS .exe/.com/.pif -- 调用 Ntvdm.exe
如果还是不能加载,CreateProcess就会失败.

与Ntvdm.exe相关的注册表在HKLM/SYSTEM/CurrentControlSet/Control/WOW下

到此,CreateProcess已经成功的打开了一个有效的文件,并且为它创建了一个section object。但并没有映射到内存中,仅仅是打开了而已。a section object被成功的创建了并不能说明此文件是一个有效的Windows image,因为文件可能是DLL或者POSIX。

接着检查注册表IFEO,系统如果发现某个程序文件在IFEO列表中,它就会首先来读取Debugger参数,如果该参数不为空,系统则会把Debugger参数里指定的程序文件名作为用户试图启动的程序执行请求来处理,而仅仅把用户试图启动的程序作为Debugger参数里指定的程序文件名的参数发送过去。 联想到了很久的映像劫持病毒。

---->> stage 2: 创建进程内核对象<<----

CreateProcess-->NtCreateProcess-->NtCreateProcessEx-->PspCreateProcess来创建进程内核对象。如下:
1.设置EPROCESS
2.创建进程地址空间
3.初始化KPROCESS // 偶在源码里面没有找到耶,太多了,反正没找到
4.完成进程地址空间的设置 // 原文中写着此时Ntdll.dll被映射到进程地址空间中
5.设置PEB                
6.完成进程内核对象的创建,审核收尾工作

----- stage2.1 设置EPROCESS-----
1.>创建EPROCESS
   //系统在启动时在PspInitPhase0()中初始化进程/线程对象的对象类型[Object types]
   // Create Object types for Thread and Process Objects.
    RtlInitUnicodeString (&NameString, L"Process");
    ObjectTypeInitializer.DefaultPagedPoolCharge = PSP_PROCESS_PAGED_CHARGE;
    ObjectTypeInitializer.DefaultNonPagedPoolCharge = PSP_PROCESS_NONPAGED_CHARGE;
    ObjectTypeInitializer.DeleteProcedure = PspProcessDelete;
    ObjectTypeInitializer.ValidAccessMask = PROCESS_ALL_ACCESS;
    ObjectTypeInitializer.GenericMapping = PspProcessMapping;

    if (!NT_SUCCESS (ObCreateObjectType (&NameString,
                                         &ObjectTypeInitializer,
                                         (PSECURITY_DESCRIPTOR) NULL,
                                         &PsProcessType))) {
        return FALSE; // PsProcessType是在Psinit.c中定义的全局变量 POBJECT_TYPE PsProcessType;
    }                 // 用它来保存对象类型的信息

    // 函数PspCreateProcess中创建EPROCESS
    // Create the process object
    Status = ObCreateObject (PreviousMode, // PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
                             PsProcessType, // 就是上面描述的那个全局变量
                             ObjectAttributes, // 函数的参数
                             PreviousMode,
                             NULL,
                             sizeof (EPROCESS),
                             0,
                             0,
                             &Process);    // 创建后的EPROCESS保存在此

    RtlZeroMemory (Process, sizeof(EPROCESS));
    ExInitializeRundownProtection (&Process->RundownProtect);
    PspInitializeProcessLock (Process);
    InitializeListHead (&Process->ThreadListHead);
2.> 设置进程Working set size的最值[20-45]
WorkingSetMinimum = PsMinimumWorkingSet; // 20
WorkingSetMaximum = PsMaximumWorkingSet; // 45
3.> 从父进程那里继承一些属性[略过]

----- stage2.2 创建进程地址空间-----
1.> page directory 即是PDT相关。 嘿嘿,想必大家都看过操作系统之类的书,上面介绍的有关内存管理的东西必须好好的看
前置知识是必须充分了解JT、MBT、PMT、PMTR、PTE、PDE等其中页表(PTE映射在系统高2GB中--0xC0000000~0xC03FFFFF的4MB空间,页目录映射在0xC0300000~...)


PS: 进程的地址空间分为4种:
    1. Boot Process--Address space is initialized during MmInit. Parent is not specified
    2. System Process--系统地址空间,看看前面介绍的这个全局变量PspInitialSystemProcess,熟悉了吧
    3. User Process (Cloned Address Space) 从指定的进程复制的
    4. User Process (New Image Address Space) 新的
------------------------------------------------------
if (SectionHandle != NULL) {
        // User Process (New Image Address Space).

        Status = MmInitializeProcessAddressSpace (Process,
                                                  NULL,
                                                  SectionObject,
                                                  &Flags,
                                                  &(Process->SeAuditProcessCreationInfo.ImageFileName));
...//省略

    } else if (Parent != NULL) { //如果存在父进程
        if (Parent != PsInitialSystemProcess) { // 如果父进程不是系统进程,说明是一个普通的父进程创建的
            Process->SectionBaseAddress = Parent->SectionBaseAddress;
            // User Process ( Cloned Address Space )
   
            Status = MmInitializeProcessAddressSpace (Process,
                                                      Parent,
                                                      NULL,
                                                      &Flags,
                                                      NULL);
            // A cloned process isn't started from an image file, so we give it the name
            // of the process of which it is a clone, provided the original has a name.
     // 暂时不重要,省略掉
        } else {
            // System Process

            Flags &= ~PROCESS_CREATE_FLAGS_ALL_LARGE_PAGE_FLAGS;
            Status = MmInitializeProcessAddressSpace (Process,
                                                      NULL,
                                                      NULL,
                                                      &Flags,
                                                      NULL);

    }

2.> Hyperspace page
3.> Working set list

----- stage2.5 设置PEB----
if (Parent && CreatePeb) {
        RtlZeroMemory (&InitialPeb, FIELD_OFFSET(INITIAL_PEB, Mutant));
        // PspCreateProcess中申明的局部变量 INITIAL_PEB InitialPeb;
        InitialPeb.Mutant = (HANDLE)(-1);
        InitialPeb.ImageUsesLargePages = (BOOLEAN) UseLargePages;
           
        if (SectionHandle != NULL) { // 如果要创建新的进程.PEB也得从新创建
            Status = MmCreatePeb (Process, &InitialPeb, &Process->Peb);

            Peb = Process->Peb;

        } else { // 如果是CLONE进程,PEB只要从父进程里面拷贝一份就可以了
            SIZE_T BytesCopied;

            InitialPeb.InheritedAddressSpace = TRUE;
            Process->Peb = Parent->Peb;
            MmCopyVirtualMemory (CurrentProcess,
                                 &InitialPeb,
                                 Process,
                                 Process->Peb,
                                 sizeof (INITIAL_PEB),
                                 KernelMode,
                                 &BytesCopied);
        }
    }
    Peb = Process->Peb;

----- stage2.6 完成进程内核对象的创建-----
1.> Audit the process creation
if (SeDetailedAuditingWithToken (NULL)) {
        SeAuditProcessCreation (Process); // Process为当前的EPROCESS
    }
2.> See if the parent has a job. If so reference the job and add the process in.
3.> 将EPROCESS插入的系统的active process链表中 [联想的断链和复制链]
    PspLockProcessList (CurrentThread);
    InsertTailList (&PsActiveProcessHead, &Process->ActiveProcessLinks);
    PspUnlockProcessList (CurrentThread);

4.> 线程创建的时间被设定,然后当线程的句柄可用的时候,返回到最初的调用者CreateProcess中[Kernel32.dll]

原创粉丝点击