Reverse Engineering des beliebten Anti-Cheat BattlEye


BattlEye ist vorwiegend ein deutscher Drittanbieter-Anti-Cheat, der hauptsächlich von dem 32-jährigen Bastian Heiko Suter entwickelt wurde . Es bietet Spieleverlagen ein benutzerfreundliches Anti-Cheat-System, das allgemeine Schutzmechanismen sowie die Erkennung von Cheats für bestimmte Spiele verwendet, um die Sicherheit zu optimieren. Wie auf der Produktwebsite angegeben, ist es immer auf dem neuesten Stand der Technik und verwendet innovative Methoden zum Schutz und zur Erkennung. Das liegt natürlich an der Nationalität des Entwicklers: QUALITY MADE IN GERMANY . BattlEye besteht aus vielen Elementen, die zusammenarbeiten, um Betrüger in Spielen zu finden, die für die Verwendung des Produkts bezahlt wurden. Die vier Hauptelemente sind:

  • BEService
    • Ein Windows-Systemdienst, der mit dem BattlEye BEServer -Server kommuniziert und Client-Server-Kommunikation mit BEDaisy und BEClient bereitstellt .
  • Bedaisy
    • Ein Windows-Kerneltreiber, der vorbeugende Ereignisverarbeitungsmechanismen und Minifilter protokolliert, um zu verhindern, dass Betrüger das Spiel illegal ändern
  • BEClient
    • Eine dynamisch verbundene Windows-Bibliothek, die für die meisten Erkennungsvektoren verantwortlich ist, einschließlich der in diesem Artikel beschriebenen. Nach der Initialisierung wird es an den Spielprozess angehängt.
  • Beserver
    • Eigener Backend-Server, der für das Sammeln von Informationen und das Ergreifen spezifischer Maßnahmen gegen Betrüger verantwortlich ist.

Shellcode


Kürzlich ist ein Dump des BattlEye-Shell-Codes im Internet aufgetaucht, und wir haben beschlossen, darüber zu schreiben, wonach genau die aktuelle Version von BattlEye sucht. Wir haben BattlEye sechs Monate lang nicht analysiert, daher ist unser letzter Speicherauszug des Shellcodes höchstwahrscheinlich veraltet. Verschiedene Teile des Codes wurden nur aus dem Speicher dieses letzten Speicherauszugs wiederhergestellt, vorausgesetzt, BattlEye hat nur den Shell-Code vervollständigt und die vorherigen Erkennungsverfahren nicht gelöscht.

Wie?


BattlEye streamt angeblich Shellcode von seinem Server zu einem Windows-Dienst namens BEService. Dieser Dienst kommuniziert mit dem im Spiel befindlichen BEClient-Modul. Der Datenaustausch erfolgt über die \.namedpipeBattleye und war bis 2018 unverschlüsselt. Jetzt werden alle übertragenen Daten mit einem XOR-Verschlüsseler mit sehr kleinen Schlüsseln verschlüsselt, wodurch es extrem einfach ist, bekannte Klartextangriffe durchzuführen. Wenn der Shell-Code an den Client übertragen wird, befindet er sich außerhalb aller bekannten Module und wird ausgeführt, was die Ermittlung vereinfacht. Um einen Shellcode-Dump zu erstellen, können Sie entweder die Windows-Standard-API-Funktionen wie CreateFile, ReadFile usw. verarbeiten und den entsprechenden Speicherbereich aller aufrufenden Module sichern (Speicherinformationen für die zurückgegebene Adresse anfordern), die sich außerhalb aller bekannten Module befinden, oder Durchsuchen Sie regelmäßig den virtuellen Speicherbereich des Spiels auf der Suche nach ausführbarem Speicher außerhalb aller bekannten Module und speichern Sie ihn auf der Festplatte. Gleichzeitig müssen Sie nachverfolgen, welche Bereiche bereits entleert wurden, damit Sie nicht viele identische Entleerungen erhalten.

Erklärung


Die im Artikel vorgestellten Fragmente des Pseudocodes sind der Schönheit halber stark modifiziert. Sie können den BattlEye-Shell-Code nicht ausgeben und diese Teile nicht sofort erkennen. Der Shellcode enthält keine Funktionsaufrufe, und viele Algorithmen im Artikel werden bereitgestellt. Tatsächlich ist dies jedoch nicht wichtig, denn wenn Sie mit dem Lesen dieser schrecklichen Antike fertig sind, haben Sie die Möglichkeit, sie zu umgehen (:

Speichersortierung


Der häufigste Erkennungsmechanismus bei Anti-Cheats ist die Speicheraufzählung und das Scannen des Speichers, um nach bekannten Cheatbildern zu suchen. Es ist einfach zu implementieren und, wie die Vergangenheit gezeigt hat, mit dem richtigen Ansatz sehr effektiv, wenn Sie die Grundlagen von Assembler nicht vergessen und den Prolog einer gemeinsamen Funktion auf die schwarze Liste gesetzt haben.

Battleye durchläuft den gesamten Adressraum des Spielprozesses (der aktuelle Prozess in diesem Kontext) und führt verschiedene Überprüfungen der Leistung der Seite durch und findet den Shell-Code außerhalb des entsprechenden Speicherbereichs.

So wird es in Battleye implementiert:

 // MEMORY ENUMERATION for (current_address = 0 // QUERY MEMORY_BASIC_INFORMATION NtQueryVirtualMemory(GetCurrentProcess(), current_address, 0, &memory_information, 0x30, &return_length) >= 0 current_address = memory_information.base_address + memory_information.region_size) { const auto outside_of_shellcode = memory_information.base_address > shellcode_entry || memory_information.base_address + memory_information.region_size <= shellcode_entry const auto executable_memory = memory_information.state == MEM_COMMIT && (memory_information.protect == PAGE_EXECUTE || memory_information.protect == PAGE_EXECUTE_READ || memory_information.protect == PAGE_EXECUTE_READWRITE const auto unknown_whitelist = memory_information.protect != PAGE_EXECUTE_READWRITE || memory_information.region_size != 100000000 if (!executable_memory || !outside_of_shellcode || !unknown_whitelist) continue // RUN CHECKS memory::anomaly_check(memory_information memory::pattern_check(current_address, memory_information memory::module_specific_check_microsoft(memory_information memory::guard_check(current_address, memory_information memory::module_specific_check_unknown(memory_information } 

Speicheranomalien


BattlEye markiert alle Anomalien im Speicheradressraum, hauptsächlich den Speicher der ausführbaren Module, die nicht dem geladenen Image entsprechen:

 void memory::anomaly_check(MEMORY_BASIC_INFORMATION memory_information) { // REPORT ANY EXECUTABLE PAGE OUTSIDE OF KNOWN MODULES if (memory_information.type == MEM_PRIVATE || memory_information.type == MEM_MAPPED) { if ((memory_information.base_address & 0xFF0000000000) != 0x7F0000000000 && // UPPER EQUALS 0x7F (memory_information.base_address & 0xFFF000000000) != 0x7F000000000 && // UPPER EQUALS 0x7F0 (memory_information.base_address & 0xFFFFF0000000) != 0x70000000 && // UPPER EQUALS 0x70000 memory_information.base_address != 0x3E0000)) { memory_report.unknown = 0 memory_report.report_id = 0x2F memory_report.base_address = memory_information.base_address memory_report.region_size = memory_information.region_size memory_report.memory_info = memory_information.type | memory_information.protect | memory_information.state battleye::report(&memory_report, sizeof(memory_report), 0 } } } 

Nach Mustern suchen


Wie oben erwähnt, durchsucht BattlEye auch den Speicher lokaler Prozesse auf das Vorhandensein verschiedener klar definierter Muster, wie aus der unten gezeigten Implementierung ersichtlich ist.

Wenn Sie diesen Pseudocode lesen, können Sie davon ausgehen, dass diese Überprüfungen umgangen werden können, indem der Codebereich jedes geladenen Moduls neu geschrieben wird, da bei der Suche nach Mustern in bekannten Bildern keine Scans durchgeführt werden. Um nicht an Integritätsprüfungen teilzunehmen, müssen Sie alle gepackten und auf die Whitelist gestellten Bereiche herunterladen und die als RWX gekennzeichneten Codebereiche neu schreiben , da wir keine Integritätsprüfungen durchführen können, ohne den Packer zu emulieren. In der aktuellen Version des BattlEye-Shellcodes sind diese Speichermuster fest codiert:

 [05 18] ojectsPUBGChinese [05 17] BattleGroundsPrivate_CheatESP [05 17] [%.0fm] %s [05 3E] 0000Neck0000Chest0000000Mouse 10 [05 3F] PlayerESPColor [05 40] Aimbot: %d02D3E2041 [05 36] HackMachine [05 4A] VisualHacks.net [05 50] 3E232F653E31314E4E563D4276282A3A2E463F757523286752552E6F30584748 [05 4F] DLLInjection-master\x64\Release\ [05 52] NameESP [05 48] Skullhack [05 55] .rdata$zzzdbg [05 39] AimBot [05 39] EB4941803C123F755C623FEB388D41D0FBEC93C977583E930EB683E1DF [05 5F] 55E9 [05 5F] 57E9 [05 5F] 60E9 [05 68] D3D11Present initialised [05 6E] [ %.0fM ] [05 74] [hp:%d]%dm [05 36] 48836424380488D4C2458488B5424504C8BC848894C24304C8BC7488D4C2460 [05 36] 741FBA80000FF15607E0085C07510F2F1087801008B8788100EB [05 36] 40F2AA156F8D2894E9AB4489535D34F9CPOSITION0000COL [05 7A] FFE090 [05 79] %s00%d00POSITION0000COLOR0000000 [05 36] 8E85765DCDDA452E75BA12B4C7B94872116DB948A1DAA6B948A7676BB948902C [05 8A] n<assembly xmlsn='urn:schemas-mi 

Diese Speichermuster enthalten auch einen Zwei-Byte-Header, nämlich den unbekannten statischen Wert 05 und eine eindeutige Kennung.

Was wir nicht sehen werden, ist, dass BattlEye auch Muster dynamisch von BEServer streamt und sie an BEClient sendet, aber wir werden dies im Artikel nicht diskutieren.

Sie werden iterativ mit dem folgenden Algorithmus gescannt:

 void memory::pattern_check(void* current_address, MEMORY_BASIC_INFORMATION memory_information) { const auto is_user32 = memory_information.allocation_base == GetModuleHandleA("user32.dll" // ONLY SCAN PRIVATE MEMORY AND USER32 CODE SECTION if (memory_information.type != MEM_PRIVATE && !is_user32) continue for (address = current_address address != memory_information.base_address + memory_information.region_size address += PAGE_SIZE) // PAGE_SIZE { // READ ENTIRE PAGE FROM LOCAL PROCESS INTO BUFFER if (NtReadVirtualMemory(GetCurrentProcess(), address, buffer, PAGE_SIZE, 0) < 0) continue for (pattern_index = 0 pattern_index < 0x1C/*PATTERN COUNT*/ ++pattern_index) { if (pattern[pattern_index].header == 0x57A && !is_user32) // ONLY DO FFE090 SEARCHES WHEN IN USER32 continue for (offset = 0 pattern[pattern_index].length + offset <= PAGE_SIZE ++offset) { const auto pattern_matches = memory::pattern_match(&address[offset], pattern[pattern_index // BASIC PATTERN MATCH if (pattern_matches) { // PATTERN FOUND IN MEMORY pattern_report.unknown = 0 pattern_report.report_id = 0x35 pattern_report.type = pattern[index].header pattern_report.data = &address[offset pattern_report.base_address = memory_information.base_address pattern_report.region_size = memory_information.region_size pattern_report.memory_info = memory_information.type | memory_information.protect | memory_information.state battleye::report(&pattern_report, sizeof(pattern_report), 0 } } } } } 

Validierung spezifischer Module (Microsoft)


Modulprüfungen melden das Vorhandensein bestimmter Module, die in das Spiel geladen wurden:

 void memory::module_specific_check_microsoft(MEMORY_BASIC_INFORMATION memory_information) { auto executable = memory_information.protect == PAGE_EXECUTE || memory_information.protect == PAGE_EXECUTE_READ || memory_information.protect == PAGE_EXECUTE_READWRITE auto allocated = memory_information.state == MEM_COMMIT if (!allocated || !executable) continue auto mmres_handle = GetModuleHandleA("mmres.dll" auto mshtml_handle = GetModuleHandleA("mshtml.dll" if (mmres_handle && mmres_handle == memory_information.allocation_base) { battleye_module_anomaly_report module_anomaly_report module_anomaly_report.unknown = 0 module_anomaly_report.report_id = 0x5B module_anomaly_report.identifier = 0x3480 module_anomaly_report.region_size = memory_information.region_size battleye::report(&module_anomaly_report, sizeof(module_anomaly_report), 0 } else if (mshtml_handle && mshtml_handle == memory_information.allocation_base) { battleye_module_anomaly_report module_anomaly_report module_anomaly_report.unknown = 0 module_anomaly_report.report_id = 0x5B module_anomaly_report.identifier = 0xB480 module_anomaly_report.region_size = memory_information.region_size battleye::report(&module_anomaly_report, sizeof(module_anomaly_report), 0 } } 

Überprüfung bestimmter Module (unbekannt)


Dem System wurde eine Überprüfung bestimmter Module hinzugefügt, die dem Server signalisiert, dass Sie Module geladen haben, die eines der folgenden Kriterien erfüllen:

 void memory::module_specific_check_unknown(MEMORY_BASIC_INFORMATION memory_information) { const auto dos_header = (DOS_HEADER*)module_handle const auto pe_header = (PE_HEADER*)(module_handle + dos_header->e_lfanew const auto is_image = memory_information.state == MEM_COMMIT && memory_information.type == MEM_IMAGE if (!is_image) return const auto is_base = memory_information.base_address == memory_information.allocation_base if (!is_base) return const auto match_1 = time_date_stamp == 0x5B12C900 && *(__int8*)(memory_information.base_address + 0x1000) == 0x00 && *(__int32*)(memory_information.base_address + 0x501000) != 0x353E900 const auto match_2 = time_date_stamp == 0x5A180C35 && *(__int8*)(memory_information.base_address + 0x1000) != 0x00 const auto match_2 = time_date_stamp == 0xFC9B9325 && *(__int8*)(memory_information.base_address + 0x6D3000) != 0x00 if (!match_1 && !match_2 && !match_3) return const auto buffer_offset = 0x00 // OFFSET DEPENDS ON WHICH MODULE MATCHES, RESPECTIVELY 0x501000, 0x1000 AND 0x6D3000 unknown_module_report.unknown1 = 0 unknown_module_report.report_id = 0x46 unknown_module_report.unknown2 = 1 unknown_module_report.data = *(__int128*)(memory_information.base_address + buffer_offset battleye::report(&unknown_module_report, sizeof(unknown_module_report), 0 } 

Wir wissen nicht, welche Module diese Kriterien erfüllen, aber wir vermuten, dass dies ein Versuch ist, eine sehr begrenzte Anzahl spezifischer Cheat-Module zu erkennen.

Nachtrag: @ how02 hat uns mitgeteilt, dass das action_x64.dll Modul einen 0x5B12C900 und einen Codebereich enthält, in den Sie schreiben können. Wie bereits erwähnt, kann dies zum Ausnutzen verwendet werden.

Speicherschutz


BattlEye implementiert auch eine sehr zweifelhafte Erkennungsprozedur, die unserer Meinung nach mit gesetztem PAGE_GUARD- Flag nach Speicher sucht , ohne tatsächlich zu überprüfen, ob das PAGE_GUARD- Flag gesetzt ist :

 void memory::guard_check(void* current_address, MEMORY_BASIC_INFORMATION memory_information) { if (memory_information.protect != PAGE_NOACCESS) { auto bad_ptr = IsBadReadPtr(current_address, sizeof(temporary_buffer auto read = NtReadVirtualMemory( GetCurrentProcess(), current_address, temporary_buffer, sizeof(temporary_buffer), 0 if (read < 0 || bad_ptr) { auto query = NtQueryVirtualMemory( GetCurrentProcess(), current_address, 0, &new_memory_information, sizeof(new_memory_information), &return_length memory_guard_report.guard = query < 0 || new_memory_information.state != memory_information.state || new_memory_information.protect != memory_information.protect if (memory_guard_report.guard) { memory_guard_report.unknown = 0 memory_guard_report.report_id = 0x21 memory_guard_report.base_address = memory_information.base_address memory_guard_report.region_size = (int)memory_information.region_size memory_guard_report.memory_info = memory_information.type | memory_information.protect | memory_information.state battleye::report(&memory_guard_report, sizeof(memory_guard_report), 0 } } } } 

Fenstersortierung


Der Shell-Code BattlEye durchläuft jedes der momentan im Spiel sichtbaren Fenster und umgeht die Fenster von oben nach unten (nach dem Z-Wert). Fenstergriffe im Spiel sind von dieser Aufzählung ausgeschlossen. Dies wird durch Aufrufen von GetWindowThreadProcessId . Daher können Sie die entsprechende Funktion an den falschen Eigentümer des Fensters binden, damit BattlEye Ihr Fenster nicht überprüft .

 void window_handler::enumerate() { for (auto window_handle = GetTopWindow window_handle window_handle = GetWindow(window_handle, GW_HWNDNEXT), // GET WINDOW BELOW ++window_handler::windows_enumerated) // INCREMENT GLOBAL COUNT FOR LATER USAGE { auto window_process_pid = 0 GetWindowThreadProcessId(window_handle, &window_process_pid if (window_process_pid == GetCurrentProcessId()) continue // APPEND INFORMATION TO THE MISC. REPORT, THIS IS EXPLAINED LATER IN THE ARTICLE window_handler::handle_summary(window_handle constexpr auto max_character_count = 0x80 const auto length = GetWindowTextA(window_handle, window_title_report.window_title, max_character_count // DOES WINDOW TITLE MATCH ANY OF THE BLACKLISTED TITLES? if (!contains(window_title_report.window_title, "CheatAut") && !contains(window_title_report.window_title, "pubg_kh") && !contains(window_title_report.window_title, "conl -") && !contains(window_title_report.window_title, "PerfectA") && !contains(window_title_report.window_title, "AIMWA") && !contains(window_title_report.window_title, "PUBG AIM") && !contains(window_title_report.window_title, "HyperChe")) continue // REPORT WINDOW window_title_report.unknown_1 = 0 window_title_report.report_id = 0x33 battleye::report(&window_title_report, sizeof(window_title_report) + length, 0 } } 

Anomalie suchen


Wenn weniger als zwei Fenster aktiviert sind, wird eine Benachrichtigung an den Server gesendet. Dies geschieht wahrscheinlich, um das Patchen der entsprechenden Funktionen zu verhindern, bei denen der BattlEye-Shell-Code keine Fenster untersuchen kann:

 void window_handler::check_count() { if (window_handler::windows_enumerated > 1) return // WINDOW ENUMERATION FAILED, MOST LIKELY DUE TO HOOK window_anomaly_report.unknown_1 = 0 window_anomaly_report.report_id = 0x44 window_anomaly_report.enumerated_windows = windows_enumerated battleye::report(&window_anomaly_report, sizeof(window_anomaly_report), 0 } 

Prozesssortierung


Durch Aufrufen von CreateToolhelp32Snapshot werden alle laufenden Prozesse durchlaufen, es werden jedoch keine Fehler verarbeitet , wodurch das CreateToolhelp32Snapshot und Vermeiden der folgenden Erkennungsverfahren sehr einfach wird:

Pfadprüfung


Befindet sich das Image in mindestens zwei Unterverzeichnissen (beginnend mit dem Stammverzeichnis der Festplatte), markiert das System die Prozesse, wenn der Pfad zum entsprechenden Image mindestens eine der folgenden Zeilen enthält:

 Desktop Temp FileRec Documents Downloads Roaming tmp.ex notepad. ...\. cmd.ex 

Wenn der Pfad zur ausführbaren Datei einer dieser Zeilen entspricht, erhält der Server eine Benachrichtigung über den Pfad zur ausführbaren Datei sowie Informationen darüber, ob der übergeordnete Prozess einer der folgenden ist (enthält das entsprechende an den Server gesendete Flag-Bit):

 steam.exe [0x01] explorer.exe [0x02] lsass.exe [0x08] cmd.exe [0x10] 

Wenn der Client den Deskriptor nicht mit den entsprechenden QueryLimitedInformation Rechten QueryLimitedInformation , setzt er das Flag-Bit 0x04 , wenn die Fehlerursache beim Fehlschlagen des OpenProcess Aufrufs nicht ERROR_ACCESS_DENIED , wodurch wir den letzten Aufzählungscontainer für den entsprechenden Flag-Wert erhalten:

 enum BATTLEYE_PROCESS_FLAG { STEAM = 0x1, EXPLORER = 0x2, ERROR = 0x4, LSASS = 0x8, CMD = 0x10 } 

Wenn der übergeordnete Prozess Steam ist, wird das Flag sofort für den Benutzer gesetzt und der Server mit der Benachrichtigungs-ID 0x40

Bildname


Wenn der Prozess eines der vielen unten aufgeführten Kriterien erfüllt, wird sofort ein Flag gesetzt und dies wird mit der Benachrichtigungs-ID 0x38 an den Server 0x38

    "Loadlibr"    "Rng "    "A0E7FFFFFF81"    "RNG "    "90E54355"    "2.6.ex"    "TempFile.exe" 

Steam Game Overlay


BattlEye überwacht den Steam-Overlay-Prozess, der für das In-Game-Overlay verantwortlich ist, das den meisten Steam-Nutzern bekannt ist. Der vollständige Hostname des Steam Games-Overlays lautet gameoverlayui.exe . Es ist bekannt, dass es häufig zum Rendern von Exploits verwendet wird, da es recht einfach ist, illegale Renderings im Spielfenster zu hacken und durchzuführen. Die Überprüfung hat folgende Bedingung:

 file size != 0 && image name contains (case insensitive) gameoverlayu 

Weitere Überprüfungen, die für die Überlagerung von Steam-Spielen spezifisch sind, sind fast identisch mit den für den Spielprozess selbst durchgeführten Prozeduren, weshalb sie im Pseudocode weggelassen werden.

Steam-Overlay-Speicher-Scan


Das Überlagern von Steam-Spielen wird auf Muster und Anomalien überprüft. Wir konnten nicht tiefer in das Kaninchenloch eindringen und herausfinden, wozu diese Muster dienen, da sie sehr verallgemeinert sind und höchstwahrscheinlich mit Cheat-Modulen zusammenhängen.

 void gameoverlay::pattern_scan(MEMORY_BASIC_INFORMATION memory_information) { // PATTERNS: // Home // F1 // FFFF83C48C30000000000 // \.pipe%s // C760000C64730 // 60C01810033D2 // ... // PATTERN SCAN, ALMOST IDENTICAL CODE TO THE AFOREMENTIONED PATTERN SCANNING ROUTINE gameoverlay_memory_report.unknown_1 = 0 gameoverlay_memory_report.report_id = 0x35 gameoverlay_memory_report.identifier = 0x56C gameoverlay_memory_report.data = &buffer[offset gameoverlay_memory_report.base_address = memory_information.base_address gameoverlay_memory_report.region_size = (int)memory_information.region_size gameoverlay_memory_report.memory_info = memory_information.type | memory_information.protect | memory_information.state battleye::report(&gameoverlay_memory_report, sizeof(gameoverlay_memory_report), 0 } 

Der Scanvorgang sucht auch nach Anomalien in Form von ausführbarem Code außerhalb der heruntergeladenen Bilder, was darauf hindeutet, dass Cracker den Code in den Overlay-Prozess eingefügt haben:

 void gameoverlay::memory_anomaly_scan(MEMORY_BASIC_INFORMATION memory_information) { // ... // ALMOST IDENTICAL ANOMALY SCAN COMPARED TO MEMORY ENUMERATION ROUTINE OF GAME PROCESS gameoverlay_report.unknown = 0 gameoverlay_report.report_id = 0x3B gameoverlay_report.base_address = memory_information.base_address gameoverlay_report.region_size = memory_information.region_size gameoverlay_report.memory_info = memory_information.type | memory_information.protect | memory_information.state battleye::report(&gameoverlay_report, sizeof(gameoverlay_report), 0 } 

Steam Game Overlay-Schutz


Wenn der Prozess des Überlagerns von Steam-Spielen durch einen Schutz von Windows-Prozessen wie Light (WinTcb) geschützt ist , erhält der Server eine Benachrichtigung darüber.

 void gameoverlay::protection_check(HANDLE process_handle) { auto process_protection = 0 NtQueryInformationProcess( process_handle, ProcessProtectionInformation, &process_protection, sizeof(process_protection), nullptr if (process_protection == 0) // NO PROTECTION return gameoverlay_protected_report.unknown = 0 gameoverlay_protected_report.report_id = 0x35 gameoverlay_protected_report.identifier = 0x5B1 gameoverlay_protected_report.data = process_protection battleye::report(&gameoverlay_protected_report, sizeof(gameoverlay_protected_report), 0 } 

Wenn der entsprechende OpenProcess- Aufruf ERROR_ACCESS_DENIED an den Overlay-Prozess zurückgibt, wird außerdem eine Benachrichtigung über den Benutzer mit der ID 3B gesendet.

Module sortieren


Es werden auch Steam-Game-Overlay-Prozessmodule durchsucht, insbesondere vgui2_s.dll und vgui2_s.dll . Für diese Module werden mehrere Prüfungen durchgeführt, beginnend mit gameoverlayui.dll .

Wenn diese Bedingung erfüllt ist: [gameoverlayui.dll+6C779] == 08BE55DC3CCCCB8????????C3CCCCCC , durchsucht der [gameoverlayui.dll+6C779] == 08BE55DC3CCCCB8????????C3CCCCCC die vtable an der in Bytes gespeicherten Adresse ???????? . Wenn sich eines dieser vtable-Elemente außerhalb des Quellmoduls gameoverlayui.dll befindet oder auf eine Anweisung int 3 verweist, wird der Benutzer mit der Benachrichtigungs-ID 3B an den Server gemeldet.

 void gameoverlay::scan_vtable(HANDLE process_handle, char* buffer, MODULEENTRY32 module_entry) { char function_buffer[16 for (vtable_index = 0 vtable_index < 20 vtable_index += 4) { NtReadVirtualMemory( process_handle, *(int*)&buffer[vtable_index], &function_buffer, sizeof(function_buffer), 0 if (*(int*)&buffer[vtable_index] < module_entry.modBaseAddr || *(int*)&buffer[vtable_index] >= module_entry.modBaseAddr + module_entry.modBaseSize || function_buffer[0] == 0xCC ) // FUNCTION PADDING { gameoverlay_vtable_report.report_id = 0x3B gameoverlay_vtable_report.vtable_index = vtable_index gameoverlay_vtable_report.address = buffer[vtable_index battleye::report(&gameoverlay_vtable_report, sizeof(gameoverlay_vtable_report), 0 } } } 

Ein spezifisches Überprüfungsverfahren wird auch für das Modul vgui2_s.dll :

 void vgui::scan() { if (!equals(vgui_buffer, "6A08B31FF561C8BD??????????FF96????????8BD????????8B1FF90")) { auto could_read = NtReadVirtualMemory( process_handle, module_entry.modBaseAddr + 0x48338, vgui_buffer, 8, 0) >= 0 constexpr auto pattern_offset = 0x48378 // IF READ DID NOT FAIL AND PATTERN IS FOUND if (could_read && equals(vgui_buffer, "6A46A06A26A")) { vgui_report.unknown_1 = 0 vgui_report.report_id = 0x3B vgui_report.unknown_2 = 0 vgui_report.address = LODWORD(module_entry.modBaseAddr) + pattern_offset // READ TARGET BUFFER INTO REPORT NtReadVirtualMemory( process_handle, module_entry.modBaseAddr + pattern_offset, vgui_report.buffer, sizeof(vgui_report.buffer), 0 battleye::report(&vgui_report, sizeof(vgui_report), 0 } } else if ( // READ ADDRESS FROM CODE NtReadVirtualMemory(process_handle, *(int*)&vgui_buffer[9], vgui_buffer, 4, 0) >= 0 && // READ POINTER TO CLASS NtReadVirtualMemory(process_handle, *(int*)vgui_buffer, vgui_buffer, 4, 0) >= 0 && // READ POINTER TO VIRTUAL TABLE NtReadVirtualMemory(process_handle, *(int*)vgui_buffer, vgui_buffer, sizeof(vgui_buffer), 0) >= 0) { for (vtable_index = 0 vtable_index < 984 vtable_index += 4 ) // 984/4 VTABLE ENTRY COUNT { NtReadVirtualMemory(process_handle, *(int*)&vgui_buffer[vtable_index], &vtable_entry, sizeof(vtable_entry), 0 if (*(int*)&vgui_buffer[vtable_index] < module_entry.modBaseAddr || *(int*)&vgui_buffer[vtable_index] >= module_entry.modBaseAddr + module_entry.modBaseSize || vtable_entry == 0xCC ) { vgui_vtable_report.unknown = 0 vgui_vtable_report.report_id = 0x3B vgui_vtable_report.vtable_index = vtable_index vgui_vtable_report.address = *(int*)&vgui_buffer[vtable_index battleye::report(&vgui_vtable_report, sizeof(vgui_vtable_report), 0 } } } 

48378 , :

 push 04 push offset aCBuildslaveSte_4 ; "c:\buildslave\steam_rel_client_win32"... push offset aAssertionFaile_7 ; "Assertion Failed: IsValidIndex(elem)" 

:

 push 04 push 00 push 02 push ?? 

vgui2_s.dll, , , vtable .

Steam


Steam :

 void gameoverlay::check_thread(THREADENTRY32 thread_entry) { const auto tread_handle = OpenThread(THREAD_SUSPEND_RESUME|THREAD_GET_CONTEXT, 0, thread_entry.th32ThreadID if (thread_handle) { suspend_count = ResumeThread(thread_handle if (suspend_count > 0) { SuspendThread(thread_handle gameoverlay_thread_report.unknown = 0 gameoverlay_thread_report.report_id = 0x3B gameoverlay_thread_report.suspend_count = suspend_count battleye::report(&gameoverlay_thread_report, sizeof(gameoverlay_thread_report), 0 } if (GetThreadContext(thread_handle, &context) && context.Dr7) { gameoverlay_debug_report.unknown = 0 gameoverlay_debug_report.report_id = 0x3B gameoverlay_debug_report.debug_register = context.Dr0 battleye::report(&gameoverlay_debug_report, sizeof(gameoverlay_debug_report), 0 } } } 

LSASS


Windows lsass.exe , Local Security Authority, , :

 if (equals(process_entry.executable_path, "lsass.exe")) { auto lsass_handle = OpenProcess(QueryInformation, 0, (unsigned int)process_entry.th32ProcessID if (lsass_handle) { for (address = 0 NtQueryVirtualMemory(lsass_handle, address, 0, &lsass_memory_info, 0x30, &bytes_needed) >= 0 address = lsass_memory_info.base_address + lsass_memory_info.region_size) { if (lsass_memory_info.state == MEM_COMMIT && lsass_memory_info.type == MEM_PRIVATE && (lsass_memory_info.protect == PAGE_EXECUTE || lsass_memory_info.protect == PAGE_EXECUTE_READ || lsass_memory_info.protect == PAGE_EXECUTE_READWRITE)) { // FOUND EXECUTABLE MEMORY OUTSIDE OF MODULES lsass_report.unknown = 0 lsass_report.report_id = 0x42 lsass_report.base_address = lsass_memory_info.base_address lsass_report.region_size = lsass_memory_info.region_size lsass_report.memory_info = lsass_memory_info.type | lsass_memory_info.protect | lsass_memory_info.state battleye::report(&lsass_report, sizeof(lsass_report), 0 } } CloseHandle(lsass_handle } } 

LSASS , , -, LSASS. BattlEye , /, ReadProcessMemory / WriteProcessMemory , BEDaisy. BEDaisy , . , , , .


BattlEye id 3C . :

  • WS_EX_TOPMOST :
    • (Unicode)
    • (Unicode)
    • Window style
    • Window extended style
    • -
    • -
  • (VM_WRITE|VM_READ)
  • :
    • ….ContentPaksTslGame-WindowsNoEditor_assets_world.pak
    • ….ContentPaksTslGame-WindowsNoEditor_ui.pak
    • ….ContentPaksTslGame-WindowsNoEditor_sound.pak

  • :
    • ….BLGameCookedContentScriptBLGame.u
  • NtGetContextThread
    • (E9),

NoEye


BattlEye NoEye: GetFileAttributesExA BE_DLL.dll , .

 void noeye::detect() { WIN32_FILE_ATTRIBUTE_DATA file_information if (GetFileAttributesExA("BE_DLL.dll", 0, &file_information)) { noeye_report.unknown = 0 noeye_report.report_id = 0x3D noeye_report.file_size = file_information.nFileSizeLow battleye::report(&noeye_report, sizeof(noeye_report), 0 } } 


Beep Null; . , , - . driver device hijacking. , IOCTL .

 void driver::check_beep() { auto handle = CreateFileA("\\.\Beep", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0 if (handle != INVALID_HANDLE_VALUE) { beep_report.unknown = 0 beep_report.report_id = 0x3E battleye::report(&beep_report, sizeof(beep_report), 0 CloseHandle(handle } } 

 void driver::check_null() { auto handle = CreateFileA("\\.\Null", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0 if (handle != INVALID_HANDLE_VALUE) { null_report.unknown = 0 null_report.report_id = 0x3E battleye::report(&null_report, sizeof(null_report), 0 CloseHandle(handle } } 

Sleep delta


Darüber hinaus kann BattlEye eine Sekunde Inaktivität vom aktuellen Thread anfordern und den Unterschied in der Anzahl der Zyklen vor und nach Inaktivität (Ruhezustand) messen:

 void sleep::check_delta() { const auto tick_count = GetTickCount Sleep(1000 const auto tick_delta = GetTickCount() - tick_count if (tick_delta >= 1200) { sleep_report.unknown = 0 sleep_report.report_id = 0x45 sleep_report.delta = tick_delta battleye::report(&sleep_report, sizeof(sleep_report), 0 } } 

7zip


BattlEye wurde um eine sehr langsame Integritätsprüfung erweitert, damit Benutzer die 7zip-Bibliothek nicht in Spielprozesse laden und Bereiche überschreiben können. Dies wurde von Benutzern durchgeführt, um den Schweregrad der zuvor beschriebenen Musterabtastungen und der Erkennung von Anomalien zu verringern. BattlEye hat einfach beschlossen, Integritätsprüfungen für diese bestimmte 7zip-Bibliothek hinzuzufügen.

 void module::check_7zip() { constexpr auto sz_7zipdll = "..\..\Plugins\ZipUtility\ThirdParty\7zpp\dll\Win64\7z.dll" const auto module_handle = GetModuleHandleA(sz_7zipdll if (module_handle && *(int*)(module_handle + 0x1000) != 0xFF1441C7) { sevenzip_report.unknown_1 = 0 sevenzip_report.report_id = 0x46 sevenzip_report.unknown_2 = 0 sevenzip_report.data1 = *(__int64*)(module_handle + 0x1000 sevenzip_report.data2 = *(__int64*)(module_handle + 0x1008 battleye::report(&sevenzip_report, sizeof(sevenzip_report), 0 } } 

Hardware-Abstraktionsschicht


BattlEye prüft, ob eine dynamisch verknüpfte Windows-Hardwareabstraktionsschichtbibliothek (hal.dll) vorhanden ist, und teilt dem Server mit, ob sie im Spiel geladen ist.

 void module::check_hal() { const auto module_handle = GetModuleHandleA("hal.dll" if (module_handle) { hal_report.unknown_1 = 0 hal_report.report_id = 0x46 hal_report.unknown_2 = 2 hal_report.data1 = *(__int64*)(module_handle + 0x1000 hal_report.data2 = *(__int64*)(module_handle + 0x1008 battleye::report(&hal_report, sizeof(hal_report), 0 } } 

Bildprüfungen


BattlEye überprüft auch verschiedene im Spiel geladene Bilder. Bei diesen Modulen handelt es sich angeblich um signierte Bilder, die auf irgendeine Weise manipuliert und in böswilliges Verhalten umgewandelt wurden. Wir können jedoch nichts weiter über sie sagen, nur über ihre Erkennung:

nvToolsExt64_1


 void module::check_nvtoolsext64_1 { const auto module_handle = GetModuleHandleA("nvToolsExt64_1.dll" if (module_handle) { nvtools_report.unknown = 0 nvtools_report.report_id = 0x48 nvtools_report.module_id = 0x5A8 nvtools_report.size_of_image = (PE_HEADER*)(module_handle + (DOS_HEADER*)(module_handle)->e_lfanew))->SizeOfImage battleye::report(&nvtools_report, sizeof(nvtools_report), 0 } } 

ws2detour_x96


 void module::check_ws2detour_x96 { const auto module_handle = GetModuleHandleA("ws2detour_x96.dll" if (module_handle) { ws2detour_report.unknown = 0 ws2detour_report.report_id = 0x48 ws2detour_report.module_id = 0x5B5 ws2detour_report.size_of_image = (PE_HEADER*)(module_handle + (DOS_HEADER*)(module_handle)->e_lfanew))->SizeOfImage battleye::report(&ws2detour_report, sizeof(ws2detour_report), 0 } } 

networkdllx64


 void module::check_networkdllx64 { const auto module_handle = GetModuleHandleA("networkdllx64.dll" if (module_handle) { const auto dos_header = (DOS_HEADER*)module_handle const auto pe_header = (PE_HEADER*)(module_handle + dos_header->e_lfanew const auto size_of_image = pe_header->SizeOfImage if (size_of_image < 0x200000 || size_of_image >= 0x400000) { if (pe_header->sections[DEBUG_DIRECTORY].size == 0x1B20) { networkdll64_report.unknown = 0 networkdll64_report.report_id = 0x48 networkdll64_report.module_id = 0x5B7 networkdll64_report.data = pe_header->TimeDatestamp battleye::report(&networkdll64_report, sizeof(networkdll64_report), 0 } } else { networkdll64_report.unknown = 0 networkdll64_report.report_id = 0x48 networkdll64_report.module_id = 0x5B7 networkdll64_report.data = pe_header->sections[DEBUG_DIRECTORY].size battleye::report(&networkdll64_report, sizeof(networkdll64_report), 0 } } } 

nxdetours_64


 void module::check_nxdetours_64 { const auto module_handle = GetModuleHandleA("nxdetours_64.dll" if (module_handle) { nxdetours64_report.unknown = 0 nxdetours64_report.report_id = 0x48 nxdetours64_report.module_id = 0x5B8 nxdetours64_report.size_of_image = (PE_HEADER*)(module_handle + (DOS_HEADER*)(module_handle)->e_lfanew))->SizeOfImage battleye::report(&nxdetours64_report, sizeof(nxdetours64_report), 0 } } 

nvcompiler


 void module::check_nvcompiler { const auto module_handle = GetModuleHandleA("nvcompiler.dll" if (module_handle) { nvcompiler_report.unknown = 0 nvcompiler_report.report_id = 0x48 nvcompiler_report.module_id = 0x5BC nvcompiler_report.data = *(int*)(module_handle + 0x1000 battleye::report(&nvcompiler_report, sizeof(nvcompiler_report), 0 } } 

wmp


 void module::check_wmp { const auto module_handle = GetModuleHandleA("wmp.dll" if (module_handle) { wmp_report.unknown = 0 wmp_report.report_id = 0x48 wmp_report.module_id = 0x5BE wmp_report.data = *(int*)(module_handle + 0x1000 battleye::report(&wmp_report, sizeof(wmp_report), 0 } } 

Modul-Aufzählungs-IDs


Als Referenz geben wir die Aufzählungs-ID für die Module an:

 enum module_id { nvtoolsext64 = 0x5A8, ws2detour_x96 = 0x5B5, networkdll64 = 0x5B7, nxdetours_64 = 0x5B8, nvcompiler = 0x5BC, wmp = 0x5BE 

TCP-Tabellen scannen


- BattlEye TCP- ( « TCP», TCP table) , IP- Cloudflare-, - pay-to-cheat xera.ph . - , launcher, . , IP- Cloudflare- . , , .

pay-to-cheat xera.ph , , . xera.ph , , , , . , , , , -, , .

 void network::scan_tcp_table { memset(local_port_buffer, 0, sizeof(local_port_buffer for (iteration_index = 0 iteration_index < 500 ++iteration_index) { // GET NECESSARY SIZE OF TCP TABLE auto table_size = 0 GetExtendedTcpTable(0, &table_size, false, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0 // ALLOCATE BUFFER OF PROPER SIZE FOR TCP TABLE auto allocated_ip_table = (MIB_TCPTABLE_OWNER_MODULE*)malloc(table_size if (GetExtendedTcpTable(allocated_ip_table, &table_size, false, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0) != NO_ERROR) goto cleanup for (entry_index = 0 entry_index < allocated_ip_table->dwNumEntries ++entry_index) { const auto ip_address_match_1 = allocated_ip_table->table[entry_index].dwRemoteAddr == 0x656B1468 // 104.20.107.101 const auto ip_address_match_2 = allocated_ip_table->table[entry_index].dwRemoteAddr == 0x656C1468 // 104.20.108.101 const auto port_match = allocated_ip_table->table[entry_index].dwRemotePort == 20480 if ( (!ip_address_match_1 && !ip_address_match_2) || !port_match) continue for (port_index = 0 port_index < 10 && allocated_ip_table->table[entry_index].dwLocalPort != local_port_buffer[port_index ++port_index) { if (local_port_buffer[port_index]) continue tcp_table_report.unknown = 0 tcp_table_report.report_id = 0x48 tcp_table_report.module_id = 0x5B9 tcp_table_report.data = BYTE1(allocated_ip_table->table[entry_index].dwLocalPort) | (LOBYTE(allocated_ip_table->table[entry_index.dwLocalPort) << 8 battleye::report(&tcp_table_report, sizeof(tcp_table_report), 0 local_port_buffer[port_index] = allocated_ip_table->table[entry_index].dwLocalPort break } } cleanup: // FREE TABLE AND SLEEP free(allocated_ip_table Sleep(10 } } 


-:

 enum BATTLEYE_REPORT_ID { MEMORY_GUARD = 0x21, MEMORY_SUSPICIOUS = 0x2F, WINDOW_TITLE = 0x33, MEMORY = 0x35, PROCESS_ANOMALY = 0x38, DRIVER_BEEP_PRESENCE = 0x3E, DRIVER_NULL_PRESENCE = 0x3F, MISCELLANEOUS_ANOMALY = 0x3B, PROCESS_SUSPICIOUS = 0x40, LSASS_MEMORY = 0x42, SLEEP_ANOMALY = 0x45, MEMORY_MODULE_SPECIFIC = 0x46, GENERIC_ANOMALY = 0x48, MEMORY_MODULE_SPECIFIC2 = 0x5B, } 

Source: https://habr.com/ru/post/de483068/


All Articles