بعد إتقان الأدوات المذكورة أعلاه، يمكنك البدء في تطوير المكونات الإضافية. أولاً، يجب أن تفكر في الاحتياجات التي يحتاج البرنامج الإضافي إلى تنفيذها والعثور على طرق العرض ووحدات التحكم ذات الصلة. الخطوة الأولى هي استخدام class-dump لتحليل APP المغطى. يمكن لـ Class-dump تحليل جميع أسماء الفئات وإعلانات الطرق بناءً على جدول الرموز في ملف Mach-O.
~ » ./class-dump -H -o /header/path WeChat
بعد تصدير ملفات الرأس، اسحب ملفات الرأس هذه إلى مشروع XCode لتسهيل عمليات البحث اللاحقة.
بعد حصولك على ملف الرأس، ستحتاج إلى قفله في فئة وحدة التحكم الخاصة بالواجهة ذات الصلة. استخدم OpenSSH للاتصال بجهاز iPhone مكسور الحماية مع جهاز Mac، واستخدم Cycript لإدخال العملية، واستدعاء الطريقة [[UIApp keyWindow] recursiveDescription]
للحصول على التسلسل الهرمي للعرض الحالي، والحصول على عنوان العرض واستدعاء [#0x2b2b2b00 nextResponder]
بشكل متكرر [#0x2b2b2b00 nextResponder]
، ستحصل أخيرًا على فئة وحدة التحكم في الواجهة.
عند هذه النقطة، هناك بشكل عام نقطتي دخول لمواصلة التحليل العكسي.
إذا أردنا تشغيل عملية الخطاف من خلال حدث اللمس لطريقة عرض على الواجهة، فهناك عمومًا طريقتان لتحديد موقع الإجراء:
[button allTargets]
للحصول على عنوان الأهداف، [button actionsForTarget: Targets forControlEvent: [button allControlEvents]]
للحصول على طريقة الإجراء المقابلة.يمكن استخدام مصدر بيانات وحدة التحكم أثناء كتابة المكونات الإضافية. يمكن الحصول على مصدر البيانات من خلال تحليل قائمة متغيرات الأعضاء لفئة وحدة التحكم، أو من خلال تحليل طريقة وكيل مصدر البيانات لبعض طرق العرض (مثل TableView). باستخدام مصدر البيانات، يمكن الحصول على معلومات أكثر ثراءً حول وحدة التحكم.
قد يكون البرنامج الإضافي البسيط قادرًا على قفل الهدف وربطه باستخدام Cycript فقط، ولكن في كثير من الحالات تحتوي وظيفة الهدف على معلمات متعددة ويكون المنطق معقدًا نسبيًا، إذا كنت تريد معرفة تفاصيل تنفيذها الداخلي، فيجب عليك ذلك استخدم أداة التحليل الثابت IDA ---- يمكنها فك التعليمات البرمجية المكتوبة في Objective-C إلى كود التجميع، وستكون تفاصيل تنفيذ الوظيفة واضحة في لمحة. عند تحليل نتائج التفكيك، نظرًا لخصائص تمرير الرسالة في Objective-C، تستدعي الرسالة العامة المرسلة فعليًا وظيفة objc_msgSend
. فقط تذكر معنى كل معلمة من objc_msgSend
واتفاقية الاتصال الخاصة بهندسة ARM، ويمكنك استعادة استدعاء الوظيفة بنجاح من رمز التجميع. تقليد الاستدعاء هو أن المعلمات الأربع الأولى للوظيفة يتم تمريرها باستخدام السجلات العامة R0-R3، ويتم دفع المزيد من المعلمات إلى المكدس، ويتم تخزين القيمة المرجعة في سجل R0. ثم [aObject aMessage: arg1];
يتوافق مع objc_msgSend(aObject, aMessage, arg1);
R0 يخزن عنوان المتلقي للرسالة، R1 يخزن المحدد، و R2 يخزن عنوان المعلمة الأولى. تنسيق الرسالة لمزيد من المعلمات هو كما يلي:
objc_msgSend(R0, R1, R2, R3, *SP, *(SP + sizeOfLastArg), …)
من خلال التنسيق أعلاه، يمكن تحليل المنطق في وظيفة معينة خطوة بخطوة. إذا تم استكمال ذلك بتصحيح الأخطاء الديناميكي بخطوة واحدة لـ LLDB وتصحيح أخطاء تتبع نقاط التوقف على عنوان بيان تجميع معين، فسوف يساعد ذلك على فهم تفاصيل تنفيذ الوظيفة.
هناك العديد من الحلول لوظيفة هدف الخطاف، ولكنها تعتمد جميعها من حيث المبدأ على الخصائص الديناميكية لـ Objective-C وتستخدم طريقة Swizzling لتحل محل التنفيذ الأصلي. هذه المرة سوف نقدم استخدام مكتبة CaptainHook بالتفصيل. يتم تنفيذ هذه المكتبة بناءً على MSHookMessageEx()
في Cydia Substrate. إعلان هذه الوظيفة هو:
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
بعد تثبيت iOSOpenDev، يمكنك استخدام قالب CaptainHook Tweak لإنشاء مشروع. تقدم مكتبة CaptainHook صيغة جديدة لكتابة دوال الخطاف. أولاً، قم بتحميل الفئة التي توجد بها الوظيفة المراد ربطها في CHConstructor()
، مثل CHLoadLateClass(UIView)
. ثم قم بتسجيل الدالة CHHook(argNumber, className, arg1, arg2)
ليتم ربطها. التعريف الكلي لـ CHConstructor
هو كما يلي:
#define CHConcat(a, b) CHConcat_(a, b)
#define CHConstructor static __attribute__((constructor)) void CHConcat(CHConstructor, __LINE__)()
المحتوى بعد __attribute__((constructor))
مضمون للتشغيل عند تحميل dylib، عادةً عند بدء تشغيل البرنامج. وبالمثل، يتم تقديم الرموز الأخرى من خلال تعريفات الماكرو.
اسمحوا لي أن أقدم لكم كيفية استخدام CaptainHook للإعلان عن وظيفة الخطاف وتنفيذها مباشرة في الكود.
CHDeclareClass ( BXViewController );
CHOptimizedMethod ( 0 , self , void , BXViewController , viewDidLoad ) {
CHSuper ( 0 , BXViewController , viewDidLoad );
/* HERE TO WRITE YOUR CODE */
}
CHDeclareMethod0 ( void , BXViewController , addFriends ) {
/* HERE TO WRITE YOUR CODE */
}
بعد اكتمال الكتابة، قم بالاتصال بجهاز iPhone الموجود لديك للتجميع لضمان إنشاء مكتبة ديناميكية تتوافق مع البنية.
استخدم الأداة yololib لإدخال ملف dylib المترجم في قائمة أوامر التحميل الخاصة بالملف القابل للتنفيذ Mach-O.
~ » ./yololib [binary] [dylib file]
يتكون هيكل ملفات Mach-O بشكل أساسي من ثلاثة أجزاء. الجزء الأمامي هو هيكل الرأس، الذي يحفظ نوع النظام الأساسي لـ Mach-O، ونوع الملف، وعدد أوامر التحميل والمعلومات الأخرى؛ متبوعًا بالرأس هو جزء أوامر التحميل، ومن خلال تحليل هذا الجزء، يتم تحديد البنية المنطقية للملف و يمكن تحديد موقعه في الملف الظاهري تخطيط في الذاكرة. تقوم أداة yololib بتغيير المعلومات الموجودة في قسم أوامر التحميل لتحميل dylib. عملية التنفيذ المحددة هي كما يلي:
نظرًا لتغير معلومات أوامر التحميل، سيتغير كل من ncmds وsizeofcmds في بنية الرأس المقابلة، لذا يجب تعديل الرأس أولاً:
// 取出 Header
fseek(newFile, top, SEEK_SET);
struct mach_header mach;
fread(&mach, sizeof(struct mach_header), 1, newFile);
NSData* data = [DYLIB_PATH dataUsingEncoding:NSUTF8StringEncoding];
// 计算 dylib 的大小
uint32_t dylib_size = (uint32_t)[data length] + sizeof(struct dylib_command);
dylib_size += sizeof(long) - (dylib_size % sizeof(long));
// 修改 cmds 和 sizeofcmds
mach.ncmds += 1;
uint32_t sizeofcmds = mach.sizeofcmds;
mach.sizeofcmds += dylib_size;
// 写回修改后的 Header
fseek(newFile, -sizeof(struct mach_header), SEEK_CUR);
fwrite(&mach, sizeof(struct mach_header), 1, newFile);
ثم قم بتغيير قسم أوامر التحميل وأضف معلومات تحميل dylib:
fseek(newFile, sizeofcmds, SEEK_CUR);
// 创建一个 dylib 类型的 command
struct dylib_command dyld;
fread(&dyld, sizeof(struct dylib_command), 1, newFile);
// 修改 dyld 结构体数据
dyld.cmd = LC_LOAD_DYLIB;
dyld.cmdsize = dylib_size;
dyld.dylib.compatibility_version = DYLIB_COMPATIBILITY_VERSION;
dyld.dylib.current_version = DYLIB_CURRENT_VER;
dyld.dylib.timestamp = 2;
dyld.dylib.name.offset = sizeof(struct dylib_command);
// 写回修改
fseek(newFile, -sizeof(struct dylib_command), SEEK_CUR);
fwrite(&dyld, sizeof(struct dylib_command), 1, newFile);
آخر البيانات المكتوبة إلى dylib.
fwrite([data bytes], [data length], 1, newFile);
استخدم أمر codesign
لإعادة تسجيل المكتبة الديناميكية التي تم إنشاؤها وجميع الملفات القابلة للتنفيذ في التطبيق (بما في ذلك ملحق APP في مجلد البرنامج المساعد). استخدم الأمر xcrun -sdk iphoneos PackageApplication -v
لحزم المكتبة الديناميكية وجميع الملفات معًا. سوف تفهم العملية برمتها. إذا كانت هناك شهادة مؤسسة، فيمكن تثبيت التطبيق الموقع والمعبأ على جهاز iPhone غير مكسور الحماية والذي يثق في الشهادة. نجم التجديف!