2013-01-04 3 views
2

J'ai réalisé un petit portage en C# de this application qui permet de charger les bibliothèques à partir de la mémoire/flux au lieu d'utiliser la fonction API LoadLibrary qui fonctionne à travers le système de fichiers. Après m'être un peu amusé avec des pointeurs et des résultats imbattables ... enfin j'ai quelque chose qui fonctionne comme prévu. Le seul problème que j'ai est que l'appel à DLLMain échoue toujours (je l'ai essayé avec Kernel32.dll et User32.dll). Je ne peux pas comprendre pourquoi et je ne sais pas comment déboguer le problème.Chargement de la bibliothèque/module à partir de la mémoire

Voici la fonction principale de mon projet (une simple application console 32 bits) qui lit une bibliothèque, il alloue en mémoire et la charge manuellement:

public static UInt32 Load(String libraryName) 
{ 
    if (libraries.ContainsKey(libraryName)) 
     return libraries[libraryName]; 

    String libraryPath = Path.Combine(Environment.SystemDirectory, libraryName); 
    Byte[] libraryBytes = File.ReadAllBytes(libraryPath); 

    fixed (Byte* libraryPointer = libraryBytes) 
    { 
     HeaderDOS* headerDOS = (HeaderDOS*)libraryPointer; 

     if ((UInt16)((headerDOS->Magic << 8) | (headerDOS->Magic >> 8)) != IMAGE_DOS_SIGNATURE) 
      return 0; 

     HeadersNT* headerNT = (HeadersNT*)(libraryPointer + headerDOS->LFANEW); 

     UInt32 addressLibrary = VirtualAlloc(headerNT->OptionalHeader.ImageBase, headerNT->OptionalHeader.SizeOfImage, AllocationType.RESERVE, MemoryProtection.READWRITE); 

     if (addressLibrary == 0) 
      addressLibrary = VirtualAlloc(0, headerNT->OptionalHeader.SizeOfImage, AllocationType.RESERVE, MemoryProtection.READWRITE); 

     if (addressLibrary == 0) 
      return 0; 

     Library* library = (Library*)Marshal.AllocHGlobal(sizeof(Library)); 
     library->Address = (Byte*)addressLibrary; 
     library->ModulesCount = 0; 
     library->Modules = null; 
     library->Initialized = false; 

     VirtualAlloc(addressLibrary, headerNT->OptionalHeader.SizeOfImage, AllocationType.COMMIT, MemoryProtection.READWRITE); 

     UInt32 addressHeaders = VirtualAlloc(addressLibrary, headerNT->OptionalHeader.SizeOfHeaders, AllocationType.COMMIT, MemoryProtection.READWRITE); 

     MemoryCopy((Byte*)headerDOS, (Byte*)addressHeaders, (headerDOS->LFANEW + headerNT->OptionalHeader.SizeOfHeaders)); 

     library->Headers = (HeadersNT*)((Byte*)addressHeaders + headerDOS->LFANEW); 
     library->Headers->OptionalHeader.ImageBase = addressLibrary; 

     CopySections(library, headerNT, libraryPointer); 

     UInt32 locationDelta = addressLibrary - headerNT->OptionalHeader.ImageBase; 

     if (locationDelta != 0) 
      PerformBaseRelocation(library, locationDelta); 

     UInt32 libraryHandle = (UInt32)library; 

     if (!BuildImportTable(library)) 
     { 
      Free(libraryName); 
      return 0; 
     } 

     FinalizeSections(library); 

     if (library->Headers->OptionalHeader.AddressOfEntryPoint == 0) 
     { 
      Free(libraryName); 
      return 0; 
     } 

     UInt32 libraryEntryPoint = addressLibrary + library->Headers->OptionalHeader.AddressOfEntryPoint; 

     if (libraryEntryPoint == 0) 
     { 
      Free(libraryName); 
      return 0; 
     } 

     LibraryMain main = (LibraryMain)Marshal.GetDelegateForFunctionPointer(new IntPtr(libraryEntryPoint), typeof(LibraryMain)); 
     UInt32 result = main(addressLibrary, DLL_PROCESS_ATTACH, 0); 

     if (result == 0) 
     { 
      Free(libraryName); 
      return 0; 
     } 

     library->Initialized = true; 

     libraries[libraryName] = libraryHandle; 

     return libraryHandle; 
    } 
} 

Et voici un exemple sur la façon de l'utiliser :

private const Byte VK_Z_BREAK = 0x5A; 

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
private delegate void KeyboardEventDelegate(Byte key, Byte scan, KeyboardFlags flags, Int32 extra); 

[Flags] 
private enum KeyboardFlags : uint 
{ 
    EXTENDEDKEY = 0x0001, 
    KEYUP = 0x0002, 
} 

public static void Main() 
{ 
    UInt32 libraryHandle = LibraryLoader.Load("User32.dll"); 

    if (libraryHandle != 0) 
    { 
     UInt32 functionHandle = LibraryLoader.GetFunctionAddress("User32.dll", "keybd_event"); 

     if (functionHandle != 0) 
     { 
      KeyboardEventDelegate s_KeyboardEvent = (KeyboardEventDelegate)Marshal.GetDelegateForFunctionPointer(new IntPtr(functionHandle), typeof(KeyboardEventDelegate)); 

      while (true) 
      { 
       s_KeyboardEvent(VK_Z_BREAK, VK_Z_SCANCODE, 0, 0); 
       s_KeyboardEvent(VK_Z_BREAK, VK_Z_SCANCODE, KeyboardFlags.KEYUP, 0); 
      } 
     } 
    } 

    Console.ReadLine(); 
} 

Si vous voulez essayer rapidement, vous pouvez télécharger le projet de this link. [EDIT] Après quelques essais, en utilisant Marshal.GetLastWin32Error() juste après avoir appelé DllMain, j'ai découvert qu'un code d'erreur 14 est en cours de production, ce qui correspond à ERROR_OUTOFMEMORY. Si je continue après l'échec d'appel DllMain et que j'obtiens l'adresse d'une fonction de bibliothèque, essayer de l'appeler en utilisant un délégué produit une exception PInvokeStackImbalance. Des indices à ce sujet?^_^

+1

ne serait-il pas plus facile (également pour la maintenance) de ne pas le porter complètement mais de fournir une petite couche C++/CLI au-dessus du projet d'origine? – stijn

+3

ne publiez pas tout votre code ici .. publiez seulement la partie qui est pertinente si vous avez toute votre logique dans une seule méthode .. alors je suggérerais refactoriser .. Je n'ai pas le temps de regarder n'importe lequel de votre code si il est plus de 10-15 lignes .. – MethodMan

+0

Que fait votre DLLMain, et qu'est-ce que cela renvoie comme résultat? Est-ce qu'il est appelé du tout (par exemple pouvez-vous faire un OutoutDebugString à partir de DLLMain et voir la sortie)? –

Répondre

9

Ce code est seulement une approximation du premier ordre de ce que le chargeur de Windows fait pour charger une DLL. Il ne peut fonctionner que pour les DLL les plus simples, la traduction de code C en C# est également très susceptible de causer des problèmes comme le problème de déséquilibre de pile que vous avez affaire. Les principaux problèmes que je vois:

  • Il ne fait rien pour s'assurer que la DLL n'était pas déjà chargée avant. C'est à peu près garanti d'être une source de problèmes lorsque vous essayez de charger kernel32.dll et user32.dll, ces DLL sont déjà chargées avant l'exécution du code managé. Ils ne prendront pas plaisir à se charger de nouveau.

  • Il ne sert à rien de s'assurer que les DLL dépendantes sont également chargées et que leurs entrypoints DllMain() sont appelés dans le bon ordre et strictement sérialisés.

  • Il ne fait rien pour traiter correctement le stub du chargeur de code managé, MSCoree.dll, ce qui rend très improbable que vous pouvez jamais charger correctement toutes les DLL qui contiennent du code en mode mixte.

  • Il ne fait rien pour s'assurer que le chargeur Windows est conscient de ces modules, ce qui rend très probable que toute demande ultérieure de la DLL échouera. Un tel échec est tout à fait impossible à diagnostiquer.

La probabilité que vous puissiez résoudre correctement ces problèmes est très faible.

Questions connexes