【Windows核心编程】如何知道程序运行中当前操作的内存地址范围,自己实现一个文件映射类

大部分人穷极一生都止步于自己的“陷阱”里,所以古人才有了破而后立的感悟!

问题来源

    此问题源于对文件映射FileMapping的改造需求。我们知道FileMapping的便利性,但可能在某个很小的开发范围内,会发现FileMapping的局限性!那就是只能对内核支持的文件对象进行映射,而内核文件对象意味着文件系统驱动,因而导致正常情况下只能对Windows支持的FAT/NTFS等文件系统中的文件进行映射。假如我有一个文件在远端服务器,我不想通过文件系统驱动的方式(网络共享也在其中)进行加载,也不想下载到本地磁盘,也许是出于对远端文件的安全保护,任何常规的文件读写方式都不被允许,而是想直接将远端文件映射到本地内存中,通过内存操作方式来操作文件数据,那现有的FileMapping就无法完成了,或者需要开发驱动才能完成。

    所以,有没有办法在用户态环境下,改造或实现一个FileMapping,让其能够解决该问题?

问题解析

    从FileMapping原理上看,操作映射有以下步骤:

1)创建时,告诉进程哪个文件有能力被映射到内存(CreateFileMapping),

2)在访问内存之前,我们还需要告知进程文件中哪段内容会被映射到内存(MapViewOfFile),再把相应的内存空间保留起来,留到需要时使用,且文件数据并未加载到该内存中。

3)在访问内存时,由内核自动映射文件数据到相应的内存。

4)映射细节:访问内存涉及到读和写,在读之前需要先将文件数据加载,在写之后需要将内存数据保存到文件

5)释放

    综上,若要实现一个FileMapping,我们需要在内存中划分保留空间,调用 VirtualAlloc 即可,接着最重要的是需要知道进程当前访问的内存地址,以及如何在进程读内存前和写内存后进行相应的“映射”操作。至于文件数据,我们可以放一边,因为当前需求是获取网络数据,当然也可以是任意其他方式读取数据(比如从串口设备中读取数据)。

寻求方案

    日常开发中,我们知道在调试时,调试器是可以知道被调试进程当前执行的代码地址,以及各种变量地址和内容的,当然我们没必要知道这么详细,而且实际中不太可能开发一个调试器去实现该功能,我们有更好的方法。通过开源的内核源码以及相关信息,我们知道程序在访问文件映射内存时,是通过触发一个异常STATUS_ACCESS_VIOLATION,让内核知道,然后再去自动加载文件数据到内存,之后再让程序重新执行访问内存,最后进程才正常继续往后面执行。

    所以,我们同样需要让知道如何触发并捕获访问内存异常的。

解决方法

1)如何触发内存访问异常?

我们知道访问空指针或者无效指针,程序就会出现异常错误,不处理异常就会导致程序崩溃。

无效指针是因为对应的内存地址没有申请,而通过VirtualAlloc申请内存后,就可以正常访问了,如下:

pBaseAddress:=VirtualAlloc(nil,1024, MEM_COMMIT, PAGE_READWRITE);

学习Windows的内存管理机制后,就知道内存属性PAGE_READWRITE代表该内存可以被读写,这里我们将内存设置为PAGE_NOACCESS,后面程序对该内存空间进行访问时,就会触发内存访问异常STATUS_ACCESS_VIOLATION(因为没有可访问属性)

也可以在后续使用VirtualProtect对内存属性进行设置,如:

VirtualProtect(pBaseAddress, 1024, PAGE_NOACCESS, oldProtect);

 

2)如何捕获异常

进一步学习Windows异常机制,可以知道通过AddVectoredExceptionHandler(和RemoveVectoredExceptionHandler)添加异常处理函数。就能够对STATUS_ACCESS_VIOLATION异常进行处理。

 

function  VectoredHandler(var ExceptionInfo: EXCEPTION_POINTERS): LONG; stdcall;
var
  oldProtect: DWORD;
  pAccessAddr: ULONG_PTR;
  pTemp: PByte;
begin
  Result := EXCEPTION_CONTINUE_EXECUTION;
  if ExceptionInfo.ExceptionRecord.ExceptionCode = STATUS_ACCESS_VIOLATION then
  begin
    pAccessAddr := UIntPtr(ExceptionInfo.ExceptionRecord.ExceptionInformation[1]);
    pAccessAddr := (pAccessAddr div 4096) *4096;
    if pAccessAddr=UIntPtr(Pointer(pBaseAddress)) then
    begin
      //EFlags::TF(bit 8, 即第9位) [Trap flag]   将该位设置为1以允许单步调试模式,清零则禁用该模式。即执行下一条指令后自动触发单步调试异常
      ExceptionInfo.ContextRecord.EFlags := ExceptionInfo.ContextRecord.EFlags or $100;
      VirtualProtect(pBaseAddress, 1024, PAGE_READWRITE, oldProtect);

      pTemp:=pBaseAddress;
      Inc(pTemp, 5);
      pTemp^ := 5;
      Form1.Memo1.Lines.Add('ChangeAccess: PAGE_READWRITE');
      Exit;
    end;
  end
  else if ExceptionInfo.ExceptionRecord.ExceptionCode = STATUS_SINGLE_STEP then
  begin
    VirtualProtect(pBaseAddress, 1024, PAGE_NOACCESS, oldProtect);
    Form1.Memo1.Lines.Add('ChangeAccess: PAGE_NOACCESS');
    Exit;
  end;

  Result := EXCEPTION_CONTINUE_SEARCH;
end;

 

var
  pAddVectoredExceptionHandler: TFnAddVectoredExceptionHandler;
  pRemoveVectoredExceptionHandler: TFnRemoveVectoredExceptionHandler;
var
  pTemp: PByte;
  oldProtect: DWORD;
  nTmp: Byte;
begin
    pVEHHandler := pAddVectoredExceptionHandler(1, @VectoredHandler); //安装VEH
    try
      pTemp := pBaseAddress;
      Inc(pTemp, 5);
      pTemp^ := 2;
      Memo1.Lines.Add('WillAccess: AfterWrite');

      Memo1.Lines.Add('WillAccess: BeforeRead');
      nTmp := pTemp^;
      if nTmp<>0 then
        ShowMessage('pTemp^='+IntToStr(nTmp))
      else
        ShowMessage('0000');
      Memo1.Lines.Add('Free');
      VirtualFree(pBaseAddress, 1024, MEM_FREE);
    finally
      pRemoveVectoredExceptionHandler(pVEHHandler);
    end;
end;

调用后输出信息:

WillAccess: BeforeWrite
STATUS_ACCESS_VIOLATION: ExceptionAddress: 00611288
AccessMode: Write
AccessAddress: 050E0005
ChangeAccess: PAGE_READWRITE
ChangeAccess: PAGE_NOACCESS
WillAccess: AfterWrite
WillAccess: BeforeRead
STATUS_ACCESS_VIOLATION: ExceptionAddress: 006112C0
AccessMode: Read
AccessAddress: 050E0005
ChangeAccess: PAGE_READWRITE
ChangeAccess: PAGE_NOACCESS
WillAccess: AfterRead

通过以上代码可以实现,访问内存的异常触发和捕获处理。

因此剩下的问题就是如何实现数据到内存的地址对应关系管理了。

最后

原理性的东西已经展示出来,功能性的东西就各自开发了。

此异常处理的原理还可以应用在代码保护,内存保护,反外挂,代码虚拟机等等地方,虽然看似简单,但是其作用真的有非常多的想象空间,实际就看每个人的经验和创作力了。

posted on 2022-05-09 11:55  峋山隐修会  阅读(355)  评论(0编辑  收藏  举报

导航