首页
社区
课程
招聘
[原创]Windows内核之载入exe文件
发表于: 1天前 307

[原创]Windows内核之载入exe文件

1天前
307

前言:

本文基于ReactOS 0.4.15源码。


在创建进程时会载入exe文件,分两步进行,首先是将可执行映像映射到用户空间,这一步可以理解为对虚拟内存地址的规划。在实际执行exe文件时会触发缺页中断,此时才会读取exe文件的内容,本文介绍的就是缺页中断的处理。


缺页中断的处理函数是MmNotPresentFaultSectionView,来看看这个函数。

    Entry = MmGetPageEntrySectionSegment(Segment, &Offset);
    
    if (Entry == 0)
    {
        /*
         * If the entry is zero, then we need to load the page.
         */
        if ((Offset.QuadPart >= (LONGLONG)PAGE_ROUND_UP(Segment->RawLength.QuadPart)) && (MemoryArea->VadNode.u.VadFlags.VadType == VadImageMap))
        {
            /* We are beyond the data which is on file. Just get a new page. */
            MI_SET_USAGE(MI_USAGE_SECTION);
            if (Process) MI_SET_PROCESS2(Process->ImageFileName);
            if (!Process) MI_SET_PROCESS2("Kernel Section");
            Status = MmRequestPageMemoryConsumer(MC_USER, FALSE, &Page);
            if (!NT_SUCCESS(Status))
            {
                MmUnlockSectionSegment(Segment);
                return STATUS_NO_MEMORY;
            }
            MmSetPageEntrySectionSegment(Segment, &Offset, MAKE_SSE(Page << PAGE_SHIFT, 1));
            MmUnlockSectionSegment(Segment);

            Status = MmCreateVirtualMapping(Process, PAddress, Attributes, Page);
            if (!NT_SUCCESS(Status))
            {
                DPRINT1("Unable to create virtual mapping\n");
                KeBugCheck(MEMORY_MANAGEMENT);
            }
            ASSERT(MmIsPagePresent(Process, PAddress));
            if (Process)
                MmInsertRmap(Page, Process, Address);

            DPRINT("Address 0x%p\n", Address);
            return STATUS_SUCCESS;
        }

        MmUnlockSectionSegment(Segment);
        MmUnlockAddressSpace(AddressSpace);

        /* The data must be paged in. Lock the file, so that the VDL doesn't get updated behind us. */
        FsRtlAcquireFileExclusive(Segment->FileObject);

        PFSRTL_COMMON_FCB_HEADER FcbHeader = Segment->FileObject->FsContext;

        Status = MmMakeSegmentResident(Segment, Offset.QuadPart, PAGE_SIZE, &FcbHeader->ValidDataLength, FALSE);

        FsRtlReleaseFile(Segment->FileObject);

        /* Lock address space again */
        MmLockAddressSpace(AddressSpace);
        if (!NT_SUCCESS(Status))
        {
            if (Status == STATUS_NO_MEMORY)
            {
                return Status;
            }
            /* Damn */
            DPRINT1("Failed to page data in!\n");
            return STATUS_IN_PAGE_ERROR;
        }

        /* Everything went fine. Restart the operation */
        return STATUS_MM_RESTART_OPERATION;
    }

首先调用MmGetPageEntrySectionSegment,此时返回的Entry为空,所以会进入if语句,此时会执行Mm函数MakeSegmentResident,这个函数内会读取exe文件的内容。之后返回STATUS_MM_RESTART_OPERATION,这会导致MmNotPresentFaultSectionView被再次调用,不过此时执行的路径就和第一次不同了。

/* We already have a page on this section offset. Map it into the process address space. */
        Page = PFN_FROM_SSE(Entry);

        Status = MmCreateVirtualMapping(Process,
                                        PAddress,
                                        Attributes,
                                        Page);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Unable to create virtual mapping\n");
            KeBugCheck(MEMORY_MANAGEMENT);
        }

        if (Process)
            MmInsertRmap(Page, Process, Address);

        /* Take a reference on it */
        MmSharePageEntrySectionSegment(Segment, &Offset);
        MmUnlockSectionSegment(Segment);

        DPRINT("Address 0x%p\n", Address);
        return STATUS_SUCCESS;

这次执行会将物理内存Page和地址PAddress绑定起来,从而可以通过虚拟内存地址PAddress访问物理内存,这样就访问到exe文件内容了。其中的Page就是在MmMakeSegmentResident函数中读取到exe文件内容并写入到Page中的,来看看这个函数。

            PMDL Mdl = IoAllocateMdl(NULL, ReadLength, FALSE, FALSE, NULL);
            
            if (!Mdl)
            {
                /* Damn. Roll-back. */
                MmLockSectionSegment(Segment);
                while (ChunkOffset < ChunkEnd)
                {
                    if (ToReadPageBits & 1)
                    {
                        LARGE_INTEGER CurrentOffset;
                        CurrentOffset.QuadPart = ChunkOffset;
                        ASSERT(MM_IS_WAIT_PTE(MmGetPageEntrySectionSegment(Segment, &CurrentOffset)));
                        MmSetPageEntrySectionSegment(Segment, &CurrentOffset, 0);
                    }
                    ToReadPageBits >>= 1;
                    ChunkOffset += PAGE_SIZE;
                }
                MmUnlockSectionSegment(Segment);
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            /* Get our pages */
            PPFN_NUMBER Pages = MmGetMdlPfnArray(Mdl);
            RtlZeroMemory(Pages, BYTES_TO_PAGES(ReadLength) * sizeof(PFN_NUMBER));
            for (UINT i = 0; i < BYTES_TO_PAGES(ReadLength); i++)
            {
                Status = MmRequestPageMemoryConsumer(MC_USER, FALSE, &Pages[i]);
                if (!NT_SUCCESS(Status))
                {
                    /* Damn. Roll-back. */
                    for (UINT j = 0; j < i; j++)
                        MmReleasePageMemoryConsumer(MC_USER, Pages[j]);
                    goto Failed;
                }
            }

在这个函数中,首先调用了IoAllocateMdl函数分配了Mdl,然后调用MmRequestPageMemoryConsumer函数分配了物理内存,注意此时分配了多个物理内存页面,而在缺页中断中只用了其中一个内存页面。分配的物理内存标号保存在Pages数组中,这个数组的位置就在Mdl数据结构的上方。

            Status = IoPageRead(FileObject, Mdl, &FileOffset, &Event, &Iosb);
                        
            if (Status == STATUS_PENDING)
            {
                KeWaitForSingleObject(&Event, WrPageIn, KernelMode, FALSE, NULL);
                Status = Iosb.Status;
            }
            
            if (Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA)
            {
                MmUnmapLockedPages(Mdl->MappedSystemVa, Mdl);//解除绑定
            }
            
            for (UINT i = 0; i < BYTES_TO_PAGES(ReadLength); i++)
            {
                ULONG_PTR Entry = MAKE_SSE(Pages[i] << PAGE_SHIFT, 0);
                LARGE_INTEGER CurrentOffset;
                CurrentOffset.QuadPart = ChunkOffset + (i * PAGE_SIZE);

                ASSERT(MM_IS_WAIT_PTE(MmGetPageEntrySectionSegment(Segment, &CurrentOffset)));

                if (SetDirty)
                    Entry = DIRTY_SSE(Entry);

                MmSetPageEntrySectionSegment(Segment, &CurrentOffset, Entry);
            }

然后调用IoPageRead函数去读取exe文件的内容,并等待读取的完成。最后将物理页面Pages封装为Entry,这一步完成就会使得MmNotPresentFaultSectionView执行第二条路径,在这个路径中从Entry中提取出Page,并完成映射。来看看IoPageRead函数。

NTSTATUS
NTAPI
IoPageRead(IN PFILE_OBJECT FileObject,
           IN PMDL Mdl,
           IN PLARGE_INTEGER Offset,
           IN PKEVENT Event,
           IN PIO_STATUS_BLOCK StatusBlock)
{
    PIRP Irp;
    PIO_STACK_LOCATION StackPtr;
    PDEVICE_OBJECT DeviceObject;
    IOTRACE(IO_API_DEBUG, "FileObject: %p. Mdl: %p. Offset: %p\n",
            FileObject, Mdl, Offset);

    /* Get the Device Object */
    DeviceObject = IoGetRelatedDeviceObject(FileObject);

    /* Allocate IRP */
    Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
    /* If allocation failed, try to see whether we can use
     * the reserve IRP
     */
    if (Irp == NULL)
    {
        /* We will use it only for paging file */
        if (MmIsFileObjectAPagingFile(FileObject))
        {
            InterlockedExchangeAdd(&IoPageReadIrpAllocationFailure, 1);
            Irp = IopAllocateReserveIrp(DeviceObject->StackSize);
        }
        else
        {
            InterlockedExchangeAdd(&IoPageReadNonPagefileIrpAllocationFailure, 1);
        }

        /* If allocation failed (not a paging file or too big stack size)
         * Fail for real
         */
        if (Irp == NULL)
        {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    /* Get the Stack */
    StackPtr = IoGetNextIrpStackLocation(Irp);

    /* Create the IRP Settings */
    Irp->MdlAddress = Mdl;
    Irp->UserBuffer = MmGetMdlVirtualAddress(Mdl);
    Irp->UserIosb = StatusBlock;
    Irp->UserEvent = Event;
    Irp->RequestorMode = KernelMode;
    Irp->Flags = IRP_PAGING_IO |
                 IRP_NOCACHE |
                 IRP_SYNCHRONOUS_PAGING_IO |
                 IRP_INPUT_OPERATION;
    Irp->Tail.Overlay.OriginalFileObject = FileObject;
    Irp->Tail.Overlay.Thread = PsGetCurrentThread();

    /* Set the Stack Settings */
    StackPtr->Parameters.Read.Length = MmGetMdlByteCount(Mdl);
    StackPtr->Parameters.Read.ByteOffset = *Offset;
    StackPtr->MajorFunction = IRP_MJ_READ;
    StackPtr->FileObject = FileObject;

    /* Call the Driver */
    return IoCallDriver(DeviceObject, Irp);
}

这里就是简单的填写Irp的各个字段然后调用驱动去完成读取工作,注意这里的

Irp->MdlAddress = Mdl;

在驱动中会使用这个字段,那么exe文件的数据会读取到哪个地方呢?读取会调用MsfsRead函数。

    if (Irp->MdlAddress)
        Buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
    else
        Buffer = Irp->UserBuffer;

这里会调用if语句代码。

#define MmGetSystemAddressForMdlSafe(_Mdl, _Priority) \
  (((_Mdl)->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA \
    | MDL_SOURCE_IS_NONPAGED_POOL)) ? \
    (_Mdl)->MappedSystemVa : \
    (PVOID) MmMapLockedPagesSpecifyCache((_Mdl), \
      KernelMode, MmCached, NULL, FALSE, (_Priority)))

这里是一个宏,会调用到MmMapLockedPagesSpecifyCache函数。

        PointerPte = MiReserveSystemPtes(PageCount, SystemPteSpace);

        Base = (PVOID)((ULONG_PTR)MiPteToAddress(PointerPte) + Mdl->ByteOffset);

        do
        {
            //
            // We're done here
            //
            if (*MdlPages == LIST_HEAD) break;

            //
            // Write the PTE
            //
            TempPte.u.Hard.PageFrameNumber = *MdlPages;
            MI_WRITE_VALID_PTE(PointerPte++, TempPte);
        } while (++MdlPages < LastPage);

        return Base;

首先获取指向Pte的指针,然后根据pte获取到虚拟内存地址Base,之后就使用这个Base访问物理内存。虚拟内存和物理内存的绑定实在do循环中进行的。

这样我们就知道了MsfsRead函数中的Buffer指向的就是Mdl上方的Pages数组中的物理页面,并且Buffer地址就是这里返回的Base。剩下的就简单了,只是向Buffer中写数据,继续看MsfsRead函数

memcpy(Buffer, &Message->Buffer, min(Message->Size,Length));

这样物理地址中就有exe文件的数据了。这里完成了虚拟内存和物理内存的绑定,必然就有地方释放,释放是在MmUnmapLockedPages函数中。

RtlZeroMemory(StartingPte, NumberOfPtes * sizeof(MMPTE));

将Pte位置值写0就清除了绑定。





[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 1天前 被zhzhz编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回