Jacek Matulewski 15 kwietnia 2015 (aktualizacja: 10 marca 2018) Programowanie Windows Biblioteki DLL Jacek Matulewski 15 kwietnia 2015 (aktualizacja: 10 marca 2018) http://www.fizyka.umk.pl/~jacek/dydaktyka/winprog/
Definicje DLL (ang. dynamic-link library) – biblioteki ładowane/łączone dynamicznie, skompilowane moduły kodu zawierające zasoby i funkcje, które mogą być współdzielone przez różne aplikacje Ładowanie biblioteki DLL oznacza włączenie jej do wspólnej przestrzeni adresowej procesu Bibliotekami DLL są m.in. pliki .dll, .cpl, .drv lub .ocx Biblioteka może eksportować część funkcji Możliwa jest zmiana (aktualizacja) biblioteki DLL bez konieczności przebudowywania używających ją programów Biblioteka DLL może być jednocześnie używana przez wiele programów
Zalety bibliotek DLL Modularność – bezpieczeństwo, także ułatwia aktualizację Współdzielenie kodu między aplikacjami, także jednocześnie (zob. chociażby biblioteki systemowe) Dynamiczne ładowanie: oszczędność pamięci, ładowane są tylko potrzebne biblioteki; po użyciu mogą być usunięte z pamięci Scenariusz wtyczek (ang. plug-in) Wykorzystanie jednego kodu w różnych językach programowania i środowiskach programistycznych Windows
Eksport funkcji z biblioteki DLL Definicje funkcji w pliku .cpp: #include <windows.h> void WyswietlKomunikat(char* s) { MessageBox(NULL, s, "Komunikat z biblioteki DLL", MB_OK); } void Test0() WyswietlKomunikat("DLL - Test0"); void Test1(int i) WyswietlKomunikat("DLL - Test1");
Eksport funkcji z biblioteki DLL Dodajemy deklaracje funkcji w pliku .h z modyfikatorami eksportu: #include <windows.h> #define __export __declspec(dllexport) //makro obecne w BCB __export void Test0(); extern "C" __export void Test1(int i); Modyfikator extern "C" użyty w C++ powoduje, że nazwa eksportowanej funkcji nie jest modyfikowana i zachowuje zgodność z C. W funkcji bez argumentów ten modyfikator nie jest potrzebny. W C++, w odróżnieniu od C, możliwe jest przeciążanie funkcji, dlatego kompilator dodaje do ich nazw informacje o argumentach np. dla funkcji Test1 będzie to _Test1@4 (VC++) gdzie 4 to liczba bajtów potrzebna dla wszystkich argumentów lub @Test@qv (BCB) .
Eksport funkcji z biblioteki DLL Nie wszystkie funkcje muszą być wyeksportowane: extern "C" __export void Test2(); void FunkcjaWewnetrzna(); ... void Test2() { WyswietlKomunikat("DLL - Test2"); FunkcjaWewnetrzna(); } void FunkcjaWewnetrzna() WyswietlKomunikat("DLL - Funkcja wewnetrzna");
Eksport funkcji z biblioteki DLL Modyfikator __stdcall – konwencja x86 wywoływania funkcji stosowana w WinAPI (przekazywanie argumentów, pobieranie wartości, ustawienia stosu) Szczegóły: http://en.wikipedia.org/wiki/X86_calling_conventions Użycie __stdcall daje pewność, że będziemy w stanie zaimportować funkcję w każdym języku i środowisku (dla Windows, także P/Invoke). Plik .h extern "C" __export int __stdcall Test3a(char* s); extern "C" __export char* __stdcall Test3b(char* s); extern "C" __export int __stdcall Test3c(int i); Plik .cpp int __stdcall Test3a(char* s) { return strlen(s); } char* __stdcall Test3b(char* s) { return s; } int __stdcall Test3c(int i) { return i; } Testy za pomocą systemowego programu RunDLL32 np. rundll32 Biblioteka,Funkcja
Embarcadero: implib i impdef Narzędzia jeszcze od Borlanda: implib – tworzy bibliotekę .lib używaną do statycznego importu bibliotek DLL impdef – pozwala na wyświetlenie listy funkcji eksportowanych z biblioteki DLL LIBRARY PROJECT1.DLL EXPORTS @Test0$qv @1 ; Test0() @Test1$qv @2 ; Test1() Test3a @4 ; Test3a Test3b @5 ; Test3b Test3c @6 ; Test3c _Test2 @3 ; _Test2 ___CPPdebugHook @7 ; ___CPPdebugHook
Microsoft: dumpbin Uniwersalne narzędzie Visual C++ c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\dumpbin.exe m.in. pozwala na wyświetlenie listy funkcji eksportowanych z biblioteki DLL: dumpbin /exports <ścieżka>\project1.dll Microsoft (R) COFF/PE Dumper Version 14.00.23506.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file t:\Wykłady\WinProg\Project1.dll File Type: DLL Section contains the following exports for Project1_Eksport.dll 00000000 characteristics 0 time date stamp 0.00 version 1 ordinal base 7 number of functions 7 number of names
Microsoft: dumpbin Wydruk z dumpbin c.d.: Microsoft (R) COFF/PE Dumper Version 14.00.23506.0 Copyright (C) Microsoft Corporation. All rights reserved. ... ordinal hint RVA name 1 0 000015D0 @Test0$qv 2 1 000015E0 @Test1$qv 3 2 000015F0 Test2 4 3 00001618 Test3a 5 4 000017A8 Test3b 6 5 00001920 Test3c 7 6 0001E0F8 ___CPPdebugHook Summary 9000 .data 1000 .edata 1000 .idata 3000 .reloc 2000 .rsrc 1D000 .text 1000 .tls
Powiadamianie o załadowaniu Biblioteka może być wyposażona w dwie funkcje uruchamiane przez system: DllMain i DllEntryPoint (ta sama sygnatura, ale są między nimi subtelne różnice): //int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) BOOL WINAPI DllMain(HINSTANCE hinst, unsigned long reason, void* lpReserved) { switch(reason) case DLL_PROCESS_ATTACH: WyswietlKomunikat("Biblioteka DLL załadowana do procesu procesu"); break; case DLL_PROCESS_DETACH: WyswietlKomunikat("Biblioteka DLL wyładowana z pamięci procesu"); default: WyswietlKomunikat("Inny powód wywołania DllMain/DllEntryPoint"); } return TRUE; //1
Powiadamianie o załadowaniu Możliwe wartości parametru reason: DLL_PROCESS_ATTACH – proces ładuje bibliotekę DLL DLL_THREAD_ATTACH – bieżący proces tworzy wątek DLL_THREAD_DETACH – wątek jest „zdrowo” kończony DLL_PROCESS_DETACH – proces wyładowuje bibliotekę DLL Użycie TerminateProcess lub TerminateThread nie spowoduje wywołania DllMain/DllEntryPoint. W aplikacjach wielowątkowych – tylko jeden wątek na raz może wywoływać DllMain/DllEntryPoint.
Import funkcji – łączenie statyczne Deklaracje funkcji w kodzie aplikacji lub innej biblioteki DLL: #define __import __declspec(dllimport) __import void Test0(); __import void Test1(int i); extern "C" __import void Test2(); extern "C" __import int __stdcall Test3a(char* s); extern "C" __import char* __stdcall Test3b(char* s); extern "C" __import int __stdcall Test3c(int i); void KlasaAplikacji::Metoda() //użyte funkcje BCB { Test0(); Test1(0); Test2(); ShowMessage((AnsiString)"EXE - "+IntToStr(Test3a("Test3a"))); ShowMessage((AnsiString)"EXE - "+Test3b("Test3b")); ShowMessage((AnsiString)"EXE - "+IntToStr(Test3c(345))); }
Import funkcji – łączenie statyczne Zalety: łatwiejsze, nieco szybsze uruchamianie (ale nie udało mi się potwierdzić) Wady: katastrofa w razie braku pliku DLL lub błędów, brak jakiegokolwiek wpływu programisty na linkowanie, tylko jeden scenariusz (ładowanie automatyczne na cały czas działania aplikacji).
Łączenie „dynamiczne” Ładowanie biblioteki: HINSTANCE DllHandle = LoadLibrary(nazwaPlikuDll.c_str()); if(DllHandle != NULL) ShowMessage("Wczytanie biblioteki DLL powiodło się") else { ShowMessage("Wczytanie biblioteki DLL nie powiodło się"); return; } Ten fragment możemy wstawić w dowolnym miejscu kodu (np. decyzja użytkownika, wykrycie pliku biblioteki).
Łączenie „dynamiczne” Pobieranie adresów funkcji (wspólna przestrzeń adresowa): //deklaracja w bibliotece DLL: __declspec(dllimport) void Test0(); //deklaracja typu typedef void (*DllTestType)(); //pobieranie adresu funkcji DllTestType Test0 = (DllTestType)GetProcAddress(DllHandle,"@Test0$qv"); //komunikat o błędzie lub uruchomienie if(Test0 == NULL) Wyświetl("Pobranie adresu funkcji Test0 nie jest możliwe"); else Test0();
Łączenie „dynamiczne” Pobieranie adresów funkcji (wspólna przestrzeń adresowa): //w bibliotece DLL: extern "C" __declspec(dllimport) void Test2(); //deklaracja typu typedef void (*DllTestType)(); //pobieranie adresu funkcji DllTestType Test2 = (DllTestType)GetProcAddress(DllHandle,"_Test2"); //komunikat o błędzie lub uruchomienie if(Test2 == NULL) ShowMessage("Pobranie adresu funkcji Test2 nie jest możliwe"); else Test2();
Łączenie „dynamiczne” Pobieranie adresów funkcji (wspólna przestrzeń adresowa): //extern "C" __declspec(dllimport) int __stdcall Test3a(char* s); //deklaracja typu typedef int (*DllTest3aType)(char* s); //pobieranie adresu funkcji DllTest3aType Test3a = (DllTest3aType)GetProcAddress(DllHandle,"Test3a"); //komunikat o błędzie lub uruchomienie if(Test3 == NULL) ShowMessage("Pobranie adresu funkcji Test3 nie jest możliwe"); else int wynik = Test3a("Test3a");
Łączenie „dynamiczne” Wyładowanie biblioteki z bieżącego procesu: if (FreeLibrary(DllHandle)) ShowMessage("Wyładowanie biblioteki powiodło się"); else ShowMessage("Wyładowanie biblioteki nie powiodło się"); Biblioteka DLL tworzy licznik dla załadowań z danego procesu: LoadLibrary go zwiększa, a FreeLibary – zmniejsza. Gdy licznik spada do zera, biblioteka jest usuwana z przestrzeni adresowej procesu. Wyładowanie biblioteki z bieżącego procesu w żaden sposób nie wpływa na jej obecność w pamięci innych procesów.
Ścieżka przeszukiwania Biblioteki DLL, których cała nazwa nie jest podana szukane są kolejno w następujących miejscach: 1. katalog, w którym jest plik .exe bieżącego procesu, 2. bieżący katalog (katalog roboczy), 3. katalog systemowy Windows (por. GetSystemDirectory) np. C:\Windows\SysWOW64 4. katalog Windows (por. GetWindowsDirectory) np. C:\Windows 5. katalogi wymienione w zmiennej środowiskowej PATH
Aplet panelu sterowania Aplety panelu sterowania to biblioteki DLL, które zawierają funkcję CplApplet (trzeba ją eksportować): long __stdcall CPlApplet(HWND hwndCPl, unsigned uMsg, long lParam1,long lParam2) { CPLINFO* pCpli = NULL; long wynik = 0; switch(uMsg) //załadowanie biblioteki, otwarcie panelu sterowania case CPL_INIT: wynik = TRUE; break; ...
Aplet panelu sterowania Aplety panelu sterowania to biblioteki DLL, które zawierają funkcję CplApplet (trzeba ją eksportować): //pytanie o ilość elementów panelu sterowania w bibliotece case CPL_GETCOUNT: wynik = 1; break; //informacja o elementach (dla każdego osobny komunikat) case CPL_INQUIRE: //jest też CPL_NEWINQUIRE pCpli = (CPLINFO*)lParam2; pCpli->idIcon = 101; pCpli->idName = 1; pCpli->idInfo = 2; pCpli->lData = 0; wynik = 0;
Aplet panelu sterowania Aplety panelu sterowania to biblioteki DLL, które zawierają funkcję CplApplet (trzeba ją eksportować): //użytkownik kliknął ikonę elementu w panelu sterowania case CPL_DBLCLK: StworzOkno(); wynik = 0; break; //zamykany panel sterowania case CPL_STOP:
Aplet panelu sterowania Aplety panelu sterowania to biblioteki DLL, które zawierają funkcję CplApplet (trzeba ją eksportować): //zamykany panel sterowania case CPL_EXIT: UsunOkno(); wynik = 0; break; default: } return wynik;
Aplet panelu sterowania Od Windows XP należy elementy panelu sterowania rejestrować (5 kroków), m.in. należy im przypisać kategorie Windows XP: HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\ Control Panel\\Extended Properties\\ {305CA226-D286-468e-B848-2B2E8E697B74} 2"; Windows Vista i późniejsze: System.ControlPanel.Category"; %SystemRoot%\\SysWOW64\\project1.cpl = numer kategorii Kategorie: https://msdn.microsoft.com/en-us/library/windows/desktop/cc144183(v=vs.85).aspx Rejestracja: https://msdn.microsoft.com/en-us/library/windows/desktop/hh127454(v=vs.85).aspx
Haki Przesyłanie komunikatów do aplikacji (mysz, klawiatura, debugowanie, wywoływanie procedury okna) może być monitorowane poprzez haki. Oznaczają one wywołanie procedury haka zdefiniowanej przez programistę i wyeksportowanej z biblioteki. Biblioteka DLL z hakiem jest niejawnie ładowana do przestrzeni adresowej każdego procesu, który otrzymuje monitorowany komunikat. Haki mogą tworzyć łańcuchy. Ustawianie haka: SetWindowsHookEx Zwalnianie haka: UnhookWindowsHookEx Procedura haka: LRESULT CALLBACK KeyboardProcEvent( int code, WPARAM wParam, LPARAM lParam)
Haki Do ustawienia haka potrzebny będzie uchwyt do instancji DLL: #include <windows.h> HINSTANCE uchwytDLL = NULL; int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { if(reason == DLL_PROCESS_ATTACH) uchwytDLL = hinst; return 1; } Deklarujemy procedurę haka dla komunikatów dot. klawiatury: extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardHookProc( int kod, WPARAM wParam, LPARAM lParam);
Haki Procedura haka: #include <fstream.h> LRESULT CALLBACK KeyboardHookProc(int kod, WPARAM wParam, LPARAM lParam) { if(kod == HC_ACTION) if((lParam & 0x80000000)==0) Beep(150,50); else Beep(50,50); if((lParam & 0x80000000)==0) ofstream txt("C:\\Users\\keylogger.txt",ios::app); if(wParam>32 && wParam<127) txt << (char)wParam; else txt << "(" << wParam << ")"; txt.close(); } return CallNextHookEx(uchwytHaka, kod, wParam, lParam);
Haki Ustawianie haka za pomocą funkcji wyeksportowanej z tej samej biblioteki (może być osobny loader): HHOOK uchwytHaka=NULL; extern "C" __declspec(dllexport) void __stdcall UstawHak() { uchwytHaka = SetWindowsHookEx( WH_KEYBOARD, (HOOKPROC)KeyboardHookProc, uchwytDLL, NULL); if(uchwytHaka==NULL) MessageBox(NULL, "Założenie haka nie powiodło się", "KeyHook", MB_OK|MB_ICONERROR); else MessageBox(NULL,"Założenie haka powiodło się", "KeyHook", MB_OK|MB_ICONINFORMATION); }
Haki Zwalnianie haka: extern "C" __declspec(dllexport) void __stdcall UsunHak() { bool wynik=UnhookWindowsHookEx(uchwytHaka); if(wynik) MessageBox(NULL,"Usuniecie haka powiodło się", "KeyHook", MB_OK|MB_ICONINFORMATION); else MessageBox(NULL,"Usuniecie haka nie powiodło się", "KeyHook", MB_OK|MB_ICONERROR); } Zastosowania: debugowanie, nagrywanie i odtwarzanie makr, keylogger, generowanie liczb losowych, wsparcie dla klawisza pomocy (F1), imitowanie myszy i klawiatury, wspierane komputerowo treningi dotyczące oprogramowania (CBT)
Dema 1. Dynamiczne ładowanie biblioteki DLL 2. Aplet panelu sterowania GetTicks.cpl 3. Hak
Przykładowe pytania Jakie są dopuszczalne nazwy funkcji entry point DLL? Dlaczego należy używać modyfikatora __stdcall w funkcjach eksportowanych z bibliotek DLL? Statyczne vs „dynamiczne” ładowanie biblioteki DLL. Jaka funkcja służy do załadowania biblioteki DLL w runtime? Jaką funkcją pobieramy adres funkcji z biblioteki DLL? Jaką funkcją biblioteka jest usuwana z pamięci? W jakiej sytuacji wywołanie funkcji FreeLibrary nie spowoduje usunięcia biblioteki z pamięci?
Przykładowe pytania Jaki program składowy Visual Studio pozwala na inspekcję funkcji eksportowanych przez bibliotekę DLL? W jakich czterech sytuacjach wywoływana jest funkcja DllMain/DllEntryPoint? Gdzie należy umieścić bibliotekę DLL pełniącą rolę apletu panelu sterowania? Jakie musi mieć rozszerzenie? Jaka funkcja zwrotna musi być w niej zdefiniowana? Jak definiuje się i jak działają haki Windows?