首页
论坛
课程
招聘
[旧帖] [原创]内核层内存扫描的探究 0.00雪花
2013-9-1 00:04 5036

[旧帖] [原创]内核层内存扫描的探究 0.00雪花

2013-9-1 00:04
5036
  内存扫描是扫描进程特征码,包括搜索进程特征(Eprocess)、进程内存读取。本文支持xp、win7、x64。
  搜索特征包括:Eprocess ActiveLink、虚拟内存搜索、物理内存搜索三种方式,下面分别做介绍。
      Eprocess ActiveLink方式:
      通过下图_eprocess结构中ActiveProcessLinks属性把各进程串接起来。通过遍历ActiveProcessLinks属性组成的链表查找所有进程。
                       
       windbg中输入dt _eprocess观察到的

代码如下:
VOID GetProcessListByEProcessList(VOID)
{
   PEPROCESS curEprocess=NULL;
   ULONG  nCount=0,ret = 0,
                    ulProcessLink=GetPlantformDependentInfo(PROCESS_LINK_OFFSET);
   ULONG_PTR ulEndAddress=0,ulEProcessAddress=0,ulFlink=0;
   
   if ((curEprocess =IoGetCurrentProcess())==NULL) //获得当前进程Eprocess
   {
    KdPrint(("IoGetCurrentProcess error!\r\n"));
    return;
   }
    
   ulEProcessAddress = (ULONG_PTR)curEprocess;
   ulEndAddress = ulEProcessAddress;
    
   //定位到system进程的下一个EProcess
    ulEProcessAddress = ulFlink - ulProcessLink;
    
    KdPrint(("-------------------------------------------\r\n"));
    KdPrint(("EProcess    PID    ImageFileName\r\n"));
    KdPrint(("-------------------------------------------\r\n"));
    
    while (ulEProcessAddress != ulEndAddress)
    {   
     //遍历EPROCESS结构,找到最小的Eprocess地址处
      ulEProcessAddress = ulFlink - ulProcessLink;
      if (ShowProcess(ulEProcessAddress)) //此处继续同步x64
                    nCount ++;
      }

KdPrint(("-------------------------------------------\r\n"));
    KdPrint(("=====Total Processes count:%d=======\r\n", nCount));
KdPrint(("-------------------------------------------\r\n"));
}

2.虚拟内存搜索方式:
   从MmSystemRangeStart(0x80000000)地址往上搜索进程的Eprocess特征。Eprocess结构体内存的分配是在非换页内存池中分配的,极端情况在非换页内存池被占满时,会在非换页内存池扩展中分配。所以只用在非换页内存池中扫描。非换页内存池地址范围如下图:  
                              
只需要扫描MmNo到nPagedPoolStartMmNonPagedPoolStart+MmSizeOfNonPagedPoolInBytes的范围。
 
 
 VOID SearchProcessInVirtualMemory()
 {
         ULONG nProcessCount = 0,ret=0;
         SIZE_T i=0;
         for(i = gMmNonPagedPoolStart; i <gMmNonPagedPoolEnd; i += 4)
         {
                 ret = VALIDpage(i);
          //内核中进程结构包括object header和object body(Eprocess)
                 if (i+OBJECT_HEADER_SIZE+
                         GetPlantformDependentInfo(EPROCESS_SIZE)>=gMmNonPagedPoolEnd)
                         break;  
                
                 if(IsaRealProcess(i))
                 {
                         if (ShowProcess(i +OBJECT_HEADER_SIZE))
                         {
                                 nProcessCount++;
                                 i += GetPlantformDependentInfo(EPROCESS_SIZE)+OBJECT_HEADER_SIZE-4;
                         }
                 }
                 else if(ret == PTE_INVALID
                         || ret == PAE_PTE_INVALID)
      {
                         i -=4;
                         i += 0x1000;//4k
                 }
                 else if(ret == PDE_INVALID)
         {
                         i-=4;
                         i+= 0x200000;//2mb
                 }
                 else if(ret == PAE_PDE_INVALID)
                 {
                         i-=4;
                         i+=0x400000;// 4mb
                 }
         KdPrint(("-------------------------------------------\r\n"));
         KdPrint(("=====   Total Processes count:%3d   =======\r\n", nProcessCount));
         KdPrint(("-------------------------------------------\r\n"));
 }
 
 BOOLEAN IsaRealProcess(SIZE_T i) //依据对象类型判断
 {
         SIZE_T pObjectType=0,ObjectTypeAddress=0,offset = i;
 
         if(!MmIsAddressValid((PVOID)(offset+OBJECT_HEADER_SIZE)))
         {
                 //KdPrint(("VALIDpage(offset+OBJECT_HEADER_SIZE) != VALID"));
                 return FALSE;
         }
 
         ObjectTypeAddress = offset  + GetPlantformDependentInfo(OBJECT_TYPE_OFFSET);
 
     if (gWinVerDetail==WINDOWS_VERSION_7)
         {
                 //win下对象类型结构变了,直接判断TypeIndex索引号相等就行
                 if ((g_ulObjectType&0x000000ff) == (pObjectType&0x000000ff))
                         return TRUE;               
         }
         else if (gWinVerDetail!=WINDOWS_VERSION_NONE)
         {
                 if (g_ulObjectType == pObjectType) //确定ObjectType是Process类型
                         return TRUE;
         }
        
         return FALSE;
 }
 
 特征包括进程结束时间、对象表、对象类型、PID。
 
 BOOLEAN  ShowProcess(ULONG pEProcess)
 {
         PLARGE_INTEGER ExitTime=NULL;
         ULONG PID =0,tablePID = 0, objTableHandle = 0,tableCode = 0;
         PUCHAR pFileName=NULL;
 
         ULONG ulRet=0;
        
         __try
         {
                 ExitTime=(PLARGE_INTEGER)(pEProcess+GetPlantformDependentInfo(EXIT_TIME_OFFSET));
                 if(ExitTime->QuadPart != 0) //已经结束的进程的ExitTime为非零
                 {
                         KdPrint(("Process info error: ExitTime is not zero!"));
                         return FALSE;
                 }
                
                 if(VALIDpage(pEProcess + GetPlantformDependentInfo(OBJECT_TABLE_OFFSET))!= VALID)
                 {
                         KdPrint(("Process info error: ObjectTable address is invalid->0x%x!",
                                 pEProcess + GetPlantformDependentInfo(OBJECT_TABLE_OFFSET)));
                         return FALSE;
                 }
                
                 objTableHandle=*(PULONG)(pEProcess+GetPlantformDependentInfo(OBJECT_TABLE_OFFSET));
                 if(objTableHandle == 0)
                 {
                         KdPrint(("Process info error: ObjectTable is zero!"));
                         return FALSE;
                 }
                
                 if(!IsProcessAlive((PEPROCESS)pEProcess))
                 {
                         KdPrint(("Process is not alive!"));
                         return FALSE;
                 }
                
                 if(VALIDpage(objTableHandle)!=VALID)
                 {
                         KdPrint(("Process info error: ObjectTable is invalid!"));
                         return FALSE;
                 }
                
                 tableCode = *(PULONG)objTableHandle;
                 if(tableCode == 0)
                 {
                         KdPrint(("Process info error: tableCode is zero!"));
                         return FALSE;
                 }
                
         if(VALIDpage(objTableHandle+GetPlantformDependentInfo(HANDLE_TABLE_PID_OFFSET))!=VALID)
                 {
                         KdPrint(("Process info error: HandleTablePid is invalid!"));
                         return FALSE;
                 }
                
                 tablePID=*(PULONG)(objTableHandle+GetPlantformDependentInfo(HANDLE_TABLE_PID_OFFSET));
                
                 if(VALIDpage(pEProcess+GetPlantformDependentInfo(PROCESS_ID_OFFSET))!=VALID)
                 {
                         KdPrint(("Process info error: ProcessId is invalid!"));
                         return FALSE;
                 }
                
                 PID = *(PULONG)(pEProcess + GetPlantformDependentInfo(PROCESS_ID_OFFSET));
                 if(tablePID == PID) //排除无用的EProcess,为什么会有无用的需要考证
                 {
                         pFileName=(PUCHAR)(pEProcess+GetPlantformDependentInfo(FILE_NAME_OFFSET));
                         KdPrint(("0x%08X  %04d   %s\r\n",pEProcess,PID,pFileName));
                         //从这里开始用RundownProtect锁
                         AddEProcess((PEPROCESS)pEProcess); //已经添加过Eprocess也返回FALSE,所以不判断返回值
                 }
                 else
                 {
                         KdPrint(("Eprocess two pid are not equal!"));
                         return FALSE;
                 }
         }
         __except(EXCEPTION_EXECUTE_HANDLER)
         {
                 KdPrint(("ShowProcess exception catched!"));
                 return FALSE;
         }
         return TRUE;
 }
   Eprocess的“进程结束时间”不为0则说明正在被删除,不用加入到扫描的结果中。从Eprocess的“对象表”取出“PID”并与Eprocess的PID字段对比,若不相等则不加入到扫描结果。检查Eprocess的“对象类型”是否为进程类型,是则加入到扫描结果,进程“对象类型”赋值为Eprocess ActiveLink方式找到的进程“对象类型”。
   由于虚拟地址是由页目录、页表映射到物理地址实现的,所以扫描虚拟地址时,需判断该虚拟地址对应的页目录、页表是否有效。有时只分配了虚拟地址,未分配实际内存,也就是为内存建立页目录、页表。使用以下函数判断虚拟地址的有效性:
  ULONG VALIDpage(SIZE_T addr)
  {
   #if defined(_WIN64) //64位操作系统
          ULONGLONG *pte=0,*pde=0;
          pde=GET_PDE_ADDR_X64(addr);
      //测试地址对应的PDE是否有效
          if(MmIsAddressValid((PVOID)pde) && (*(PULONGLONG)pde & 0x1) != 0)
          {
                  //是大页面则没有PTE,返回地址有效
                  if((*(PULONGLONG)pde & 0x80) != 0)
                          return VALID;
                  pte=GET_PTE_ADDR_X64(addr);
          //测试地址对应的PTE是否有效
                  if(MmIsAddressValid((PVOID)pte) && (*(PULONGLONG)pte & 0x1) != 0)
                          return VALID;
                  else
                          return PTE_INVALID_X64;
          }
          return PDE_INVALID_X64;
  #else  //32位操作系统
          if(g_bIsPAEmode) //是PAE模式
          {
                  ULONGLONG pte=0,pde=0;
      pde = 0xc0600000 + (((addr>>21)<<3) );
                  if(MmIsAddressValid((PVOID)pde)
                          && (*(PULONGLONG)pde & 0x1) != 0)
                  {
                          //large page
                          if((*(PULONGLONG)pde & 0x80) != 0)
                                  return VALID;
                          pte = 0xc0000000 + (((addr>>12)<<3));
                          if(MmIsAddressValid((PVOID)pte)
                                  && (*(PULONGLONG)pte & 0x1) != 0)
                                  return VALID;
                          else
                                  return PAE_PTE_INVALID;
                  }
                  return PAE_PDE_INVALID;
          }
          else  //不是PAE模式
          {
                  …
          }
  #endif
  }
   
   代码中判断了是否为PAE模式,PAE是为了使32位系统支持4GB以上的内存的机制。   在PAE技术支持下,系统可以拥有两种大小的物理页面:传统的4KB和2MB页面(注意,不是4MB)。同时,采用PAE技术的页式地址转换与传统的IA32方式有了非常大的变化:首先,IA32中的两级页表在PAE中变成了三层,CR3指向的不再是页目录表,而被称为页目录指针表(Page Directory Pointer Table)下图分别表示了未启用PAE, 4 KB的页和启用PAE,4 KB的页。下图分别表示了启用PAE 4K的页、启用PAE 2MB的页、未启用PAE 4K的页、未启用PAE 4MB的页。
                                              
                  PAE with 4KB pages

                              
                  PAE with 2 MB pages

                              
                          not PAE with 4KB pages

                              
                                not PAE with 4MB pages
   
   32位系统可配置开启或不开启PAE模式,x64是开启PAE模式的。

    3.物理内存搜索方式:
  如果Rootkit设法篡改了系统页表的页表项( PTE) 使其重定向为存有垃圾数据的影子物理页面或者针对特定页面插入了不一致的TLB 缓存表项,则通过虚拟地址无法读取到真实内存数据。另外,基于虚拟化技术的Rootkit 还可以利用影子页表技术接管内存,为操作系统提供不真实的地址映射关系。所以引入物理内存扫描解决虚拟内存数据获取不真实的问题。
     虚拟地址->物理地址的转换通过进程内核结构Eprocess的CR3寄存器得到页目录地址,加上虚拟地址中的页目录索引寻址到页目录项,得到页表地址。页表地址+虚拟地址中的页表索引得到页面地址,最后页面地址和页内偏移相加得到物理地址。
                                          
                                          虚拟地址到物理地址的页式管理
  物理内存搜索先申请一块内存(通常是一整页4K的大小,以下简称为Dummy内存),修改Dummy内存的PTE为待扫描内存的PTE,从而指向同一页物理地址。达到跳过映射直接扫描物理内存的目的,有效防止了高级Rootkit的隐藏。
   CPU为加快访问内存加入了TLB(Translation Lookaside Buffer)机制,不用经过虚拟地址到物理地址的映射,直接从TLB中找到对应的关系。所以修改了Dummy内存的PTE后,需要更新下TLB,否则从TLB中访问,修改失效。
   修改Dummy内存PTE为待扫描内存地址PTE的代码如下:
   
   BOOLEAN DummyPageFrameNumToPhysic(  //修改dummy mem 页帧编号为实际使用内存帧编号
                           PPHY_DATA_PAE phy_data
                           ,ULONGLONG  phy_addres
                           )
   {
           SIZE_T phy_addr=0;
           ULONGLONG page_index = phy_addres/phy_data->page_size,phy_addr_pae=0,old_cr3 = 0;
           BOOLEAN ret = FALSE;
           PMMPTE_PAE pPtePae=NULL;
           PMMPTE_X86 pPte=NULL;
          
           if(g_bIsPAEmode) //是PAE模式
           {
                   phy_addr_pae = *phy_data->pte_pfn_addr_pae;
                   pPtePae = GetPaePte(phy_addr_pae);

                   pPtePae->u.Hard.Valid =1; //使dummy mem 的PTE有效、可写
                   pPtePae->u.Hard.Write = 1;
                   pPtePae->u.Hard.Owner =1;
                   pPtePae->u.Hard.PageFrameNumber = page_index;
   
                   _disable(); //禁止中断
                   EnableWriteOP(&old_cr3);  //允许内核地址可写
                   InterlockedExchange64(phy_data->pte_pfn_addr_pae,phy_addr_pae);
                   RestoreWriteOP(old_cr3);
                   _enable();
           }
           else //不是PAE模式
           {
                   …
           }
   }
   
  物理内存搜索方式同样可以搜索非换页内存池加快处理速度。但需要做一些额外的工作。原来使用内核函数MmGetPhysicalMemoryRanges可直接获得所需扫描内存的物理地址范围。物理内存的地址是按4K一个页面顺次排列的,页帧号依次递增。这样要扫描的物理地址后除以4K就得到要扫描的页帧号,然后修改dummy内存的PTE为得到的页帧号就行了。
  但改为只扫描非换页内存池后,范围值是虚拟地址,需自己实现虚拟地址到物理地址的转换过程(PDE->PTE->PageFrameNumber),具体步骤如下:
  1)得到非换页内存池的起始PDE和结束PDE。
  2)依次遍历其中的每个PDE,若是大页面,且是XP和2003系统,计算出PDE对应的起始和结束PageFrameNumber,对应修改dummy PTE的PageFrameNumber。若是win7系统,同一个PDE中的PTE的PFN都不一定连续,需要遍历每个PTE。因为内存紧张时,大页面被释放,从而形成小页面,造成不连续。
  3)若是小页面还需遍历这个PDE的每个PTE,计算出每个PTE的PageFrameNumber,对应修改dummy。因为同一个PDE中的PTE的PFN不一定连续。

VOID SearchEProcessInPhysicMemory(VOID)
{
        ULONG        nCount = 0,size = 0,ulPageSize=0;
        ULONGLONG ullCurPfn=0,ullStartPfn=0,ullEndPfn=0;
        ULONGLONG *pStartPdePae=NULL,*pEndPdePae=NULL,*pStartPtePae=NULL;
        SIZE_T *pStartPde=NULL,*pEndPde=NULL,*pStartPte=NULL;
        SIZE_T ulBeginAddr=0,ulEndAddr=0,va = 0,ulCurPDEMappedVA=0,ulEndPDEMappedVA=0;
        PMMPTE_PAE pRealPtePae= NULL;
        PMMPTE_X86 pRealPte= NULL;
        BOOLEAN ret = FALSE,bLargePage=FALSE,bStartScanAddr=TRUE,bAcquire=FALSE,bLocked=FALSE;
        KIRQL old_irql = KeGetCurrentIrql();//,irql_lock;

        g_bPhysicalScan=TRUE;
       
        if(g_bIsPAEmode)
        {
                ulPageSize=2*1024*1024;
                pStartPdePae=PAE_GET_PDE_ADDR(gMmNonPagedPoolStart);
                pEndPdePae=PAE_GET_PDE_ADDR(gMmNonPagedPoolEnd);
                //对齐PDE起始地址,方便计算
                ulCurPDEMappedVA=MiPdeStartAddressPae(pStartPdePae);
                ulEndPDEMappedVA=MiPdeStartAddressPae(pEndPdePae);
        }
        else
        {
                ulPageSize=4*1024*1024;
                pStartPde=GET_PDE_ADDR(gMmNonPagedPoolStart);
                pEndPde=GET_PDE_ADDR(gMmNonPagedPoolEnd);
                //对齐PDE起始地址,方便计算
                ulCurPDEMappedVA=MiPdeStartAddress(pStartPde);
                ulEndPDEMappedVA=MiPdeStartAddress(pEndPde);
        }
       
        ulCurPDEMappedVA-=ulPageSize;
        while(ulCurPDEMappedVA<=gMmNonPagedPoolEnd) //遍历每个PDE        {       
                ulCurPDEMappedVA+=ulPageSize;
                if (bStartScanAddr) //要扫描的起始地址不一定是PDE开始处,排除不相干地址加快扫描速度
                {
                        bStartScanAddr=FALSE;
                        ulBeginAddr=gMmNonPagedPoolStart;
                        ulEndAddr=ulCurPDEMappedVA+ulPageSize-1;
                        if ((ullStartPfn=GetAddressPTEPFN(ulBeginAddr,TRUE,&bLargePage))==0)
                                continue;
                        if ((ullEndPfn=GetAddressPTEPFN(ulEndAddr,FALSE,&bLargePage))==0)
                                continue;
                }
                else if (ulCurPDEMappedVA==ulEndPDEMappedVA) //同上,排除结束地址后面的不相干地址
                {
                        ulBeginAddr=ulCurPDEMappedVA;
                        ulEndAddr=gMmNonPagedPoolEnd-1;
                        if ((ullStartPfn=GetAddressPTEPFN(ulBeginAddr,TRUE,&bLargePage))==0)
                                continue;
                        if ((ullEndPfn=GetAddressPTEPFN(ulEndAddr,FALSE,&bLargePage))==0)
                                continue;
                }
                else
                {
                        ulBeginAddr=ulCurPDEMappedVA;
                        ulEndAddr=ulCurPDEMappedVA+ulPageSize-1;
                        if ((ullStartPfn=GetAddressPTEPFN(ulBeginAddr,TRUE,&bLargePage))==0)
                                continue;
                        if ((ullEndPfn=GetAddressPTEPFN(ulEndAddr,FALSE,&bLargePage))==0)
                                continue;
                }
               
                if (bLargePage) //大页面
                {       
                        //同一个PDE中的PTE的PFN都不一定连续,需要遍历每个PTE
                        //内存紧张时,大页面被释放,从而形成小页面,造成不连续
                        for(;ulBeginAddr<ulEndAddr;ulBeginAddr+=4*1024)
                        {
                                //KdPrint(("大页面方式,正在扫描第%d帧!",ulCurPfn));
                                if ((ullCurPfn=GetAddressPTEPFN(ulBeginAddr,TRUE,&bLargePage))==0)
                                        continue;
                                if (!DummyPageFrameNumToPhysic(&gPhyData,ullCurPfn))
                                        continue;
                                va =gPhyData.mem_addr;
                                if(!CheckEProcess((PVOID)va,4*1024,&nCount))  //检查是否Eprocess
                                {
                                        KdPrint(("CheckEProcess in search mem error\n"));
                                        continue;
                                }
                        }
                }
                else //小页面(4K),//大页面也和小页面相同               
   {       
                        ......
                }
        }

     KdPrint(("-------------------------------------------\r\n"));
     KdPrint(("=====Total Processes count:%d=======\r\n", nCount));
   KdPrint(("-------------------------------------------\r\n"));
}

ULONGLONG InternalGetAddressPTEPFN(SIZE_T ulAddress,BOOLEAN *bLargePage)
{
        ULONGLONG ullPFN=0;
        PMMPTE_PAE pRealPtePae= NULL,pRealPdePae=NULL;
        PMMPTE_X86 pRealPteX86= NULL,pRealPdeX86=NULL;

        PMMPTE_X64 pRealPteX64= NULL,pRealPdeX64= NULL;
        ULONGLONG *pPdePae=NULL,*pPtePae=NULL;
        SIZE_T *pPde=NULL,*pPte=NULL;
        BOOLEAN bPdeLocked=FALSE,bPteLocked=FALSE;

        if (!bLargePage)
                return 0;

        if(g_bIsPAEmode)
        {
                pPdePae=PAE_GET_PDE_ADDR(ulAddress);
                pPtePae=PAE_GET_PTE_ADDR(ulAddress);
                //页表不会被换出,直接用MmIsAddressValid取
                if (!pPdePae || !MmIsAddressValid((PVOID)pPdePae))
                        return 0;
                pRealPdePae= GetPaePde(*pPdePae);
                if(pRealPdePae->u.Hard.Valid == 0)
                        return 0;
                //没pte的(LargePage)修改为: PDE的高11位(PageFrameNumber) + 虚拟地址的低21位
                //也可这样计算 PAE模式下,PTE的PFN= PDE的PFN+地址的第10-19位
                if (pRealPdePae->u.Hard.LargePage==1)
                {
                        //高4字节为0
                        ullPFN=((pRealPdePae->u.Long&0xFFE00000)+(ulAddress&0x1FFFFF))/0x1000;
                        *bLargePage=TRUE;
                }
                else //有pte的(小页面)直接修改为真实内存的pte的pfn,也可通过PDE的pfn计算得到PTE的pfn
                {
                        if (!pPtePae || !MmIsAddressValid((PVOID)pPtePae))
                                return 0;
                        pRealPtePae = GetPaePte(*pPtePae);
                        if(pRealPtePae->u.Hard.Valid == 0)
                                return 0;
                        ullPFN=pRealPtePae->u.Hard.PageFrameNumber;
                }
        }
        else //非PAE模式
        {
         ......
        }

        return ullPFN;
}

ULONGLONG GetAddressPTEPFN(SIZE_T ulAddress,BOOLEAN bStartAddr,BOOLEAN *bLargePage)
{
        ULONGLONG ullRet=0;
        ullRet=InternalGetAddressPTEPFN(ulAddress,bLargePage);
        if (ullRet==0)
        {
                if (bStartAddr) //有可能PTE内容为0,缩小范围再获取
                        ullRet= InternalGetAddressPTEPFN(ulAddress+0x1000,bLargePage);
                else
                        ullRet= InternalGetAddressPTEPFN(ulAddress-0x1000,bLargePage);
        }
        return ullRet;
}

      结合以上3种搜索的结果,去掉重复的Eprocess,得出进程列表。扫描进程特征的流程如下图:
                                    
  读取进程内存:
  使用原型PTE读取进程内存。原型PTE是磁盘中的文件在内存中的映射,通过PEPROCESS->SECTION_OBJECT- >SEGMENT->PrototypePte获得。原型PTE以数组方式呈现,其中一个元素表示一页,PEPROCESS->SECTION_OBJECT- >SEGMENT->TotalNumberOfPtes表示数组大小。元素用MMPTE结构体表示:
   typedef struct _MMPTE
   {
           union
           {
                   ULONGLONG Long;
                   MMPTE_HIGHLOW HighLow;
                   MMPTE_HARDWARE Hard;
                   HARDWARE_PTE Flush;
                   MMPTE_PROTOTYPE Proto;
                   MMPTE_SOFTWARE Soft;
                   MMPTE_TRANSITION Trans;
                   MMPTE_SUBSECTION Subsect;
           }u;
   } MMPTE, *PMMPTE;

    每一页对应六种状态:物理内存PTE、Map文件PTE、转移PTE、未知PTE、页面文件PTE、要求零页面PTE 。以下分别描述其意义及读取其数据的方法:
    物理内存PTE:表示页在物理内存中。直接读取其内容即可。
    Map文件PTE:表示页是映射到内存中的。获取SectionObject对应的文件指针,及文件中对应位置后读取文件内容即可。
      转移PTE:表示页正在从内存交换到磁盘,但此时还在内存中。可通过虚拟地址读取其内容。
    未知PTE:表示1)已经分配地址,没有分配物理地址 2)出错。直接填充全0。
    页面文件PTE:表示已交换到磁盘文件。获取页面文件的指针,并读取指定位置的内容。
  要求零页面PTE:表示磁盘中的文件映射到内存后,节大小的差值用零填充。PE文件中节大小在磁盘和在内存中是不同的,在磁盘中是0x200,在内存中是0x1000。所以映射到内存后节大小变大,多余部分用零填充。读取其内容直接填充全0。
      读取进程内存流程图如下所示:
                                                                                    
      其中x64操作系统直接映射到文件中读取,因为x64有PatchGuard,不会修改页表映射关系。
 
根据原型PTE状态读取进程内存代码如下:
   
BOOLEAN ReadMemFromSection(
                                                   PEPROCESS_LIST ep_list_entry,
                                                   MMPTE_X86 *pPrototype,
                                                   MMPTE_PAE *pPrototypePae,
                                                   ULONG ulTotalNumberOfPtes
                                                   ,PFILE_OBJECT  pFile
                                                   ,ULONG pageIndex
                                                   ,PHY_DATA_PAE* pmm_reader
                                                   ,ULONG size
                                                   )
{
        BOOLEAN ret = FALSE,bCanLock=FALSE,bAcquireSucess=FALSE;
        ULONG        i = pageIndex;
        ULONGLONG phy_addr=0;
        UCHAR*                buf = (UCHAR*)pmm_reader->mem_addr;
        LARGE_INTEGER StartingOffset={0};
        KIRQL         oldIrql= KeGetCurrentIrql(),irql_lock;

        if(!pFile || !pmm_reader || pmm_reader->page_size != size || (!pPrototype&&!pPrototypePae))
                return ret;

        if(g_bIsPAEmode)
        {
                KdPrint(("PAE mode!\r\n"));

                if(i < ulTotalNumberOfPtes)
                {
                        MMPTE_PAE  cur_mmpte ={0};
                        cur_mmpte = pPrototypePae[i];
                        if(cur_mmpte.u.Hard.Valid == 1)        //在物理内存中是有效的               
    {
                                phy_addr = cur_mmpte.u.Hard.PageFrameNumber*pmm_reader->page_size;
                                DummyPageFrameNumToPhysic(pmm_reader,phy_addr);
                                //拷贝物理内存中的数据
                                RtlCopyMemory(pProcessData,buf,pmm_reader->page_size);                                        ret = TRUE;
                        }
                        else //不在物理内存中
                        {
                                if(cur_mmpte.u.Soft.Prototype == 1) //去映射文件中读
                                {                                                                       
                                        PVOID  address = (PVOID)(&pPrototypePae[i]),pSection=NULL;
                                        pSection = MiGetSubsectionAddressPae(&cur_mmpte);
                                        if (address && MmIsAddressValid(address)
                                                && pSection && MmIsAddressValid(pSection))
                                        {
                                                StartingOffset.QuadPart=CallMiStartingOffset(pSection, address);                                                 if(ReadFromMapFile(ep_list_entry,pFile,StartingOffset.QuadPart,
                                                                pmm_reader->page_size,irql_lock))
                                                                ret = TRUE;
                                        }
                                }
                                else if(cur_mmpte.u.Soft.Transition == 1) //转移pte                                                        {   
                                        phy_addr = cur_mmpte.u.Trans.PageFrameNumber*pmm_reader->page_size;
                                        DummyPageFrameNumToPhysic(pmm_reader,phy_addr);
                                        RtlCopyMemory(pProcessData,buf,pmm_reader->page_size);
                                        ret = TRUE;
                                }
                                else if(cur_mmpte.u.Long == 0LL) //无效pte:1.已经分配地址,没有分配物理地址2.出错。
                                {   
                                        RtlZeroMemory(pProcessData,pmm_reader->page_size);
                                        ret = FALSE;       
                                }
                                else if(cur_mmpte.u.Soft.PageFileHigh == 0) //要求零页面
                                {
                                        RtlZeroMemory(pProcessData,pmm_reader->page_size);
                                        ret = TRUE;    //特征码扫描时要写入
                                }
                                else //"位于页面文件中"
                                {
                        if(ReadFromPageFile(ep_list_entry,(ULONG)cur_mmpte.u.Soft.PageFileLow,
                (ULONGLONG)cur_mmpte.u.Soft.PageFileHigh,pmm_reader->page_size,irql_lock))
                                                ret = TRUE;
                                }
                        }
                } //if(i < ulTotalNumberOfPtes)
        }
        else //不是PAE模式
        {
                …
        }
        return ret;

补充:
     扫描进程内存时,需要加RundownProtect锁,此锁在Eprocess结构中,使用 ExAcquireRundownProtection、ExReleaseRundownProtection。

【公告】 [2022大礼包]《看雪论坛精华22期》发布!收录近1000余篇精华优秀文章!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (9)
雪    币: 14
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
浪子wj 活跃值 2013-9-1 18:20
2
0
好文章,学习了。。。
雪    币: 613
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
天高 活跃值 2013-9-1 18:58
3
0
make
雪    币: 255
活跃值: 活跃值 (1035)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
sxpp 活跃值 1 2013-9-1 19:58
4
0
留记号!
雪    币: 81
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
asdli 活跃值 2013-9-1 22:55
5
0
mark  好文
雪    币: 2623
活跃值: 活跃值 (1768)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2013-9-3 15:46
6
0
这文章应该设精啊。。。。。。。。比起好多精华帖子来说强多了。。。。。。
雪    币: 2623
活跃值: 活跃值 (1768)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2013-9-3 16:00
7
0
楼主继续讲ReadFromPageFile函数ReadFromMapFile
雪    币: 5077
活跃值: 活跃值 (470)
能力值: ( LV12,RANK:220 )
在线值:
发帖
回帖
粉丝
yirucandy 活跃值 4 2013-9-3 16:14
8
0
BOOLEAN ReadFromPageFile(PEPROCESS_LIST ep_list_entry,
		ULONG pageIndex
		,ULONGLONG pageOffset
		,ULONG size 
		,KIRQL irql_lock)
{
	BOOLEAN ret = FALSE;
	PFILE_OBJECT pFile=NULL;

	if(!g_MmPagingFile || !ep_list_entry)
	{
		//KdPrint(("MmPagingFile is null\n"));
		return ret;
	}

	if (MmIsAddressValid((PVOID)(GeKdDebuggerDataBlock->
		MmNumberOfPagingFiles.VirtualAddress))
		&& pageIndex>=(ULONG)*((PULONG)GeKdDebuggerDataBlock->
		MmNumberOfPagingFiles.VirtualAddress))
	{
		//KdPrint(("pageIndex is outIndex of MmPagingFile\n"));
		return ret;
	}

	if(!MmIsAddressValid((PVOID)(g_MmPagingFile+pageIndex)) 
	   || !MmIsAddressValid((PVOID)(*(g_MmPagingFile+pageIndex))))
	{
		//KdPrint(("MmPagingFile array's element is valid!\n"));
		return ret;
	}

	__try
	{
		if (gWinVerDetail==WINDOWS_VERSION_7)
		{
			PMMPAGING_FILE_WIN7 pMmPageingFile= 
				(PMMPAGING_FILE_WIN7)(*(g_MmPagingFile+pageIndex));
			if (!pMmPageingFile || !MmIsAddressValid(pMmPageingFile))
				return ret;
			ret = ReadFromMapFile(ep_list_entry,pMmPageingFile->File,
				pageOffset,size,irql_lock);
		}
		else if (gWinVerDetail==WINDOWS_VERSION_VISTA_2008)
		{
			PMMPAGING_FILE_VISTA pMmPageingFile= 
				(PMMPAGING_FILE_VISTA)(*(g_MmPagingFile+pageIndex));
			if (!pMmPageingFile || !MmIsAddressValid(pMmPageingFile))
				return ret;
			ret = ReadFromMapFile(ep_list_entry,pMmPageingFile->File,
				pageOffset,size,irql_lock);
		}
		else if (gWinVerDetail==WINDOWS_VERSION_XP)
		{
			PMMPAGING_FILE_XP pMmPageingFile= 
				(PMMPAGING_FILE_XP)(*(g_MmPagingFile+pageIndex));
			if (!pMmPageingFile || !MmIsAddressValid(pMmPageingFile))
				return ret;
			ret = ReadFromMapFile(ep_list_entry,pMmPageingFile->File,
				pageOffset,size,irql_lock);
		}
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		ret = FALSE;
	}

	return ret;
}


BOOLEAN ReadFromMapFile(PEPROCESS_LIST ep_list_entry
		,PFILE_OBJECT pFilePointer
		,ULONGLONG Start
		,ULONG Length
		,KIRQL irql_lock)
{
	BOOLEAN ret = FALSE;
	IO_STATUS_BLOCK IoStatus;
	LARGE_INTEGER Offset;
	NTSTATUS Status;
	PMDL Mdl=NULL;
	KEVENT Event;
	PIRP pIrp=NULL; 

	UNREFERENCED_PARAMETER(ep_list_entry);

	if(!pFilePointer || !MmIsAddressValid(pFilePointer)
	   || !pFilePointer->DeviceObject 
	   || !MmIsAddressValid(pFilePointer->DeviceObject)
	   || Length <= 0)
	  return FALSE;

	RtlZeroMemory(pProcessData,Length);

	Mdl = IoAllocateMdl(pProcessData, Length, FALSE, FALSE, NULL);  //需申请全局唯一
	if (!Mdl)
		return FALSE;
 
	Offset.QuadPart = Start;

	MmBuildMdlForNonPagedPool(Mdl);
	Mdl->MdlFlags |= MDL_IO_PAGE_READ;

	KeInitializeEvent(&Event, NotificationEvent, FALSE); //NotificationEvent不自动重置

	KeEnterCriticalRegion();
	Status = GeIoPageRead(pFilePointer, Mdl, &Offset, &Event, &IoStatus,pIrp);
	if (Status == STATUS_PENDING) 
	{   
		//无限等待,即使原型PTE状态改变了(读取页面文件偏移、长度变了),驱动也不会卡死
		KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
		Status = IoStatus.Status;
	}
	KeLeaveCriticalRegion();

	if ((!NT_SUCCESS(Status) && Status != STATUS_END_OF_FILE)
		|| Status== STATUS_TIMEOUT)
	{
		if(Mdl)
			IoFreeMdl(Mdl);
		return FALSE;
	}
	if(IoStatus.Status == STATUS_END_OF_FILE 
		|| IoStatus.Status == STATUS_SUCCESS)
		ret = TRUE;

	if(Mdl)
		IoFreeMdl(Mdl);

	return ret;
}
雪    币: 5
活跃值: 活跃值 (201)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
诶一 活跃值 2013-9-3 16:15
9
0
顶一下 ,还是挺不错的..
雪    币: 2623
活跃值: 活跃值 (1768)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2013-9-3 16:28
10
0
不知是哪位大神的马甲。。。。
游客
登录 | 注册 方可回帖
返回