Pembaruan shellcode Major BattlEye
Waktu berlalu, anti-cheat berubah, dan untuk meningkatkan efektivitas produk, fungsi muncul dan menghilang di dalamnya. Setahun yang lalu, saya menyiapkan deskripsi terperinci dari shellcode BattlEye di
blog saya [
terjemahan di Habré], dan bagian artikel ini akan menjadi refleksi sederhana dari perubahan yang dilakukan pada shellcode.
Stempel Waktu Hitam
Dalam analisis BattlEye baru-baru ini, hanya ada dua cap waktu kompilasi dalam daftar larangan bayangan, dan sepertinya para pengembang memutuskan untuk menambahkan lebih banyak lagi:
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 (?)
Saya tidak dapat mengidentifikasi cap waktu yang tersisa, dan dua
0xF ******* adalah hash yang dibuat oleh majelis deterministik Visual Studio. Terima kasih kepada @mottikraus dan T0B1 untuk mengidentifikasi beberapa cap waktu.
Pemeriksaan Modul
Seperti yang ditunjukkan analisis utama, fitur utama BattlEye adalah enumerasi modul, dan sejak saat analisis terakhir, modul lain ditambahkan ke daftar:
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); } }
Ini mungkin deteksi dll proxy tertentu, karena ukuran tabel redirect diperiksa di sini.
Judul jendela
Dalam analisis sebelumnya, berbagai penyedia cheat ditandai dengan nama jendela, tetapi sejak itu shellcode telah berhenti memeriksa header jendela ini. Daftar judul jendela telah sepenuhnya diganti oleh:
Chod's
Satan5
Nama gambar
BattlEye terkenal karena menggunakan metode deteksi yang sangat primitif, dan salah satunya adalah daftar nama hitam. Setiap tahun, daftar nama gambar yang dilarang semakin lama, dan selama 11 bulan terakhir lima nama baru telah ditambahkan:
frAQBc8W.dll
C:\\Windows\\mscorlib.ni.dll
DxtoryMM_x64.dll
Project1.dll
OWClient.dll
Perlu dicatat bahwa kehadiran modul dengan nama yang sesuai dengan salah satu item dalam daftar tidak akan berarti bahwa Anda akan segera dilarang. Mesin pelaporan juga menyampaikan informasi modul dasar, yang kemungkinan besar digunakan untuk membedakan kode curang dari tabrakan di server BattlEye.
7-zip
7-Zip banyak digunakan dan terus digunakan oleh peserta dalam adegan cheat sebagai pengisi memori untuk rongga kode (kode-gua). BattlEye mencoba menangani ini dengan melakukan pemeriksaan integritas yang
sangat buruk, yang telah berubah sejak artikel saya sebelumnya:
void module::check_7zip() { const auto module_handle = GetModuleHandleA("..\\..\\Plugins\\ZipUtility\\ThirdParty\\7zpp\\dll\\Win64\\7z.dll");
Tampaknya para pengembang BattlEye telah menebak bahwa artikel saya sebelumnya telah menyebabkan banyak pengguna mem-bypass cek ini hanya dengan menyalin byte yang diinginkan ke lokasi yang diperiksa oleh BattlEye. Bagaimana mereka memperbaiki situasi? Kami menggeser verifikasi sebanyak delapan byte dan terus menggunakan metode buruk yang sama untuk memeriksa integritas. Partisi executable read-only, dan yang perlu Anda lakukan adalah mengunduh 7-Zip dari disk dan membandingkan partisi yang dipindahkan satu sama lain; jika ada perbedaan, maka ada sesuatu yang salah. Serius, kawan, melakukan pemeriksaan integritas tidaklah sulit.
Pemeriksaan jaringan
Menghitung tabel TCP masih berfungsi, tetapi setelah saya merilis analisis sebelumnya yang mengkritik pengembang karena menandai alamat IP Cloudflare, mereka masih menghapus pemeriksaan ini. Anti-cheat masih melaporkan port yang digunakan xera.ph untuk koneksi, tetapi pengembang menambahkan cek baru untuk menentukan apakah proses dengan koneksi memiliki perlindungan aktif (mungkin ini dilakukan menggunakan handler).
void network::scan_tcp_table { memset(local_port_buffer, 0, sizeof(local_port_buffer); for (iteration_index = 0; iteration_index; < 500 ++iteration_index) {
Terima kasih, Anda memilih dan abstrak
BattlEye Stack Bypass
Permainan meretas adalah permainan kucing dan tikus yang konstan, sehingga rumor trik baru menyebar seperti api. Pada bagian ini, kita akan melihat teknik heuristik baru yang baru-baru ini ditambahkan ke gudang senjata kita oleh penyedia besar anti-cheat BattlEye. Paling sering, teknik ini disebut stack walking. Biasanya mereka diimplementasikan dengan memproses fungsi dan melalui tumpukan untuk mencari tahu siapa yang secara spesifik memanggil fungsi ini. Mengapa Anda perlu melakukan ini? Seperti program lainnya, peretasan gim video memiliki serangkaian fungsi terkenal yang mereka gunakan untuk mendapatkan informasi dari keyboard, output ke konsol, atau menghitung ekspresi matematika tertentu. Selain itu, video game hacks suka menyembunyikan keberadaan mereka, baik di memori atau di disk, sehingga perangkat lunak anti-cheat tidak menemukannya. Tetapi yang dilupakan oleh program cheat adalah mereka secara rutin memanggil fungsi dari perpustakaan lain, dan ini dapat digunakan untuk mendeteksi heurist secara curang. Dengan mengimplementasikan mesin stack traversal untuk fungsi-fungsi seperti
std::print
, kita dapat menemukan cheat ini bahkan jika mereka bertopeng.
BattlEye
menerapkan "tumpukan bypass", meskipun fakta bahwa ini tidak diumumkan secara publik dan pada saat publikasi artikel hanya ada desas-desus. Perhatikan tanda kutip - apa yang akan Anda lihat di sini sebenarnya bukan tur tumpukan nyata, tetapi hanya kombinasi memeriksa alamat pengirim dan dump program panggilan. Implementasi stack traversal yang sebenarnya akan melalui stack dan menghasilkan stack panggilan nyata.
Seperti yang saya jelaskan di artikel sebelumnya tentang BattlEye, sistem anti-cheat secara dinamis mengalirkan shellcode ke dalam game ketika sedang berjalan. Kode shell ini memiliki ukuran dan tugas yang berbeda, dan tidak dikirimkan secara bersamaan. Sifat luar biasa dari sistem semacam itu adalah bahwa para peneliti perlu menganalisis secara dinamis anti-cheat selama pertandingan multi-pemain, yang mempersulit penentuan karakteristik anti-cheat ini. Hal ini juga memungkinkan anti-cheat untuk menerapkan berbagai tindakan pada pengguna yang berbeda, misalnya, untuk mentransfer modul invasif yang lebih dalam hanya kepada orang yang memiliki rasio pembunuhan dan kematian yang sangat tinggi, dan sejenisnya.
Salah satu kode shell ini, BattlEye, bertanggung jawab untuk melakukan analisis tumpukan ini; kita akan menyebutnya
shellcode8kb karena sedikit lebih kecil dibandingkan dengan
shellcodemain , yang saya dokumentasikan di
sini . Kode shell kecil ini menggunakan fungsi
AddVectoredExceptionHandler menyiapkan handler pengecualian vektor dan kemudian mengatur jebakan interupsi pada fungsi-fungsi berikut:
GetAsyncKeyState
GetCursorPos
IsBadReadPtr
NtUserGetAsyncKeyState
GetForegroundWindow
CallWindowProcW
NtUserPeekMessage
NtSetEvent
sqrtf
__stdio_common_vsprintf_s
CDXGIFactory::TakeLock
TppTimerpExecuteCallback
Untuk melakukan ini, ia hanya berputar di sekitar daftar fungsi yang digunakan secara standar, mengatur instruksi pertama dari fungsi yang sesuai ke
int3 , yang digunakan sebagai breakpoint. Setelah mengatur breakpoint, semua panggilan ke fungsi yang sesuai melewati penangan pengecualian yang memiliki akses penuh ke register dan tumpukan. Memiliki akses ini, pawang pengecualian membuang alamat program panggilan dari atas tumpukan, dan jika salah satu kondisi heuristik terpenuhi, 32 byte fungsi panggilan dibuang dan dikirim ke server BattlEye dengan pengidentifikasi laporan
0x31 :
__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;
Seperti yang dapat kita lihat, pengendali pengecualian membuang semua fungsi panggilan jika terjadi perubahan yang tidak serius pada halaman memori atau ketika fungsi tersebut tidak termasuk dalam modul proses yang diketahui (jenis halaman memori MEM_IMAGE tidak diatur oleh pembuat manual). Itu juga membuang fungsi panggilan ketika gagal memanggil
NtQueryVirtualMemory sehingga menipu tidak mengikat panggilan sistem ini dan menyembunyikan modul mereka dari tumpukan dump. Kondisi terakhir sebenarnya cukup menarik, ini menandai semua fungsi panggilan yang menggunakan
gadget jmp qword ptr [rbx] - metode yang digunakan untuk "menipu alamat pengirim". Album ini
dirilis oleh co-sekretaris saya, julukan namazso. Tampaknya para pengembang BattlEye melihat bahwa orang menggunakan metode spoofing ini dalam gim mereka dan memutuskan untuk membidiknya secara langsung. Perlu disebutkan di sini bahwa metode yang dijelaskan oleh namazsos berfungsi dengan baik, cukup gunakan gadget yang berbeda, atau sangat berbeda, atau hanya register yang berbeda - tidak masalah.
Tip Pengembang BattlEye:
CDXGIFactory::TakeLock
dalam memori Anda salah karena Anda (secara tidak sengaja atau sengaja) mengaktifkan padding CC, yang sangat berbeda setiap kali Anda kompilasi. Untuk kompatibilitas maksimum, Anda harus menghapus bantalan (byte pertama dalam tanda tangan) dan kemungkinan besar Anda akan menangkap lebih banyak curang :)
Struktur lengkap yang dikirim ke server BattlEye terlihat seperti ini:
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; };
Pengakuan hypervisor di BattlEye
Permainan kucing dan tikus di bidang permainan peretasan terus menjadi sumber inovasi dalam eksploitasi dan perang melawan penipuan. Penggunaan teknologi virtualisasi dalam permainan peretasan mulai berkembang secara aktif setelah munculnya hypervisor yang mudah digunakan seperti
DdiMon Satoshi Tanda dan
hvpp Peter Benes. Kedua proyek ini digunakan oleh sebagian besar penipu bayaran dari adegan hacker bawah tanah karena ambang masuk yang rendah dan dokumentasi terperinci. Rilis ini kemungkinan akan mempercepat perlombaan senjata di bidang hypervisors, yang sekarang mulai mewujud dalam komunitas peretas game. Inilah yang dikatakan administrator dari salah satu komunitas peretas game terbesar dengan julukan
wlan tentang situasi ini:
Dengan munculnya sistem hypervisor yang siap digunakan untuk permainan hacking, menjadi tidak terhindarkan bahwa anti-cheat seperti BattlEye akan fokus pada pengenalan umum virtualisasi.
Meluasnya penggunaan hypervisor disebabkan oleh peningkatan terbaru dalam anti-cheat, yang membuat peretas sangat sedikit kesempatan untuk memodifikasi game dengan cara tradisional. Popularitas hypervisor dapat dijelaskan oleh kesederhanaan menghindari anti-cheat, karena virtualisasi menyederhanakan penyembunyian informasi menggunakan mekanisme seperti
syscall hooks dan
virtualisasi MMU .
Baru-baru ini, BattlEye telah menerapkan pengenalan hypervisor umum seperti platform yang disebutkan di atas (DdiMon, hvpp) menggunakan deteksi berbasis waktu. Pengakuan ini mencoba mendeteksi nilai waktu instruksi CPUID yang tidak standar. CPUID adalah instruksi yang relatif murah untuk peralatan nyata, biasanya hanya membutuhkan dua ratus siklus, dan dalam lingkungan virtual, pelaksanaannya dapat memakan waktu sepuluh kali lebih banyak karena operasi yang tidak perlu yang disebabkan oleh mesin introspeksi. Mesin introspeksi tidak seperti peralatan nyata, yang hanya melakukan operasi dengan cara yang diharapkan, karena berdasarkan kriteria sewenang-wenang ia melacak dan secara kondisional mengubah data yang dikembalikan ke tamu.
Fakta menyenangkan: CPUID secara aktif digunakan dalam prosedur pengenalan sementara ini karena merupakan instruksi dengan output tanpa syarat, serta instruksi dengan serialisasi yang tidak terjangkau. Ini berarti bahwa CPUID digunakan sebagai
penghalang dan memastikan bahwa instruksi sebelum dan sesudah diikuti; pada saat yang sama, penentuan waktu menjadi independen dari pemesanan ulang instruksi yang biasa. Anda juga dapat menggunakan instruksi seperti
XSETBV , yang juga melakukan jalan keluar tanpa syarat, tetapi untuk memastikan waktu yang bebas, ini akan memerlukan semacam instruksi penghalang sehingga tidak ada pemesanan ulang yang terjadi sebelum atau setelah itu, yang mempengaruhi keandalan pengaturan waktu.
Pengakuan
Berikut ini adalah prosedur pengenalan dari modul BattlEye “BEClient2”; Saya melakukan reverse engineering dan membuat ulang kode di pseudo-C, dan kemudian mempostingnya di
twitter . Sehari setelah tweet saya, para pengembang BattlEye tiba-tiba mengubah kebingungan BEClient2, tampaknya berharap bahwa ini akan mencegah saya menganalisis modul. Kebingungan sebelumnya tidak berubah selama lebih dari setahun, tetapi berubah sehari setelah tweet saya tentang hal itu - kecepatan yang mengesankan.
void battleye::take_time() {
Seperti yang saya katakan di atas, ini adalah teknik pengenalan yang paling umum menggunakan instruksi yang diinterupsi tanpa syarat. Namun, ini rentan terhadap waktu palsu, dan kami akan membicarakan ini secara rinci di bagian selanjutnya.
Bypass pengakuan
Metode pengenalan ini memiliki masalah. Pertama, ini rentan terhadap waktu palsu, yang biasanya dilakukan dengan dua cara: dengan menggeser TSC di VMCS atau dengan mengurangi TSC setiap kali CPUID dieksekusi. Ada banyak cara lain untuk menangani serangan berbasis waktu, tetapi yang terakhir jauh lebih mudah diimplementasikan, karena Anda dapat menjamin bahwa waktu pelaksanaan instruksi akan berada dalam satu atau dua siklus siklus sinkronisasi eksekusi pada peralatan nyata. Kesulitan menemukan teknik pemalsuan saat ini tergantung pada pengalaman pengembang. Di bagian selanjutnya, kita akan melihat pemalsuan waktu dan meningkatkan implementasi yang dibuat di BattlEye. Alasan kedua untuk cacat metode pengenalan ini adalah bahwa keterlambatan CPUID (runtime) dalam prosesor yang berbeda sangat berbeda tergantung pada nilai lembar. Mungkin perlu hingga 70-300 siklus jam untuk menyelesaikannya. Masalah ketiga dengan prosedur pengenalan ini adalah menggunakan SetThreadPriority. Fungsi Windows ini digunakan untuk mengatur nilai prioritas dari deskriptor aliran yang diberikan, namun OS tidak selalu mendengarkan permintaan. Fungsi ini hanyalah saran untuk meningkatkan prioritas utas, dan tidak ada jaminan bahwa itu akan terjadi. Dengan demikian, ada kemungkinan bahwa metode ini akan dipengaruhi oleh interupsi atau proses lainnya.
Dalam hal ini, mudah untuk melewati pengenalan, dan teknik pemalsuan waktu yang dijelaskan secara efektif mengalahkan metode pengenalan ini. Jika pengembang BattlEye ingin meningkatkan metode ini, maka bagian berikut memberikan beberapa rekomendasi.
Perbaikan
Fitur ini dapat ditingkatkan dengan banyak cara. Pertama, Anda dapat dengan sengaja menonaktifkan interupsi dan memaksakan prioritas utas dengan mengubah CR8 ke IRQL tertinggi. Ini juga akan ideal untuk mengisolasi cek ini dalam satu inti CPU. Peningkatan lainnya: Anda harus menggunakan timer yang berbeda, tetapi banyak dari mereka yang tidak seakurat TSC, tetapi ada satu timer seperti yang disebut APERF, atau Clock Kinerja Aktual. Saya merekomendasikan timer ini karena lebih sulit untuk menipu dengan itu dan hanya mengakumulasi penghitung ketika prosesor logis dalam kondisi daya C0. Ini adalah alternatif yang bagus untuk menggunakan TSC. Anda juga dapat menggunakan ACPI, HPET, timer PIT, timer GPU, timer NTP, atau timer PPERF, yang mirip dengan APERF, tetapi menghitung ukuran yang dianggap sebagai instruksi pelaksanaan. Kerugiannya adalah Anda harus mengaktifkan HWP, yang dapat dinonaktifkan oleh operator perantara, dan oleh karena itu tidak berguna.
Di bawah ini adalah versi perbaikan dari prosedur pengenalan yang harus dilakukan di kernel:
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;
Catatan: IET adalah singkatan dari Instruction Execution Time.
Namun, prosedur ini masih bisa sangat tidak dapat diandalkan dalam mendeteksi hypervisor umum, karena runtime CPUID dapat sangat bervariasi.
Akan lebih baik untuk membandingkan IET dari dua instruksi. Salah satunya harus memiliki penundaan eksekusi yang lebih lama daripada CPUID. Sebagai contoh, itu mungkin FYL2XP1 - instruksi aritmatika yang membutuhkan waktu lebih lama untuk menyelesaikan daripada rata-rata IET dari instruksi CPUID. Selain itu, tidak menyebabkan jebakan dalam hypervisor dan waktunya dapat diukur dengan andal. Menggunakan kedua fungsi ini, fungsi pembuatan profil dapat membuat larik untuk menyimpan instruksi IET, CPUID dan FYL2XP1. Dengan menggunakan pengatur waktu APERF, dimungkinkan untuk mendapatkan jam awal dari instruksi aritmatika, menjalankan instruksi dan menghitung delta jam untuknya. Hasil dapat disimpan dalam array IET untuk siklus profil N, mendapatkan nilai rata-rata, dan mengulangi proses untuk CPUID. Jika waktu pelaksanaan instruksi CPUID lebih panjang dari instruksi aritmatika,maka ini adalah tanda yang dapat diandalkan bahwa sistem ini virtual, karena instruksi aritmatika dalam keadaan apa pun tidak dapat menghabiskan lebih banyak waktu daripada mengeksekusi CPUID untuk mendapatkan informasi tentang produsen atau versi. Prosedur pengenalan semacam itu juga akan dapat mendeteksi mereka yang menggunakan offset / penskalaan TSC.Saya ulangi, pengembang harus memaksakan pengikatan pada inti komputasi untuk melakukan pemeriksaan ini pada satu inti, menonaktifkan interupsi dan memaksa IRQL untuk menetapkan nilai maksimum untuk menjamin data yang konsisten dan andal. Akan mengejutkan jika para pengembang BattlEye memutuskan untuk mengimplementasikan ini, karena itu membutuhkan lebih banyak usaha. Dalam driver kernel, BattlEye makan dua rutinitas pengenalan mesin virtual lainnya, tetapi ini adalah topik untuk artikel lain.