الهندسة العكسية لمكافحة BeatlEye شعبية الغش


BattlEye هي في الغالب شركة ألمانية من طرف ثالث لمكافحة الغش ، تم تطويرها بشكل رئيسي من قبل باستيان هايكو سوتر ( 32 عامًا). يوفر (أو يحاول تزويد) ناشري الألعاب بنظام سهل الاستخدام لمكافحة الغش يستخدم آليات الحماية العامة ، بالإضافة إلى اكتشاف الغش في ألعاب معينة لتحسين الأمان. كما هو مذكور في موقع المنتج ، يبقى دائمًا في قمة التكنولوجيا الحديثة ويستخدم أساليب مبتكرة للحماية والاكتشاف ؛ من الواضح أن هذا نتيجة لجنسية المطور: QUALITY MADE IN GERMANY يتكون BattlEye من العديد من العناصر التي تعمل معًا للعثور على الغشاشين في الألعاب التي دفعت مقابل استخدام المنتج. العناصر الأربعة الرئيسية هي:

  • BEService
    • هي خدمة نظام Windows تتصل بخادم BattlEye BEServer ، الذي يوفر اتصال خادم عميل مع BEDaisy و BEClient .
  • BEDaisy
    • برنامج تشغيل Windows kernel يقوم بتسجيل آليات معالجة الأحداث الوقائية وعوامل التصفية المصغرة لمنع الغشاشين من تعديل اللعبة بطريقة غير قانونية
  • BEClient
    • مكتبة Windows متصلة بشكل حيوي مسؤولة عن معظم متجهات الكشف ، بما في ذلك تلك الموضحة في هذه المقالة. بعد التهيئة ، تصبح متصلة بعملية اللعبة.
  • BEServer
    • خادم الخلفية الملكية ، المسؤول عن جمع المعلومات واتخاذ تدابير محددة ضد الغشاشين.

كود القشرة


في الآونة الأخيرة ، ظهر تفريغ لرمز قذيفة BattlEye على الإنترنت ، وقررنا أن نكتب عما يبحث عنه بالضبط الإصدار الحالي من BattlEye. لم نقم بتحليل BattlEye لمدة ستة أشهر ، لذلك من المحتمل أن يكون تفريغ آخر إصدار لقذيفة لدينا قديمًا. تم استرداد أجزاء مختلفة من الرمز فقط من الذاكرة من هذا التفريغ الأخير ، على افتراض أن BattlEye أكملت فقط رمز shell ولم تحذف إجراءات الكشف السابقة.

كيف؟


يزعم أن BattlEye تقوم بتشغيل دلالات shellcode من خادمها إلى خدمة Windows تسمى BEService. تتصل هذه الخدمة بوحدة BEClient الموجودة داخل اللعبة. يتم تنفيذ تبادل البيانات من خلال \.namedpipeBattleye وحتى عام 2018 كان غير مشفر. الآن يتم تشفير جميع البيانات المرسلة باستخدام برنامج تشفير xor مع مفاتيح صغيرة للغاية ، مما يجعل من السهل للغاية تنفيذ هجمات النص العادي المعروفة. عندما يتم إرسال رمز shell إلى العميل ، فإنه يتم وضعه وتنفيذه خارج جميع الوحدات المعروفة ، مما يسهل تحديده. لإنشاء ملف تفريغ shellcode ، يمكنك إما معالجة وظائف Windows API القياسية مثل CreateFile ، و ReadFile ، وما إلى ذلك ، وتفريغ مساحة الذاكرة المقابلة لجميع وحدات الاتصال (التي تطلب معلومات الذاكرة للعنوان المرتجع) الموجودة خارج جميع الوحدات النمطية المعروفة ، أو قم بمسح مساحة الذاكرة الظاهرية للعبة بشكل دوري بحثًا عن الذاكرة القابلة للتنفيذ خارج جميع الوحدات المعروفة ، وقم بتفريغها على القرص. في الوقت نفسه ، تحتاج إلى تتبع المناطق التي تم إلقاؤها بالفعل ، وبالتالي لن تحصل على العديد من المقالب المماثلة.

تفسير


يتم تعديل بشدة شظايا الكود الكاذب المقدمة في المقالة من أجل الجمال. لن تتمكن من تفريغ كود BattlEye shell والتعرف على الفور على هذه الأجزاء ؛ لا يحتوي كود القشرة على استدعاءات الوظائف ، ويتم نشر العديد من الخوارزميات في المقالة. لكن في الحقيقة ، هذا ليس مهمًا ، لأنه عند الانتهاء من القراءة حول هذه العصور القديمة الرهيبة ، ستتاح لك الفرصة للتغلب عليها (:

فرز الذاكرة


آلية الكشف الأكثر شيوعًا في مكافحة الغش هي تعداد الذاكرة ومسح الذاكرة للبحث عن صور الغش المعروفة . من السهل تنفيذها ، وكما يتضح من الماضي ، فإن الطريقة الصحيحة فعالة للغاية إذا لم تنسَ أساسيات المجمّع وأدرجت في القائمة السوداء للمهمة المشتركة.

يتكرر Battleye على مساحة العنوان بالكامل لعملية اللعبة (العملية الحالية في هذا السياق) ويقوم بإجراء عمليات فحص متنوعة على أداء الصفحة وإيجاد رمز shell خارج مساحة الذاكرة المقابلة.

إليك كيفية تنفيذه في 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 } 

الشذوذ الذاكرة


تضع BattlEye جميع الحالات الشاذة في مساحة عنوان الذاكرة ، وخاصة ذاكرة الوحدات القابلة للتنفيذ التي لا تتوافق مع الصورة التي تم تحميلها:

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

المسح الضوئي للأنماط


كما ذكر أعلاه ، فإن BattlEye تقوم أيضًا بمسح ذاكرة العمليات المحلية لوجود أنماط مختلفة محددة بوضوح ، كما يتضح من التطبيق الموضح أدناه.

عند قراءة هذا الرمز الزائف ، يمكنك تخمين أنه يمكن التحايل على هذه الفحوصات من خلال إعادة كتابة منطقة الشفرة لكل وحدة نمطية محمّلة ، نظرًا لأنها لن تفحص بحثًا عن أنماط في صور معروفة. من أجل عدم الوقوع في اختبارات النزاهة ، تحتاج إلى تنزيل جميع المناطق المعبأة والقائمة البيضاء وإعادة كتابة مساحات الرمز التي تحمل علامة RWX ، لأنه لا يمكننا إجراء اختبارات النزاهة دون محاكاة باكر. في الإصدار الحالي من كود shell BattlEye ، تكون أنماط الذاكرة هذه مشفرة بشكل ثابت:

 [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 

تحتوي أنماط الذاكرة هذه أيضًا على رأس ذي بايتين ، وهما القيمة الثابتة غير المعروفة 05 ومعرّف فريد.

ما لن نراه هو أن BattlEye تقوم أيضًا بتدفق الأنماط بشكل حيوي من BEServer وإرسالها إلى BEClient ، لكننا لن نناقش ذلك في المقالة.

يتم مسحها بشكل تكراري بواسطة الخوارزمية التالية:

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

التحقق من صحة الوحدات النمطية المحددة (Microsoft)


تقرير الشيكات وحدة الإبلاغ عن وجود وحدات محددة تحميلها في اللعبة:

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

التحقق من وحدات محددة (غير معروف)


تمت إضافة فحص للوحدات النمطية المحددة إلى النظام ، مما يشير إلى الخادم الذي قمت بتحميل الوحدات النمطية التي تفي بأي من هذه المعايير:

 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 } 

لا نعرف الوحدات النمطية التي تفي بهذه المعايير ، لكننا نشك في أن هذه محاولة لاكتشاف مجموعة محدودة جدًا من وحدات الغش المحددة.

إضافة: @ how02 أبلغتنا أن الوحدة النمطية action_x64.dll لها 0x5B12C900 زمني 0x5B12C900 وتحتوي على منطقة 0x5B12C900 برمجية يمكنك الكتابة إليها ؛ كما ذكر سابقا ، وهذا يمكن أن تستخدم للاستغلال.

حماية الذاكرة


تطبق BattlEye أيضًا إجراء كشف مشكوك فيه للغاية ، والذي ، في رأينا ، يبحث عن الذاكرة باستخدام مجموعة علامة PAGE_GUARD ، دون التحقق فعليًا من تحديد علامة PAGE_GUARD :

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

نافذة الفرز


رمز shell BattlEye يتكرر عبر كل من النوافذ المرئية حاليًا أثناء اللعبة ، متجاوزًا النوافذ من الأعلى إلى الأسفل (حسب القيمة z). يتم استبعاد GetWindowThreadProcessId الإطار داخل اللعبة من هذا التعداد ، ويتم تحديد ذلك عن طريق استدعاء GetWindowThreadProcessId . لذلك ، يمكنك ربط الوظيفة المطابقة بالمالك الخاطئ للإطار حتى لا يتحقق BattlEye من نافذتك .

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

بحث الشذوذ


في حالة فحص أقل من نافذتين ، يتم إرسال إشعار إلى الخادم. من المحتمل أن يتم ذلك لمنع ترقيع الوظائف المقابلة التي لا تسمح لرمز BattlEye shell بفحص أي نوافذ:

 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 } 

عملية الفرز


عن طريق استدعاء CreateToolhelp32Snapshot يتكرر على جميع العمليات الجارية ، لكنه لا يعالج أي أخطاء ، مما يجعل من السهل جدا التصحيح وتجنب إجراءات الكشف التالية:

فحص المسار


إذا كانت الصورة داخل اثنين من الدلائل الفرعية على الأقل (حساب من جذر القرص) ، فسيقوم النظام بوضع علامة على العمليات إذا كان المسار إلى الصورة المقابلة يحتوي على واحد على الأقل من هذه الخطوط:

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

إذا كان المسار إلى الملف القابل للتنفيذ يتوافق مع أحد هذه الأسطر ، فإن الخادم يتلقى إخطارًا حول المسار إلى الملف القابل للتنفيذ ، بالإضافة إلى معلومات حول ما إذا كانت العملية الأصل هي أحد الإجراءات التالية (تحتوي على بت الإشارة المطابقة المرسلة إلى الخادم):

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

إذا لم يتمكن العميل من فتح الواصف باستخدام حقوق QueryLimitedInformation المناسبة ، فسوف يقوم بتعيين علامة البت 0x04 ، إذا لم يكن سبب الخطأ عند فشل استدعاء OpenProcess هو ERROR_ACCESS_DENIED ، مما يعطينا حاوية التعداد الأخيرة لقيمة العلامة المقابلة:

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

إذا كانت العملية الرئيسية تعمل بالبخار ، فسيتم تعيين العلامة على الفور للمستخدم ويتم إبلاغ الخادم بهذا الأمر باستخدام معرف الإعلام 0x40

اسم الصورة


إذا كانت العملية تلبي أيًا من المعايير الكثيرة الموضحة أدناه ، فسيتم تعيين علامة على الفور ويتم إبلاغ ذلك إلى الخادم باستخدام معرف الإعلام 0x38

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

لعبة البخار تراكب


تراقب BattlEye عملية تراكب ألعاب Steam ، المسؤولة عن التراكب داخل اللعبة ، والمعروفة لدى معظم مستخدمي Steam. اسم المضيف الكامل لتراكب ألعاب Steam هو gameoverlayui.exe ؛ من المعروف أنه يستخدم غالبًا لتقديم عمليات الاستغلال ، لأنه من السهل جدًا اختراق عمليات التقديم غير القانونية وتنفيذها في نافذة اللعبة. التحقق له الشرط التالي:

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

تتطابق الفحوصات الإضافية المحددة لتراكب ألعاب Steam مع الإجراءات التي يتم تنفيذها لعملية اللعبة نفسها ، وبالتالي ، يتم حذفها في الشفرة الزائفة.

البخار تراكب مسح الذاكرة


يتم فحص عملية تراكب ألعاب Steam بحثًا عن الأنماط والشذوذ. لم نتمكن من التعمق في حفرة الأرانب ، ومعرفة ماهية هذه الأنماط ، لأنها معممة للغاية وعلى الأرجح مرتبطة بوحدات الغش.

 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 } 

يبحث إجراء المسح أيضًا عن أي حالات شاذة في شكل تعليمات برمجية قابلة للتنفيذ خارج الصور التي تم تنزيلها ، مما يشير إلى أن المفرقعات حقنت الكود في عملية التراكب:

 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 محمية ببعض الحماية من عمليات Windows مثل Light (WinTcb) ، فسيتلقى الخادم إشعارًا بذلك.

 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 } 

بالإضافة إلى ذلك ، إذا أعادت استدعاء OpenProcess المقابلة ERROR_ACCESS_DENIED إلى عملية التراكب ، يتم إرسال إشعار حول المستخدم ذي المعرف 3B .

وحدات الفرز


يتم أيضًا البحث عن وحدات عملية تراكب ألعاب Steam ، وعلى وجه الخصوص ، gameoverlayui.dll vgui2_s.dll و gameoverlayui.dll . يتم إجراء العديد من الاختبارات لهذه الوحدات ، بدءًا من gameoverlayui.dll .

إذا تم استيفاء هذا الشرط: [gameoverlayui.dll+6C779] == 08BE55DC3CCCCB8????????C3CCCCCC ???????? . إذا كان أي من هذه العناصر vtable خارج gameoverlayui.dll الوحدة النمطية المصدر أو يشير إلى إرشادات int 3 ، ثم يتم إبلاغ المستخدم إلى الخادم مع معرف إعلام 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 } } } 

يتم أيضًا تنفيذ إجراء تحقق محدد vgui2_s.dll النمطية 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 أيضًا:

 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 ، والمعروفة أيضًا باسم عملية مرجع الأمان المحلي ، ويتم الإبلاغ عن جميع الحالات الشاذة إلى الخادم ، كما في حالة الشيكتين السابقتين:

 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 المعلومات المختلفة وترسلها إلى الخادم باستخدام معرف الإعلام 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. إن وجدت ، يتم إنشاء إشعار. في الحالة الطبيعية ، لا يتوفر هذان الجهازان في النظام ، وقد يشير هذا إلى قيام شخص ما بتشغيل الجهاز يدويًا. هذه التقنية تسمى اختطاف جهاز التشغيل. هذا لضمان أن يتم تبادل بيانات 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 } } 

دلتا النوم


بالإضافة إلى ذلك ، يمكن لـ BattlEye طلب ثانية واحدة من عدم النشاط من الخيط الحالي وقياس الفرق في عدد الدورات قبل وبعد عدم النشاط (النوم):

 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 يتحقق أيضا الصور المختلفة التي تم تحميلها في اللعبة. من المفترض أن هذه الوحدات عبارة عن صور موقعة يتم التعامل معها بطريقة أو بأخرى ، مما يؤدي إلى تغيير سلوكها إلى خبيث ، لكن لا يمكننا قول أي شيء عنها ، فقط حول اكتشافها:

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

ومب


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

معرفات وحدة التعداد


كمرجع ، نعطي معرف التعداد للوحدات النمطية:

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

مسح جداول TCP


يبحث كود shell BattlEye عن قائمة باتصالات TCP للنظام بأكمله (المعروف باسم جدول TCP) ويبلغ إذا كان المستخدم متصلاً بأحد عناوين IP الخاصة ببوابة Cloudflare التابعة لموقع الدفع الإلكتروني الألمانية -cheat دعا xera.ph . تمت إضافة هذه الآلية إلى كود shell للكشف عن المستخدمين الذين يديرون المشغل عندما تكون اللعبة قيد التشغيل ، مما يسهل التعرف عليهم. المشكلة الوحيدة في هذه الآلية هي أن عناوين IP الخاصة ببوابة Cloudflare يمكنها تغيير المالكين لاحقًا. وإذا قام مالكها الجديد بتوزيع البرامج التي تتصل بخوادمه عبر منفذ معين ، فستحدث دون أدنى شك محفزات إيجابية مضادة للغش.

دفع إلى الغش مستخدمي مزود خدمة 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 } } 

أنواع الإخطارات


فيما يلي للإشارة إلى جميع أنواع الإعلامات المعروفة من رمز shell:

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


All Articles