0. Vorwort
1. Die Theorie
1.1 Das PE-Format
1.2 Code-Injection
2. Umsetzung in die Praxis
2.1 Header manipulieren
2.2 Unsere Section bauen und in die DLL schreiben
0. Vorwort
In diesem Tutorial wollen wir unseren Code in eine fremde DLL bringen ohne dass sich diese gemappt im Arbeitsspeicher befindet. Wir sorgen dafür, dass unser Code als [Only registered and activated users can see links. Click Here To Register...] angenommen und danach der originale EntryPoint aufgerufen wird, wenn wir es wollen. Das schöne hierbei ist, dass das für alle Prozesse gilt, die die DLL nutzen.
Eigentlich wollte ich etwas mehr schreiben und alles detaillierter Erläutern, wusste aber nicht genau, was und befinde, dass es relativ gut so ist, wie es ist :D
Ich bin eben nicht so toll, wenn's um's Erklären geht und etwas unkoordiniert, was man sicher auch hier wiederfindet. Nichtsdestotrotz hoffe ich natürlich, dass es halbwegs lehrreich ist, für die, die es noch nicht kannten :)
Wenn euch etwas fehlt (das tut es sicherlich), schreibt es in den Thread oder mir per PN, dann füge ich es hinzu ;)
Um meinen konfusen Ausführungen folgen zu können solltet ihr nebenbei Erklärungen zum [Only registered and activated users can see links. Click Here To Register...] und zum [Only registered and activated users can see links. Click Here To Register...] offen haben. Ich werde hier nicht auf jedes einzelne Feld der Strukturen eingehen, sondern werde nur wichtige benennen und ihr dürft sie euch dann im MSDN anschauen, tut mir leid :D
Weiterhin möchte ich euch bitten, es zu entschuldigen, wenn ich zu oft das Wort "Header" benutze. Tut mir Leid und ich hoffe, dass es nicht allzu stark stört.
1. Die Theorie
1.1 Das PE-Format
1.2 Code-Injection
So, nun zur Theorie des Manipulierens. Der Plan ist ja unseren Code als Entrypoint verwenden zu lassen. Aber wo ist Platz für unseren Code? Wenn wir ihn einfach so an's Ende der DLL klatschen, wird er nicht gemappt, das ist ja nicht Sinn der Sache. Aber wenn wir unseren Code an die Code-Section klatschen und dessen Größe nur anpassen, müssen wir alle darauffolgenden Sections verändern, da sich deren Daten ja dann an anderer Stelle befinden. Die Lösung: Wir machen einfach eine neue Section, deren Daten (unser Code) wir an's Ende der DLL packen. Was muss dafür gemacht werden? Wir müssen einen neuen Section-Header einfügen, die Anzahl der Section, die im PE-Header stehen, um 1 erhöhen, den Entrypoint auf unsere Funktion in unserer Section verschieben und unsere Daten mitsamt dem veränderten Rest auf die Festplatte schreiben. Zudem müssen wir irgendwo den alten Entrypoint speichern. Hier schlage ich vor, dass wir uns eine Struct bauen, in der wir den alten Entrypoint und Daten für unsere Funktion unterbringen. Jetzt wird man sich sicher fragen: Welche Daten sollen warum dort rein? OK, wenn ihr einen String benutzt, nutzt ihr eigentlich nicht den String an sich, sondern ein Pointer auf ein Char-Array. Dieses Array liegt im kompilierten Programm aber in der Data-Section und ist für uns somit nur über umwege zu erreichen. Daher nutzen wir also die Struct, um solche Daten, dort unterzubringen.
Da wir schon bei Einschränkungen sind: Außer dieser Einschränkung gibt es noch weitere:
- die Funktion darf maximal 4KB an lokalen Variablen benutzen (für mehr kann man ja die Struktur nutzen). Dies kommt daher, dass ja eigentlich für die lokalen Variablen der Stackpointer um deren Größe reduziert wird (sub esp, <Größe der lokalen Variablen>). Werden aber mehr als 4KB lokaler Variablen gebraucht, wird ESP nicht direkt reduziert, sondern über eine spezielle Funktion, welche gecallt wird. In der DLL (bzw. im Prozess, der die DLL lädt) ist diese Funktion natürlich nicht da, oder an einem anderen Platz, weswegen der Call in's Leere geht.
- viele Compiler machen Buffer Security Checks, unter anderem um die Integrität des Stacks zu gewährleisten. Diese werden aber auch durch externe Funktionen erledigt, weswegen auch die wegmüssen (bei den meisten Compilern ist das der /Gz-Switch).
- alle API-Aufrufe, die nicht zur kernel32.dll oder user32.dll gehören, dürfen nicht direkt aufgerufen werden, da sich die Module, die die Funktionen beinhalten an unterschiedlichen Stellen befinden können. Daher müssen wir hier zuerst mit GetModuleHandle das Modul bekommen und dann mit GetProcessAddress die Adresse der Funktion, die wir dann aufrufen wollen (vergesst nicht, nur Strings aus unserer Struktur zu nehmen).
So also unsere Section beinhaltet erst unsere Daten-Struktur und dann unsere Funktion für den neuen EntryPoint. Aber wie können wir aus unserer Funktion auf die Datenstruktur (anfang der Section) zugreifen? Ich habe dieses Problem so gelöst, dass ich den relativen Pointer zur Section (und damit ja zur Datenstruktur) in das "LoaderFlags"-Feld des NTHeader.OptionalHeader geschrieben habe. Dieses Feld ist laut MSDN "obsolete", weswegen wir es deswegen einfach missbrauchen :P
2. Umsetzung in die Praxis
So jetzt erstmal zum vorgeplänkel unseres Programms. Wir müssen die Opfer-DLL einlesen (nicht mit LoadLibrary oder sonstwie mappen!). Wie, ist jedem selbst überlassen. Am Ende befindet sie sich jedenfalls im Arbeitsspeicher und wir haben einen Pointer, der auf das erste Byte der DLL zeigt.
2.1 Header manipulieren
So, um die Header zu manipulieren, müssen wir sie erstmal kriegen. Nach dem PE-Format befindet sich ganz am Anfang der DOS-Header, also ab dem ersten Byte. Wir können also gleich einen IMAGE_DOS_HEADER-Pointer aus dem DLL-Pointer machen. Dann nutzen wir das Feld e_lfanew, um an den NT-Header (= PE-Header) zu kommen. Natürlich müssen wir diesen Wert noch mit der Basis addieren, da es nur ein relativer Pointer ist.
Auf Basis des NT-Headers holen wir uns nun das Section-Header-Array. Da es ja direkt an den NTHeaders anschließt können wir hier den pNTHeader mit der NTHeader-Größe addieren, um einen Pointer auf das Section-Header-Array zu kriegen, richtig? Leider nicht, da der OptionalHeader, der Teil des NT-Headers ist eine variable Größe haben kann, da sein IMAGE_DATA_DIRECTORY-Array unterschiedlich viele Einträge haben kann (obwohl es MEISTENS 16 hat). Glücklicherweise bietet der FileHeader, welcher auch Teil, des NTHeaders ist (ich hoffe, ihr habt die MSDN-Links vom Vorwort offen) ein Feld, welches passend mit "SizeOfOptionalHeader" benannt wurde. Wir addieren also zum NTHeader-Pointer 4 für die Signatur und sizeof(IMAGE_FILE_HEADER) und das oben benannte Feld um dann einen Pointer auf das SectionHeader-Array zu erhalten.
Gut, aber wofür brauchen wir das? Da wir ja unsere eigene Section machen wollen, müssen wir auch den Header für diese zusammenschrauben. Wir müssen also eine virtuelle Adresse finden, die noch Platz genug für unsere Section bietet, also noch nicht durch andere Sections belegt ist. Wir gehen also die Sections durch und packen unsere Section ganz an's Ende. Der folgende Code dürfte durch diese Hintergrundinformationen selbsterklärend sein:
Danach steht in dwVAofSection eine durch 0x1000 teilbare virtuelle Adresse für unsere Section. Gemappt ist unsere Section also ab der Adresse Modulbase + dwVAofSection verfügbar.
Jetzt kommen wir zum "PointerToRawData" für unsere Section. Da wir unsere Section ja an's Ende der DLL packen wollen muss hier also die aktuelle Größe der DLL rein. Die einzige Einschränkung ist, dass PointerToRawData uns SizeOfRawData vielfaches des FileAlignment-Feldes des FileHeaders im NTHeader sein müssen. Aber das ist ja kein Problem für uns. Wenn die Größe der Datei nicht glatt durch den FileAlignment-Wert teilbar ist (ist bei mir noch nicht vorgekommen), schreiben wir eben noch ein kleines Buffer vor unserer Section in die Datei. Genauso schrieben wir ein kleines Buffer nach unserer Section in die Datei, wenn die Größe unserer Section nicht durch den FileAlignment-Wert teilbar ist (das kommt öfter vor).
Wir berechnen nach diesen vorgaben nun also die Größe unserer Section (SizeOfRawData und Misc.VirtualSize) und den "PointerToRawData":
dwFuncLength ist am Anfang von 2.2 erläutert
"dwPreSectionBufferLength" dient später dazu, einen Buffer in der neuen DLL zwischen der alten DLL und unserer Section einzubauen, damit PointerToRawData die FileAlignment-Auflagen erfüllt und nicht auf irgendwelchem Müll zeigt.
Wichtig ist auch noch das "Characteristics"-Feld im Section Header. Da wir ja unseren Code ausführen, unsere Datenstruktur lesen und vielleicht auch zur Laufzeit bearbeiten müssen. Setzen wir also auch so unser Characteristics-Feld fest:
Zum schluss kreieren wir noch unseren Header (kann man natürlich schon dabei machen, ohne die ganzen lokalen Variablen, aber naja) und geben der Section einen Namen (max. 8 Stellen):
Diesen SectionHeader kopieren wir nach Abfrage, ob denn noch Platz für einen weiteren SectionHeader ist, einfach an's Ende des SectionHeader-Arrays:
Als nächstes passen wir den NTHeader noch an:
Wegen SectionData und SomeData siehe 2.2
2.2 Unsere Section bauen und in die DLL schreiben
Ok, wir wollen also unsere Section schreiben. Mehr als unser Code und die Datenstruktur muss da ja eigentlich nicht rein. Aber wie bekommen wir unseren Code da rein? Entweder wir schreiben die gesamte Funktion mit ASM, und kopieren dann die Offsets da rein, oder wir machen uns eine Funktion in unserem Manipulator, kompilieren sie und fügen das dann zur Laufzeit ein. Aber wie kriegen wir die Länge unserer Funktion? Man könnte das Programm natürlich einmal kompilieren und dann mit einem Debugger nachschauen oder eine der disasm libs nutzen.
In diesem Tutorial bedienen wir uns aber eines kleinen Tricks (nicht schön, aber einfach): direkt nach unserer Funktion machen wir noch eine und rechnen dann einfach
Im kompiliertem Programm sieht dass dann so aus:
So kriegen wir relativ genau die Länge unserer Funktion. Wichtig hierbei ist, dass in den Compileroptionen das Incremental Linking ausgestellt wird oder die beiden Funktionen als static erklärt werden. Sonst liefert der '&'-Operator nicht die Adresse der eigentlichen Funktion, sondern die eines Jumps zur Funktion. Und da die Jumps zu den Funktionen meist direkt hintereinander stehen, wird man dann hier immer 1 herausbekommen, was natürlich falsch ist.
OK, kommen wir mal kurz zu unserem Code bzw. was wir eigentlich machen wollen. Hier werde ich mal nur die Funktion MessageBeep(0) ausführen und danach den originalen EntryPoint aufrufen.
In die Datenstruktur kommt daher nur der relative Pointer zum originalen EntryPoint rein. Das ist also völlig ausreichend:
Wir wissen ja, dass NTHeader->LoaderFlags einen relativen Pointer auf unsere Section enthält und dass unsere Datenstruktur zuerst in unserer Section steht.
Aufgrunddessen sieht unsere Funktion wie folgt aus:
Das "+ (DWORD)hModule" macht aus Pointern, die relativ zur Modulbase sind absolute Pointer, die wir auch wirklich nutzen können.
Kommen wir zum schreiben unserer Section. Da wir natürlich an die Richtlinien für die Größe unserer Section gebunden sind, allokieren wir erstmal Platz für unsere Section bevor wir sie schreiben:
Nun kopieren wir hier unsere Datenstruktur hinein und danach unseren Code:
Wenn wir wollen können wir auch noch den Rest des Section-Buffers (da er ja aufgrund der FileAlignment-Richtlinie größer ist, als DatenStruktur + Funktion) mit Nullen oder so füllen, aber das könnt ihr ja sicher auch ohne mich.
Jetzt schreiben wir also das alte DLL-Buffer wieder in eine DLL auf die Festplatte, hängen unsere Section dran und sind fertig :D
Ähnlich könnt ihr es auch mit exportierten Funktionen der DLL machen. Dahingehend solltet ihr euch das "DataDirectory"-Feld des OptionalHeaders anschauen.
1. Die Theorie
1.1 Das PE-Format
1.2 Code-Injection
2. Umsetzung in die Praxis
2.1 Header manipulieren
2.2 Unsere Section bauen und in die DLL schreiben
0. Vorwort
In diesem Tutorial wollen wir unseren Code in eine fremde DLL bringen ohne dass sich diese gemappt im Arbeitsspeicher befindet. Wir sorgen dafür, dass unser Code als [Only registered and activated users can see links. Click Here To Register...] angenommen und danach der originale EntryPoint aufgerufen wird, wenn wir es wollen. Das schöne hierbei ist, dass das für alle Prozesse gilt, die die DLL nutzen.
Eigentlich wollte ich etwas mehr schreiben und alles detaillierter Erläutern, wusste aber nicht genau, was und befinde, dass es relativ gut so ist, wie es ist :D
Ich bin eben nicht so toll, wenn's um's Erklären geht und etwas unkoordiniert, was man sicher auch hier wiederfindet. Nichtsdestotrotz hoffe ich natürlich, dass es halbwegs lehrreich ist, für die, die es noch nicht kannten :)
Wenn euch etwas fehlt (das tut es sicherlich), schreibt es in den Thread oder mir per PN, dann füge ich es hinzu ;)
Um meinen konfusen Ausführungen folgen zu können solltet ihr nebenbei Erklärungen zum [Only registered and activated users can see links. Click Here To Register...] und zum [Only registered and activated users can see links. Click Here To Register...] offen haben. Ich werde hier nicht auf jedes einzelne Feld der Strukturen eingehen, sondern werde nur wichtige benennen und ihr dürft sie euch dann im MSDN anschauen, tut mir leid :D
Weiterhin möchte ich euch bitten, es zu entschuldigen, wenn ich zu oft das Wort "Header" benutze. Tut mir Leid und ich hoffe, dass es nicht allzu stark stört.
1. Die Theorie
1.1 Das PE-Format
[Only registered and activated users can see links. Click Here To Register...]
Dieses Bild zeigt uns letztendlich alles, was wir über den Grundlegenden Aufbau des PE-Formates wissen sollten. Jede PE-Datei fängt mit dem DOS-Header an. Für uns ist der nur wichtig, da diese Struktur einen relativen Pointer auf den für uns relevanten PE-Header beinhaltet. Der PE-Header ist für uns wichtiger. Er enthält die relative Adresse des EntryPointes (den wollen wir ja ändern) und die Anzahl der Sektoren. Direkt nach dem PE-Header kommt ein Array aus Sektor-Headern. Die Länge bzw. die Anzahl der Indices dieses Arrays kann man dem PE-Header entnehmen. Jede Section benutzt bestimmte Daten aus der DLL und wird an eine bestimmte Adresse relativ zur Modulbase gemappt. Ist die Größe der Section größer, als die angegebenen Daten (steht alles im Header :P ), so wird der Rest mit Nullen gefüllt.1.2 Code-Injection
So, nun zur Theorie des Manipulierens. Der Plan ist ja unseren Code als Entrypoint verwenden zu lassen. Aber wo ist Platz für unseren Code? Wenn wir ihn einfach so an's Ende der DLL klatschen, wird er nicht gemappt, das ist ja nicht Sinn der Sache. Aber wenn wir unseren Code an die Code-Section klatschen und dessen Größe nur anpassen, müssen wir alle darauffolgenden Sections verändern, da sich deren Daten ja dann an anderer Stelle befinden. Die Lösung: Wir machen einfach eine neue Section, deren Daten (unser Code) wir an's Ende der DLL packen. Was muss dafür gemacht werden? Wir müssen einen neuen Section-Header einfügen, die Anzahl der Section, die im PE-Header stehen, um 1 erhöhen, den Entrypoint auf unsere Funktion in unserer Section verschieben und unsere Daten mitsamt dem veränderten Rest auf die Festplatte schreiben. Zudem müssen wir irgendwo den alten Entrypoint speichern. Hier schlage ich vor, dass wir uns eine Struct bauen, in der wir den alten Entrypoint und Daten für unsere Funktion unterbringen. Jetzt wird man sich sicher fragen: Welche Daten sollen warum dort rein? OK, wenn ihr einen String benutzt, nutzt ihr eigentlich nicht den String an sich, sondern ein Pointer auf ein Char-Array. Dieses Array liegt im kompilierten Programm aber in der Data-Section und ist für uns somit nur über umwege zu erreichen. Daher nutzen wir also die Struct, um solche Daten, dort unterzubringen.
Da wir schon bei Einschränkungen sind: Außer dieser Einschränkung gibt es noch weitere:
- die Funktion darf maximal 4KB an lokalen Variablen benutzen (für mehr kann man ja die Struktur nutzen). Dies kommt daher, dass ja eigentlich für die lokalen Variablen der Stackpointer um deren Größe reduziert wird (sub esp, <Größe der lokalen Variablen>). Werden aber mehr als 4KB lokaler Variablen gebraucht, wird ESP nicht direkt reduziert, sondern über eine spezielle Funktion, welche gecallt wird. In der DLL (bzw. im Prozess, der die DLL lädt) ist diese Funktion natürlich nicht da, oder an einem anderen Platz, weswegen der Call in's Leere geht.
- viele Compiler machen Buffer Security Checks, unter anderem um die Integrität des Stacks zu gewährleisten. Diese werden aber auch durch externe Funktionen erledigt, weswegen auch die wegmüssen (bei den meisten Compilern ist das der /Gz-Switch).
- alle API-Aufrufe, die nicht zur kernel32.dll oder user32.dll gehören, dürfen nicht direkt aufgerufen werden, da sich die Module, die die Funktionen beinhalten an unterschiedlichen Stellen befinden können. Daher müssen wir hier zuerst mit GetModuleHandle das Modul bekommen und dann mit GetProcessAddress die Adresse der Funktion, die wir dann aufrufen wollen (vergesst nicht, nur Strings aus unserer Struktur zu nehmen).
So also unsere Section beinhaltet erst unsere Daten-Struktur und dann unsere Funktion für den neuen EntryPoint. Aber wie können wir aus unserer Funktion auf die Datenstruktur (anfang der Section) zugreifen? Ich habe dieses Problem so gelöst, dass ich den relativen Pointer zur Section (und damit ja zur Datenstruktur) in das "LoaderFlags"-Feld des NTHeader.OptionalHeader geschrieben habe. Dieses Feld ist laut MSDN "obsolete", weswegen wir es deswegen einfach missbrauchen :P
2. Umsetzung in die Praxis
So jetzt erstmal zum vorgeplänkel unseres Programms. Wir müssen die Opfer-DLL einlesen (nicht mit LoadLibrary oder sonstwie mappen!). Wie, ist jedem selbst überlassen. Am Ende befindet sie sich jedenfalls im Arbeitsspeicher und wir haben einen Pointer, der auf das erste Byte der DLL zeigt.
2.1 Header manipulieren
So, um die Header zu manipulieren, müssen wir sie erstmal kriegen. Nach dem PE-Format befindet sich ganz am Anfang der DOS-Header, also ab dem ersten Byte. Wir können also gleich einen IMAGE_DOS_HEADER-Pointer aus dem DLL-Pointer machen. Dann nutzen wir das Feld e_lfanew, um an den NT-Header (= PE-Header) zu kommen. Natürlich müssen wir diesen Wert noch mit der Basis addieren, da es nur ein relativer Pointer ist.
Code:
IMAGE_NT_HEADER* pNTHeader = (IMAGE_NT_HEADER*)(((IMAGE_DOS_HEADER*)pDLL)->e_lfanew + (DWORD)pDLL);
Code:
IMAGE_SECTION_HEADER* pSectionHeaders = (IMAGE_SECTION_HEADER*)((DWORD)pNTHeader + 4 + pNTHeader->FileHeader.SizeOfOptionalHeader);
Code:
for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++)
{
if (dwVAofSection < (pSectionHeaders[i].VirtualAddress + pSectionHeaders[i].Misc.VirtualSize))
{
dwVAofSection = (pSectionHeaders[i].VirtualAddress + pSectionHeaders[i].Misc.VirtualSize) + 0x1000 - ((pSectionHeaders[i].VirtualAddress + pSectionHeaders[i].Misc.VirtualSize) % 0x1000);
}
}
Jetzt kommen wir zum "PointerToRawData" für unsere Section. Da wir unsere Section ja an's Ende der DLL packen wollen muss hier also die aktuelle Größe der DLL rein. Die einzige Einschränkung ist, dass PointerToRawData uns SizeOfRawData vielfaches des FileAlignment-Feldes des FileHeaders im NTHeader sein müssen. Aber das ist ja kein Problem für uns. Wenn die Größe der Datei nicht glatt durch den FileAlignment-Wert teilbar ist (ist bei mir noch nicht vorgekommen), schreiben wir eben noch ein kleines Buffer vor unserer Section in die Datei. Genauso schrieben wir ein kleines Buffer nach unserer Section in die Datei, wenn die Größe unserer Section nicht durch den FileAlignment-Wert teilbar ist (das kommt öfter vor).
Wir berechnen nach diesen vorgaben nun also die Größe unserer Section (SizeOfRawData und Misc.VirtualSize) und den "PointerToRawData":
Code:
DWORD dwSectionSize = dwFuncLength + sizeof(SomeData) + NTHeader->OptionalHeader.FileAlignment - (dwContentLength % NTHeader->OptionalHeader.FileAlignment);
Code:
if (dwSizeOfDLL % pNTHeader->OptionalHeader.FileAlignment == 0)
{
dwPointerToRawData = dwSizeOfDLL;
}
else
{
dwPointerToRawData = dwSizeOfDLL + pNTHeader->OptionalHeader.FileAlignment - (dwSizeOfDLL % pNTHeader->OptionalHeader.FileAlignment);
dwPreSectionBufferLength = dwPointerToRawData - dwSizeOfDLL;
}
Wichtig ist auch noch das "Characteristics"-Feld im Section Header. Da wir ja unseren Code ausführen, unsere Datenstruktur lesen und vielleicht auch zur Laufzeit bearbeiten müssen. Setzen wir also auch so unser Characteristics-Feld fest:
Code:
DWORD dwCharacteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_CODE;
Code:
IMAGE_SECTION_HEADER MyHeader; memcpy(MyHeader.Name, ".Jeoni", 8); MyHeader.Misc.VirtualSize = dwSectionSize; MyHeader.SizeOfRawData = dwSectionSize; MyHeader.VirtualAddress = dwVAofSection; MyHeader.PointerToRawData = dwPointerToRawData; // restliche Felder mit 0 füllen
Code:
if ((NTHeader->FileHeader.NumberOfSections + 1) * sizeof(IMAGE_SECTION_HEADER) <= NTHeader->OptionalHeader.SizeOfHeaders)
{
memcpy((void*)((DWORD)pSectionHeaders + ((NTHeader->FileHeader.NumberOfSections)*sizeof(IMAGE_SECTION_HEADER))), (void*)&MySectionHeader, sizeof(IMAGE_SECTION_HEADER));
}
Code:
SectionData.OriginalEntryPoint = NTHeader->OptionalHeader.AddressOfEntryPoint; NTHeader->OptionalHeader.LoaderFlags = dwVAofSection; NTHeader->OptionalHeader.AddressOfEntryPoint = dwVAofSection + sizeof(SomeData); NTHeader->FileHeader.NumberOfSections += 1; NTHeader->OptionalHeader.SizeOfImage += dwSectionSize; NTHeader->OptionalHeader.SizeOfCode += dwSectionSize; // da wir der Section ja Code-Characteristics gegeben haben
2.2 Unsere Section bauen und in die DLL schreiben
Ok, wir wollen also unsere Section schreiben. Mehr als unser Code und die Datenstruktur muss da ja eigentlich nicht rein. Aber wie bekommen wir unseren Code da rein? Entweder wir schreiben die gesamte Funktion mit ASM, und kopieren dann die Offsets da rein, oder wir machen uns eine Funktion in unserem Manipulator, kompilieren sie und fügen das dann zur Laufzeit ein. Aber wie kriegen wir die Länge unserer Funktion? Man könnte das Programm natürlich einmal kompilieren und dann mit einem Debugger nachschauen oder eine der disasm libs nutzen.
In diesem Tutorial bedienen wir uns aber eines kleinen Tricks (nicht schön, aber einfach): direkt nach unserer Funktion machen wir noch eine und rechnen dann einfach
Code:
DWORD dwFuncLength = &EndFunction - &NewEntryPoint;
bool _stdcall NewEntryPoint(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) // EntryPoint gemäß MSDN
{
// ...
}
__declspec(naked) void EndFunction(){}
[Only registered and activated users can see links. Click Here To Register...]
So kriegen wir relativ genau die Länge unserer Funktion. Wichtig hierbei ist, dass in den Compileroptionen das Incremental Linking ausgestellt wird oder die beiden Funktionen als static erklärt werden. Sonst liefert der '&'-Operator nicht die Adresse der eigentlichen Funktion, sondern die eines Jumps zur Funktion. Und da die Jumps zu den Funktionen meist direkt hintereinander stehen, wird man dann hier immer 1 herausbekommen, was natürlich falsch ist.
OK, kommen wir mal kurz zu unserem Code bzw. was wir eigentlich machen wollen. Hier werde ich mal nur die Funktion MessageBeep(0) ausführen und danach den originalen EntryPoint aufrufen.
In die Datenstruktur kommt daher nur der relative Pointer zum originalen EntryPoint rein. Das ist also völlig ausreichend:
Code:
struct SomeData
{
DWORD OriginalEntryPoint;
}SectionData;
Aufgrunddessen sieht unsere Funktion wie folgt aus:
Code:
bool _stdcall NewEntryPoint(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
MessageBeep(0);
IMAGE_NT_HEADERS32* NTHeader = (IMAGE_NT_HEADERS32*)(((IMAGE_DOS_HEADER*)hModule)->e_lfanew + (DWORD)hModule);
SomeData* Data = (SomeData*)(NTHeader->OptionalHeader.LoaderFlags + (DWORD)hModule);
DLLEntry_t pOrigEntryPoint = (DLLEntry_t)(Data->OrginalEntryPoint + (DWORD)hModule);
return pOrigEntryPoint(hModule, ul_reason_for_call, lpReserved);
}
Kommen wir zum schreiben unserer Section. Da wir natürlich an die Richtlinien für die Größe unserer Section gebunden sind, allokieren wir erstmal Platz für unsere Section bevor wir sie schreiben:
Code:
void* pMySection = malloc(pMySectionHeader->SizeOfRawData);
Code:
memcpy(pMySection, &SectionData, sizeof(SomeData)); memcpy((void*)((DWORD)pMySection + sizeof(SomeData)), &NewEntryPoint, dwFuncLength;
Jetzt schreiben wir also das alte DLL-Buffer wieder in eine DLL auf die Festplatte, hängen unsere Section dran und sind fertig :D
Ähnlich könnt ihr es auch mit exportierten Funktionen der DLL machen. Dahingehend solltet ihr euch das "DataDirectory"-Feld des OptionalHeaders anschauen.