الرئيسية التحديثات BattlEye shellcode
بمرور الوقت ، تتغير مكافحة الغش ، ولزيادة فعالية المنتج ، تظهر الوظائف وتختفي فيها. قبل عام ، أعددت وصفًا تفصيليًا لرمز BattlEye shellcode في
مدونتي [
الترجمة على Habré] ، وسيكون هذا الجزء من المقالة انعكاسًا بسيطًا للتغييرات التي أجريت على كود القشرة.
الطوابع الزمنية على القائمة السوداء
في تحليل حديث لـ BattlEye ، لم يكن هناك سوى طوابع زمنية للتجميع في قائمة حظر الظل ، ويبدو أن المطورين قرروا إضافة المزيد:
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 (?)
لم أتمكن من تحديد الطوابع الزمنية المتبقية ، واثنين من
0xF ******* هما التجزئة التي أنشأتها التجميعات الحتمية لـ Visual Studio. بفضلmottikraus و T0B1 لتحديد بعض الطوابع الزمنية.
الشيكات وحدة
كما أظهر التحليل الرئيسي ، فإن الميزة الرئيسية في BattlEye هي تعداد الوحدات ، ومنذ لحظة التحليل الأخير ، تمت إضافة وحدة أخرى إلى القائمة:
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); } }
ربما هذا هو الكشف عن بعض dlls الوكيل ، حيث يتم التحقق من حجم جدول إعادة التوجيه هنا.
عناوين النوافذ
في التحليل السابق ، تم وضع علامة على مختلف مزودي الغش بأسماء النوافذ ، ولكن منذ ذلك الحين توقف كود القشرة عن التحقق من رؤوس النوافذ هذه. تم استبدال قائمة عناوين النوافذ بالكامل بـ:
Chod's
Satan5
أسماء الصور
تشتهر BattlEye باستخدام طرق الكشف البدائية للغاية ، وأحدها هو قائمة سوداء بأسماء الصور. في كل عام ، تصبح قائمة أسماء الصور المحظورة أطول ، وعلى مدار الأحد عشر شهرًا الماضية تمت إضافة خمسة أسماء جديدة:
frAQBc8W.dll
C:\\Windows\\mscorlib.ni.dll
DxtoryMM_x64.dll
Project1.dll
OWClient.dll
تجدر الإشارة إلى أن وجود وحدة نمطية لها اسم مطابق لأي عنصر من عناصر القائمة لن يعني أنه سيتم حظرك على الفور. ينقل محرك التقارير أيضًا معلومات الوحدة الأساسية ، والتي يتم استخدامها على الأرجح لتمييز عمليات الاحتيال عن التصادمات على خادم BattlEye.
7 زيب
تم استخدام 7-Zip على نطاق واسع ويستمر استخدامه من قبل المشاركين في مشهد الغش كملء للذاكرة لفراغات الكود (أكواد الكود). تحاول BattlEye التعامل مع هذا من خلال إجراء فحص نزاهة رديء
للغاية ، والذي تغير منذ مقالتي السابقة:
void module::check_7zip() { const auto module_handle = GetModuleHandleA("..\\..\\Plugins\\ZipUtility\\ThirdParty\\7zpp\\dll\\Win64\\7z.dll");
يبدو أن مطوري BattlEye خمنوا أن مقالتي السابقة دفعت العديد من المستخدمين لتجاوز هذا الاختيار ببساطة عن طريق نسخ البايتات المرغوبة إلى الموقع الذي تم التحقق منه بواسطة BattlEye. كيف أصلحوا الموقف؟ لقد غيرنا عملية التحقق بمقدار ثمانية بايت واستمرنا في استخدام نفس الطريقة السيئة للتحقق من النزاهة. القسم القابل للتنفيذ للقراءة فقط ، وكل ما عليك القيام به هو تنزيل 7-Zip من القرص ومقارنة الأقسام التي تم نقلها مع بعضها البعض ؛ إذا كان هناك أي تباينات ، فهناك خطأ ما. على محمل الجد ، يا شباب ، أداء اختبارات النزاهة ليس بالأمر الصعب.
فحص الشبكة
لا يزال تعداد جدول TCP يعمل ، ولكن بعد أن أصدرت تحليلًا سابقًا ينتقد المطورين بسبب الإبلاغ عن عناوين IP الخاصة بـ Cloudflare ، ما زالوا يزيلون هذا الاختبار. لا يزال نظام مكافحة الغش يبلغ عن المنفذ الذي يستخدمه 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) {
شكرا IChooseYou ومجردة
BattlEye كومة الالتفافية
ألعاب القرصنة هي لعبة قطة وفأر مستمرة ، لذلك تنتشر شائعات عن حيل جديدة مثل النار. في هذا الجزء ، سوف نلقي نظرة على التقنيات الاستكشافية الجديدة التي تمت إضافتها مؤخرًا إلى ترسانتنا من قِبل موفر كبير من مكافحة الغش BattlEye. في معظم الأحيان ، وتسمى هذه التقنيات المشي المكدس. عادةً ما يتم تنفيذها من خلال معالجة دالة والانتقال من خلال المكدس لمعرفة من الذي يطلق على هذه الوظيفة تحديدًا. لماذا تحتاج إلى القيام بذلك؟ مثل أي برنامج آخر ، لدى المتسللين لألعاب الفيديو مجموعة من الوظائف المعروفة التي يستخدمونها للحصول على معلومات من لوحة المفاتيح أو الإخراج إلى وحدة التحكم أو حساب تعبيرات رياضية معينة. بالإضافة إلى ذلك ، يحب المتسللون إلى ألعاب الفيديو إخفاء وجودهم ، سواء في الذاكرة أو على القرص ، حتى لا يعثر عليهم برنامج مكافحة الغش. ولكن ما تنساه برامج الغش هو أنها تستدعي الوظائف بانتظام من مكتبات أخرى ، ويمكن استخدام هذا لاكتشاف الغش مجهولة. من خلال تطبيق محرك traversal المكدس لوظائف مثل
std::print
، يمكننا العثور على هذه الخداع حتى لو كانت ملثمين.
نفذت BattlEye "تجاوز كومة" ، على الرغم من حقيقة أنه لم يتم الإعلان عن ذلك بشكل علني وفي وقت نشر المقال لم تكن هناك سوى شائعات. انتبه إلى علامات الاقتباس - ما ستراه هنا ليس حقًا جولة مكدس حقيقية ، ولكن مجرد مزيج من التحقق من عنوان المرسل وتفريغ برنامج الاتصال. قد يمر تطبيق traversal مكدس حقيقي خلال مكدس وإنشاء مكدس استدعاء حقيقي.
كما أوضحت في مقال سابق حول BattlEye ، فإن نظام مكافحة الغش يعمل بشكل حيوي على تدوين كود القشرة إلى اللعبة عند تشغيلها. هذه الرموز قذيفة لها أحجام ومهام مختلفة ، ولا تنتقل في وقت واحد. من الخصائص المميزة لهذا النظام أن الباحثين بحاجة إلى تحليل حيوي لمكافحة الغش أثناء المباراة متعددة اللاعبين ، مما يعقد تحديد خصائص هذا الغش المضاد. كما يسمح لمكافحة الغش بتطبيق تدابير مختلفة على مستخدمين مختلفين ، على سبيل المثال ، لنقل وحدة نمطية أكثر عمقًا فقط إلى شخص لديه نسبة عالية بشكل غير عادي من جرائم القتل والوفيات ، وما شابه ذلك.
أحد رموز shell هذه ، BattlEye ، مسؤول عن إجراء تحليل المكدس هذا ؛ سوف نسميها
shellcode8kb لأنها أصغر قليلاً مقارنة بـ
shellcodemain ، الذي
وثقته هنا . هذا رمز shell صغير باستخدام الدالة
AddVectoredExceptionHandler بإعداد معالج استثناء vectorized ومن ثم تعيين اعتراض المقاطعة على الوظائف التالية:
GetAsyncKeyState
GetCursorPos
IsBadReadPtr
NtUserGetAsyncKeyState
GetForegroundWindow
CallWindowProcW
NtUserPeekMessage
NtSetEvent
sqrtf
__stdio_common_vsprintf_s
CDXGIFactory::TakeLock
TppTimerpExecuteCallback
للقيام بذلك ، فإنه يتكرر ببساطة حول قائمة الوظائف المستخدمة بشكل قياسي ، مع تعيين التعليمة الأولى من الوظيفة المقابلة إلى
int3 ، والتي يتم استخدامها كنقطة توقف. بعد تعيين نقطة توقف ، تمر جميع المكالمات إلى الوظيفة المقابلة من خلال معالج الاستثناء ، والذي لديه حق الوصول الكامل إلى السجلات والمكدس. بعد هذا الوصول ، يقوم المعالج الاستثنائي بتفريغ عنوان برنامج الاستدعاء من أعلى المكدس ، وإذا تم استيفاء أحد الشروط الاستدلالية ، يتم تفريغ 32 بايت من وظيفة الاستدعاء وإرسالها إلى خادم BattlEye بمعرف التقرير
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;
كما نرى ، يقوم المعالج الاستثنائي بتفريغ جميع وظائف الاستدعاء في حالة حدوث تغيير غير منتظم في صفحة الذاكرة أو عندما لا تنتمي الوظيفة إلى وحدة نمطية عملية معروفة (لم يتم تعيين نوع صفحة الذاكرة MEM_IMAGE بواسطة مصممي الخرائط اليدوية). كما يقوم بتفريغ وظائف الاتصال عندما يفشل في استدعاء
NtQueryVirtualMemory بحيث لا يرتبط الغش بمكالمة النظام هذه ويخفي الوحدة النمطية الخاصة بهم من تفريغ المكدس. الشرط الأخير مثير للاهتمام للغاية ، فهو يمثل جميع وظائف الاتصال التي تستخدم أداة
jmp qword ptr [rbx] - الطريقة المستخدمة في "محاكاة ساخرة لعنوان المرسل". تم
إصداره من قِبل عضو السكرتير المشارك الخاص بي namazso. يبدو أن مطوري BattlEye رأوا أن الناس يستخدمون طريقة الخداع هذه في ألعابهم وقرروا توجيهها مباشرة. تجدر الإشارة هنا إلى أن الطريقة التي وصفها namazsos تعمل بشكل جيد ، ما عليك سوى استخدام أداة مختلفة أو مختلفة تمامًا أو مجرد سجل مختلف - لا يهم.
تلميح مطور BattlEye: إن
CDXGIFactory::TakeLock
في ذاكرتك غير صحيح لأنك قمت (بطريق الخطأ أو عن قصد) بتمكين حشوة CC ، والتي تكون مختلفة تمامًا في كل مرة تقوم فيها بالتجميع. لتحقيق أقصى قدر من التوافق ، تحتاج إلى إزالة الحشو (البايت الأول في التوقيع) ، وبالتالي ستحصل على الأرجح على المزيد من الغشاشين :)
تبدو البنية الكاملة المرسلة إلى خادم BattlEye كما يلي:
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 في BattlEye
لا تزال لعبة القط والفأر في مجال اختراق الألعاب مصدرًا للابتكار في عمليات الاستغلال ومكافحة الغش. بدأ استخدام تقنية المحاكاة الافتراضية في ألعاب القرصنة في التطور بنشاط بعد ظهور برامج Hypervisor سهلة الاستخدام مثل
DdiMon Satoshi Tanda و
hvpp Peter Benes. يتم استخدام هذين المشروعين من قبل معظم الغش المدفوع لمشهد القراصنة تحت الأرض بسبب عتبة الدخول المنخفضة والوثائق التفصيلية. من المحتمل أن تؤدي هذه الإصدارات إلى تسريع سباق التسلح في مجال برامج Hypervisor ، التي بدأت الآن تظهر في مجتمع المتسللين. إليك ما يقوله المسؤول عن واحدة من أكبر مجتمعات اختراق الألعاب باستخدام
WLN للاسم المستعار حول هذا الموقف:
مع ظهور أنظمة Hypervisor الجاهزة للاستخدام في اختراق الألعاب ، أصبح من المحتم أن تركز برامج مكافحة الخداع مثل BattlEye على التعرف العام على المحاكاة الافتراضية.
يرجع الاستخدام الواسع النطاق لبرامج Hypervisor إلى التحسينات الحديثة في مكافحة الغش ، والتي تركت للمتسللين فرصًا قليلة جدًا لتعديل الألعاب بطرق تقليدية. يمكن تفسير شعبية برامج Hypervisor ببساطة تجنب مكافحة الغش ، لأن الظاهرية تعمل على تبسيط إخفاء المعلومات باستخدام آليات مثل
ربطات syscall و
MMU الافتراضية .
في الآونة الأخيرة ، نفذت BattlEye التعرف على برامج Hypervisor الشائعة مثل الأنظمة الأساسية المذكورة أعلاه (DdiMon، hvpp) باستخدام الاكتشاف المستند إلى الوقت. يحاول هذا الاعتراف اكتشاف قيم وقت تعليمات CPUID غير القياسية. CPUID هي تعليمة منخفضة التكلفة نسبيًا على معدات حقيقية ، وعادة ما تتطلب مائتي دورة فقط ، وفي البيئة الافتراضية ، يمكن أن يستغرق تنفيذه عشر مرات أطول بسبب العمليات غير الضرورية التي يسببها محرك الاستبطان. محرك الاستبطان لا يشبه المعدات الحقيقية ، التي تقوم ببساطة بتنفيذ العملية بالطريقة المتوقعة ، لأنه على أساس معيار تعسفي يتتبع ويغير البيانات التي يتم إرجاعها إلى الضيف بشكل مشروط.
حقيقة ممتعة: يتم استخدام CPUID بنشاط في إجراءات التعرف المؤقتة هذه لأنها تعليمة ذات ناتج غير مشروط ، بالإضافة إلى تعليمة غير متسلسلة. هذا يعني أن وحدة المعالجة المركزية (CPUID) تستخدم
كحاجز وتضمن اتباع التعليمات قبل وبعد ذلك ؛ في الوقت نفسه ، تصبح توقيتات مستقلة عن إعادة ترتيب التعليمات المعتادة. يمكنك أيضًا استخدام إرشادات مثل
XSETBV ، والتي تؤدي أيضًا إلى خروج غير مشروط ، ولكن لضمان توقيت مستقل ، سيتطلب ذلك نوعًا من تعليمات الحاجز بحيث لا يحدث أي إعادة ترتيب قبله أو بعده ، مما يؤثر على موثوقية التوقيت.
اعتراف
فيما يلي إجراء الاعتراف من وحدة BattlEye "BEClient2" ؛ لقد أجريت الهندسة العكسية وأعدت إنشاء الشفرة في pseudo-C ، ثم نشرتها على
twitter . في اليوم التالي لتغريدتي ، غير مطورو BattlEye بشكل غير متوقع تشويش BEClient2 ، على أمل أن يمنعني هذا من تحليل الوحدة. لم يتغير التعتيم السابق لأكثر من عام ، لكنه تغير في اليوم التالي لتغريدتي حوله - سرعة مثيرة للإعجاب.
void battleye::take_time() {
كما قلت أعلاه ، هذه هي تقنية التعرف الأكثر شيوعًا باستخدام تعليمات تم اعتراضها دون قيد أو شرط. ومع ذلك ، فهي عرضة لوقت مزيف ، وسنتحدث عن ذلك بالتفصيل في القسم التالي.
تجاوز الاعتراف
هذه الطريقة الاعتراف لديه مشاكل. أولاً ، من المحتمل حدوث وقت مزيف ، ويتم ذلك عادة بطريقتين: عن طريق تحويل TSC في VMCS أو تقليل TSC في كل مرة يتم فيها تنفيذ CPUID. هناك العديد من الطرق الأخرى للتعامل مع الهجمات المرتكزة على الوقت ، ولكن هذا الأخير أسهل في التنفيذ ، لأنه يمكنك ضمان أن يكون وقت تنفيذ التعليمات خلال دورة واحدة أو ساعتين من تزامن التنفيذ على معدات حقيقية. تعتمد صعوبة اكتشاف تقنية التزييف هذه المرة على خبرة المطور. في القسم التالي ، سننظر في تزييف الوقت وتحسين التنفيذ الذي تم إنشاؤه في BattlEye. السبب الثاني لهذا الخلل في طريقة التعرف هو أن تأخير CPUID (وقت التشغيل) في معالجات مختلفة يختلف اختلافًا كبيرًا اعتمادًا على قيمة الورقة. قد يستغرق الأمر ما يصل إلى 70-300 دورة على مدار الساعة لإكماله. المشكلة الثالثة في إجراء التعرف هذا هي استخدام SetThreadPriority. تُستخدم وظيفة Windows هذه لتعيين قيمة أولوية واصف الدفق المحدد ، لكن نظام التشغيل لا يستمع دائمًا إلى الطلب. هذه الوظيفة هي ببساطة اقتراح لزيادة أولوية الخيط ، وليس هناك ما يضمن حدوث ذلك. وبالتالي ، من الممكن أن تتأثر هذه الطريقة بالانقطاعات أو العمليات الأخرى.
في هذه الحالة ، من السهل تجاوز التعرف ، والتقنية الموضحة لوقت التزييف تؤدي إلى هزيمة طريقة التعرف هذه بشكل فعال. إذا أراد مطورو BattlEye تحسين هذه الطريقة ، فإن القسم التالي يقدم بعض التوصيات.
تحسن
يمكن تحسين هذه الميزة بعدة طرق. أولاً ، يمكنك تعطيل المقاطعات عن عمد وفرض أولوية سلسلة الرسائل عن طريق تغيير CR8 إلى أعلى IRQL. سيكون أيضًا مثاليًا لعزل هذا الاختيار في وحدة المعالجة المركزية واحدة. تحسين آخر: يجب عليك استخدام أجهزة ضبط وقت مختلفة ، ولكن العديد منها ليس دقيقًا مثل TSC ، ولكن يوجد جهاز ضبط وقت مثل APERF timer ، أو Actual Performance Clock. أوصي بهذا المؤقت لأنه من الصعب الغش به ويتراكم في عداد فقط عندما يكون المعالج المنطقي في حالة الطاقة C0. هذا بديل رائع لاستخدام TSC. يمكنك أيضًا استخدام ACPI أو HPET أو PIT timer أو GPU timer أو NTP timer أو PPERF timer ، والذي يشبه APERF ، ولكن تحسب التدابير التي يُنظر إليها على أنها تعليمات تنفيذية. عيب هذا هو أنك تحتاج إلى تمكين HWP ، والتي يمكن تعطيلها من قبل المشغل الوسيط ، وبالتالي فهي غير مجدية.
يوجد أدناه نسخة محسنة من إجراء التعرف الذي يجب إجراؤه في 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;
ملاحظة: IET تعني "وقت تنفيذ التعليمات".
ومع ذلك ، لا يزال الإجراء غير موثوق به للغاية في الكشف عن برامج Hypervisor الشائعة ، نظرًا لأن أوقات تشغيل CPUID يمكن أن تختلف اختلافًا كبيرًا.
سيكون من الأفضل مقارنة IET من الإرشادات اثنين. واحد منهم يجب أن يكون تأخير تنفيذ أطول من CPUID. على سبيل المثال ، قد يكون FYL2XP1 - تعليمة حسابية تستغرق وقتًا أطول قليلاً لإكمالها من متوسط IET لتعليم CPUID. بالإضافة إلى ذلك ، لا يسبب أي مصائد في برنامج Hypervisor ويمكن قياس وقتها بشكل موثوق. باستخدام هاتين الوظيفتين ، يمكن أن تنشئ وظيفة التوصيف صفيفًا لتخزين تعليمات IET CPUID و FYL2XP1. باستخدام مؤقت APERF ، سيكون من الممكن الحصول على الساعة الأولية لتعليم حسابي ، وتنفيذ التعليمات وحساب دلتا الساعة لذلك. يمكن تخزين النتائج في صفيف IET لدورات التنميط N ، والحصول على متوسط القيمة ، وتكرار العملية لـ CPUID. إذا كان وقت تنفيذ تعليمة CPUID أطول من التعليمات الحسابية ،هذه علامة موثوقة على أن النظام ظاهري ، لأن التعليمات الحسابية تحت أي ظرف من الظروف قد تقضي وقتًا أطول من تنفيذ CPUID للحصول على معلومات حول الشركة المصنعة أو الإصدار. سيكون هذا الإجراء للتعرف أيضًا على من يستخدمون TSC offset / scaling.وأكرر ، يتعين على المطورين فرض تمكين الربط للورق الحسابي لإجراء هذا الفحص على لب واحد ، وتعطيل المقاطعات وإجبار IRQL على تعيين الحد الأقصى للقيمة لضمان بيانات متسقة وموثوقة. سيكون من المدهش إذا قرر مطورو BattlEye تنفيذ ذلك ، لأنه يتطلب بذل المزيد من الجهد. في برنامج تشغيل kernel ، يأكل BattlEye روتينين آخرين للتعرف على الجهاز الظاهري ، ولكن هذا موضوع لمقال آخر.