
في الآونة الأخيرة ، بدأ ظهور
الكبسولات على العديد من المواقع الأجنبية - وهو نظام يزيد من أمان الموقع وسرعته وفي نفس الوقت يعقد بشكل كبير حياة مطوري البرامج. جوهر هذا النظام هو الحماية الشاملة باستخدام جافا سكريبت ، والتي ، بالمناسبة ، تعلمت العديد من برامج الروبوت DDOS بالفعل تنفيذ CloudFlare وحتى تجاوزه. اليوم سوف ندرس الغلاف الداخلي ، ونكتب deobfuscator JS script ونعلم بوت
DDOS الخاص بنا للتغلب عليه!
يتم أخذ الموقع في لقطة الشاشة أدناه كمثال جيد لمقالة ، ولا يختلف عن الآخرين ، في منتديات الموضوعات المشكوك فيها ، يبحث الكثيرون عنها ، ولكن لدي مهمة أخرى - برنامج لأتمتة إجراءات مختلفة مع المواقع.
دعنا نبدأ بفحص الاستفسارات ، يوجد فقط 2 منها ، وهنا الأول:

يقوم هذا الطلب بتحميل نص برمجي مشوش بشكل طبيعي:

يؤدي تغيير تقييم إلى
document.write () إلى توفير رمز يمكن قراءته بشكل طفيف:

لا أعرف السبب ، ولكن الأدوات التلقائية لتنسيق رمز الإخراج تكسر هذا الرمز ، لذلك كان علي تنسيقه بيدي وإعطاء المتغيرات أسماء عادية. لجميع هذه التلاعبات ، استخدمت المفكرة البسيطة ووظيفة استبدال النص ، ونتيجة لذلك يمكننا الانتقال وفحص السطر الأول:
var _0x3a59=['wpgXPQ==','cVjDjw==','Tk/DrFl/','GMOMd8K2w4jCpw==','wpkwwpE=','w6zDmmrClMKVHA==', ... ,'w4w+w5MGBQI=','w6TDr8Obw6TDlTJQaQ=='];
هذا مصفوفة تحتوي على أسماء وظائف مشفرة وسلاسل أخرى ، لذلك نحتاج إلى البحث عن وظيفة تقوم بفك تشفير هذا الصفيف ، لا حاجة للذهاب بعيدًا:
رمز إلغاء التشويش var Decrypt=function(base64_Encoded_Param,Key_Param){ var CounterArray=[], CRC=0x0, TempVar, result='', EncodedSTR=''; base64_Encoded_Param=atob(base64_Encoded_Param); for(var n=0,input_length=base64_Encoded_Param.length;n<input_length;n++){ EncodedSTR+='%'+('00'+base64_Encoded_Param.charCodeAt(n).toString(0x10)).slice(-0x2); } base64_Encoded_Param=decodeURIComponent(EncodedSTR); for(var n=0x0;n<0x100;n++){ CounterArray[n]=n; } for(n=0x0;n<0x100;n++){ CRC=(CRC+CounterArray[n]+Key_Param.charCodeAt(n%Key_Param.length))%0x100; TempVar=CounterArray[n]; CounterArray[n]=CounterArray[CRC]; CounterArray[CRC]=TempVar; } n=0x0; CRC=0x0; for(var i=0x0;i<base64_Encoded_Param.length;i++){ n=(n+0x1)%0x100; CRC=(CRC+CounterArray[n])%0x100; TempVar=CounterArray[n]; CounterArray[n]=CounterArray[CRC]; CounterArray[CRC]=TempVar; result+=String.fromCharCode(base64_Encoded_Param.charCodeAt(i)^CounterArray[(CounterArray[n]+CounterArray[CRC])%0x100]); } return result; };
إذا نسميها بشكل منفصل:
ParamDecryptor('0x0', '0Et]')
وستكون هذه النتيجة بعيدة عما توقعنا. وكل شيء هو المسؤول عن وظيفة أخرى:
var shift_array=function(number_of_shifts){ while(--number_of_shifts){ EncodedParams['push'](EncodedParams['shift']()); } };
وهو في مكان غير متوقع للغاية - في وظيفة يتم استدعاؤها في البداية وتتحقق من ملفات تعريف الارتباط. على ما يبدو ، قام المطورون "بحماية" فحص ملف تعريف الارتباط من قطعه. كما ترى - لا يوجد شيء معقد ، تقوم الحلقة ببساطة بتغيير المصفوفة بعدد العناصر المحدد ، في حالتنا 223 عنصرًا. من أين يأتي هذا الرقم السحري 223؟ لقد أخذت هذا الرقم من مكالمة إلى وظيفة التحقق من ملفات تعريف الارتباط ، يبدو أن 0xdf هناك ويمضي على هذا المسار:
بطبيعة الحال ، يتغير في كل مرة ، من يشكك ...
الآن كل ما تبقى هو استبدال جميع المكالمات
var _0x85e545=this[ParamDecryptor('0x0', '0Et]')];
على
var _0x85e545=this['window'];
أو الأفضل من ذلك
var ThisWindow=this.window;
لقد أجريت التحويل الأخير بشكل منتظم. أوه نعم ، لقد نسوا تمامًا إعطاء الخطوط التالية:
\x77\x6f\x4a\x33\x58\x68\x6b\x59\x77\x34\x44\x44\x6e\x78\x64\x70
إلى العرض العادي. لا يوجد شيء معقد ، هذا UrlEncode عادي ، قم بتغيير \ x إلى٪ وفك الشفرة للحصول على السطر التالي:
woJ3XhkYw4DDnxdp
ثم بدأت في استبدال جميع المكالمات
ParamDecryptor('0x0', '0Et]')
إلى خطوط مشفرة بالفعل باستخدام وظيفة مكتوبة ذاتيا من وحدتي. نعم ، الرمز لا يتألق بالجمال ، كانت المواعيد النهائية تحترق (كما كان مطلوبًا عادةً أمس) وكنت كسولًا جدًا للتفكير
لأنني اعتدت البرمجة باستخدام الماوس ، ومع ذلك ، فإنه يعمل بشكل جيد:

أعيدت كتابة الرمز من المصدر تقريبًا 1v1.
بعد ذلك ، لفتت انتباهي طريقة أخرى لإخفاء الرمز. اضطررت إلى كتابة دالة كبيرة إلى حد ما تبحث عن مثل هذه المكالمات:
case'7':while(_0x30fe16["XNg"](_0x13d8ee,_0x5a370d))
واستبدالها بنظائرها الأبسط:
قائمة الميزات الرائعة var _0x30fe16={ 'XNg':function _0x19aabd(_0x425e3c,_0x481cd6){return _0x425e3c<_0x481cd6;}, 'sUd':function _0x320363(_0xa24206,_0x49d66b){return _0xa24206&_0x49d66b;}, 'wMk':function _0x32974a(_0x2cdcf4,_0x250e85){return _0x2cdcf4>>_0x250e85;}, 'FnU':function _0x22ce98(_0x2f5577,_0x4feea7){return _0x2f5577<<_0x4feea7;}, 'mTe':function _0x35a8bc(_0x11fecf,_0x29718e){return _0x11fecf&_0x29718e;}, 'doo':function _0x5ce08b(_0x4e5976,_0x4757ea){return _0x4e5976>>_0x4757ea;}, 'vmP':function _0x5d415c(_0x39dc96,_0x59022e){return _0x39dc96<<_0x59022e;}, 'bGL':function _0xd49b(_0x7e8c9f,_0x301346){return _0x7e8c9f|_0x301346;}, 'rXw':function _0x4dfb4d(_0x39d33a,_0x36fd1e){return _0x39d33a<<_0x36fd1e;}, 'svD':function _0x387610(_0x3cd4f7,_0x58fd9e){return _0x3cd4f7&_0x58fd9e;}, 'cuj':function _0x472c54(_0x4e473a,_0x26f3fd){return _0x4e473a==_0x26f3fd;}, 'OrY':function _0x3c6e85(_0x445d0b,_0x1caacf){return _0x445d0b|_0x1caacf;}, 'AKn':function _0x4dac5b(_0x521c05,_0x27b6bd){return _0x521c05>>_0x27b6bd;}, 'gtj':function _0x5416f0(_0x3e0965,_0x560062){return _0x3e0965&_0x560062;} };
للحصول على:
case'7':while(_0x13d8ee < _0x5a370d){
خوارزمية العمل بسيطة للغاية ، في الواقع ، نحن فقط نستبدل المتغيرات:
- نجد اسم الصفيف في حالتنا: _0x30fe16
- معلمات إدخال Parsim: _0x425e3c ، _0x481cd6
- نص دالة Parsim: _0x425e3c <_0x481cd6
- استبدل _0x425e3c بـ _0x13d8ee
- استبدل _0x481cd6 بـ _0x5a370d
- نحصل على : _0x13d8ee <_0x5a370d
- استبدل _0x30fe16.XNg (_0x13d8ee، _0x5a370d) بالرمز أعلاه
- كرر حتى تنتهي الوظائف
يتم تحليل اسم الوظيفة ومعلماتها ونصها بواسطة شخص عادي. بالطبع ، في النسخة النهائية من الوحدة ، هذه الطريقة ليست مطلوبة بشكل خاص ، هناك مكالمة واحدة فقط أكثر تشويشًا قليلاً ، لكن العميل قال أن يفعل كل شيء وبالتالي قام به ، بالإضافة إلى ذلك ، أصبحت الوظائف الأخرى أكثر وضوحًا أيضًا. على مواقع أخرى هناك أيضًا مثل هذه التصاميم:
إظهار رمز كبير وسيئ var _0x4dc9f4 = { 'NTSjj': _0x3d6e1f.dEVDh, 'tZeHx': function(_0x2a40cd, _0x2faf22) { return _0x3d6e1f.JIehC(_0x2a40cd, _0x2faf22); }, 'ocgoO': "https://site/login", 'WmiOO': _0x3d6e1f.vsCuf };
كما ترى هنا ، تشير معلمة من صفيف إلى آخر. لقد قمت بحل هذه المشكلة ببساطة:
- Parsim جميع المصفوفات
- نحن ننظفهم من الكود
- انسخ كل العناصر في مصفوفة ترابطية (الاسم ، القيمة)
- في حلقة بواسطة بحث متكرر ، نبحث عن جميع الوظائف المتداخلة
- استبدل الوظائف المتداخلة بالإجراءات التي تقوم بها
- قم بتوسيع جميع الروابط إلى السلاسل بنفس الطريقة
بعد تطبيق طريقة إزالة التعتيم هذه ، أصبحت الشفرة أقل إرباكًا. يمكنك على الفور ملاحظة وظيفة base64 بطريقتين. الأول:
CharArray="ABCDE...XYZabcde...xyz0123456789+/";
والثاني:
if(!window["btoa"])window["btoa"]=_0x386a89;
لم يعد بإمكانك التراجع والانتقال إلى وظائف أخرى أكثر أهمية ، أو أن تكون أكثر دقة ، إلى وظيفة تعمل مع ملفات تعريف الارتباط. لقد وجدتها على السطر
incap_ses_ ولاحظت شريحة تشويش أخرى -
تشفير الشفرة باستخدام الحلقات:
أظهر الكود var _0x290283="4|2|5|0|3|1"["split"]('|'), _0x290611=0x0; while(!![]){ switch(_0x290283[_0x290611++]){ case'0':for(var n=0x0;n<CookieArray["length"];n++){ var _0x27e53a=CookieArray[n]["substr"](0x0,CookieArray[n]["indexOf"]('=')); var _0x4b4644=CookieArray[n]["substr"](CookieArray[n]["indexOf"]('=')+0x1,CookieArray[n]["length"]); if(_0x5ebd6a["test"](_0x27e53a)){ResultCookieArray[ResultCookieArray["length"]]=_0x4b4644;} } continue; case'1':return ResultCookieArray;continue; case'2':var _0x5ebd6a=new this.window.RegExp("^\s?incap_ses_");continue; case'3':_0x4d5690();continue; case'4':var ResultCookieArray=new this.window.Array();continue; case'5':var CookieArray=this.window.document.cookie["split"](';');continue; } break; }
كل شيء بسيط للغاية هنا: نعيد ترتيب الخطوط وفقًا لترتيب التنفيذ: 4 | 2 | 5 | 0 | 3 | 1 ونحصل على الوظيفة الأصلية. لا حاجة أيضًا إلى طريقة إزالة التعتيم هذه في الإصدار النهائي ، لكنها لم تسبب مشاكل كبيرة ، حيث يتم تحليل كل شيء بطريقة أولية ، والشيء الرئيسي الذي يجب مراعاته هو أنه يمكن أن يكون هناك حلقات متداخلة ، وبالتالي قمت فقط بالبحث العودي.
وظيفة ملفات تعريف الارتباط var _0x30fe16={ function _0x2829d5(){ var ResultCookieArray=new this.window.Array(); var _0x5ebd6a=new this.window.RegExp("^\s?incap_ses_"); var CookieArray=this.window.document.cookie["split"](';'); for(var n=0x0;n<CookieArray["length"];n++){ var _0x27e53a=CookieArray[n]["substr"](0x0,CookieArray[n]["indexOf"]('=')); var _0x4b4644=CookieArray[n]["substr"](CookieArray[n]["indexOf"]('=')+0x1,CookieArray[n]["length"]); if(_0x5ebd6a["test"](_0x27e53a)){ResultCookieArray[ResultCookieArray["length"]]=_0x4b4644;} } _0x4d5690(); return ResultCookieArray; }
يقوم ببساطة
بتخزين قيم جميع ملفات تعريف الارتباط التي تبدأ بـ
incap_ses_ في
مصفوفة ثم تقوم طريقة أخرى بحساب المجموع الاختباري لها ببساطة عن طريق جمع رموز ASCII:
أظهر الكود function TIncapsula.CharCRC(text: string): string; var i, crc:integer; begin crc:=0; for i:=1 to Length(text) do crc:=crc+ord(text[i]); result:=IntToStr(crc); end; function TIncapsula.GetCookieDigest: string; var i:integer; res:string; begin res:=''; for i:=0 to FCookies.Count-1 do begin if res='' then res:=CharCRC(browserinfo+FCookies[i]) else res:=res+','+CharCRC(browserinfo+FCookies[i]); end; result:=res; end;
سنحتاج إلى المجموع الاختباري قليلاً ، والآن لنكتشف نوع الوظيفة التي يتم استدعاؤها من _0x4d5690 التي يتم استدعاؤها من أماكن مختلفة. للقيام بذلك ، ما عليك سوى إلقاء نظرة على الأساليب المطلوبة وتعيين الأسماء المناسبة لها:
function CheckDebugger(){ if(new this.window.Date()["getTime"]() - RunTime) > 0x1f4){ FuckDebugger(); } }
كاتب هذا النص ساذج جدا :)
نقطة مهمة أخرى:
ParamDecryptor('0x65', '\x55\xa9\xf9\x1c\x1a\xd5\xfc\x60')
من هنا نحتاج إلى الأحرف الخمسة الأولى:
ca3XP ، سأخبرك بالسبب أدناه. وتذكر ، كنا نحسب المجموع الاختباري من قيم ملفات تعريف الارتباط؟ الآن نحن بحاجة لهم للحصول على ما يسمى التجزئة.
وظيفة التجزئة function TIncapsula.GetDigestHash(Digest: string): string; var i:integer; CookieDigest, res:string; begin CookieDigest:=GetCookieDigest;
قارن:

عظيم! تبقى الخطوة الأخيرة - تلقي ملفات تعريف الارتباط للاستجابة:
ResCooka=((((ParamDecryptor(btoa(PluginsInfo),"ca3XP")+",digest=")+DigestArray)+",s=")+AllDigestHash); Set_Cookies("___utmvc",btoa(ResCooka),0x14);
أضافت الشفرة الأصلية في البداية معلمات المتصفح المشفرة في base64 إلى نهاية صفيف AllParams المحول ، و "مشفرة" باستخدام وظيفة ParamDecryptor باستخدام مفتاح
ca3XP ثم حذفت العنصر الذي تمت إضافته سابقًا. يمكنني أن أفترض أن هذا العكاز تم إنشاؤه بسبب ميزة صغيرة: تقبل وظيفة ParamDecryptor فهرس العنصر في الصفيف والمفتاح ، مما يعني أنه يمكنك نقل السلسلة هناك فقط من خلال الصفيف. لماذا لا تفعل ذلك بشكل صحيح؟ المبرمجين يا سيدي.
حسنًا ، في الواقع كل شيء ، ملف تعريف الارتباط جاهز ، يبقى تثبيته وإرسال طلب. صحيح أنك لن تقبل ذلك بسبب تفاصيل صغيرة واحدة أفضل أن أبقى صامتًا بشأنها.
التحسين
إن أجزاء الكود في دلفي هي مجرد نموذج أولي. تم إعادة كتابة جميع كود deobfuscator في المجمع بسبب متطلبات العملاء وزادت سرعة التنفيذ عدة مرات. ما يلي كان له تأثير إيجابي على السرعة:
- قطع القطع الزائدة من التعليمات البرمجية والمصفوفات في تكرار واحد من الحلقة. يعد ذلك ضروريًا لتقليل كمية التعليمات البرمجية بشكل كبير وتسريع البحث في المستقبل.
- نظرًا لأن الوظائف في الرمز ليست مختلطة ، فنحن نعرف موقعها التقريبي ، لذلك إذا كانت وظيفة تثبيت ملف تعريف الارتباط في النهاية ، فأنت بحاجة إلى البحث عنها على الأقل من الوسط.
- ابحث عن الميزات الرئيسية مقدمًا. تبحث خوارزمية التجميع عنها حتى في وقت تنظيف الرمز من القمامة غير الضرورية.
- في النسخة النهائية ، تخلصت من حوالي نصف وظائف deobfuscator التي كانت ضرورية لفهم الكود ولم تكن ضرورية للبوت لأن المعلمات الضرورية حصلت دون مشاكل
الخلاصة
عندما أقوم بزيارة الموقع ، أريده أن يعمل بسرعة ، وأن تشويش النصوص البرمجية لـ JS باستخدام هذه الطريقة لا يحترم المستخدم. هل هذا يساعد على الحماية من البوتات؟ لا ، بالطبع ، كما ترون ، فإن التكلفة تكلف حرفيا خلال المساء ، وتكاليف قليلة فقط من السندويشات وعدد قليل من أكواب الشاي.
الغرض من هذه المقالة هو التحدث عن مبدأ تشغيل هذا الحل وإظهار مدى جدواه. لأسباب واضحة ، لن يتم نشر وحدة جاهزة للتحايل على هذه الحماية (بعد تسربها ، تحول توقف اللعبة الضعيف إلى akamai) ، الذي يحتاج إلى القيام بذلك بنفسه بناءً على هذه الدراسة (التي تم إجراؤها منذ حوالي عام ولا تزال ذات صلة) ، وأطفال المدارس الذين يريدون لترويش حسابات الآخرين تذهب إلى الغابة. إذا كانت لديك أسئلة ، فأنا مستعد دائمًا للإجابة عنها في التعليقات.