Wichtige BattlEye-Shellcode-Updates
Die Zeit vergeht, Anti-Cheats ändern sich, und um die Wirksamkeit des Produkts zu erhöhen, erscheinen und verschwinden Funktionen in ihnen. Vor einem Jahr habe ich in meinem
Blog eine detaillierte Beschreibung des BattlEye-Shellcodes erstellt. Dieser Teil des Artikels spiegelt die Änderungen wider, die am Shellcode vorgenommen wurden.
Zeitstempel auf der schwarzen Liste
In einer kürzlich durchgeführten BattlEye-Analyse waren nur zwei Kompilierungszeitstempel in der Liste der Schattenverbote enthalten, und es sieht so aus, als hätten die Entwickler beschlossen, viel mehr hinzuzufügen:
0x5B12C900 (action_x64.dll)
0x5A180C35 (TerSafe.dll, Epic Games)
0xFC9B9325 (?)
0x456CED13 (d3dx9_32.dll)
0x46495AD9 (d3dx9_34.dll)
0x47CDEE2B (d3dx9_32.dll)
0x469FF22E (d3dx9_35.dll)
0x48EC3AD7 (D3DCompiler_40.dll)
0x5A8E6020 (?)
0x55C85371 (d3dx9_32.dll)
0x456CED13 (?)
0x46495AD9 (D3DCompiler_40.dll)
0x47CDEE2B (D3DX9_37.dll)
0x469FF22E (?)
0x48EC3AD7 (?)
0xFC9B9325 (?)
0x5A8E6020 (?)
0x55C85371 (?)
Ich konnte die verbleibenden Zeitstempel nicht identifizieren, und die beiden
0xF ******* sind die Hashes, die von den deterministischen Assemblys von Visual Studio erstellt wurden. Vielen Dank an @mottikraus und T0B1 für die Identifizierung einiger Zeitstempel.
Modulprüfungen
Wie die Hauptanalyse gezeigt hat, besteht das Hauptmerkmal von BattlEye in der Aufzählung der Module. Ab dem Zeitpunkt der letzten Analyse wurde der Liste ein weiteres Modul hinzugefügt:
void battleye::misc::module_unknown1() { if (!GetProcAddress(current_module, "NSPStartup")) return; if (optional_header.data_directory[4].size == 0x1B20 || optional_header.data_directory[4].size == 0xE70 || optional_header.data_directory[4].size == 0x1A38 || timestamp >= 0x5C600000 && timestamp < 0x5C700000) { report_module_unknown report = {}; report.unknown = 0; report.report_id = 0x35; report.val1 = 0x5C0; report.timestamp = timestamp; report.image_size = optional_header.size_of_image; report.entrypoint = optional_header.address_of_entry_point; report.directory_size = optional_header.data_directory[4].size; battleye::report(&report, sizeof(report), false); } }
Dies ist wahrscheinlich die Erkennung bestimmter Proxy-DLLs, da hier die Größe der Umleitungstabelle überprüft wird.
Fenstertitel
In der vorherigen Analyse wurden verschiedene Cheat-Anbieter mit Fensternamen gekennzeichnet, aber seitdem überprüft der Shellcode diese Fensterköpfe nicht mehr. Die Liste der Fenstertitel wurde komplett ersetzt durch:
Chod's
Satan5
Bildnamen
BattlEye ist dafür bekannt, sehr primitive Erkennungsmethoden zu verwenden, und eine davon ist eine schwarze Liste von Bildnamen. Jedes Jahr wird die Liste der verbotenen Bildnamen länger und in den letzten 11 Monaten wurden fünf neue hinzugefügt:
frAQBc8W.dll
C:\\Windows\\mscorlib.ni.dll
DxtoryMM_x64.dll
Project1.dll
OWClient.dll
Es ist zu beachten, dass das Vorhandensein eines Moduls mit einem Namen, der einem der Elemente in der Liste entspricht, nicht bedeutet, dass Sie sofort gesperrt werden. Die Berichts-Engine überträgt auch grundlegende Modulinformationen, die am wahrscheinlichsten zur Unterscheidung von Cheats von Kollisionen auf dem BattlEye-Server verwendet werden.
7-Reißverschluss
7-Zip war weit verbreitet und wird von Teilnehmern der Cheatszene weiterhin als Speicherfüller für Code-Voids (Code-Caves) verwendet. BattlEye versucht, dem entgegenzuwirken, indem es eine
sehr schlechte Integritätsprüfung durchführt, die sich seit meinem vorherigen Artikel geändert hat:
void module::check_7zip() { const auto module_handle = GetModuleHandleA("..\\..\\Plugins\\ZipUtility\\ThirdParty\\7zpp\\dll\\Win64\\7z.dll");
Es scheint, dass die BattlEye-Entwickler vermutet haben, dass mein vorheriger Artikel viele Benutzer dazu veranlasst hat, diese Überprüfung zu umgehen, indem sie einfach die gewünschten Bytes an den von BattlEye überprüften Speicherort kopieren. Wie haben sie die Situation behoben? Wir haben die Überprüfung um acht Bytes verschoben und weiterhin die gleiche schlechte Methode zum Überprüfen der Integrität verwendet. Die schreibgeschützte ausführbare Partition. Sie müssen lediglich 7-Zip von der Festplatte herunterladen und die verschobenen Partitionen miteinander vergleichen. Wenn es Unstimmigkeiten gibt, stimmt etwas nicht. Im Ernst, Leute, es ist nicht so schwer, Integritätsprüfungen durchzuführen.
Netzwerkprüfung
Die Aufzählung der TCP-Tabelle funktioniert immer noch, aber nachdem ich eine frühere Analyse veröffentlicht habe, in der Entwickler kritisiert wurden, dass sie Cloudflare-IP-Adressen kennzeichnen, haben sie diese Prüfung immer noch entfernt. Anti-Cheat meldet immer noch den Port, den xera.ph für die Verbindung verwendet, aber die Entwickler haben eine neue Prüfung hinzugefügt, um festzustellen, ob der Prozess mit der Verbindung einen aktiven Schutz hat (vermutlich wird dies mit dem Handler durchgeführt).
void network::scan_tcp_table { memset(local_port_buffer, 0, sizeof(local_port_buffer); for (iteration_index = 0; iteration_index; < 500 ++iteration_index) {
Danke IChooseYou und Zusammenfassung
BattlEye Stack Bypass
Hacking Games ist ein ständiges Katz- und Mausspiel, daher verbreiten sich Gerüchte über neue Tricks wie ein Feuer. In diesem Teil werden wir uns mit neuen heuristischen Techniken befassen, die kürzlich von einem großen Anbieter von Anti-Cheats, BattlEye, in unser Arsenal aufgenommen wurden. Am häufigsten werden diese Techniken als Stack-Walking bezeichnet. Normalerweise werden sie implementiert, indem eine Funktion verarbeitet und der Stapel durchsucht wird, um herauszufinden, wer diese Funktion speziell aufgerufen hat. Warum musst du das tun? Wie jedes andere Programm verfügen Videospiel-Hacks über eine Reihe bekannter Funktionen, mit denen sie Informationen von der Tastatur abrufen, auf der Konsole ausgeben oder bestimmte mathematische Ausdrücke berechnen können. Darüber hinaus lieben es Videospiel-Hacks, ihre Existenz im Speicher oder auf der Festplatte zu verbergen, damit Anti-Cheat-Software sie nicht findet. Aber was Cheat-Programme vergessen, ist, dass sie regelmäßig Funktionen aus anderen Bibliotheken aufrufen, und dies kann verwendet werden, um unbekannte Cheats heuristisch zu erkennen. Durch die Implementierung der Stack-Traversal-Engine für Funktionen wie
std::print
können wir diese Cheats auch dann finden, wenn sie maskiert sind.
BattlEye
implementierte einen „Stack-Bypass“, obwohl dies nicht öffentlich angekündigt wurde und zum Zeitpunkt der Veröffentlichung des Artikels nur Gerüchte im Umlauf waren. Achten Sie auf die Anführungszeichen - was Sie hier sehen, ist nicht wirklich eine Stapel-Tour, sondern nur eine Kombination aus der Überprüfung der Absenderadresse und dem Speicherauszug des aufrufenden Programms. Eine echte Stack-Traversal-Implementierung würde den Stack durchlaufen und einen echten Call-Stack generieren.
Wie ich in einem früheren Artikel über BattlEye erklärt habe, überträgt das Anti-Cheat-System den Shellcode dynamisch in das Spiel, wenn es ausgeführt wird. Diese Shell-Codes haben unterschiedliche Größen und Aufgaben und werden nicht gleichzeitig übertragen. Eine bemerkenswerte Eigenschaft eines solchen Systems ist, dass die Forscher den Anti-Cheat während des Multiplayer-Spiels dynamisch analysieren müssen, was die Bestimmung der Eigenschaften dieses Anti-Cheats erschwert. Es ermöglicht Anti-Cheat auch, verschiedene Maßnahmen auf verschiedene Benutzer anzuwenden, um beispielsweise ein stärker invasives Modul nur auf eine Person zu übertragen, die ein ungewöhnlich hohes Verhältnis von Morden und Todesfällen und dergleichen aufweist.
Einer dieser Shell-Codes, BattlEye, ist für die Durchführung dieser Stapelanalyse verantwortlich. wir werden es
shellcode8kb nennen, da es im Vergleich zu
shellcodemain , das ich
hier dokumentiert
habe, etwas kleiner
ist . Dieser kleine Shell-Code, der die
AddVectoredExceptionHandler- Funktion verwendet, bereitet einen vektorisierten Ausnahmehandler vor und setzt dann Interrupt-Traps für die folgenden Funktionen:
GetAsyncKeyState
GetCursorPos
IsBadReadPtr
NtUserGetAsyncKeyState
GetForegroundWindow
CallWindowProcW
NtUserPeekMessage
NtSetEvent
sqrtf
__stdio_common_vsprintf_s
CDXGIFactory::TakeLock
TppTimerpExecuteCallback
Dazu
durchläuft es einfach die Liste der standardmäßig verwendeten Funktionen und setzt die erste Anweisung der entsprechenden Funktion auf
int3 , die als Haltepunkt verwendet wird. Nach dem Setzen eines Haltepunkts durchlaufen alle Aufrufe der entsprechenden Funktion den Exception-Handler, der vollen Zugriff auf die Register und den Stack hat. Bei diesem Zugriff gibt der Ausnahmehandler die Adresse des aufrufenden Programms oben im Stapel aus. Wenn eine der heuristischen Bedingungen erfüllt ist, werden 32 Byte der aufrufenden Funktion ausgegeben und mit der Berichtskennung
0x31 an den BattlEye-Server
gesendet :
__int64 battleye::exception_handler(_EXCEPTION_POINTERS *exception) { if (exception->ExceptionRecord->ExceptionCode != STATUS_BREAKPOINT) return 0; const auto caller_function = *(__int64 **)exception->ContextRecord->Rsp; MEMORY_BASIC_INFORMATION caller_memory_information = {}; auto desired_size = 0;
Wie wir sehen können, gibt der Exception-Handler alle aufrufenden Funktionen aus, wenn sich die Speicherseite kurzfristig ändert oder wenn die Funktion nicht zu einem bekannten Prozessmodul gehört (der Speicherseitentyp MEM_IMAGE wurde nicht von manuellen Zuordnern festgelegt). Außerdem werden aufrufende Funktionen
ausgegeben , wenn
NtQueryVirtualMemory nicht aufgerufen werden
kann, sodass Cheats nicht an diesen Systemaufruf
gebunden werden und ihr Modul vor dem Stapelspeicherauszug verbergen. Die letzte Bedingung ist tatsächlich sehr interessant. Sie kennzeichnet alle aufrufenden Funktionen, die das
Gadget jmp qword ptr [rbx] verwenden - die Methode, mit der die "
Rücksprungadresse gefälscht " wird. Es wurde
von meinem Co-Sekretär Mitglied Spitznamen namazso veröffentlicht. Es scheint, dass die Entwickler von BattlEye gesehen haben, dass die Leute diese Methode des Spoofings in ihren Spielen verwenden, und beschlossen, direkt darauf zu zielen. Erwähnenswert ist hier, dass die von namazsos beschriebene Methode gut funktioniert, nur ein anderes Gadget verwendet oder ganz anders oder nur ein anderes Register - es spielt keine Rolle.
BattlEye-Entwicklertipp: Das
CDXGIFactory::TakeLock
in Ihrem Speicher verwenden, ist nicht korrekt, da Sie (versehentlich oder absichtlich) das CC-Padding aktiviert haben, das bei jedem Kompilieren sehr unterschiedlich ist. Um maximale Kompatibilität zu erreichen, müssen Sie die Füllung (das erste Byte in der Signatur) entfernen, damit Sie höchstwahrscheinlich mehr Betrüger fangen können :)
Die vollständige Struktur, die an den BattlEye-Server gesendet wird, sieht folgendermaßen aus:
struct __unaligned battleye_stack_report { __int8 unknown; __int8 report_id; __int8 val0; __int64 caller; __int64 function_dump[4]; __int64 allocation_base; __int64 base_address; __int32 region_size; __int32 type_protect_state; };
Hypervisor-Erkennung in BattlEye
Das Katz- und Mausspiel im Bereich der Hacking-Spiele ist weiterhin eine Quelle der Innovation bei Exploits und der Bekämpfung von Betrügern. Der Einsatz von Virtualisierungstechnologie in Hacking-Spielen begann sich nach dem Aufkommen von so
benutzerfreundlichen Hypervisoren wie
DdiMon Satoshi Tanda und
hvpp Peter Benes aktiv zu entwickeln. Diese beiden Projekte werden aufgrund der niedrigen Einstiegsschwelle und der detaillierten Dokumentation von den meisten bezahlten Cheats der Underground-Hacker-Szene genutzt. Diese Veröffentlichungen dürften das Wettrüsten auf dem Gebiet der Hypervisoren beschleunigen, das sich nun in der Community der Game-Hacker manifestiert.
Folgendes sagt der Administrator einer der größten Gaming-Hacking-Communities mit dem Spitznamen
wlan zu dieser Situation:
Mit dem Aufkommen gebrauchsfertiger Hypervisor-Systeme für Hacking-Spiele wurde es unvermeidlich, dass Anti-Cheats wie BattlEye sich auf die allgemeine Erkennung von Virtualisierung konzentrierten.
Die weitverbreitete Verwendung von Hypervisoren ist auf die jüngsten Verbesserungen bei der Betrugsbekämpfung zurückzuführen, die Hackern nur sehr wenige Möglichkeiten ließen, Spiele auf herkömmliche Weise zu modifizieren. Die Popularität von Hypervisoren lässt sich durch die einfache Vermeidung von Anti-Cheat erklären, da die Virtualisierung das Verbergen von Informationen mithilfe von Mechanismen wie
Syscall Hooks und
MMU-Virtualisierung vereinfacht.
Vor kurzem hat BattlEye die Erkennung gängiger Hypervisoren wie der oben genannten Plattformen (DdiMon, hvpp) mithilfe der zeitbasierten Erkennung implementiert. Diese Erkennung versucht, nicht standardmäßige CPUID-Befehlszeitwerte zu erkennen. CPUID ist eine relativ kostengünstige Anweisung für reale Geräte, die normalerweise nur zweihundert Zyklen benötigt. In einer virtuellen Umgebung kann die Ausführung aufgrund unnötiger Operationen, die durch die Introspection-Engine verursacht werden, zehnmal länger dauern. Die Introspection-Engine unterscheidet sich von realen Geräten, die den Vorgang einfach in der erwarteten Weise ausführen, da sie die an den Gast zurückgegebenen Daten anhand eines beliebigen Kriteriums verfolgt und bedingt ändert.
Unterhaltsame Tatsache: CPUID wird in diesen temporären Erkennungsprozeduren aktiv verwendet, da es sich um eine Anweisung mit einer bedingungslosen Ausgabe sowie um eine Anweisung mit nicht privilegierter Serialisierung handelt. Dies bedeutet, dass die CPUID als
Barriere verwendet wird und sicherstellt, dass die Anweisungen vor und nach ihr befolgt werden. Gleichzeitig werden die Timings unabhängig von der üblichen Neuordnung von Anweisungen. Sie können auch Befehle wie
XSETBV verwenden , die ebenfalls einen bedingungslosen Exit ausführen. Um jedoch ein unabhängiges Timing zu gewährleisten, ist eine Art Barrierebefehl erforderlich, damit keine vorher oder nachher erfolgende Neuordnung die Zuverlässigkeit der Timings beeinträchtigt.
Anerkennung
Das Folgende ist das Erkennungsverfahren aus dem BattlEye-Modul „BEClient2“; Ich habe das Reverse Engineering durchgeführt und den Code in Pseudo-C neu erstellt und dann auf
Twitter gepostet. Am Tag nach meinem Tweet änderten die BattlEye-Entwickler unerwartet die Verschleierung von BEClient2 und hofften anscheinend, dass dies mich daran hindern würde, das Modul zu analysieren. Die vorherige Verschleierung änderte sich nicht länger als ein Jahr, sondern erst am Tag nach meinem Tweet - eine beeindruckende Geschwindigkeit.
void battleye::take_time() {
Wie ich oben sagte, ist dies die gebräuchlichste Erkennungstechnik, bei der bedingungslos abgefangene Anweisungen verwendet werden. Es ist jedoch anfällig für falsche Zeiten, und wir werden im nächsten Abschnitt ausführlich darauf eingehen.
Erkennungs-Bypass
Diese Erkennungsmethode weist Probleme auf. Erstens ist es anfällig, Zeit zu fälschen, was normalerweise auf zwei Arten geschieht: durch Verschieben der TSC in VMCS oder durch Verringern der TSC bei jeder Ausführung der CPUID. Es gibt viele andere Möglichkeiten, um mit zeitbasierten Angriffen umzugehen. Letztere sind jedoch viel einfacher zu implementieren, da Sie sicherstellen können, dass die Ausführungszeit des Befehls innerhalb von ein oder zwei Taktzyklen nach der Ausführungssynchronisierung auf realen Geräten liegt. Die Schwierigkeit, diese Zeitfälschungstechnik zu entdecken, hängt von der Erfahrung des Entwicklers ab. Im nächsten Abschnitt werden wir uns mit Fälschungen und Verbesserungen der in BattlEye erstellten Implementierung befassen. Der zweite Grund für diesen Erkennungsmethodenfehler ist, dass die CPUID-Verzögerung (Laufzeit) in verschiedenen Prozessoren je nach Wert des Blatts sehr unterschiedlich ist. Es kann bis zu 70-300 Taktzyklen dauern, bis der Vorgang abgeschlossen ist. Das dritte Problem bei diesem Erkennungsverfahren ist die Verwendung von SetThreadPriority. Diese Windows-Funktion wird verwendet, um den Prioritätswert eines bestimmten Stream-Deskriptors festzulegen. Das Betriebssystem hört jedoch nicht immer auf die Anforderung. Diese Funktion ist lediglich ein Vorschlag, um die Priorität des Threads zu erhöhen, und es gibt keine Garantie dafür, dass dies der Fall ist. Daher ist es möglich, dass diese Methode von Unterbrechungen oder anderen Prozessen betroffen ist.
In diesem Fall ist es einfach, die Erkennung zu umgehen, und die beschriebene Technik der Zeitfälschung beseitigt diese Erkennungsmethode effektiv. Wenn die Entwickler von BattlEye diese Methode verbessern möchten, enthält der folgende Abschnitt einige Empfehlungen.
Verbesserung
Diese Funktion kann auf viele Arten verbessert werden. Erstens können Sie absichtlich Interrupts deaktivieren und die Priorität eines Threads erzwingen, indem Sie CR8 auf das höchste IRQL ändern. Ideal wäre es auch, diese Prüfung in einem CPU-Kern zu isolieren. Eine weitere Verbesserung: Sie sollten verschiedene Timer verwenden, aber viele davon sind nicht so genau wie TSC, aber es gibt einen solchen Timer namens APERF oder Actual Performance Clock. Ich empfehle diesen Timer, weil es schwieriger ist, damit zu cheaten, und weil er nur dann einen Zähler ansammelt, wenn sich der logische Prozessor im Energiezustand C0 befindet. Dies ist eine großartige Alternative zur Verwendung von TSC. Sie können auch den ACPI-, HPET-, PIT-Timer, GPU-Timer, NTP-Timer oder PPERF-Timer verwenden, der dem APERF-Timer ähnelt, jedoch Maßnahmen zählt, die als Ausführungsanweisungen wahrgenommen werden. Dies hat den Nachteil, dass Sie HWP aktivieren müssen, das vom Zwischenoperator deaktiviert werden kann, und daher unbrauchbar ist.
Nachfolgend finden Sie eine verbesserte Version des Erkennungsverfahrens, das im Kernel ausgeführt werden sollte:
void battleye::take_time() { std::uint32_t cpuid_regs[4] = {}; _disable(); const auto aperf_pre = __readmsr(IA32_APERF_MSR) << 32; __cpuid(&cpuid_regs, 1); const auto aperf_post = __readmsr(IA32_APERF_MSR) << 32; const auto aperf_diff = aperf_post - aperf_pre;
Hinweis: IET steht für Instruction Execution Time.
Das Verfahren kann jedoch bei der Erkennung gängiger Hypervisoren immer noch sehr unzuverlässig sein, da die CPUID-Laufzeiten stark variieren können.
Es wäre besser, den IET der beiden Anweisungen zu vergleichen. Eine davon sollte eine längere Ausführungsverzögerung aufweisen als die CPUID. Zum Beispiel kann es sich um FYL2XP1 handeln - eine arithmetische Anweisung, deren Ausführung etwas länger dauert als die durchschnittliche IET der CPUID-Anweisung. Darüber hinaus verursacht es keine Traps im Hypervisor und seine Zeit kann zuverlässig gemessen werden. Mit diesen beiden Funktionen könnte die Profilerstellungsfunktion ein Array zum Speichern der IET-Anweisungen CPUID und FYL2XP1 erstellen. Unter Verwendung des APERF-Timers wäre es möglich, den Anfangstakt einer arithmetischen Anweisung zu erhalten, die Anweisung auszuführen und das Delta der Uhr dafür zu berechnen. Die Ergebnisse könnten im IET-Array für N Profilierungszyklen gespeichert werden, um den Durchschnittswert zu erhalten und den Prozess für die CPUID zu wiederholen. Wenn die Ausführungszeit des CPUID-Befehls länger als der arithmetische Befehl ist,Dann ist dies ein zuverlässiges Zeichen dafür, dass das System virtuell ist, da eine arithmetische Anweisung unter keinen Umständen mehr Zeit als die Ausführung der CPUID aufwenden kann, um Informationen über den Hersteller oder die Version zu erhalten. Ein solches Erkennungsverfahren kann auch solche erkennen, die TSC-Offset / Skalierung verwenden., , IRQL , . , BattlEye , . BattlEye , .