如何解決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:\Windows\WinSxS中存放各種不同版本的運行時庫

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是一個范圍較大的配置文件,可在以后為某些新的功能增加更多配置項,而運行時庫依賴配置僅僅是其中一項


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM