Alle Artikel

In diesem Artikel geht es um Rootkits und Techniken, die sie verwenden. Speziell geht es um API-Hooking. Doch vorher: Was ist ein Rootkit? Ein Rootkit ist eine Art von Malware. Sie kommt meistens mit anderen Arten, beispielsweise Botclients, Viren, Würmern, Trojanern und sorgt dafür, dass diese unentdeckt bleibt.

Ein Beispiel

Nehmen wir an, es gäbe eine Datei namens `winlogon.exe`, die sich in `%AppData%` eingenistet hat, einem versteckten Verzeichnis, in dem Programme ihre Daten ablegen können. Es gibt aber auch eine Datei namens `winlogon.exe` im Windows-Verzeichnis, diese Datei ist eine wichtige Systemdatei. Wenn man nun in den Taskmanager guckt, dann ist `winlogon.exe` doppelt vorhanden - das sollte die meisten User stutzig machen. Nun kann man einfach herausfinden, dass eine der beiden Dateien in `%AppData%` liegt, einem ungewöhnlichen Ort für Systemdateien. Man beendet diese, löscht `winlogon.exe`, entfernt noch ein paar Autostart-Einträge und das System ist wieder sauber. Solche Versuche verhindern Rootkits.

Angriffspunkte für Rootkits

Rootkits klinken sich in die Ebene zwischen den Programmen und dem Betriebssystem ein und verhindern, dass die Programme bestimmte Informationen bekommen, beispielsweise, dass ein bestimmter Prozess läuft, dass eine bestimmte Datei vorhanden ist, dass eine Datei geöffnet werden kann, und so weiter. Die Ebene zwischen dem Betriebssystem und Programmen heißt **WinAPI**. Zumindest bei Windows, dieser Artikel wird sich komplett auf Windows beziehen. Die WinAPI ist in aktuellen, 32/64-bit Systemen in den Dateien `user32.dll`, `gdi32.dll` und `kernel32.dll` untergebracht. Eine weitere Ebene darunter ist die NT-API, eine undokumentierte API, die die WinAPI mit Informationen versorgt.

Der Grund für diese API ist, dass es unter Windows mehrere Subsysteme gibt: Win32, WOW64, POSIX, OS/2, und diese APIs greifen auf diese NT-API zu. Auf heutigen Installationen sind POSIX und OS/2 schon längst weggeflogen, trotzdem gibt es diese zusätzliche Ebene. Hier können sich Rootkits auch einklinken, die Funktionen liegen in der Datei `ntdll.dll`. Im Gegensatz zu den gerade behandelten Userland-Rootkits, haben die Kernel-Rootkits noch eine weitere Ebene, sie können sich auch in den Kernel einklinken. Dafür haben sie entweder einen Treiber oder schreiben im Kernel-Arbeitsspeicher herum. Kernel-Rootkits werden nicht in diesem Artikel behandelt und wurden nur der Vollständigkeit halber erwähnt.

Welche Funktionen lohnen sich zum Übeschreiben?

Funktionen, die neue Prozesse erstellen

  • CreateProcess
  • CreateProcessAsUser
  • CreateProcessWithLogonW

Funktionen, die auf Dateien zugreifen

  • CreateFile [auch zum Öffnen verwendet]
  • CreateFile2
  • CreateDirectory
  • CopyFile
  • DeleteFile
  • FindFirstFile
  • FindNextFile
  • MoveFile
  • OpenFile

Funktionen, die Prozesse auflisten

  • NtQuerySystemInformation
  • Process32First
  • Process32Next

Einfach zum Spaß

  • MessageBoxA
  • MessageBoxW

Wie überschreibe ich Funktionen?

Es gibt einige Hook-Bibliotheken, die den folgenden Code stark verkürzen und vereinfachen könnten und dabei noch stabiler wären. Beispiele dafür sind MS Detours oder mHook. Da es hier aber eher darum geht, die Funktionsweise dahinter zu verstehen, werde ich auf solche Bibliotheken verzichten und alles selber schreiben. Ich empfehle aber trotzdem, wenn man die Techniken hier in einem echten Projekt anwenden will, auf eine Bibliothek zurückzugreifen. Als Entwicklungsumgebung verwende ich **Microsoft Visual Studio 2010**.

Vorraussetzungen

* C++ Kenntnisse
* Assembler-Kenntnisse [optional]

Ein kleines Testprogramm


Wer folgendes nicht versteht, für den ist das Tutorial ungeeignet. Unser erstes Ziel ist, dass der Text und der Titel in der Konsole ausgegeben werden.

#include <Windows.h>
 
int main() {
    system("pause");
    MessageBoxA(nullptr, "Beispieltext", "Beispielmeldung", MB_OK);
    system("pause");
}

Die Zielfunktion

Wir erstellen erst einmal eine Funktion namens `OurMessageBoxA`, die genau die gleiche Signatur besitzt, das bedeutet: gleicher Rückgabewert (int), gleiche Aufrufkonvention (WINAPI) und die gleichen Parameter `(HWND, LPCSTR, LPCSTR, UINT)`


int WINAPI OurMessageBoxA(HWND hwnd,LPCSTR text,LPCSTR title,UINT type)
{
    std::cout<<"---------------------------------\n";
    std::cout<<"|"<<std::setw(30)<<title<<"|\n";
    std::cout<<"---------------------------------\n";
    std::cout<<"|"<<std::setw(30)<<text<<"|\n";
    std::cout<<"---------------------------------\n";
    return IDOK;
}

Assembler-Code

Jetzt müssen wir nur noch dafür sorgen, dass unsere neue Funktion anstatt der alten aufgerufen wird. Jetzt machen wir uns die Hände schmutzig und schreiben im RAM herum. Dabei kann dank Windows nichts Schlimmes passieren. Wir überschreiben die ersten paar Bytes von MessageBoxA mit Maschinencode. In Assemblerform sieht er so aus:


mov eax, <32-bit-Addresse>
jmp eax


Der dazugehörige Maschinencode sieht so aus:


0xb8 0xXX 0xXX 0xXX 0xXX
0xff 0xe0


Die Bytes mit X stehen für eine 32-Bit Addresse, diese wird in 4 Teile aufgeteilt und dann ein Array geschrieben. Damit es besser zum Verstehen ist, hier der komplette Code bisher:

#include <Windows.h>
#include "functions.h"
 
int WINAPI OurMessageBoxA(HWND hwnd,LPCSTR text,LPCSTR title,UINT type);

void install_hook() {
    DWORD our_address = reinterpret_cast<DWORD>(&OurMessageBoxA);
    unsigned char funcMem[7];
    //mov eax,our_address
    funcMem[0] = 0xb8;
    funcMem[1] = our_address & 0xFF;
    funcMem[2] = (our_address >> 8) & 0xFF;
    funcMem[3] = (our_address >> 16) & 0xFF;
    funcMem[4] = (our_address >> 24) & 0xFF;
 
    //jmp eax
    funcMem[5]=0xff;
    funcMem[6]=0xe0;
}
 
int WINAPI OurMessageBoxA(HWND hwnd,LPCSTR text,LPCSTR title,UINT type)
{
    std::cout<<"---------------------------------\n";
    std::cout<<"|"<<std::setw(30)<<title<<"|\n";
    std::cout<<"---------------------------------\n";
    std::cout<<"|"<<std::setw(30)<<text<<"|\n";
    std::cout<<"---------------------------------\n";
    return IDOK;
}

Der Hooking-Vorgang

Nun kopieren wir das Array an die Addresse von `MessageBoxA`. Diese finden wir mit `GetProcAddress()` und `GetModuleHandle()`. Wir speichern sie in `messageBoxA_address`. Damit Windows uns das Kopieren erlaubt, müssen wir die Berechtigungen ändern. Wir müssen den Codebereich im RAM beschreibbar machen. Das geht einfach mit `VirtualProtect()`. Wenn der Code beschreibbar ist, kopieren wir das Array und danach stellen wir die alten Berechtigungen wieder her.

DWORD oldProtection;
VirtualProtect((LPVOID)messageBoxA_address,1024,PAGE_EXECUTE_READWRITE,&oldProtection);
memcpy((void*)messageBoxA_address,funcMem,7);
VirtualProtect((LPVOID)messageBoxA_address,1024,oldProtection,nullptr);

Der Test

Jetzt können wir testen, ob unser Hook funktioniert. Wenn ja, dann sollten wir eine ASCII-Art Message Box in der Konsole bekommen.

Damit wirklich nichts fehlt, hier ist der komplette Code für `install_hook` und `OurMessageBoxA`. In der Funktion `main()` wurde vor allem anderen ein Aufruf an `install_hook` eingefügt


#include <Windows.h>

int main() {
    install_hook();
    system("pause");
    MessageBoxA(nullptr, "Beispieltext", "Beispielmeldung", MB_OK);
    system("pause");
}

#include <Windows.h>
#include "functions.h"
 
int WINAPI OurMessageBoxA(HWND hwnd,LPCSTR text,LPCSTR title,UINT type);
 
void install_hook() {
    DWORD our_address = reinterpret_cast<DWORD>(&OurMessageBoxA);
    unsigned char funcMem[7];
    //mov eax, our_address
    funcMem[0] = 0xb8;
    funcMem[1] =  our_address & 0xFF;
    funcMem[2] = ( our_address >> 8 ) & 0xFF;
    funcMem[3] = ( our_address >> 16 ) & 0xFF;
    funcMem[4] = ( our_address >> 24 ) & 0xFF;
 
    //jmpeax
    funcMem[5]=0xff;
    funcMem[6]=0xe0;
   
    DWORD messageBoxA_address = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
   
    DWORD oldProtection;
    VirtualProtect((LPVOID)messageBoxA_address,1024,PAGE_EXECUTE_READWRITE,&oldProtection);
    memcpy((void*)messageBoxA_address,funcMem,7);
    VirtualProtect((LPVOID)messageBoxA_address,1024,oldProtection,nullptr);
}
 
int WINAPI OurMessageBoxA(HWND hwnd,LPCSTR text,LPCSTR title,UINT type)
{
    std::cout<<"---------------------------------\n";
    std::cout<<"|"<<std::setw(30)<<title<<"|\n";
    std::cout<<"---------------------------------\n";
    std::cout<<"|"<<std::setw(30)<<text<<"|\n";
    std::cout<<"---------------------------------\n";
    return IDOK;
}

Die originale Funktion aufrufen

Wir haben nun erfolgreich einen Hook geschrieben, dabei aber auch die Funktion `MessageBoxA` zerstört. Jetzt wollen wir das wieder in Ordung bringen. Wir speichern uns die ersten 7 Bytes der Funktion, patchen sie, wenn die Funktion aufgerufen wird, patchen wir sie wieder zurück, haben eine normale `MessageBoxA` zur Verfügung, danach patchen wir sie wieder und alles ist beim Alten.

Globale Variablen

Eigentlich sollte man keine globale Variablen verwenden und stattdessen alles über Parameter übergeben, da wir aber die Signatur einhalten müssen, geht das leider nicht. Deshalb übergeben wir folgende Informationen als globale Variablen: Die alte Funktionsaddresse, die neue Funktionsaddresse, die 7 Byte Backup und die 7 Byte Assembler-Code. Mit diesen Infos können wir die Funktion in beide Richtungen patchen. Aber als erstes passen wir die Funktion `install_hook` an, sodass sie die neuen, globalen Variablen verwendet. Diese packen wir der Übersichtlichkeit halber in einen Namespace:


namespace globals_MessageBoxA {
    typedef int (WINAPI *MessageBoxAPtr)(HWND,LPCSTR,LPCSTR,UINT);
    MessageBoxAPtr origFuncPtr = reinterpret_cast<MessageBoxAPtr>(GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA"));
    MessageBoxAPtr newFuncPtr=&OurMessageBoxA;
    unsigned char funcMem[7];
    unsigned char payloadMem[7];
}


Mit typedef definieren wir uns eine Abkürzung für den Funktionspointer auf MessageBoxA. So sieht das Füllen des Codes jetzt aus:


//mov eax,our_address
globals_MessageBoxA::payloadMem[0]=0xb8;
globals_MessageBoxA::payloadMem[1]=(DWORD)globals_MessageBoxA::newFuncPtr&0xFF;
globals_MessageBoxA::payloadMem[2]=((DWORD)globals_MessageBoxA::newFuncPtr>>8 )&0xFF;
globals_MessageBoxA::payloadMem[3]=((DWORD)globals_MessageBoxA::newFuncPtr>>16)&0xFF;
globals_MessageBoxA::payloadMem[4]=((DWORD)globals_MessageBoxA::newFuncPtr>>24)&0xFF;

//jmp eax
globals_MessageBoxA::payloadMem[5]=0xff;
globals_MessageBoxA::payloadMem[6]=0xe0;



Zum Patchen verwenden wir nun folgenden Code, wir sichern auch schon die ersten 7 Bytes in funcMem.


memcpy(globals_MessageBoxA::funcMem,globals_MessageBoxA::origFuncPtr,7);

DWORD oldProtection;
VirtualProtect(globals_MessageBoxA::origFuncPtr,1024,PAGE_EXECUTE_READWRITE,&oldProtection);
memcpy(globals_MessageBoxA::origFuncPtr,globals_MessageBoxA::payloadMem,7);
VirtualProtect(globals_MessageBoxA::origFuncPtr,1024,oldProtection,nullptr);

Das Zurückpatchen

Jetzt fehlt nur noch das zurückpatchen und wir sind fertig!


DWORD oldProtection;
VirtualProtect(globals_MessageBoxA::origFuncPtr,1024,PAGE_EXECUTE_READWRITE,&oldProtection);
memcpy(globals_MessageBoxA::origFuncPtr,globals_MessageBoxA::funcMem,7);
VirtualProtect(globals_MessageBoxA::origFuncPtr,1024,oldProtection,nullptr);


Da wir recht weit am Ende des Artikels sind, hier noch einmal den kompletten Code.


#include <Windows.h>
 
int main() {
    install_hook();
    system("pause");
    MessageBoxA(nullptr, "Beispieltext", "Beispielmeldung", MB_OK);
    system("pause");
}

// --------------------

#include <Windows.h>
#include "functions.h"
 
int WINAPI OurMessageBoxA(HWND hwnd,LPCSTR text,LPCSTR title,UINT type);

namespace globals_MessageBoxA {
    typedef int (WINAPI *MessageBoxAPtr)(HWND,LPCSTR,LPCSTR,UINT);
    MessageBoxAPtr origFuncPtr = reinterpret_cast<MessageBoxAPtr>(GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA"));
    MessageBoxAPtr newFuncPtr=&OurMessageBoxA;
    unsigned char funcMem[7];
    unsigned char payloadMem[7];
}

void install_hook() {
    // mov eax, our_address
    globals_MessageBoxA::payloadMem[0] = 0xb8;
    globals_MessageBoxA::payloadMem[1] = (DWORD)globals_MessageBoxA::newFuncPtr & 0xFF;
    globals_MessageBoxA::payloadMem[2] = ((DWORD)globals_MessageBoxA::newFuncPtr >> 8 ) & 0xFF;
    globals_MessageBoxA::payloadMem[3] = ((DWORD)globals_MessageBoxA::newFuncPtr >> 16) & 0xFF;
    globals_MessageBoxA::payloadMem[4] = ((DWORD)globals_MessageBoxA::newFuncPtr >> 24) & 0xFF;

    // jmp eax
    globals_MessageBoxA::payloadMem[5] = 0xff;
    globals_MessageBoxA::payloadMem[6] = 0xe0;

    memcpy(globals_MessageBoxA::funcMem, globals_MessageBoxA::origFuncPtr, 7);

    DWORD oldProtection;
    VirtualProtect(globals_MessageBoxA::origFuncPtr, 1024, PAGE_EXECUTE_READWRITE, &oldProtection);
    memcpy(globals_MessageBoxA::origFuncPtr, globals_MessageBoxA::payloadMem, 7);
    VirtualProtect(globals_MessageBoxA::origFuncPtr, 1024, oldProtection, nullptr);
}
 
int WINAPI OurMessageBoxA(HWND hwnd,LPCSTR text,LPCSTR title,UINT type)
{
    // DE-PATCH
    DWORD oldProtection;
    VirtualProtect(globals_MessageBoxA::origFuncPtr, 1024, PAGE_EXECUTE_READWRITE, &oldProtection);
    memcpy(globals_MessageBoxA::origFuncPtr, globals_MessageBoxA::funcMem, 7);
    VirtualProtect(globals_MessageBoxA::origFuncPtr, 1024, oldProtection, nullptr);
    // DE-PATCH ENDE
   
    std::string t(text);
    t += "\nAPI was hooked";

    int ret = globals_MessageBoxA::origFuncPtr(hwnd, t.c_str(), title, type);
   
    // PATCH
    oldProtection;
    VirtualProtect(globals_MessageBoxA::origFuncPtr, 1024, PAGE_EXECUTE_READWRITE, &oldProtection);
    memcpy(globals_MessageBoxA::origFuncPtr, globals_MessageBoxA::payloadMem, 7);
    VirtualProtect(globals_MessageBoxA::origFuncPtr, 1024, oldProtection, nullptr);
    // PATCH ENDE
}

Nun ist der Hook fertig, wir können mit den Parametern und den Rückgabewerten anstellen, was wir wollen. API Hooking ist eine mächtige Technik, vor allem im Zusammenhang mit DLL-Injection, einen Artikel weiter unten.

Variablen im Code

  • globals_MessageBoxA::origFuncPtr: Funktionszeiger auf den Speicherbereich von MessageBoxA
  • globals_MessageBoxA::newFuncPtr: Funktionszeiger auf OurMessageBoxA
  • globals_MessageBoxA::funcMem: Backup der ersten 7 Bytes von MessageBoxA
  • globals_MessageBoxA::payloadMem: Assemblercode, der die Funktion auf unsere umleitet.

Zum Artikel

Über ein etwas älteres Video von Sethbling bin ich auf das Spiel LightsOut aufmerksam geworden. Man hat ein Spielfeld mit zufällig angeschalteten "Lichtern". Wenn man auf ein Feld klickt werden die Felder direkt daneben und es selber umgeschaltet. Auf einem komplett gelösten Spielfeld würde also ein Druck irgendwo in die Mitte ein Plus-Zeichen erscheinen lassen. Am Rand werden die Felder abgeschnitten. Wie schon angedeutet ist das Ziel des Spiels alle Lichter auszuschalten.

Strategie

Wie bei den meisten Spielen gibt es auch bei diesem eine Strategie. Man fängt in der zweiten Reihe an und sieht nach, wo in der Reihe darüber Felder an sind. Auf diese klickt man. Dadurch gehen die Lichter in der oberen Reihe aus. Die hätten wir also schonmal. Dann geht es so weiter, bis in die letzte Reihe. Entweder alles ist danach gelöst oder wir haben noch Restfelder in der letzten Reihe. Nach meiner Beobachtung kann ein 4x4 mit dieser Methode ohne Restfelder gelöst werden, bei einem 5x5 werden sehr wahrscheinlich eine der folgenden Kombinationen übrig bleiben:

+ = aktiv

. = nicht aktiv

  1. ..+++ => ...+.
  2. .+.+. => .+..+
  3. .++.+ => +....
  4. +...+ => ...++
  5. +.++. => ....+
  6. ++.++ => ..+..
  7. +++.. => .+...

Nach dem Pfeil (=>) stehen die Felder, die man in der oberen Reihe klicken muss. Danach geht es wieder nach der oberen Methode weiter. Diesmal sollte sich das Feld ohne Restfelder lösen lassen. Bei einem 9x9 gibt es nur zwei Möglichkeiten: Fertig gelöst oder abwechselnd an und aus (+.+.+.+.+). Hier muss man oben in die Linke Ecke ein Klick setzen und wieder die obere Methode anwenden. Diese Methode nennt sich auch "Chase the lights".

Das Spiel als Windows-Programm

Mir war etwas langweilig also habe ich mir gedacht, ich kann das Ganze doch in Windows Forms nachprogrammieren. Die fertige .exe findet sich hier: LightsOut.exe - Dropbox Mit im Programm enthalten ist ein Solver, der selbstständig die obige Methode ausführt und danach doppelte Klicks herausfiltert. Leider ist die Methode nicht optimal, aber dennoch effektiver als normales lösen mit "Chase the lights"

Quellenangaben

Die Lösungsmethode ist von Logicgamesonline.com LightsOut Tutorial aus dem Englischen übersetzt worden. Auf dieser Seite gibt es eine täglich wechselnde 9x9 Lights Out Herausforderung. Ich glaube es wird Zeit, dass ich meinen Solver umschreibe ;)

Zum Artikel

Dieser Artikel ist eine Fortsetzung zu Blackphantom.de Windows API Hooking, den ich für BlackY verfasst habe. Die Themen haben nicht so viel miteinander zu tun, es sind aber beides Techniken, die in Rootkits verwendet werden. Wie in dem vorherigen Artikel wird sich dieser auf Windows beziehen.

Die Grundlagen

In allen modernen Betriebssystemen sind Prozesse voneinander abgeschottet. Keiner kann in den Speicher der anderen Prozesse schreiben oder etwas lesen. Code wird immer in einem eigenen Adressraum ausgeführt.

DLLs (Dynamic Link Library) sind Dateien, die zwar Code enthalten, aber nicht selber ausgeführt werden können. Dazu benötigen sie .exe-Dateien, die die DLLs aufrufen. DLLs können leichter ausgetauscht werden als das Hauptprogramm, so können Softwarepakete einfacher auf den neuesten Stand gebracht werden. Außerdem müssen sie nicht schon beim Kompilieren vorhanden sein, nur das Interface, die Art, wie die DLL angesprochen wird, muss bekannt sein. In Paint.NET lassen sich zum Beispiel Erweiterungen über DLLs nachladen.

Damit DLLs die Daten aus dem Hauptprogramm weiterverarbeiten können, muss es einen Weg für sie geben, daran zu kommen. Damit das nicht zu umständlich wird, laufen .exe und DLL im gleichen Adressraum. Wer jetzt noch den Artikel über API Hooking im Kopf hat, der erinnert sich vielleicht daran, dass man fürs Überschreiben von Funktionen im gleichen Adressraum sein muss. Also, schreiben wir einfach eine DLL, in der wir den Code hooken. Doch wie bekommen wir unser Zielprogramm dazu, unsere DLL zu laden?

DLL Injektionsmethoden

  • Globaler Hook, über die SetWindowsHookEx-Funktion
  • AppInit_DLLs, über die Registry
  • Nachladen über LoadLibraryA, mittels eines Remote Threads
  • Direktes Schreiben einer Ladeprozedur in den Speicherbereich eines anderen Prozesses

Die einfachste Möglichkeit geht über die Registry. Allerdings muss die DLL dafür signiert werden. Die Methode, die in diesem Artikel behandelt wird, ist die Methode über LoadLibraryA.

Das Zielprogramm

Das Ziel unserer Injektion wird sein, die Hintergrundfarbe der Konsole zu verändern. Unser Zielprogramm ist deshalb ein kleines "Hello World"-Programm, das zusätzlich die aktuelle PID ausgibt. So brauchen wir nicht im Taskmanager danach suchen.

#include <Windows.h>
#include <iostream>


int main()
{
std::cout << "PID: " << GetCurrentProcessId() << "\n";
system("pause");
MessageBoxA(nullptr, "Test", "Test", MB_OK);
system("pause");
}

Die DLL

DLLs haben einen Einsprungspunkt, der aufgerufen wird, wenn die DLL von einer .exe geladen oder getrennt wird. Für den Fall, dass die DLL geladen wird, rufen wir die WinAPI-Funktion zum ändern der Hintergrundfarbe auf. Wir können auch eine MessageBox öffnen, um zu zeigen, dass der DLL-Code nun läuft.

#include <Windows.h>

BOOL WINAPI DllMain(HINSTANCE dllInst, DWORD reason, LPVOID reserved)
{
if (reason == DLL_PROCESS_ATTACH)
{
MessageBoxA(nullptr, "Dll erfolgreich injected", "Dll Injector", MB_OK);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_GREEN);
}
else if (reason == DLL_PROCESS_DETACH)
{
MessageBoxA(nullptr, "Dll wird entfernt", "Dll Injector", MB_OK);
}
return TRUE;
}

Der Injektor

Nun gehts zum Hauptteil dieses Artikels: Der Injektor. Er soll mit 2 Parametern aufgerufen werden, der Ziel-PID und der Ziel-DLL. Deshalb prüfen wir erst, ob auch alle Parameter vorhanden sind (argc == 3). Falls nicht, geben wir Hinweise zur Benutzung aus.

// Check if all required arguments are present
    if (argc != 3)
    {
        // If not, display usage message
        usage();
        cin.get();
        return EXIT_FAILURE;
    }

Funktion usage:

void usage()
{
    using namespace std;
    cout << "Usage: DLL_Injector TargetPID DLL\n";
}

Als nächstes holen wir uns das SeDebugPrivilege. Das erlaubt unserem Programm in den Arbeitsspeicher anderer Programme zu schreiben. Hierfür schreiben wir uns eine Funktion enableDebugPriv().

#include <Windows.h>
#include <stdexcept>


void enableDebugPriv()
{
HANDLE hToken;
LUID SeDebugNameValue;
TOKEN_PRIVILEGES TokenPrivileges;

if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &SeDebugNameValue))
{
TokenPrivileges.PrivilegeCount = 1;
TokenPrivileges.Privileges[0].Luid = SeDebugNameValue;
TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
{
CloseHandle(hToken);
}
else
{
CloseHandle(hToken);
throw std::runtime_error("Couldn't adjust token privileges!");
}
}
else
{
CloseHandle(hToken);
throw std::runtime_error("Couldn't look up privilege value!");
}
}
else
{
throw std::runtime_error("Couldn't open process token!");
}
}

Falls das gut gegangen ist (Exceptions fangen nicht vergessen), parsen wir die Parameter und holen uns ein Prozess-Handle. Das geht mit der Funktion OpenProcess. Wir holen uns einfach PROCESS_ALL_ACCESS, natürlich kann man hier auch nur die benötigten Rechte angeben.

// Parse the parameters
int pid = strTo<int>(argv[1]);
std::string pathToDll = argv[2];

while (pid == 0)
{
cout << "PID: ";
cin >> pid;
}

cout << "Opening Process " << pid << "...\n";
// Open the process
HANDLE remoteProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
if (remoteProcessHandle == nullptr)
{
int error = GetLastError();
cout << "Failed to open process. \nError code: " << error << "\nDescription: " << errorCodeToMessage(error);
cin.get();
return EXIT_FAILURE;
}

Danach holen wir uns Speicher im Prozess. Hier wird der Pfad zur DLL gespeichert. Achtung! Der Pfad muss nullterminiert sein, deshalb brauchen wir ein Byte zusätzlich.

        cout << "Allocating memory...\n";
// Allocate memory in the target's address space
void* pathmem = VirtualAllocEx(remoteProcessHandle, nullptr, pathToDll.size() + 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pathmem == nullptr)
{
int error = GetLastError();
cout << "Failed to allocate memory. \nError code: " << error << "\nDescription: " << errorCodeToMessage(error);
cin.ignore(cin.rdbuf()->in_avail());
cin.get();
return EXIT_FAILURE;
}

In den eben reservierten Speicher schreiben wir nun den Pfad.

	cout << "Writing memory...\n";
// Write the path of the dll in the target's address space
if (!WriteProcessMemory(remoteProcessHandle, pathmem, pathToDll.c_str(), pathToDll.size() + 1, nullptr))
{
int error = GetLastError();
cout << "Failed to write into target's memory. \nError code: " << error << "\nDescription: " << errorCodeToMessage(error);
cin.ignore(cin.rdbuf()->in_avail());
cin.get();
return EXIT_FAILURE;
}

Jetzt kommt der interessante Teil. Wir erstellen einen Thread, der in einem anderen Prozess abläuft. Dafür nehmen wir allerdings nicht eine eigene Funktion, sondern eine, die schon in kernel32.dll vorhanden ist: LoadLibraryA. Die Signatur ist folgende:

HMODULE LoadLibraryA(char* path);

Und die Signatur der Thread-Funktion (LPTHREAD_START_ROUTINE):

DWORD ThreadProc(void* parameter);

Wen wir nun den Thread starten, geben wir LoadLibraryA die Adresse des vorhin reservierten Speichers. Da die Signaturen zusammenpassen, funktioniert der Aufruf.

	cout << "Creating remote thread...\n";
// Create a remote thread in the other process' address space
HANDLE remoteThreadHandle = CreateRemoteThread(remoteProcessHandle, nullptr, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"), pathmem, CREATE_SUSPENDED, nullptr);
if (remoteThreadHandle == nullptr)
{
int error = GetLastError();
cout << "Failed to run the thread. \nError code: " << error << "\nDescription: " << errorCodeToMessage(error);
cin.ignore(cin.rdbuf()->in_avail());
cin.get();
return EXIT_FAILURE;
}

cout << "Run the thread...\n";
// Let the process run
ResumeThread(remoteThreadHandle);

Am Schluss warten wir noch auf den Thread.

	cout << "Waiting for thread to finish...\n";
// Wait for it
WaitForSingleObject(remoteThreadHandle, INFINITE);

cout << "Thread finished.\nSuccess\n";

Die Visual Studio 2013 Projektmappe gibt es in meiner Dropbox zum Download.

32/64 Bit

Falls die Zielanwendung eine 32-Bit Anwendung ist, müssen Injektor und DLL auch in 32-Bit kompiliert worden sein. Gleiches gilt bei 64-Bit Anwendungen.

Zum Artikel

Wie auch andere Seiten im Netz läuft diese Seite mit einem CMS. Die Vorteile liegen auf der Hand: Man muss sich nicht mit HTML oder PHP-Code herumschlagen, das Veröffentlichen von Artikeln ist einfach, man kann User anlegen, die verschiedene Rechte haben, etc.

Viele dieser Vorteile treffen bei mir allerdings nicht zu. Denn dieses CMS wird von mir entwickelt. Das bedeutet zwar auch, dass ich es recht einfach erweitern kann und mich nicht in fremden Code einarbeiten muss, Bugs schnell selber fixen kann, aber auch, dass ich mich mit PHP und HTML herumschlage. Zum Glück macht mir das weniger aus.

Leider ist das CMS nicht so komplett wie ich es gerne haben will. Joomla, Drupal und andere haben weitaus mehr zu bieten. Warum entwickle ich also ein eigenes CMS? Einfache Antwort: Es macht mir Spaß. Vor dieser Version hatte ich schonmal ein CMS-Projekt am Laufen, allerdings habe ich dabei recht viel dazugelernt, dass ich, wenn ich es richtig machen wollte, alles komplett neu schreiben müsste. Hier kam mir ein Schulprojekt aus dem Computertechnik-Unterricht gelegen. Die Aufgabe war es eine Homepage über ein vorgegebenes Thema zu erstellen. Da habe ich mir gesagt, ich schreib das jetzt komplett neu und gebe das ab. Ein komplettes CMS abzugeben ist zwar weit übers Ziel hinaus, aber warum nicht? Ist ja alles selbst geschrieben ;)

Das CMS ist übrigens Open Source und steht auf GitHub zum Forken bereit. Jeder, der etwas zu diesem Projekt beitragen will, kann gerne helfen. Eine ToDo-Liste findet sich auch in dem Repo.

https://github.com/patrick246/PCMS

Zum Artikel

Es ist soweit: Die Kommentarfunktion ist fertig. Nun ja, fast. Ein paar Features, wie eine Blacklist, Markup/down oder andere Komfortfeatures wie eine Linkerkennung fehlen noch.

Die Grundfunktionen stehen aber soweit und man kann jetzt unter jedem Artikel seinen Senf dazugeben. Übrigens: Falls jemand einen Bug entdeckt, kann ihn gerne unter https://github.com/patrick246/PCMS/issues melden. Dort ist übrigens auch der Code des CMS einsehbar. Andere Bugmeldungen oder auch feature requests sind immer willkommen.

Zum Artikel

Da DynDNS den Support für kostenlose Domains am Ende des Monats einstellt, steige ich nun auf No-IP.com um. Das bedeutet leider, dass ich diesen Domain-Namen nicht behalten kann. Die neue Adresse lautet:

http://p246.noip.me

Bis zum Ende des Monats oder bis zum nächsten IP-Wechsel meines Providers wird diese Seite noch unter dem alten Namen verfügbar sein.

Die neue Domain ist leider noch nicht allen DNS-Servern bekannt. Googles DNS-Server kennen sie bereits (8.8.8.8 und 8.8.4.4). Mittlerweile müssten alle DNS-Server meine neue Domain kennen.

 - patrick246

Zum Artikel

Herlich Willkommen auf meiner Seite

Zum Artikel

Patrick Hahn

Untere Frankenstr. 4

74382 Neckarwestheim

Deutschland

Tel.: 017647246457

Email: patricksilashahn [_at_] gmx [_dot_] net

Zum Artikel