Biblioteki DLL
Jacek Matulewski
15 kwietnia 2015
(aktualizacja: 10 marca 2018) Programowanie Windows
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");
break;
default:
WyswietlKomunikat("Inny powód wywołania DllMain/DllEntryPoint");
break;
}
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;
break;
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:
wynik = 0;
break;
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:
wynik = 0;
break;
}
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:
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\
Control Panel\\Extended Properties\\
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?