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?^_^
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
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
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)? –