Änderungen in der beliebten BattlEye-Antichita und Möglichkeiten, sie zu umgehen


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"); // --- REMOVED --- // if (module_handle && *(int*)(module_handle + 0x1000) != 0xFF1441C7) // --- ADDED --- if (module_handle && *(int*)(module_handle + 0x1008) != 0x83485348) { 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), false); } } 

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) { // 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) { // --- REMOVED --- // 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 // +++ ADDED +++ const auto target_process = OpenProcess(QueryLimitedInformation, 0, ip_table->table[entry_index].dwOwningPid); const auto protected = target_process == INVALID_HANDLE && GetLastError() == 0x57; if (!protected) { CloseHandle(target_process); return; } const auto port_match = allocated_ip_table->table[entry_index].dwRemotePort == 20480; 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), false); local_port_buffer[port_index] = allocated_ip_table->table[entry_index].dwLocalPort; break } } cleanup: // FREE TABLE AND SLEEP free(allocated_ip_table); Sleep(10 } } 

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; // QUERY THE MEMORY PAGE OF THE CALLER const auto call_failed = NtQueryVirtualMemory( GetCurrentProcess(), caller_function, MemoryBasicInformation, &caller_memory_information, sizeof(caller_memory_information), &desired_size) < 0; // IS THE MEMORY SOMEHOW NOT COMMITTED? (WOULD SUGGEST VAD MANIPULATIUON) const auto non_commit = caller_memory_information.State != MEM_COMMIT; // IS THE PAGE EXECUTABLE BUT DOES NOT BELONG TO A PROPERLY LOADED MODULE? const auto foreign_image = caller_memory_information.Type != MEM_IMAGE && caller_memory_information.RegionSize > 0x2000; // IS THE CALL BEING SPOOFED BY NAMAZSO? const auto spoof = *(_WORD *)caller_function == 0x23FF; // jmp qword ptr [rbx] // FLAG ALL ANBORMALITIES if (call_failed || non_commit || foreign_image || spoof) { report_stack.unknown = 0; report_stack.report_id = 0x31; report_stack.hook_id = hook_id; report_stack.caller = (__int64)caller_function; report_stack.function_dump[0] = *caller_function; report_stack.function_dump[1] = caller_function[1]; report_stack.function_dump[2] = caller_function[2]; report_stack.function_dump[3] = caller_function[3]; if (!call_failed) { report_stack.allocation_base = caller_memory_information.AllocationBase; report_stack.base_address = caller_memory_information.BaseAddress; report_stack.region_size = caller_memory_information.RegionSize; report_stack.type_protect_state = caller_memory_information.Type | caller_memory_information.Protect | caller_memory_information.State; } battleye::report(&report_stack, sizeof(report_stack), false); return -1; } } 

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() { // SET THREAD PRIORITY TO THE HIGHEST const auto old_priority = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); // CALCULATE CYCLES FOR 1000MS const auto timestamp_calibrator = __rdtsc(); Sleep(1000); const auto timestamp_calibration = __rdtsc() - timestamp_calibrator; // TIME CPUID auto total_time = 0; for (std::size_t count = 0; count < 0x6694; count++) { // SAVE PRE CPUID TIME const auto timestamp_pre = __rdtsc(); std::uint32_t cpuid_data[4] = {}; __cpuid(cpuid_data, 0); // SAVE THE DELTA total_time += __rdtsc() - timestamp_pre; } // SAVE THE RESULT IN THE GLOBAL REPORT TABLE battleye::report_table[0x1A8] = 10000000 * total_time / timestamp_calibration / 0x65; // RESTORE THREAD PRIORITY SetThreadPriority(GetCurrentThread(), old_priority); } 

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; // CPUID IET ARRAY STORE // BATTLEYE REPORT TABLE STORE _enable(); } 

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 , .

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


All Articles