كسر "صدع" بسيط مع غيدرة - الجزء 1

ربما يعرف الكثير من الأشخاص بالفعل نوعًا من الوحش هذا - Ghidra ("Hydra") وما الذي يأكله البرنامج عن كثب ، على الرغم من أن هذه الأداة كانت متاحة للجمهور مؤخرًا فقط - في مارس من هذا العام. لن أزعج القراء بوصف Hydra ووظائفها وما إلى ذلك. أنا متأكد من أن الأشخاص الموجودين في هذا الموضوع قد درسوا كل هذا بالفعل ، والذين لم يطلعوا بعد على الموضوع - يمكنهم القيام بذلك في أي وقت ، حيث أصبح من السهل الآن العثور على معلومات مفصلة على الإنترنت. بالمناسبة ، تم بالفعل تغطية أحد جوانب Hydra (تطوير المكونات الإضافية له) على Habré (مقالة ممتازة!) سأقدم فقط الروابط الرئيسية:


لذلك ، Hydra عبارة عن أداة فك تجميع وتقطيع تفاعلية مجانية عبر النظام الأساسي مع بنية معيارية ، مع دعم تقريبًا لجميع أبنية وحدة المعالجة المركزية الرئيسية وواجهة رسومية مرنة للعمل مع التعليمات البرمجية المفككة ، والذاكرة ، والرمز المسترجع (decompiled) ، ورموز تصحيح الأخطاء ، وأكثر من ذلك بكثير .

دعونا نحاول كسر شيء مع هذا هيدرا!

الخطوة 1. البحث ودراسة الكراك


ك "ضحية" نجد برنامج "crackme" بسيط. لقد ذهبت للتو إلى crackmes.one ، المشار إليه في البحث مستوى الصعوبة = 2-3 ("بسيط" و "متوسط") ، ولغة المصدر للبرنامج = "C / C ++" والنظام الأساسي = "Multiplatform" ، كما في لقطة الشاشة أدناه:



أسفر البحث عن 2 نتيجة (باللون الأخضر أدناه). تحولت الكراك الأول إلى 16 بت ولم تبدأ على Win10 64 بت ، ولكن الثانية ( level_2 بواسطة seveb ) ظهرت. يمكنك تنزيله من هذا الرابط .

تحميل وتفريغ الكراك. كلمة مرور الأرشيف ، كما هو موضح في الموقع ، هي crackmes.de . في الأرشيف ، نجد دليلين متوافقين مع نظامي Linux و Windows. على جهازي ، أذهب إلى دليل Windows وألتقي فيه "القابل للتنفيذ" الوحيد - level_2.exe . لنركض ونرى ما تريد:



يبدو وكأنه المشكله! عند بدء التشغيل ، لا يعرض البرنامج أي شيء. نحن نحاول تشغيلها مرة أخرى ، ونمررها بسلسلة تعسفية كمعلمة (فجأة ، هل تنتظر المفتاح؟) - ومرة ​​أخرى لا شيء ... ولكن لا تيأس. لنفترض أنه يتعين علينا أيضًا اكتشاف معلمات الإطلاق كمهمة! حان الوقت للكشف عن "السكين السويسري" - هيدرا.

الخطوة 2. إنشاء مشروع في هيدرا والتحليل الأولي


افترض أن لديك بالفعل Hydra مثبتة. إن لم يكن بعد ، ثم كل شيء بسيط.

تثبيت غيدرا
1) تثبيت JDK الإصدار 11 أو أعلى (لدي 12 )

2) قم بتنزيل Hydra (على سبيل المثال ، من هنا ) وتثبيته (في وقت كتابة هذا التقرير ، كان أحدث إصدار من Hydra هو 9.0.2 ، ولدي 9.0.1)

نطلق Hydra وفي مدير المشروع المفتوح يتم إنشاء مشروع جديد على الفور ؛ أعطيتها الاسم crackme3 (بمعنى ، تم إنشاء مشاريع crackme و crackme2 بالفعل من أجلي). المشروع ، في الواقع ، دليل للملفات ، يمكنك إضافة أي ملفات إليه للدراسة (exe ، dll ، إلخ). سنضيف على الفور level_2.exe ( ملف | استيراد أو المفتاح I فقط):



نرى أنه قبل الاستيراد ، حددت هيدرا الدجال التجريبي الخاص بنا باعتباره PE 32 بت (قابل للتنفيذ محمول) لنظامي التشغيل Win32 OS و x86. بعد الاستيراد ، لدينا انتظار لمزيد من المعلومات:



هنا ، بالإضافة إلى عمق البت المذكور أعلاه ، قد لا نزال مهتمين بترتيب endianness ، والذي في حالتنا هو القليل (من البايت المنخفض إلى العالي) ، والذي كان متوقعًا لمنصة Intel 86.

مع تحليل أولي ، لقد انتهينا.

الخطوة 3. إجراء التحليل التلقائي


حان الوقت لبدء التحليل التلقائي الكامل للبرنامج في هيدرا. يتم ذلك بالنقر المزدوج فوق الملف المقابل (level_2.exe). بفضل هيكلها المعياري ، توفر Hydra جميع وظائفها الأساسية بنظام مكون إضافي يمكن إضافته / تعطيله أو تطويره بشكل مستقل. نفس الشيء مع التحليل - كل مكون إضافي مسؤول عن نوع التحليل. لذلك ، أولاً ، نواجه هذه النافذة حيث يمكنك تحديد أنواع تحليل الاهتمام:

نافذة إعدادات التحليل

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

لذلك ، فإن التحليل كامل. يتم عرض نتائجها في نافذة متصفح كود:



هذه النافذة هي النافذة الرئيسية للعمل في هيدرا ، لذلك يجب عليك دراستها بعناية أكبر.

رمز متصفح واجهة نظرة عامة
تقسم إعدادات الواجهة الافتراضية النافذة إلى ثلاثة أجزاء.

في الجزء المركزي ، النافذة الرئيسية - قائمة من المفكك ، والتي تشبه إلى حد ما "إخوانها" في المؤسسة الدولية للتنمية ، OllyDbg ، إلخ. بشكل افتراضي ، تكون الأعمدة في هذه القائمة (من اليسار إلى اليمين): عنوان الذاكرة ، شفرة تشغيل الأمر ، أمر ASM ، معلمات أمر ASM ، مرجع تبادلي (إن أمكن). بطبيعة الحال ، يمكن تغيير العرض من خلال النقر على الزر في شكل جدار من الطوب في شريط الأدوات في هذه النافذة. بصراحة ، لم أر مثل هذا التكوين المرن لمخرج المفكك في أي مكان ، إنه مناسب للغاية.

في الجزء الأيسر من لوحة 3:

  1. أقسام البرنامج (انقر على الماوس للتنقل خلال الأقسام)
  2. شجرة الشخصيات (الواردات ، الصادرات ، الوظائف ، الرؤوس ، إلخ.)
  3. اكتب شجرة المتغيرات المستخدمة

بالنسبة لنا ، فإن النافذة الأكثر فائدة هنا هي شجرة الرموز ، والتي تتيح لك العثور بسرعة ، على سبيل المثال ، على وظيفة باسمها والانتقال إلى العنوان المقابل.

على الجانب الأيمن توجد قائمة برمز فك الشفرة (في حالتنا ، في C).

بالإضافة إلى النوافذ الافتراضية ، في قائمة النافذة ، يمكنك اختيار ووضع العشرات من النوافذ الأخرى وعرضها في أي مكان في المتصفح. للراحة ، أضفت إطار بايت ونافذة مع رسم بياني دالة إلى الوسط ، وإلى اليمين ، متغيرات السلسلة (سلاسل) وجدول وظائف (وظائف). هذه النوافذ متاحة الآن في علامات تبويب منفصلة. وأيضًا ، يمكن فصل أي نوافذ وجعلها "عائمة" ، ووضعها وتغيير حجمها حسب تقديرك - وهذا أيضًا حل مدروس للغاية ، في رأيي.

الخطوة 4. تعلم خوارزمية البرنامج - main () وظيفة


حسنًا ، دعنا ننتقل إلى التحليل المباشر لبرامج الكراك لدينا. في معظم الحالات ، يجب أن تبدأ بالبحث عن نقطة دخول البرنامج ، أي الوظيفة الرئيسية التي يطلق عليها عندما تبدأ. مع العلم أن الكراك قد كتب في C / C ++ ، فإننا نعتقد أن اسم الوظيفة الرئيسية سيكون الرئيسي () أو شيء من هذا القبيل :) يقال ويفعل. أدخل "الرئيسي" في مرشح شجرة الرموز (في اللوحة اليمنى) وانظر الوظيفة _main () في قسم الوظائف . انتقل إليها بنقرة ماوس.

نظرة عامة على الوظيفة الرئيسية () وإعادة تسمية الوظائف الغامضة


في قائمة disassembler ، يتم عرض قسم الكود المقابل على الفور ، وعلى اليمين نرى الكود C المعطل لهذه الوظيفة. تجدر الإشارة إلى ميزة واحدة أكثر ملاءمة لمزامنة اختيار Hydra: عندما يختار الماوس مجموعة من أوامر ASM ، يتم تمييز قسم الرمز المقابل في أداة فك الترميز والعكس. بالإضافة إلى ذلك ، إذا كانت نافذة عرض الذاكرة مفتوحة ، تتم مزامنة التخصيص مع الذاكرة. كما يقولون ، كل عبقري بسيط!

على الفور ، لاحظت ميزة مهمة للعمل في هيدرا (على عكس ، على سبيل المثال ، العمل في المؤسسة الدولية للتنمية). يركز العمل في Hydra بشكل أساسي على تحليل الشفرة المضغوطة . لهذا السبب ، فإن مبدعي هيدرا (نتذكر - نحن نتحدث عن جواسيس من NSA :)) قد أولوا اهتمامًا كبيرًا بجودة فك التشفير وراحة التعامل مع الكود. على وجه الخصوص ، يمكن للمرء ببساطة الانتقال إلى تحديد الوظائف والمتغيرات وأقسام الذاكرة بمجرد النقر المزدوج في الشفرة. وأيضًا ، يمكن إعادة تسمية أي متغير ووظيفة على الفور ، وهو أمر مريح للغاية ، لأن الأسماء الافتراضية لا تحمل معنى ويمكن أن تكون مربكة. كما سترى لاحقًا ، سنستخدم هذه الآلية غالبًا.

لذلك ، هنا هي الوظيفة الرئيسية () ، والتي "تشريح" هيدرا على النحو التالي:

القائمة الرئيسية ()
int __cdecl _main(int _Argc,char **_Argv,char **_Env) { bool bVar1; int iVar2; char *_Dest; size_t sVar3; FILE *_File; char **ppcVar4; int local_18; ___main(); if (_Argc == 3) { bVar1 = false; _Dest = (char *)_text(0x100,1); local_18 = 0; while (local_18 < 3) { if (bVar1) { _text(_Dest,0,0x100); _text(_Dest,_Argv[local_18],0x100); break; } sVar3 = _text(_Argv[local_18]); if (((sVar3 == 2) && (((int)*_Argv[local_18] & 0x7fffffffU) == 0x2d)) && (((int)_Argv[local_18][1] & 0x7fffffffU) == 0x66)) { bVar1 = true; } local_18 = local_18 + 1; } if ((bVar1) && (*_Dest != 0)) { _File = _text(_Dest,"rb"); if (_File == (FILE *)0x0) { _text("Failed to open file"); return 1; } ppcVar4 = _construct_key(_File); if (ppcVar4 == (char **)0x0) { _text("Nope."); _free_key((void **)0x0); } else { _text("%s%s%s%s\n",*ppcVar4 + 0x10d,*ppcVar4 + 0x219,*ppcVar4 + 0x325,*ppcVar4 + 0x431); _free_key(ppcVar4); } _text(_File); } _text(_Dest); iVar2 = 0; } else { iVar2 = 1; } return iVar2; } 


يبدو أن كل شيء يبدو عاديًا - تعريفات المتغيرات وأنواع C القياسية والظروف والحلقات ومكالمات الوظائف. لكن من خلال إلقاء نظرة فاحصة على الكود ، نلاحظ أنه لسبب ما لم يتم تعريف أسماء بعض الوظائف واستبدالها بوظيفة pseudo- _text () (في نافذة أداة فك التشفير - .text () ). لنبدأ بتحديد هذه الوظائف.

النقر المزدوج على نص المكالمة الأولى

  _Dest = (char *)_text(0x100,1); 

نرى أن هذه مجرد وظيفة التفاف حول وظيفة calloc () القياسية ، والتي تستخدم لتخصيص الذاكرة للبيانات. لذلك دعونا فقط إعادة تسمية هذه الوظيفة إلى calloc2 () . اضبط المؤشر على رأس الوظيفة ، واتصل بقائمة السياق وحدد إعادة تسمية وظيفة (مفتاح التشغيل السريع - L ) وأدخل اسمًا جديدًا في الحقل الذي يفتح:



نرى أنه تمت إعادة تسمية الوظيفة على الفور. نعود إلى الجسم الرئيسي () (زر الخلف في شريط الأدوات أو Alt + <- ) ونرى أنه هنا بدلاً من الكلمة المفتاحية _text () calloc2 () الغامضة بالفعل. ! ممتاز

نحن نفعل الشيء نفسه مع جميع وظائف المجمع الأخرى: نذهب إلى تعريفاتهم واحدة تلو الأخرى ، وننظر في ما يقومون به ، وإعادة تسميتهم (أضفت الفهرس 2 إلى الأسماء القياسية لوظائف C) ونعود إلى الوظيفة الرئيسية.

نحن نفهم رمز الوظيفة الرئيسي ()


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

 if (_Argc == 3) { ... } 

_Argc هو عدد معلمات سطر الأوامر (الوسائط) التي تم تمريرها إلى main () . وهذا يعني أن برنامجنا "يأكل" وسيطات اثنين (الوسيطة الأولى ، كما نتذكر ، هي دائمًا الطريق إلى الملف القابل للتنفيذ).

حسنا ، دعنا ننتقل. هنا نقوم بإنشاء سلسلة C (صفيف char ) من 256 حرفًا:

 char *_Dest; _Dest = (char *)calloc2(0x100,1); //  new char[256]  C++ 

التالي لدينا حلقة من 3 التكرار. في ذلك ، نتحقق أولاً من تحديد علامة bVar1 ، وإذا كان الأمر كذلك ، انسخ وسيطة سطر الأوامر التالية (السلسلة) إلى _Dest :

 while (i < 3) { /*    .  */ if (bVar1) { /*   */ memset2(_Dest,0,0x100); /*    _Dest    */ strncpy2(_Dest,_Argv[i],0x100); break; } ... } 

يتم تعيين هذه العلامة عند تحليل الوسيطة التالية:

 n_strlen = strlen2(_Argv[i]); if (((n_strlen == 2) && (((int)*_Argv[i] & 0x7fffffffU) == 0x2d)) && (((int)_Argv[i][1] & 0x7fffffffU) == 0x66)) { bVar1 = true; } 

يحسب السطر الأول طول هذه الوسيطة. علاوة على ذلك ، يتحقق الشرط من أن طول الوسيطة يجب أن يكون 2 والحرف قبل الأخير == "-" والحرف الأخير == "f". لاحظ كيف يقوم "decompiler" بترجمة استخراج الأحرف من السلسلة باستخدام قناع البايت.
يمكن تجسس القيم العشرية للأرقام ، وفي نفس الوقت أحرف ASCII المقابلة ، عن طريق الضغط على المؤشر فوق الحرف السداسي العشري المقابل. لا يعمل تعيين ASCII دائمًا (؟) ، لذلك أوصي بالنظر في جدول ASCII على الإنترنت. يمكنك أيضًا مباشرة في Hydra تحويل الأرقام القياسية من أي نظام أرقام إلى أي نظام آخر (عبر قائمة السياق -> تحويل ) ، في هذه الحالة سيتم عرض هذا الرقم في كل مكان في نظام الأرقام المحدد (في أداة فك الشفرة وفي أداة فك الشفرة) ؛ ولكن شخصيا ، أنا أفضل أن أترك الهيكس في الكود من أجل الانسجام في العمل ، لأنه عناوين الذاكرة ، الإزاحة ، إلخ. يتم تعيين hexes في كل مكان.
بعد الحلقة يأتي هذا الرمز:

 if ((bVar1) && (*_Dest != 0)) { /*    1) "-f"  2)  -         */ _File = fopen2(_Dest,"rb"); if (_File == (FILE *)0x0) { /*  1    */ perror2("Failed to open file"); return 1; } ... } 

أنا هنا على الفور وأضاف التعليقات. نحن نتحقق من صحة الوسائط ("-f path_to_file") ونفتح الملف المقابل (تم تمرير الوسيطة الثانية ، والتي قمنا بنسخها إلى _Dest). سيتم قراءة الملف بالتنسيق الثنائي ، كما هو موضح بواسطة المعلمة "rb" للدالة fopen () . إذا فشلت القراءة (على سبيل المثال ، الملف غير متوفر) ، يتم عرض رسالة خطأ في دفق stderror وينتهي البرنامج بالكود 1.

التالي هو الأكثر إثارة للاهتمام:

  /* !!!     !!! */ ppcVar3 = _construct_key(_File); if (ppcVar3 == (char **)0x0) { /*    ,  "Nope" */ puts2("Nope."); _free_key((void **)0x0); } else { /*    -      */ printf2("%s%s%s%s\n",*ppcVar3 + 0x10d,*ppcVar3 + 0x219,*ppcVar3 + 0x325,*ppcVar3 + 0x431); _free_key(ppcVar3); } fclose2(_File); 

يتم تمرير واصف الملف المفتوح ( _File ) إلى الدالة _construct_key () ، والتي من الواضح أنها تقوم بالتحقق من المفتاح المطلوب. هذه الدالة تقوم بإرجاع صفيف بايت ثنائي الأبعاد ( char ** ) ، والذي يتم تخزينه في متغير ppcVar3 . إذا كانت المصفوفة فارغة ، فسيتم عرض "Nope" الموجزة على وحدة التحكم (أي ، في رأينا ، "Nope!") ويتم تحرير الذاكرة. بخلاف ذلك (إذا لم تكن المصفوفة فارغة) ، يتم عرض المفتاح الصحيح ظاهرًا كما يتم تحرير الذاكرة. في نهاية الوظيفة ، يتم إغلاق واصف الملف ، ويتم تحرير الذاكرة ، ويتم إرجاع قيمة iVar2 .

لذلك ، أدركنا الآن أننا بحاجة:

1) إنشاء ملف ثنائي مع المفتاح الصحيح ؛
2) اجتياز طريقها في الكراك بعد الحجة "-f"

في الجزء الثاني من المقالة ، سنقوم بتحليل الوظيفة _construct_key () ، والتي ، كما اكتشفنا ، مسؤولة عن التحقق من المفتاح المطلوب في الملف.

Source: https://habr.com/ru/post/ar447450/


All Articles