Membalikkan rekayasa BattlEye anti-cheat populer


BattlEye didominasi oleh anti-cheat pihak ketiga Jerman, terutama dikembangkan oleh Bastian Heiko Suter yang berusia 32 tahun. Ini memberikan (atau mencoba untuk memberikan) penerbit game dengan sistem anti-cheat yang mudah digunakan yang menggunakan mekanisme perlindungan umum, serta deteksi cheat untuk game tertentu untuk mengoptimalkan keamanan. Seperti yang dinyatakan di situs web produk, selalu berada di puncak teknologi modern dan menggunakan metode perlindungan dan deteksi yang inovatif; jelas ini adalah konsekuensi dari kebangsaan pengembang: QUALITY MADE IN GERMANY BattlEye terdiri dari banyak elemen yang bekerja sama untuk menemukan curang dalam permainan yang telah membayar untuk penggunaan produk. Empat elemen utama adalah:

  • BEService
    • Layanan sistem Windows yang berkomunikasi dengan server BattlEye BEServer , yang menyediakan komunikasi client-server dengan BEDaisy dan BEClient .
  • BEDaisy
    • Pengandar kernel Windows yang mencatat mekanisme pemrosesan acara preventif dan filter mini untuk mencegah curang mengubah game secara ilegal
  • Menjadi
    • Pustaka Windows yang terhubung secara dinamis yang bertanggung jawab untuk sebagian besar vektor deteksi, termasuk yang dijelaskan dalam artikel ini. Setelah inisialisasi, ia menjadi melekat pada proses game.
  • Beserver
    • Server backend milik, yang bertanggung jawab untuk mengumpulkan informasi dan mengambil tindakan spesifik terhadap curang.

Shellcode


Baru-baru ini, tumpukan kode shell BattlEye muncul di Internet, dan kami memutuskan untuk menulis tentang apa yang sebenarnya dicari versi BattlEye. Kami tidak menganalisis BattlEye selama enam bulan, jadi dump shellcode terakhir kami kemungkinan besar sudah usang. Berbagai bagian kode dipulihkan hanya dari memori dari dump terakhir ini, dengan asumsi bahwa BattlEye hanya menyelesaikan kode shell dan tidak menghapus prosedur deteksi sebelumnya.

Bagaimana?


BattlEye diduga melakukan stream shellcode dari servernya ke layanan Windows yang disebut BEService. Layanan ini berkomunikasi dengan modul BEClient yang terletak di dalam game. Pertukaran data dilakukan melalui \.namedpipeBattleye dan hingga 2018 tidak dienkripsi. Sekarang semua data yang dikirim dienkripsi dengan xor-enkripsi dengan kunci yang sangat kecil, yang membuatnya sangat mudah untuk melakukan serangan plaintext yang terkenal. Ketika kode shell ditransmisikan ke klien, itu terletak dan dieksekusi di luar semua modul yang dikenal, yang membuatnya mudah untuk ditentukan. Untuk membuat dump shellcode, Anda dapat memproses fungsi Windows API standar seperti CreateFile, ReadFile, dll., Dan membuang area memori yang sesuai dari semua modul panggilan (meminta informasi memori untuk alamat yang dikembalikan) yang berada di luar semua modul yang dikenal, atau memindai ruang memori virtual game secara berkala untuk mencari memori yang dapat dieksekusi di luar semua modul yang diketahui, dan membuangnya ke disk. Pada saat yang sama, Anda perlu melacak area mana yang sudah dibuang, sehingga Anda tidak akan mendapatkan banyak dump identik.

Penjelasan


Potongan-potongan pseudocode yang disajikan dalam artikel sangat dimodifikasi demi kecantikan. Anda tidak akan dapat membuang kode shell BattlEye dan segera mengenali bagian-bagian ini; shellcode tidak berisi panggilan fungsi, dan banyak algoritma dalam artikel ini digunakan. Tetapi pada kenyataannya, ini tidak penting, karena ketika Anda selesai membaca tentang zaman kuno yang mengerikan ini, Anda akan memiliki kesempatan untuk mengatasinya (:

Penyortiran memori


Mekanisme deteksi yang paling umum dalam anti-cheat adalah enumerasi memori dan pemindaian memori untuk mencari gambar cheat yang diketahui . Mudah untuk diimplementasikan dan, seperti yang ditunjukkan oleh masa lalu, dengan pendekatan yang tepat, cukup efektif jika Anda tidak melupakan dasar-dasar assembler dan memasukkan prolog fungsi umum dalam daftar hitam.

Battleye mengulangi seluruh ruang alamat dari proses game (proses saat ini dalam konteks ini) dan melakukan berbagai pemeriksaan pada kinerja halaman dan menemukan kode shell di luar ruang memori yang sesuai.

Inilah cara penerapannya di Battleye:

 // 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 } 

Anomali memori


BattlEye menandai semua anomali di ruang alamat memori, terutama memori modul yang dapat dieksekusi yang tidak sesuai dengan gambar yang dimuat:

 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 } } } 

Memindai Pola


Seperti disebutkan di atas, BattlEye juga memindai memori proses lokal untuk keberadaan berbagai pola yang jelas, seperti yang dapat dilihat dari implementasi yang ditunjukkan di bawah ini.

Saat membaca pseudo-code ini, Anda bisa menebak bahwa pemeriksaan ini dapat dielakkan dengan menulis ulang area kode dari setiap modul yang dimuat, karena mereka tidak akan memindai mencari pola dalam gambar yang dikenal. Agar tidak jatuh ke pemeriksaan integritas, Anda perlu mengunduh semua area yang dikemas dan masuk daftar putih serta menulis ulang area kode yang ditandai sebagai RWX , karena kami tidak dapat melakukan pemeriksaan integritas tanpa meniru pembuat paket. Dalam versi saat ini dari shelll BattlEye, pola-pola memori ini adalah kode-keras:

 [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 

Pola memori ini juga berisi header dua byte, yaitu nilai statis 05 tidak diketahui dan pengidentifikasi unik.

Apa yang tidak akan kita lihat adalah bahwa BattlEye juga secara dinamis mengalirkan pola dari BEServer dan mengirimkannya ke BEClient , tetapi kita tidak akan membahas ini dalam artikel.

Mereka dipindai secara iteratif oleh algoritma berikut:

 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 } } } } } 

Validasi modul tertentu (Microsoft)


Pemeriksaan modul melaporkan keberadaan modul tertentu yang dimuat ke dalam gim:

 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 } } 

Memeriksa modul tertentu (tidak diketahui)


Pemeriksaan modul tertentu telah ditambahkan ke sistem, yang memberi sinyal server bahwa Anda telah memuat modul yang memenuhi salah satu kriteria berikut:

 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 } 

Kami tidak tahu modul mana yang memenuhi kriteria ini, tetapi kami menduga bahwa ini adalah upaya untuk mendeteksi serangkaian modul cheat spesifik yang sangat terbatas.

Tambahan: @ how02 memberi tahu kami bahwa modul action_x64.dll memiliki 0x5B12C900 waktu 0x5B12C900 dan berisi area kode yang dapat Anda tulis; seperti yang disebutkan sebelumnya, ini dapat digunakan untuk mengeksploitasi.

Perlindungan memori


BattlEye juga mengimplementasikan prosedur deteksi yang sangat meragukan, yang, menurut pendapat kami, mencari memori dengan set flag PAGE_GUARD , tanpa benar-benar memeriksa apakah flag PAGE_GUARD diatur :

 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 } } } } 

Penyortiran Jendela


Kode shell BattlEye beriterasi di atas setiap jendela yang saat ini terlihat selama permainan, melewati jendela dari atas ke bawah (berdasarkan nilai-z). Gagang jendela di dalam gim dikecualikan dari enumerasi ini, dan ini ditentukan dengan memanggil GetWindowThreadProcessId . Oleh karena itu, Anda dapat mengikat fungsi yang sesuai ke pemilik jendela yang salah sehingga BattlEye tidak memeriksa jendela Anda .

 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 } } 

Cari anomali


Jika kurang dari dua jendela diperiksa, pemberitahuan dikirim ke server. Ini mungkin dilakukan untuk mencegah penambalan fungsi yang sesuai yang tidak memungkinkan kode shell BattlEye memeriksa jendela apa pun:

 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 } 

Proses Penyortiran


Dengan memanggil CreateToolhelp32Snapshot mengulangi semua proses yang sedang berjalan, tetapi tidak memproses kesalahan , membuatnya sangat mudah untuk ditambal dan menghindari prosedur deteksi berikut:

Pemeriksaan jalur


Jika gambar berada di dalam setidaknya dua subdirektori (dihitung dari akar disk), sistem akan menandai proses jika jalur ke gambar yang sesuai berisi setidaknya satu dari baris berikut:

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

Jika jalur ke file yang dapat dieksekusi sesuai dengan salah satu dari baris ini, server menerima pemberitahuan tentang jalur ke file yang dapat dieksekusi, serta informasi tentang apakah proses induk adalah salah satu dari berikut ini (berisi bit bendera terkait yang dikirim ke server):

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

Jika klien tidak dapat membuka deskriptor dengan hak QueryLimitedInformation sesuai, maka itu akan menetapkan bit flag 0x04 , jika penyebab kesalahan ketika panggilan OpenProcess gagal bukan ERROR_ACCESS_DENIED , yang memberi kami wadah enumerasi terakhir untuk nilai flag yang sesuai:

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

Jika proses induk adalah steam, maka flag langsung diatur untuk pengguna dan server diberitahu tentang hal ini dengan notifikasi 0x40

Nama gambar


Jika proses memenuhi salah satu dari banyak kriteria yang disajikan di bawah ini, maka Anda langsung menetapkan bendera dan ini dilaporkan ke server dengan notifikasi 0x38

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

Hamparan Game Steam


BattlEye memonitor proses overlay permainan Steam, yang bertanggung jawab atas overlay dalam game, yang diketahui oleh sebagian besar pengguna Steam. Nama host lengkap dari hamparan Steam Games adalah gameoverlayui.exe ; diketahui bahwa itu sering digunakan untuk merender eksploitasi, karena cukup mudah untuk meretas dan melakukan perenderan ilegal di jendela permainan. Verifikasi memiliki kondisi berikut:

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

Pemeriksaan lebih lanjut khusus untuk overlay game Steam hampir identik dengan prosedur yang dilakukan untuk proses game itu sendiri, oleh karena itu, dihilangkan dalam pseudo-code.

Pemindaian memori overlay uap


Proses overlay game Steam dipindai untuk menemukan pola dan anomali. Kami tidak dapat masuk lebih jauh ke dalam lubang kelinci, dan mencari tahu untuk apa pola-pola ini, karena mereka sangat digeneralisasikan dan kemungkinan besar terkait dengan modul cheat.

 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 } 

Prosedur pemindaian juga mencari anomali dalam bentuk kode yang dapat dieksekusi di luar gambar yang diunduh, menunjukkan bahwa cracker menyuntikkan kode ke dalam proses overlay:

 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 } 

Perlindungan Hamparan Steam Game


Jika proses overlay game Steam dilindungi oleh beberapa perlindungan terhadap proses Windows seperti Light (WinTcb) , maka server akan menerima pemberitahuan tentang hal itu.

 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 } 

Selain itu, jika panggilan OpenProcess yang sesuai mengembalikan ERROR_ACCESS_DENIED ke proses overlay, pemberitahuan dikirimkan tentang pengguna dengan id 3B .

Modul Penyortiran


Modul proses overlay permainan uap juga dicari, khususnya, vgui2_s.dll dan gameoverlayui.dll . Beberapa pemeriksaan dilakukan untuk modul-modul ini, dimulai dengan gameoverlayui.dll .

Jika kondisi ini terpenuhi: [gameoverlayui.dll+6C779] == 08BE55DC3CCCCB8????????C3CCCCCC , maka [gameoverlayui.dll+6C779] == 08BE55DC3CCCCB8????????C3CCCCCC memindai [gameoverlayui.dll+6C779] == 08BE55DC3CCCCB8????????C3CCCCCC vtable pada alamat yang disimpan dalam byte ???????? . Jika salah satu dari elemen vtable ini berada di luar modul sumber gameoverlayui.dll atau menunjuk ke instruksi int 3 , maka pengguna dilaporkan ke server dengan id pemberitahuan 3B .

 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 } } } 

Prosedur verifikasi khusus juga dilakukan untuk 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


, BattlEye (sleep):

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

 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 } } 


BattlEye Windows (hal.dll), , .

 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 } } 


BattlEye juga memeriksa berbagai gambar yang dimuat ke dalam game. Modul-modul ini seharusnya merupakan gambar yang ditandatangani yang entah bagaimana dimanipulasi, mengubah perilakunya menjadi jahat, tetapi kami tidak bisa mengatakan apa-apa lagi tentang mereka, hanya tentang pendeteksian mereka:

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 } } 

Pengidentifikasi enumerasi modul


Untuk referensi, kami memberikan id enumerasi untuk modul:

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

Pindai tabel TCP


- 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/id483068/


All Articles