• 如何解决R6034错误,实现在WIN7以上版本通过LoadLibrary加载msvcr90.dll等DLL


    为什么VC编译的程序在不同系统上运行经常报错?

        在XP(SP2 ?)以前,安装VC运行时库时,安装包只会将各种DLL释放到system32目录并注册相关信息到注册表,这样LoadLibrary时加载这些DLL也不会出错,因为对于应用程序来说,当前只有一个对应的运行时库被注册到系统中,不存在多个不同版本的问题,但也很容易造成兼容性问题,如使用VS2008编译的程序,在仅安装了VS2005运行时库的系统中可能会出现崩溃错误。

    不同版本运行时库带来的冲突

        假如一个VC2005编译的程序,一旦出现运行时库不匹配而导致运行错误,我们就需要安装匹配的VC2005运行时库到系统里就可以解决了.

        但是如果问题那么容易解决就好了,不过对于这种问题如何解决,痛苦的也是微软的码农。

        在上面VC2005程序问题的基础上,再扩展一下,假如同时又有一个VC2008编译的程序运行出现错误,这样我们又得安装VC2008的运行时库了,新的运行时库又会覆盖掉System32里的同名DLL,那么问题来了,原来的VC2005程序又无法运行了,崩溃!不可能每运行一次就安装一次运行时库把?

        所以,微软在新的补丁中提供了一个叫Side-By-Side的模块,也就是简称SXS,该模块最初应该不是为了解决以上问题的,只是顺带而已,SXS应该是为了解决混乱的.NET Framework而设计的。但是不管怎样,有了SXS,就可以在系统中同时安装各种不同版本的运行时库也不会有冲突了。

        既然系统中安装了不同版本的运行时库,那么程序怎么知道自己要使用的到底是VC2005还是VC2008的运行库呢?

    微软如何设计SXS来解决不同运行环境所带来的兼容性问题的?

    在XP sp3,以及Vista, WIN7,WIN8,WIN10 后,为了在系统中同时提供多种CPU平台和不同VS版本的运行时库来兼容不同的应用软件,微软做了大概下面3个改进,

    1)PE文件自带依赖库签名信息

    2)在C:WindowsWinSxS中存放各种不同版本的运行时库

    3)有了上面的2个改进,剩下的无非就是系统运行EXE文件或者加载DLL时,如何根据依赖库签名信息来正确加载相应的运行时库了。

    以上就是系统的Side-By-Side(SXS)组件设计时所要实现的大体目标和功能了。

    程序开发中如何让SXS的识别不同版本依赖库?

          在VS开发时,可在工具中设置manifest来指定当前程序的依赖库签名信息(运行时库名称,版本,如win32 x86 版本号 等等),manifest可以嵌入到资源中,也可以放在本地的xml格式的文本文件,目前最标准的也是VS开发工具默认做法,就是将运行时库通过导入表静态链接到PE文件(EXE或DLL),同时将manifest嵌入到PE文件的资源里,资源号是RT_MANIFEST(24),再交给系统的PE加载器去自动加载对应的运行时库。

          虽然本文是说MSVCR90.DLL,但原理上是可以适用于各种各样的运行时库的。

    Delphi开发时没法设置manifest资源,会出现什么问题?

    注意:RAD 10已经可以设置manifest资源了

          系统在创建进程时,在NTDLL和内核中有部分API和代码统称为PE加载器,在EXE使用LoadLibrary加载DLL时,内部也是一个类似的且专门映射DLL到进程的PE加载器,所以这里说加载器仅仅是一个模块概念,并不是一个实际的EXE程序。

          在Delphi开发的EXE程序中,如果采用Loadlibrary去动态加载MSVCR90.DLL,在XP上大部分是正常的,在VISTA/WIN7以上可能就会报错R6034,这个错误是MSVCR90.DLL中在入口函数的运行环境自检中所报的错误,大体上是该DLL会判断当前进程的内存环境中是否包含正确的manifest,没有则报错,实际中该错误可能并不是真的因为错误版本的DLL报错的,只是该DLL为了安全起见而报错,当然真正原因是因为调用LoadLibrary的EXE程序没有嵌入包含运行时库依赖信息的manifest,所以加载器无法通过当前进程的manifest判断是否加载的DLL就是EXE所期望的正确的MSVCR90.DLL,最终报错。

    Delphi中如何解决没法正确加载Manifest资源所导致的问题?

          要解决该问题,就需要让加载器能够获取到正确的manifest信息,而manifest信息其实只是一个载体,实际上的使用对象是叫做ActiveContext,也就是说manifest最终会被ActiveContext加载并接管处理。而加载器也是通过ActiveContext来处理被解析映射过的manifest信息。

        总体上,就是只要进程中存在正确的ActiveContext,加载器就能够取得正确的依赖库路径并加载。

        文章到这里已经相对清晰了,要解决本文提到的问题,现在变成了如何创建包含manifest的ActiveContext,到这里还没有解释ActiveContext是什么,有兴趣的朋友可以查找MSDN中关于“Activation Context”的信息。

        下面给出代码展示如何使用LoadLibrary加载msvcr90.dll,先看一段正常但会报错的代码:

         

    procedure LoadMSVCRT();
    var
      sDllName: string;
    begin
      sDllName := 'MSVCR90.DLL';
      hLib := LoadLibrary(PChar(sDllName));
    end;
    

      上面这段代码在WIN7或WIN10上一执行,通常情况下就会弹出R6034的错误,而以下代码则可避免该问题

    procedure LoadMSVCRT();
    var
      sDllName: string;
      hLib: HModule;
      aActCtx: TActCtx;
      hCtx: THandle;
      lpCookie: ULONG_PTR;
    begin
      // 激活清单文件,可正确加载MSVCR90.DLL
      FillChar(aActCtx, SizeOf(TActCtx), 0);
      aActCtx.cbSize := SizeOf(TActCtx);
      aActCtx.lpSource := PChar(ExtractFilePath(ParamStr(0))+'MSVCRT.manifest');
      hCtx := Winapi.Windows.CreateActCtx(aActCtx);
      if hCtx<>INVALID_HANDLE_VALUE then
      begin
        Winapi.Windows.ActivateActCtx(hCtx, lpCookie);
      end;
      sDllName := 'MSVCR90.DLL';
      hLib := LoadLibrary(PChar(sDllName));
      if hCtx<>INVALID_HANDLE_VALUE then
      begin
        DeactivateActCtx(0, lpCookie);
        ReleaseActCtx(hCtx);
      end;
    end;
    

      以上代码所使用的manifest内容随便找一下就有,具体如下(从某个VC程序的资源中拷贝出来的):

    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
          <requestedPrivileges>
            <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
          </requestedPrivileges>
        </security>
      </trustInfo>
      <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
        </dependentAssembly>
      </dependency>
    </assembly>

         注意其中的版本号,如果系统中没有安装对应的运行时库,仍然会有可能报错。这里说可能会报错,是因为系统的SXS会在找不到对应的依赖库时,会自动尝试匹配与manifest中接近的库文件(如当PE文件中的IAT无法被全部正常解析,则会尝试其他更新的依赖库,直到成功或失败)。

          

         本文提到的方法同样适用于shellcode对PE文件加壳时需要面对的manifest处理问题,另外,manifest可以在代码中直接生成到内存,而ActiveContext除了上面以文件的加载方式外,还可以直接在内存中加载内容,具体可以查看MSDN中关于ActiveContext的使用方法。

         

    注:

    A . 关于WinSxs(即Windows Side-by-Side),更高级的操作可查找Sxs.dll相关API,号称完美解决不同版本COM和DLL兼容性问题(最初应该是解决.NET不同framework版本的兼容性问题)

    B. manifest是一个范围较大的配置文件,可在以后为某些新的功能增加更多配置项,而运行时库依赖配置仅仅是其中一项

  • 相关阅读:
    C#多线程之Parallel中 类似于for的continue,break的方法
    C#中静态类、静态方法和静态变量的简单说明
    C# 类属性封装、字段的详解
    BackgroundWorker 后台进程控制窗体label、richtextbook内容刷新
    asp.net 实现 tts
    System.Speech.Synthesis 添加暂停、继续功能
    微软TTS,Neospeech TTS 简单使用
    VS2015如何批量统改相同变量名
    VS2015时显示无法启动此程序 因为计算机中丢失ucrtbased.dll
    Windows环境下MinGW/gcc安装和环境配置
  • 原文地址:https://www.cnblogs.com/caibirdy1985/p/5130427.html
Copyright © 2020-2023  润新知