تحديث 2020.1.14 لا يتطلب تثبيت كسر الحماية
هذه المقالة عبارة عن برنامج تعليمي حول عكس ملف WeChat الثنائي لتحقيق إعادة توجيه مقاطع الفيديو القصيرة في Moments، بدءًا من رمز التجميع الأولي وحتى التثبيت النهائي لإعادة التوقيع والعمليات الأخرى، ستعلمك خطوة بخطوة كيفية اللعب باستخدام WeChat! بعد أن تتعلم ذلك، سيكون من السهل إجراء هندسة عكسية لوظائف WeChat الأخرى.
تنقسم هذه المقالة إلى جزأين نظرًا لطولها، يشرح الجزء الأول العمل العكسي، أي كيفية العثور على الوظائف والأساليب ذات الصلة لتنفيذها. ويشرح الجزء الثاني كيفية إعادة التوقيع على التثبيت على الأجهزة غير المعطلة للحماية عملية التثبيت على أجهزة jailbroken مفصلة.
يوفر الجزء الثاني من النص أيضًا رمز WeChat لالتقاط المظاريف الحمراء تلقائيًا وتعديل عدد خطوات WeChat، ويمكن العثور عليها خطوة بخطوة باتباع روتين هذه المقالة ولن يتم تكرارها هنا.
قبل التدريب، تحتاج إلى إعداد هاتف مكسور الحماية وتثبيت جميع الأدوات المذكورة أدناه. كل من IDA وReveal عبارة عن نسختين متصدعتين، وتبلغ تكلفة النسخة الأصلية من IDA أكثر من 2000 دولار، وهي تستحق المال مقابل هذه الأداة الرائعة للهندسة العكسية، ومع ذلك، إذا لم تكن شركة متخصصة في الهندسة العكسية، فلا يوجد ذلك أحتاج إلى استخدام الإصدار الأصلي. سيكون الإصدار المتصدع التالي من Windows على ما يرام، ولا يمكنني العثور عليه على نظام Mac حتى الآن. يمكنك استخدام القادوس ليحل محل IDA على نظام Mac، وهو أيضًا أداة هندسة عكسية قوية جدًا. دون مزيد من اللغط، دعونا نبدأ!
ملاحظة: الملف الثنائي WeChat المعكوس في هذه المقالة هو الإصدار 6.3.28. إذا كان إصدار WeChat مختلفًا، فإن العنوان الأساسي في الملف الثنائي مختلف أيضًا.
بيئة عكسية لجهاز MacOS + iPhone5S 9.1 مكسور الحماية <br> استخدم Dumpdecrypted أولاً لتحطيم غلاف WeChat (إذا كنت لا تعرف كيف، يرجى قراءة هذا البرنامج التعليمي الذي كتبته)، واحصل على ملف WeChat.decrypted، وقم أولاً بإلقاء هذا الملف في IDA لتحليله (ملف ثنائي يبلغ حجمه حوالي 60 ميجابايت) يستغرق حوالي 40 دقيقة للتحليل بواسطة IDA End)، استخدم class-dump لتصدير جميع ملفات الرأس
LeonLei-MBP:~ gaoshilei$ class-dump -S -s -H /Users/gaoshilei/Desktop/reverse/binary_for_class-dump/WeChat.decrypted -o /Users/gaoshilei/Desktop/reverse/binary_for_class-dump/class-Header/WeChat
لدي حماة! يوجد إجمالي 8000 ملف رأس، ويمتلك WeChat قدرًا كبيرًا من العمل! تهدئة مشاعرك، وجمع أفكارك والاستمرار. للحصول على رابط تنزيل مقطع فيديو قصير، ابحث عن طريقة العرض التي تقوم بتشغيل الفيديو، واتبع الأدلة للعثور على عنوان URL للفيديو القصير. استخدم Reveal لعرض نافذة تشغيل الفيديو القصير. يمكنك أن ترى أن كائن WCContentItemViewTemplateNewSigh هو نافذة تشغيل الفيديو القصير. وتشمل طرق العرض الفرعية الخاصة به WCSightView وSightView وSightPlayerView. عند حفظ مقطع فيديو في المفضلة، اضغط لفترة طويلة على الفيديو ليظهر الخيار، لذلك قد تكون هناك طرق متعلقة بالإيماءات في فئة WCContentItemViewTemplateNewSight، ابحث عن أدلة في ملف الرأس الذي تم تصديره للتو.
- (void)onLongTouch;
- (void)onLongPressedWCSight:(id)arg1;
- (void)onLongPressedWCSightFullScreenWindow:(id)arg1;
ترتبط هذه الطرق بإيماءة الضغط لفترة طويلة، ابحث عن هذه الوظائف في IDA واعرضها واحدة تلو الأخرى. يعد onLongPressedWCSight وonLongPressedWCSightFullScreenWindow بسيطين نسبيًا، وonLongTouch طويل نسبيًا، ووجدت أن طريقة Favorites_Add يتم استدعاؤها داخليًا، لأنه عند الضغط لفترة طويلة على الفيديو، يظهر خيار هو المفضلة، ورأيت استدعاء هذه الوظيفة
ADRP X8, #selRef_sightVideoPath@PAGE
LDR X1, [X8,#selRef_sightVideoPath@PAGEOFF]
لقد حصلت على عنوان الفيديو القصير هنا، ويمكن التكهن بأن هذه الوظيفة مرتبطة بالمجموعة، فلنكسر هذه النقطة ونختبرها.
(lldb) im li -o -f
[ 0] 0x000000000003c000 /var/mobile/Containers/Bundle/Application/2F1D52EC-C57E-4F95-B715-EF04351232E8/WeChat.app/WeChat(0x000000010003c000)
يمكنك أن ترى أن ASLR الخاص بـ WeChat هو 0x3c000. ابحث عن العناوين الأساسية لهذه الوظائف الثلاث في IDA وقم بتعيين نقاط التوقف على التوالي.
(lldb) br s -a 0x1020D3A10+0x3c000
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol110094$$WeChat + 28, address = 0x000000010210fa10
(lldb) br s -a 0x1020D3370+0x3c000
Breakpoint 2: where = WeChat`___lldb_unnamed_symbol110091$$WeChat + 8, address = 0x000000010210f370
(lldb) br s -a 0x1020D33E4+0x3c000
Breakpoint 3: where = WeChat`___lldb_unnamed_symbol110092$$WeChat + 12, address = 0x000000010210f3e4
ارجع إلى WeChat واضغط لفترة طويلة على الفيديو القصير لترى كيف يتم تشغيل نقطة التوقف.
Process 3721 stopped
* thread #1: tid = 0x658fc, 0x000000010210f370 WeChat`___lldb_unnamed_symbol110091$$WeChat + 8, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x000000010210f370 WeChat`___lldb_unnamed_symbol110091$$WeChat + 8
WeChat`___lldb_unnamed_symbol110091$$WeChat:
-> 0x10210f370 <+8>: add x29, sp, #16 ; =16
0x10210f374 <+12>: mov x19, x0
0x10210f378 <+16>: adrp x8, 4968
0x10210f37c <+20>: ldr x0, [x8, #744]
(lldb) c
Process 3721 resuming
Process 3721 stopped
* thread #1: tid = 0x658fc, 0x000000010210fa10 WeChat`___lldb_unnamed_symbol110094$$WeChat + 28, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010210fa10 WeChat`___lldb_unnamed_symbol110094$$WeChat + 28
WeChat`___lldb_unnamed_symbol110094$$WeChat:
-> 0x10210fa10 <+28>: add x29, sp, #96 ; =96
0x10210fa14 <+32>: sub sp, sp, #96 ; =96
0x10210fa18 <+36>: mov x19, x0
0x10210fa1c <+40>: adrp x8, 4863
……
لقد وجد أنه تم تشغيل نقطة التوقف 2 أولاً، ثم تم تشغيل نقطة التوقف 1، ثم تم تشغيل نقطتي التوقف 2 و1 بمجرد أن أصبحت كل نقطة توقف 3 هادئة جدًا. يمكنك استبعاد الاتصال بين onLongPressedWCSightFullScreenWindow ومجموعة مقاطع الفيديو القصيرة. يجب العثور على آثار مقاطع الفيديو القصيرة في الطريقتين المتبقيتين. ابحث عن C حتى V، وابحث عن M باتباع القرائن التي تم تجربتها واختبارها! استخدم cycript لإدخال WeChat والحصول على وحدة التحكم حيث يوجد العرض الذي يقوم بتشغيل الفيديو القصير.
cy# [#0x138c18030 nextResponder]
#"<WCTimeLineCellView: 0x138c34620; frame = (0 0; 319 249); tag = 1048577; layer = <CALayer: 0x138362ba0>>"
cy# [#0x138c34620 nextResponder]
#"<UITableViewCellContentView: 0x138223c70; frame = (0 0; 320 256); gestureRecognizers = <NSArray: 0x1384ec480>; layer = <CALayer: 0x138081dc0>>"
cy# [#0x138223c70 nextResponder]
#"<MMTableViewCell: 0x138c9f930; baseClass = UITableViewCell; frame = (0 307; 320 256); autoresize = W; layer = <CALayer: 0x1382dcd10>>"
cy# [#0x138c9f930 nextResponder]
#"<UITableViewWrapperView: 0x137b57800; frame = (0 0; 320 504); gestureRecognizers = <NSArray: 0x1383db660>; layer = <CALayer: 0x138af20c0>; contentOffset: {0, 0}; contentSize: {320, 504}>"
cy# [#0x137b57800 nextResponder]
#"<MMTableView: 0x137b8ae00; baseClass = UITableView; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x138adb590>; layer = <CALayer: 0x138956890>; contentOffset: {0, 99.5}; contentSize: {320, 3193}>"
cy# [#0x137b8ae00 nextResponder]
#"<UIView: 0x138ade5c0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x138ac9990>>"
cy# [#0x138ade5c0 nextResponder]
#"<WCTimeLineViewController: 0x1379eb000>"
ابحث عن وحدة التحكم التي ينتمي إليها WCContentItemViewTemplateNewSight من خلال سلسلة المستجيب وهي WCTimeLineViewController. لم يتم العثور على أدلة قيمة في ملف الرأس لهذه الفئة، لكننا لاحظنا أن العرض الذي يوجد به الفيديو القصير ينتمي إلى MMTableVIewCell (انظر مخطط تحليل الكشف أعلاه)، وهو TableView وبيانات الخلية الأكثر شيوعًا لكل نظام iOS. يتم تعيينه من خلال طريقة الوكيل UITableViewDataSource - tableView:cellForRowAtIndexPath:
من خلال هذه الطريقة، يمكنك بالتأكيد معرفة ظل M. ابحث عن [WCTimeLineViewController tableView:cellForRowAtIndexPath:]
في IDA وحدد العنوان الأساسي 0x10128B6B0:
__text:000000010128B6B0 ADRP X8, #selRef_genNormalCell_indexPath_@PAGE
الوظيفة هنا هي طريقة إنشاء الخلايا في WCTimeLineViewController، بالإضافة إلى هذه الطريقة، هناك ثلاث طرق أخرى لإنشاء الخلايا في هذه الفئة:
- (void)genABTestTipCell:(id)arg1 indexPath:(id)arg2;
- (void)genRedHeartCell:(id)arg1 indexPath:(id)arg2;
- (void)genUploadFailCell:(id)arg1 indexPath:(id)arg2;
من المعنى الحرفي، يمكننا أن نخمن أن الوضع الطبيعي ينبغي أن يكون وسيلة لتوليد خلايا فيديو صغيرة. استمر في البحث عن أدلة في IDA
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
تم العثور على الطريقة المذكورة أعلاه في طريقة genNormalCell:IndexPath:
: يمكنك تخمين أن هذه الطريقة هي طريقة للحصول على بيانات TimeLine (اللحظات) ويجب أيضًا الحصول عليها من خلال هذه الطريقة استدعاء طريقة تسمى selRef_getTimelineDataItemOfIndex_
، يبدو أن الحصول على DataItem هو مصدر بيانات الخلية! بعد ذلك، استخدم LLDB لتعيين نقاط التوقف للتحقق من التخمين. يمكنك العثور على العنوان الأساسي المطابق لهذه الطريقة من خلال IDA: 0x101287CE4. قم أولاً بطباعة إزاحة ASLR لتشغيل WeChat.
LeonLei-MBP:~ gaoshilei$ lldb
(lldb) process connect connect://localhost:1234
(lldb) im li -o -f
[0] 0x0000000000050000 /var/mobile/Containers/Bundle/Application/2DCE8F30-9B6B-4652-901C-37EB1FF2A40D/WeChat.app/WeChat(0x0000000100050000)
لذا فإن موقع نقطة التوقف لدينا هو 0x50000+0x101287CE4
(lldb) br s -a 0x50000+0x101287CE4
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol63721$$WeChat + 252, address = 0x00000001012d7ce4
اطبع قيمة x0
(lldb) po $x0
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
احصل على كائن WCDataItem. قيمة x0 هنا هي القيمة المرجعة بعد تنفيذ selRef_getTimelineDataItemOfIndex_
، ثم قم بتغيير قيمة x0.
(lldb) register write $x0 0
(lldb) c
في هذا الوقت، ستجد أن محتوى الفيديو الصغير الذي نريد تحديثه كله فارغ.
في هذه المرحلة، وجدنا طريقة للحصول على البيانات المصدر للفيديو القصير، والسؤال هو كيف نحصل على WCDataItem؟ استمر في إلقاء نظرة على حالة الاتصال لوظيفة تحليل IDA:
WCTimeLineViewController - (باطل)genNormalCell:(id) IndexPath:(id)
__text:0000000101287BCC STP X28, X27, [SP,#var_60]!
__text:0000000101287BD0 STP X26, X25, [SP,#0x60+var_50]
__text:0000000101287BD4 STP X24, X23, [SP,#0x60+var_40]
__text:0000000101287BD8 STP X22, X21, [SP,#0x60+var_30]
__text:0000000101287BDC STP X20, X19, [SP,#0x60+var_20]
__text:0000000101287BE0 STP X29, X30, [SP,#0x60+var_10]
__text:0000000101287BE4 ADD X29, SP, #0x60+var_10
__text:0000000101287BE8 SUB SP, SP, #0x80
__text:0000000101287BEC MOV X19, X3
__text:0000000101287BF0 MOV X22, X0
__text:0000000101287BF4 MOV W25, #0x100000
__text:0000000101287BF8 MOVK W25, #1
__text:0000000101287BFC MOV X0, X2
__text:0000000101287C00 BL _objc_retain
__text:0000000101287C04 MOV X28, X0
__text:0000000101287C08 MOV X0, X19
__text:0000000101287C0C BL _objc_retain
__text:0000000101287C10 MOV X20, X0
__text:0000000101287C14 STR X20, [SP,#0xE0+var_98]
__text:0000000101287C18 ADRP X8, #selRef_row@PAGE
__text:0000000101287C1C LDR X1, [X8,#selRef_row@PAGEOFF]
__text:0000000101287C20 BL _objc_msgSend
__text:0000000101287C24 MOV X26, X0
__text:0000000101287C28 ADRP X8, #selRef_section@PAGE
__text:0000000101287C2C LDR X19, [X8,#selRef_section@PAGEOFF]
__text:0000000101287C30 MOV X0, X20
__text:0000000101287C34 MOV X1, X19
__text:0000000101287C38 BL _objc_msgSend
__text:0000000101287C3C STR X0, [SP,#0xE0+var_A8]
__text:0000000101287C40 MOV X0, X20
__text:0000000101287C44 MOV X1, X19
__text:0000000101287C48 BL _objc_msgSend
__text:0000000101287C4C MOV X2, X0
__text:0000000101287C50 ADRP X8, #selRef_calcDataItemIndex_@PAGE
__text:0000000101287C54 LDR X1, [X8,#selRef_calcDataItemIndex_@PAGEOFF]
__text:0000000101287C58 MOV X0, X22
__text:0000000101287C5C BL _objc_msgSend
__text:0000000101287C60 MOV X21, X0
__text:0000000101287C64 STR X21, [SP,#0xE0+var_C0]
__text:0000000101287C68 ADRP X8, #classRef_MMServiceCenter@PAGE
__text:0000000101287C6C LDR X0, [X8,#classRef_MMServiceCenter@PAGEOFF]
__text:0000000101287C70 ADRP X8, #selRef_defaultCenter@PAGE
__text:0000000101287C74 LDR X1, [X8,#selRef_defaultCenter@PAGEOFF]
__text:0000000101287C78 STR X1, [SP,#0xE0+var_B8]
__text:0000000101287C7C BL _objc_msgSend
__text:0000000101287C80 MOV X29, X29
__text:0000000101287C84 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287C88 MOV X19, X0
__text:0000000101287C8C ADRP X8, #classRef_WCFacade@PAGE
__text:0000000101287C90 LDR X0, [X8,#classRef_WCFacade@PAGEOFF]
__text:0000000101287C94 ADRP X8, #selRef_class@PAGE
__text:0000000101287C98 LDR X1, [X8,#selRef_class@PAGEOFF]
__text:0000000101287C9C STR X1, [SP,#0xE0+var_B0]
__text:0000000101287CA0 BL _objc_msgSend
__text:0000000101287CA4 MOV X2, X0
__text:0000000101287CA8 ADRP X8, #selRef_getService_@PAGE
__text:0000000101287CAC LDR X1, [X8,#selRef_getService_@PAGEOFF]
__text:0000000101287CB0 STR X1, [SP,#0xE0+var_A0]
__text:0000000101287CB4 MOV X0, X19
__text:0000000101287CB8 BL _objc_msgSend
__text:0000000101287CBC MOV X29, X29
__text:0000000101287CC0 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287CC4 MOV X20, X0
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
__text:0000000101287CCC LDR X1, [X8,#selRef_getTimelineDataItemOfIndex_@PAGEOFF]
__text:0000000101287CD0 STR X1, [SP,#0xE0+var_C8]
__text:0000000101287CD4 MOV X2, X21
__text:0000000101287CD8 BL _objc_msgSend
__text:0000000101287CDC MOV X29, X29
__text:0000000101287CE0 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287CE4 MOV X21, X0
__text:0000000101287CE8 MOV X0, X20
......
المعلمة التي تم تمريرها إلى selRef_getTimelineDataItemOfIndex_
هي x2. يمكنك أن ترى أن x21 الذي تم تمريره إلى x2 هو القيمة المرجعة للدالة selRef_calcDataItemIndex_
، وهو نوع بيانات طويل غير موقع. بمواصلة التحليل، فإن المتصل بوظيفة selRef_getTimelineDataItemOfIndex_
هو القيمة المرجعة لـ selRef_getService_
في الخطوة السابقة، بعد تحليل نقطة التوقف، وجد أنه كائن WCFacade
. دعونا نفرز الاستدعاءات إلى selRef_getTimelineDataItemOfIndex_
:
المتصل هو القيمة المرجعة لـ selRef_getService_
؛ المعلمة هي القيمة المرجعة لـ selRef_calcDataItemIndex_
<br> دعونا نحول انتباهنا إلى هاتين الوظيفتين ونستخدم نفس المبدأ لتحليل كيفية تنفيذ المكالمة.
selRef_getService_
أولاً:MMServiceCenter
، تم تعيين x19 في الموضع 0x101287C88 هو إرجاع قيمة [MMServiceCenter defaultCenter]
.[WCFacade class]
.selRef_calcDataItemIndex_
:WCTimeLineViewController
المتصل به x0 في الموضع 0x101287C58.selRef_section
0x101287C4C، وجد أن selRef_section
الواردة تأتي من selRef_section
أو x3WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
، لذا فإن معلمة selRef_calcDataItemIndex_
هي [IndexPath section]
.getTimelineDataItemOfIndex:
المرور [[MMServiceCenter defaultCenter] getService:[WCFacade class]]
للحصول عليها، يمكن الحصول على معلماتها من خلال الوظيفة التالية
[WCTimeLineViewController calcDataItemIndex:[indexPath section]]
تشعر دائما وكأن هناك شيئا مفقودا؟ لم نحصل على مسار الفهرس بعد! الخطوة التالية هي الحصول على IndexPath، وهو أمر بسيط نسبيًا، لأننا في [WCContentItemViewTemplateNewSight onLongTouch]
، حتى نتمكن من الحصول على MMTableViewCell وMMTableView وWCTimeLineViewController بالتسلسل من خلال [self nextResponder]
، ثم الحصول على IndexPath من خلال [MMTableView indexPathForCell:MMTableViewCell]
.
بعد القيام بذلك، حصلنا على كائن WCDataItem. يجب أن يكون التركيز التالي على WCDataItem، وأخيرًا نحصل على الفيديو القصير الذي نريده. ابحث عن أدلة في الملف الرئيسي لهذه الفئة نظرًا لأنه لا يمكن تشغيل الفيديو إلا بعد التنزيل، فيجب الحصول على مسار الفيديو هنا، لذا انتبه إلى السمات أو الطرق المتعلقة بعنوان url والمسار، ثم ابحث عن ما يلي. المشتبه بهم.
@property(retain, nonatomic) NSString *sourceUrl2;
@property(retain, nonatomic) NSString *sourceUrl;
- (id)descriptionForKeyPaths;
- (id)keyPaths;
ارجع إلى LLDB واطبع هذه القيم بنقاط توقف لترى ما هو موجود هناك.
(lldb) po [$x0 keyPaths]
<__NSArrayI 0x15f74e9d0>(
tid,
username,
createtime,
commentUsers,
contentObj
)
(lldb) po [$x0 descriptionForKeyPaths]
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
(lldb) po [$x0 sourceUrl]
nil
(lldb) po [$x0 sourceUrl2]
nil
لا توجد أدلة قيمة، لكنني لاحظت وجود WCContentItem في WCDataItem. ويبدو أن الطريقة الوحيدة للبدء هي هنا.
@property(retain, nonatomic) NSString *linkUrl;
@property(retain, nonatomic) NSString *linkUrl2;
@property(retain, nonatomic) NSMutableArray *mediaList;
اطبعه في LLDB
(lldb) po [[$x0 valueForKey:@"contentObj"] linkUrl]
https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/common_page__upgrade&v=1
(lldb) po [[$x0 valueForKey:@"contentObj"] linkUrl2]
nil
(lldb) po [[$x0 valueForKey:@"contentObj"] mediaList]
<__NSArrayM 0x15f985e10>(
<WCMediaItem: 0x15dfebdf0>
)
يوجد كائن WCMediaItem في مصفوفة mediaList، ويتم استخدامه بشكل عام لتمثيل الفيديو والصوت. ابحث بسرعة عن ملف الرأس وابحث عنه.
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForData;
- (id)pathForSightData;
- (id)pathForTempAttachVideoData;
- (id)videoStreamForData;
من بين السمات والأساليب المذكورة أعلاه، من المرجح أن يحصل pathForSightData
على مسار فيديو قصير.
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] dataUrl]
type[1], url[http://vweixinf.tc.qq.com/102/20202/snsvideodownload?filekey=30270201010420301e020166040253480410d14adcddf086f4e131d11a5b1cca1bdf0203039fa00400&bizid=1023&hy=SH&fileparam=302c0201010425302302040fde55e20204580ebd3602024eea02031e8d7d02030f42400204d970370a0201000400], enckey[0], encIdx[-1], token[]
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForData]
/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForSightData]
/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45.mp4
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForAttachVideoData]
nil
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] videoStreamForData]
nil
حصلت على عنوان URL للشبكة والمسار المحلي للفيديو القصير! هنا يمكنك استخدام iFunBox أو scp لنسخ هذا الملف من وضع الحماية لمعرفة ما إذا كان مقطع فيديو قصيرًا يجب تشغيله على هذه الخلية.
LeonLei-MBP:~ gaoshilei$ scp [email protected]:/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45.mp4 Desktop/
8fb0cdd77208de5b56169fb3458b45.mp4 100% 232KB 231.9KB/s 00:00
افتحه باستخدام QuickTime واكتشف أنه بالفعل الفيديو القصير الذي نبحث عنه. تحقق مرة أخرى من صحة عنوان URL. افتح dataUrl المطبوع أعلاه في المتصفح واكتشف أنه هذا الفيديو القصير أيضًا. وبتحليل هذا الفصل يمكننا استخلاص الاستنتاجات التالية:
في هذه المرحلة، تم الانتهاء من تحليل المسار وطريقة الحصول على الفيديو القصير لتحقيق إعادة التوجيه، نحتاج إلى مواصلة تحليل إصدار WeChat Moments.
هذا القسم عبارة عن منعطف قمت به عندما كنت أبحث عن وظيفة إعادة توجيه الفيديو القصير، وفي النهاية لم أجد طريقة لتنفيذها، ومع ذلك، فهو يوفر أيضًا بعض الأفكار والأساليب الشائعة الاستخدام في الهندسة العكسية. إذا كنت لا ترغب في قراءته، يمكنك الانتقال إلى القسم الثاني.
افتح واجهة التصوير للفيديو القصير واستخدم cycript لإدخاله. نحتاج إلى معرفة الطريقة المستخدمة لنشر الفيديو القصير، ثم التحقق من النوافذ الموجودة في النافذة الحالية (لأن تصوير الفيديو القصير ليس كذلك). تم إجراؤه في النافذة الرئيسية لتطبيق UIApplication)
cy# [UIApp windows].toString()
(
"<iConsoleWindow: 0x125f75e20; baseClass = UIWindow; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x125f77b70>; layer = <UIWindowLayer: 0x125df4810>>",
"<SvrErrorTipWindow: 0x127414d40; baseClass = UIWindow; frame = (0 64; 320 45); hidden = YES; gestureRecognizers = <NSArray: 0x12740d930>; layer = <UIWindowLayer: 0x1274030b0>>",
"<MMUIWindow: 0x127796440; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x1278083c0>; layer = <UIWindowLayer: 0x127796750>>",
"<UITextEffectsWindow: 0x1270e0d40; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x1270b4ba0>>",
"<NewYearActionSheet: 0x127797e10; baseClass = UIWindow; frame = (0 0; 320 568); hidden = YES; userInteractionEnabled = NO; layer = <UIWindowLayer: 0x1277d5490>>"
)
لقد وجد أن الصفحة الحالية تحتوي على إجمالي 5 نوافذ، من بينها MMUIWindow هي النافذة التي تم فيها تصوير الفيديو القصير وطباعة بنية شجرة واجهة المستخدم الخاصة بها.
cy# [#0x127796440 recursiveDescription]
النتيجة المطبوعة طويلة جدًا، لذا لن أنشرها. ابحث عن هذا الزر وهو الزر الخاص بتصوير مقاطع الفيديو القصيرة
| | | | | | <UIButton: 0x1277a9d70; frame = (89.5 368.827; 141 141); opaque = NO; gestureRecognizers = <NSArray: 0x1277aaeb0>; layer = <CALayer: 0x1277a9600>>
| | | | | | | <UIView: 0x1277aa0a0; frame = (0 0; 141 141); userInteractionEnabled = NO; tag = 252707333; layer = <CALayer: 0x1277aa210>>
| | | | | | | | <UIImageView: 0x1277aa2e0; frame = (0 0; 141 141); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1277aa490>>
ومن ثم تنفيذ
cy# [#0x1277a9d70 setHidden:YES]
ووجدت أن زر التصوير اختفى مما أكد شكوكي. للعثور على حدث الاستجابة للزر، يمكنك العثور عليه من خلال الهدف
cy# [#0x1277a9d70 allTargets]
[NSSet setWithArray:@[#"<MainFrameSightViewController: 0x1269a4600>"]]]
cy# [#0x1277a9d70 allControlEvents]
193
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:193]
null
لقد وجدت أن الزر ليس له إجراء مطابق، وهو أمر غريب! يجب أن يكون لـ UIButton هدف وإجراء، وإلا فلن يتمكن الزر من الاستجابة للأحداث. دعونا نجرب أحداث التحكم الأخرى
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchDown]
@["btnPress"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpOutside]
@["btnRelease"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpInside]
@["btnRelease"]
اتضح أن أحداث ControlEvent الثلاثة هذه لها إجراءات مقابلة، فلنلقي نظرة على قيم هذه التعدادات الثلاثة.
typedef enum UIControlEvents : NSUInteger {
UIControlEventTouchDown = 1 << 0,
UIControlEventTouchDownRepeat = 1 << 1,
UIControlEventTouchDragInside = 1 << 2,
UIControlEventTouchDragOutside = 1 << 3,
UIControlEventTouchDragEnter = 1 << 4,
UIControlEventTouchDragExit = 1 << 5,
UIControlEventTouchUpInside = 1 << 6,
UIControlEventTouchUpOutside = 1 << 7,
UIControlEventTouchCancel = 1 << 8,
......
} UIControlEvents;
يمكن ملاحظة أن UIControlEventTouchDown يتوافق مع 1، وUIControlEventTouchUpInside يتوافق مع 128، وUIControlEventTouchUpOutside يتوافق مع 64، ومجموع الثلاثة هو بالضبط 193! اتضح أنه عند استدعاء [#0x1277a9d70 allControlEvents]
، يجب أن تكون القيمة التي تم إرجاعها عبارة عن تعداد، إذا كان هناك العديد من التعدادات، فأضف قيمها. أشعر بنفس الطريقة! لقد قمنا الآن بطباعة الإجراءات المقابلة لأحداث ControlEvents الثلاثة واستمرنا مع LLDB+IDA للتحليل الديناميكي.
نظرًا لأننا نحتاج إلى إيجاد طريقة لنشر مقاطع فيديو قصيرة، فإننا لا نهتم بوظيفة btnPress
المقابلة، بل نركز على btnRelease
، وهي الطريقة التي سيتم استدعاؤها بعد تحرير زر التصوير. ابحث عن هذه الطريقة في IDA وقم بتعيين نقطة التوقف التالية بعد العثور عليها
(lldb) br s -a 0xac000+0x10209369C
Breakpoint 4: where = WeChat`___lldb_unnamed_symbol108894$$WeChat + 32, address = 0x000000010213f69c
Process 3813 stopped
* thread #1: tid = 0xf1ef0, 0x000000010213f69c WeChat`___lldb_unnamed_symbol108894$$WeChat + 32, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
frame #0: 0x000000010213f69c WeChat`___lldb_unnamed_symbol108894$$WeChat + 32
WeChat`___lldb_unnamed_symbol108894$$WeChat:
-> 0x10213f69c <+32>: bl 0x1028d0b60 ; symbol stub for: objc_msgSend
0x10213f6a0 <+36>: cmp w0, #2 ; =2
0x10213f6a4 <+40>: b.ne 0x10213f6dc ; <+96>
0x10213f6a8 <+44>: adrp x8, 5489
قم بتصوير مقطع فيديو قصير بهاتفك المحمول ثم قم بتحريره، مما يؤدي إلى تفعيل نقطة التوقف، مما يوضح أن تخميننا صحيح. بمواصلة التحليل، وجدنا أن الكود من الجانب الأيمن من الصورة أعلاه، وبعد النظر إليه، لا توجد طريقة للانتقال إلى إصدار الفيديو، ومع ذلك، إذا نظرت بعناية، فهناك كتلة كتلة تأخير النظام ويقع على 0x102093760. ثم نتبع نقطة التوقف وننتقل إلى العنوان المخزن في x16 عند 0x1028255A0
(lldb) si
Process 3873 stopped
* thread #1: tid = 0xf62c4, 0x00000001028d9598 WeChat`dispatch_after, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x00000001028d9598 WeChat`dispatch_after
WeChat`dispatch_after:
-> 0x1028d9598 <+0>: adrp x16, 1655
0x1028d959c <+4>: ldr x16, [x16, #1056]
0x1028d95a0 <+8>: br x16
WeChat`dispatch_apply:
0x1028d95a4 <+0>: adrp x16, 1655
(lldb) po $x2
<__NSStackBlock__: 0x16fd49f88>
لقد وجد أن المعلمة x2 التي تم تمريرها عبارة عن كتلة، فلنراجع وظيفة الإرسال_بعد مرة أخرى.
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
تحتوي هذه الوظيفة على ثلاث معلمات، وهي Submit_time_t، وdisplay_queue_t، وdisplay_block_t، وx2 المطبوعة هنا هي الكتلة التي سيتم تمريرها، لذلك نعتقد أنه سيكون هناك تأخير بعد تصوير الفيديو القصير، ثم قم بتنفيذ الكتلة التي تم تمريرها للتو. لذلك يجب أن يكون x2. إذا كانت هناك استدعاءات طريقة أخرى، فإن الخطوة التالية هي معرفة موقع هذه الكتلة.
(lldb) memory read --size 8 --format x 0x16fd49f88
0x16fd49f88: 0x000000019f8fd218 0x00000000c2000000
0x16fd49f98: 0x000000010214777c 0x0000000102fb0e60
0x16fd49fa8: 0x000000015da32600 0x000000015ea1a430
0x16fd49fb8: 0x000000015cf5fee0 0x000000016fd49ff0
0x000000010214777c هو موقع الكتلة، بالطبع، يجب طرح إزاحة ASLR الحالية لـ WeChat، والعنوان النهائي في IDA هو 0x10209377C btnRelease
هذا الروتين الفرعي بسيط جدًا ويحتوي على طريقة واحدة فقط selRef_logicCheckState_
والتي قد تكون هدفنا. دعونا نرى أولا من الذي دعا هذه الطريقة
(lldb) br s -a 0xb4000+0x1020937BC
......
Process 3873 stopped
* thread #1: tid = 0xf62c4, 0x00000001021477bc WeChat`___lldb_unnamed_symbol108895$$WeChat + 64, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
frame #0: 0x00000001021477bc WeChat`___lldb_unnamed_symbol108895$$WeChat + 64
WeChat`___lldb_unnamed_symbol108895$$WeChat:
-> 0x1021477bc <+64>: adrp x8, 5489
0x1021477c0 <+68>: ldr x1, [x8, #1552]
0x1021477c4 <+72>: orr w2, wzr, #0x1
0x1021477c8 <+76>: ldp x29, x30, [sp, #16]
(lldb) po $x0
<MainFrameSightViewController: 0x15d1f0c00>
لقد وجدت أنه لا يزال يتم استدعاؤه بواسطة كائن MainFrameSightViewController، لذلك يجب أن يكون selRef_logicCheckState_
أيضًا في ملف الرأس لهذه الفئة، وقد بحثت عنه ووجدته.
- (void)logicCheckState:(int)arg1;
ابحث عن [MainFrameSightViewController logicCheckState:] في النافذة اليسرى لـ IDA واكتشف أن هذه الطريقة معقدة للغاية وتحتوي على الكثير من المنطق، لذلك لا تقلق واتبعها ببطء. لقد وجدنا قفزة تبديل عند 0x102094D6C، وكانت الفكرة واضحة جدًا، نحتاج فقط إلى العثور على الخط الذي تم تصوير الفيديو القصير فيه، وسيساعدنا النظر إلى الأسفل على LLDB. قم بتعيين نقطة التوقف عند 0x102094D6C. سيتم تشغيل نقطة التوقف هذه عدة مرات عند تصوير مقاطع فيديو قصيرة. يمكنك تعطيل نقطة التوقف قبل التصوير، وتمكين نقطة التوقف قبل تركها، وطباعة قيمة x8 في هذا الوقت.
(lldb) p/x $x8
(unsigned long) $38 = 0x0000000102174e10
x8 هو مؤشر، والعنوان الذي يشير إليه هو 0x102174e10. استخدم هذا العنوان مطروحًا منه إزاحة ASLR الحالية للعثور على العنوان الأساسي في IDA، وقد وجد أنه 0x102094E10 بعد الموضع 0x102094E24، انتقل إلى 0x1020951C4. يحتوي هذا الفرع على محتوى أقل ويحتوي على ثلاث وظائف.
loc_1020951C4
ADRP X8, #selRef_hideTips@PAGE
LDR X1, [X8,#selRef_hideTips@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
ADRP X8, #selRef_finishWriter@PAGE
LDR X1, [X8,#selRef_finishWriter@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
ADRP X8, #selRef_turnCancelBtnForFinishRecording@PAGE
LDR X1, [X8,#selRef_turnCancelBtnForFinishRecording@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
B loc_102095288
من بينها، يجب التركيز على selRef_finishWriter
و selRef_turnCancelBtnForFinishRecording
، ويبدو أن هاتين الطريقتين تعني نهاية تسجيل الفيديو القصير. من خلال النظر إلى المتصل، وجدنا أن هاتين الطريقتين تنتميان إلى MainFrameSightViewController. استمر في التحقق من هاتين الطريقتين في IDA. لقد وجدت طريقة تسمى f_switchToSendingPanel
بالقرب من نهاية selRef_finishWriter
عند 0x102094248، وقمت بتعيين نقطة توقف، ثم قمت بتصوير الفيديو، ووجدت أن هذه الطريقة لم يتم تشغيلها. لا ينبغي استدعاء واجهة النشر من خلال هذه الطريقة، لذا استمر في العودة إلى طريقة selRef_finishWriter
؛ واستدعاء الطريقة selRef_stopRecording
في موقع 0x1020941DC، وسيجد المتصل الذي يطبعها أن هذه الطريقة تنتمي إلى SightFacade
، ويستمر في البحث عن تنفيذ هذا الأسلوب في المؤسسة الدولية للتنمية. يتم استدعاء selRef_stopRecord
مرة أخرى في الموضع 0x101F9BED4 لهذه الطريقة. ويكتشف المتصل أيضًا أن هذه الطريقة تنتمي إلى SightCaptureLogicF4. إنها تشبه إلى حد ما تقشير البصل وتستمر في البحث عن تنفيذ هذه الطريقة. يتم استدعاء selRef_finishWriting
مرة أخرى في الموضع 0x101A98778 داخل هذه الطريقة. باستخدام نفس المبدأ، وجد أن هذه الطريقة تنتمي إلى SightMovieWriter. لقد تم تقشير ثلاث طبقات، فلنتابع:
يتم تقسيم سطرين عند الموضع 0x10261D004 في SightMovieWriter - (void)finishWriting
يتم تعيين نقطة توقف في هذا الموضع، ثم يتم تشغيل نقطة التوقف بعد تصوير الفيديو القصير، وتتم طباعة قيمة x19.
(lldb) po $x19
<OS_dispatch_queue: CAPTURE.CALLBACK[0x13610bcd0] = { xrefcnt = 0x4, refcnt = 0x4, suspend_cnt = 0x0, locked = 1, target = com.apple.root.default-qos.overcommit[0x1a0aa3700], width = 0x0, running = 0x0, barrier = 1 }>
لذلك، لن ينتقل الرمز إلى loc_10261D054 ولكن إلى اليسار، وجد أن الكتلة ممكّنة. هذه الكتلة هي الروتين الفرعي sub_10261D0AC والعنوان هو 0x10261D0AC كما هو موضح أدناه:
ويمكن ملاحظة أنه مقسم بشكل أساسي إلى سطرين. قمنا بتعيين نقطة توقف في نهاية المربع الأول، وهي 0x10261D108. بعد تشغيل نقطة التوقف بعد التصوير، تتم طباعة قيمة x0 على أنها 1. رمز التجميع هنا يكون
__text:000000010261D104 CMP X0, #2
__text:000000010261D108 B.EQ loc_10261D234
سوف ينتقل B.EQ إلى loc_10261D234 فقط عندما تكون نتيجة الخطوة السابقة 0، ولكن النتيجة هنا ليست 0. قم بتغيير قيمة x0 إلى 2 بحيث تكون نتيجة الخطوة السابقة 0.
(lldb) po $x0
1
(lldb) register write $x0 2
(lldb) po $x0
2
في هذا الوقت، حرر نقطة التوقف وانتظر للانتقال إلى واجهة نشر الفيديو القصيرة، والنتيجة هي أنها عالقة في هذه الواجهة دون أي استجابة، لذلك أعتقد أن منطق إدراك القفزة يجب أن يكون على السطر على اليمين. واستمر في البحث على طول السطر الموجود على اليمين: وجد أنه تم استدعاء الطريقة التالية على 0x10261D3AC على اليمين
- (void)finishWritingWithCompletionHandler:(void (^)(void))handler;
هذه الطريقة هي طريقة مقدمة من النظام في AVAssetWriter، وهي عملية يتم إجراؤها بعد اكتمال كتابة الفيديو. نظرًا لوجود معلمة واحدة فقط، فإن المتغير المقابل هو x2، والقيمة تتم طباعة x2.
(lldb) po $x2
<__NSStackBlock__: 0x16e086c78>
(lldb) memory read --size 8 --format x 0x16e086c78
0x16e086c78: 0x00000001a0aa5218 0x00000000c2000000
0x16e086c88: 0x00000001026d94b0 0x0000000102fc98c0
0x16e086c98: 0x0000000136229fd0 0x000000016e086d00
0x16e086ca8: 0x00000001997f5318 0xfffffffec9e882ff
وابحث عن موضع الكتلة 0x10261D4B0 من خلال ذاكرة المكدس (يجب طرح إزاحة ASLR)
sub_10261D4B0
var_20= -0x20
var_10= -0x10
STP X20, X19, [SP,#var_20]!
STP X29, X30, [SP,#0x20+var_10]
ADD X29, SP, #0x20+var_10
MOV X19, X0
LDR X0, [X19,#0x20]
ADRP X8, #selRef_stopAmr@PAGE
LDR X1, [X8,#selRef_stopAmr@PAGEOFF]
BL _objc_msgSend
LDR X0, [X19,#0x20]
ADRP X8, #selRef_compressAudio@PAGE
LDR X1, [X8,#selRef_compressAudio@PAGEOFF]
LDP X29, X30, [SP,#0x20+var_10]
LDP X20, X19, [SP+0x20+var_20],#0x20
B _objc_msgSend
; End of function sub_10261D4B0
يتم استدعاء طريقتين فقط، أحدهما هو selRef_stopAmr
لإيقاف عمرو (تنسيق صوتي)، والآخر هو selRef_compressAudio
لضغط الصوت، ولا ينبغي وضع العملية التالية بعد التصوير في هاتين الطريقتين اللتين كنت أبحث عنهما لفترة طويلة وما زلت لا أملك أدنى فكرة. يبدو أن هذا الطريق ميت، فلا تقع في مشكلة، تراجع بشكل استراتيجي وابحث عن مداخل أخرى.
متعة السير في الاتجاه المعاكس هي أنه يمكنك تجربة متعة النجاح في طريقك للعثور على الحقيقة، وقد تسير أيضًا في الاتجاه الخاطئ وتبتعد أكثر فأكثر عن الحقيقة واستمر في المضي قدمًا!
(نظرًا لأنه تمت ترقية WeChat سرًا في الخلفية، فإن المحتوى التالي هو إصدار ASLR الخاص بـ WeChat 6.3.30، ويستند التحليل أعلاه إلى الإصدار 6.3.28)
لاحظ أنه عند النقر على زر الكاميرا في الزاوية اليمنى العليا من دائرة الأصدقاء، ستظهر ورقة في الأسفل، أولها هو الفيديو القصير "ابدأ هنا" واستخدم cycript لمعرفة الحدث الذي يتوافق معه زر "الرؤية". ل.
iPhone-5S:~ root# cycript -p "WeChat"
cy# [UIApp windows].toString()
`(
"<iConsoleWindow: 0x14d6ccc00; baseClass = UIWindow; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x14d7df110>; layer = <UIWindowLayer: 0x14d7d6f60>>",
"<SvrErrorTipWindow: 0x14eaa5800; baseClass = UIWindow; frame = (0 0; 320 45); hidden = YES; gestureRecognizers = <NSArray: 0x14e9e8950>; layer = <UIWindowLayer: 0x14e9e6510>>",
"<UITextEffectsWindow: 0x14ec38ba0; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x14ec39360>>",
"<UITextEffectsWindow: 0x14e9c67a0; frame = (0 0; 320 568); layer = <UIWindowLayer: 0x14d683ff0>>",
"<UIRemoteKeyboardWindow: 0x14f226e40; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x14d6f4de0>>",
"<NewYearActionSheet: 0x14f1704a0; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14ef9bf90>; layer = <UIWindowLayer: 0x14ef61a20>>"
)`
cy# [#0x14f1704a0 recursiveDescription].toString()
الورقة الموجودة في الأسفل هي NewYearActionSheet، ثم اطبع مخطط هيكل شجرة واجهة المستخدم لـ NewYearActionSheet (إنها طويلة جدًا لذا لن أنشرها). ثم اكتشف أن زر UIButton المطابق لـ Sight هو 0x14f36d470
cy# [#0x14f36d470 allTargets]
[NSSet setWithArray:@[#"<NewYearActionSheet: 0x14f1704a0; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14ef9bf90>; layer = <UIWindowLayer: 0x14ef61a20>>"]]]
cy# [#0x14f36d470 allControlEvents]
64
cy# [#0x14f36d470 actionsForTarget:#0x14f1704a0 forControlEvent:64]
@["OnDefaultButtonTapped:"]
يمكن العثور على الحدث المرتبط بالزر من خلال actionsForTarget:forControlEvent:
الخاصة بـ UIControl. طريقة تشغيل زر Sight هي OnDefaultButtonTapped:
ارجع إلى IDA وابحث عن هذه الطريقة في NewYearActionSheet، ونواصل تحليل هذه الطريقة فقط selRef_dismissWithClickedButtonIndex_animated
، من خلال طباعة المتصل الخاص بها، نجد أنها لا تزال NewYearActionSheet. استمر في النقر للعثور على طريقة newYearActionSheet_clickedButtonAtIndex
. ويبدو أنها ليست NewYearActionSheet نفسها التي تطبع المتصل x0، ونجد أنها تنتمي إلى فئة WCTimeLineViewController. اتبع نقطة التوقف واستدعاء الطريقة #selRef_showSightWindowForMomentWithMask_byViewController_scene
في الموضع 0x1012B78CC. من خلال الملاحظة، وجدنا أن المتصل بهذه الطريقة هو القيمة المرجعة x0 في الموضع 0x1012B78AC. أعتقد أن هذه الطريقة موجودة في SightFacade ذهبت إلى ملف الرأس للعثور عليه بهذه الطريقة
- (void)showSightWindowForMomentWithMask:(id)arg1 byViewController:(id)arg2 scene:(int)arg3;
يجب أن تكون هذه الطريقة هي طريقة الانتقال إلى واجهة الفيديو الصغيرة. اطبع معلماتها بشكل منفصل أدناه
(lldb) po $x2
<UIImage: 0x14f046660>, {320, 568}
(lldb) po $x3
<WCTimeLineViewController: 0x14e214800>
(lldb) po $x4
2
(lldb) po $x0
<SightFacade: 0x14f124b40>
من بينها، x2 وx3 وx4 تتوافق مع ثلاث معلمات على التوالي، وهو x0 هو المتصل. لقد وجد أنه تتم تهيئة واجهة تصوير الفيديو القصير بهذه الطريقة، أولاً، تتم تهيئة MainFrameSightViewController، ثم يتم إنشاء UINavigationController لوضع MainFrameSightViewController فيه، ثم تتم تهيئة MMWindowController للاتصال.
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
تقوم هذه الطريقة بوضع UINavigationController وإكمال جميع أعمال إنشاء واجهة المستخدم لواجهة تصوير الفيديو القصير. بعد اكتمال التصوير، أدخل إلى واجهة النشر في هذا الوقت، استخدم cycript لتجد أن وحدة التحكم الحالية هي SightMomentEditViewController. ومن هنا تنشأ فكرة، ألا يكفي تخطي واجهة التصوير السابقة والدخول مباشرة إلى واجهة النشر ؟ نقوم بإنشاء SightMomentEditViewController بأنفسنا ونضعه في UINavigationController، ثم نضع وحدة التحكم في التنقل هذه في MMWindowController. **(لقد كتبت التعديل هنا للتحقق، وستتم كتابة أفكار التعديل المحددة لاحقًا)** والنتيجة هي أن واجهة النشر يمكن أن تظهر بالفعل، لكن شريط التنقل الخاص بشريط التنقل يغطي الواجهة الأصلية، والواجهة بأكملها الواجهة شفافة وقبيحة، وبعد اكتمال النشر، لا يمكن تدمير MMWindowController بالكامل، وستظل في واجهة النشر. هذه ليست النتيجة التي نريدها، لكننا نكسب الكثير على الأقل يمكننا الاتصال مباشرة بواجهة النشر، ويمكن إعادة توجيه مقاطع الفيديو الصغيرة بشكل طبيعي. تخميني الشخصي هو أن السبب وراء عدم إمكانية تدمير الواجهة الحالية هو أن MMWindowController أنشأ نافذة جديدة، وهي ليست مثل keyWindow حيث يوجد TimeLine. لا يمكن لأسلوب تشغيل الزر في SightMomentEditViewController تدمير هذه النافذة، لذلك لدي تخمين جريء ألا يمكنني فقط عرض SightMomentEditViewController مباشرة على WCTimeLineViewController الحالي؟
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
أليس من الجميل أن نعرضها بهذه الطريقة؟ ومع ذلك، من خلال مراقبة ملف الرأس الخاص بـ SightMomentEditViewController، جنبًا إلى جنب مع العناصر الموجودة على الواجهة عند إصدار الفيديو القصير، فمن المتوقع أن إنشاء وحدة التحكم هذه يتطلب سمتين على الأقل، إحداهما هي مسار الفيديو القصير، والأخرى هي الصورة المصغرة للفيديو القصير. هذين المفتاحين إذا تم إعطاء السمة إلى SightMomentEditViewController، فيجب عرضها بشكل طبيعي.
SightMomentEditViewController *editSightVC = [[%c(SightMomentEditViewController) alloc] init];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UIImage *image = [[self valueForKey:@"_sightView"] getImage];
[editSightVC setRealMoviePath:localPath];
[editSightVC setMoviePath:localPath];
[editSightVC setRealThumbImage:image];
[editSightVC setThumbImage:image];
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
يمكن عرض واجهة نشر الفيديو القصيرة بشكل طبيعي ويمكن استخدام جميع الوظائف بشكل طبيعي. المشكلة الوحيدة هي أن زر الإرجاع ليس له أي تأثير ولا يمكن تدمير SightMomentEditViewController. استخدم cycript للتحقق من حدث الإجراء الخاص بالزر الأيسر واكتشف أن وظيفة الاستجابة الخاصة به هي - (void)popSelf;
بحاجة إلى إعادة هذه الطريقة الكتابة
- (void)popSelf
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}
في هذا الوقت، انقر فوق زر العودة للخروج بشكل طبيعي. بالإضافة إلى ذلك، تم العثور على طريقة تسمى - (void)sendSightToFriend;
في WCContentItemViewTemplateNewSight، والتي يمكنها إعادة توجيه مقاطع الفيديو القصيرة مباشرة إلى الأصدقاء .
تدعم إعادة توجيه مقاطع الفيديو القصيرة 4 وظائف، إعادة التوجيه إلى اللحظات، وإعادة التوجيه إلى الأصدقاء، والحفظ في ألبوم الصور المحلي، ونسخ رابط الفيديو القصير إلى لوحة اللصق. إذا لم يتم تنزيل الفيديو القصير، فسيؤدي الضغط لفترة طويلة إلى إظهار رابط URL الخاص بالفيديو القصير فقط.
نحن هنا بحاجة إلى ربط فئتين، وهما WCContentItemViewTemplateNewSight وSightMomentEditViewController. قم بربط أسلوب onLongTouch في WCContentItemViewTemplateNewSight ثم قم بإضافة القائمة المنبثقة، وأضف أساليب الاستجابة بدورها كما يلي:
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UISaveVideoAtPathToSavedPhotosAlbum(localPath, nil, nil, nil);
}
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UISaveVideoAtPathToSavedPhotosAlbum(localPath, nil, nil, nil);
SightMomentEditViewController *editSightVC = [[%c(SightMomentEditViewController) alloc] init];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UIImage *image = [[self valueForKey:@"_sightView"] getImage];
[editSightVC setRealMoviePath:localPath];
[editSightVC setMoviePath:localPath];
[editSightVC setRealThumbImage:image];
[editSightVC setThumbImage:image];
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
[self sendSightToFriend];
UIMenuController *menuController = [UIMenuController sharedMenuController];
if (menuController.isMenuVisible) return;//防止出现menu闪屏的情况
[self becomeFirstResponder];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
BOOL isExist =[[NSFileManager defaultManager] fileExistsAtPath:localPath];
UIMenuItem *retweetMenuItem = [[UIMenuItem alloc] initWithTitle:@"朋友圈" action:@selector(SLRetweetSight)];
UIMenuItem *saveToDiskMenuItem = [[UIMenuItem alloc] initWithTitle:@"保存到相册" action:@selector(SLSightSaveToDisk)];
UIMenuItem *sendToFriendsMenuItem = [[UIMenuItem alloc] initWithTitle:@"好友" action:@selector(SLSightSendToFriends)];
UIMenuItem *copyURLMenuItem = [[UIMenuItem alloc] initWithTitle:@"复制链接" action:@selector(SLSightCopyUrl)];
if(isExist){
[menuController setMenuItems:@[retweetMenuItem,sendToFriendsMenuItem,saveToDiskMenuItem,copyURLMenuItem]];
}else{
[menuController setMenuItems:@[copyURLMenuItem]];
}
[menuController setTargetRect:CGRectZero inView:self];
[menuController setMenuVisible:YES animated:YES];
لقد وضعت ملف التعديل المحدد على github WCSightRetweet
@interface WCUrl : NSObject
@property(retain, nonatomic) NSString *url;
@end
@interface WCContentItem : NSObject
@property(retain, nonatomic) NSMutableArray *mediaList;
@end
@interface WCDataItem : NSObject
@property(retain, nonatomic) WCContentItem *contentObj;
@end
@interface WCMediaItem : NSObject
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForSightData;
@end
@interface MMServiceCenter : NSObject
+ (id)defaultCenter;
- (id)getService:(Class)arg1;
@end
@interface WCFacade : NSObject
- (id)getTimelineDataItemOfIndex:(long long)arg1;
@end
@interface WCSightView : UIView
- (id)getImage;
@end
@interface WCContentItemViewTemplateNewSight : UIView{
WCSightView *_sightView;
}
- (WCMediaItem *)iOSREMediaItemFromSight;
- (void)iOSREOnSaveToDisk;
- (void)iOSREOnCopyURL;
- (void)sendSightToFriend;
@end
@interface SightMomentEditViewController : UIViewController
@property(retain, nonatomic) NSString *moviePath;
@property(retain, nonatomic) NSString *realMoviePath;
@property(retain, nonatomic) UIImage *thumbImage;
@property(retain, nonatomic) UIImage *realThumbImage;
- (void)makeInputController;
@end
@interface MMWindowController : NSObject
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
- (void)showWindowAnimated:(_Bool)arg1;
@end
@interface WCTimeLineViewController : UIViewController
- (long long)calcDataItemIndex:(long long)arg1;
@end
@interface MMTableViewCell : UIView
@end
@interface MMTableView : UIView
- (id)indexPathForCell:(id)cell;
@end
THEOS_DEVICE_IP = 192.168.0.115//手机所在的IP
include $(THEOS)/makefiles/common.mk
ARCHS = arm64//支持的CPU架构
TWEAK_NAME = WCTimelineSightRetweet
WCTimelineSightRetweet_FILES = Tweak.xm
WCTimelineSightRetweet_FRAMEWORKS = UIKit CoreGraphics//导入系统的framework
include $(THEOS_MAKE_PATH)/tweak.mk
after-install::
install.exec "killall -9 WeChat"//安装完成杀掉的进程
لا يحتاج ملف التحكم إلى التعديل، ثم قم بتنفيذ الأمر make package install
لتثبيته على الهاتف المحمول، وسيتم إيقاف WeChat، ثم قم بفتح WeChat مرة أخرى، وتمت إضافة وظيفة إعادة توجيه مقاطع الفيديو القصيرة.
تثبيت macports (تتطلب عملية التثبيت اتصال VPN، وإلا فلن يكون التثبيت ناجحًا)
بعد تثبيت MacPorts، افتح الوحدة الطرفية وأدخل sudo port -v selfupdate
لتحديث MacPorts إلى الإصدار الأحدث، الأمر الذي قد يستغرق وقتًا طويلاً.
بعد تحديث MacPorts، قم بتثبيت ملف DPKG وأدخل sudo port -f install dpkg
في الجهاز.
قم بتنزيل وتثبيت iOSOpendev. إذا فشل التثبيت، يمكنك استخدام Command + L
للتحقق من وجود مشاكل أثناء التثبيت.
PackageKit: Install Failed: Error Domain=PKInstallErrorDomain Code=112 "运行软件包“iOSOpenDev-1.6-2.pkg”的脚本时出错。" UserInfo={NSFilePath=./postinstall, NSURL=file://localhost/Users/ice/Downloads/iOSOpenDev-1.6-2.pkg#iodsetup.pkg, PKInstallPackageIdentifier=com.iosopendev.iosopendev162.iod-setup.pkg, NSLocalizedDescription=运行软件包“iOSOpenDev-1.6-2.pkg”的脚本时出错。} {
NSFilePath = "./postinstall";
NSLocalizedDescription = "U8fd0U884cU8f6fU4ef6U5305U201ciOSOpenDev-1.6-2.pkgU201dU7684U811aU672cU65f6U51faU9519U3002";
NSURL = "file://localhost/Users/ice/Downloads/iOSOpenDev-1.6-2.pkg#iodsetup.pkg";
PKInstallPackageIdentifier = "com.iosopendev.iosopendev162.iod-setup.pkg";
}
إليك الحل: قم بتنزيل مجلد المواصفات في iOSOpenDevInstallSolve
5. لإصلاح مشكلة فشل التثبيت، افتح مجلد المواصفات الذي تم تنزيله في الخطوة 4. يجب أن يكون هناك 8 ملفات فيه. إذا كان لديك أكواد x متعددة مثبتة، فيرجى وضعها في أكواد x المقابلة.
(1) يتم وضع الملفات الأربعة التي تبدأ بـ iPhoneOS في المجلد /Application/Xcode/Content/Developer/Platforms/IphoneOS.platform/Developer/Library/Xcode/Specifications (إذا لم يكن الأمر كذلك، فيرجى إنشاء مجلد المواصفات بنفسك)
(2) يتم وضع الملفات الأربعة الأخرى التي تبدأ بـ iPhone Simulator في المجلد /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications (إذا لم يكن الأمر كذلك، فيرجى إنشاء ملف أيضًا)
(3) قم بإنشاء مجلد usr ضمن المجلد /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/، وقم بإنشاء مجلد باسم bin ضمن مجلد usr.
ملاحظة: في بعض الأحيان تظهر رسالة تفيد بفشل التثبيت. افتح مشروعًا جديدًا في Xcode. إذا كان هناك iOSOpenDev في قائمة الخيارات الخاصة بالمشروع، فهذا يعني أن التثبيت ناجح.
لتثبيت حزمة ipa، يمكنك أيضًا استخدام أدوات مثل itool، ولكن يمكن لـ ideviceinstaller رؤية عملية التثبيت، مما يسهل علينا العثور على سبب الخطأ.
تنفيذ الأمر
brew install ideviceinstaller
إذا تمت مطالبتك بأنه لا يمكن العثور على أمر Brew، فهذا يعني أنه لم يتم تثبيت Homebrew على جهاز Mac الخاص بك.
رسائل الخطأ الشائعة:
ERROR: Could not connect to lockdownd, error code -5
في هذا الوقت، ما عليك سوى إعادة تثبيت libimobiledevice (لأن برنامج ideviceinstaller يعتمد على العديد من المكونات الإضافية الأخرى)
قم بتنفيذ الأمر التالي:
$ brew uninstall libimobiledevice
$ brew install --HEAD libimobiledevice
قم بتنزيل أداة إعادة التوقيع على iOS App Signer* (وفر الكثير من عمليات سطر الأوامر، وأعد التوقيع بنقرة واحدة!)*
(3) قم بتنزيل تطبيق WeChat المخترق
نظرًا لأن حزمة AppStore مشفرة (مغلقة) ولا يمكن إعادة تسجيلها، فيجب عليك استخدام حزمة مقفلة، ويمكنك استخدام dumpdecrypted لتفريغ الغلاف بنفسك، أو يمكنك استخدام مساعد PP أو مساعد itool مباشرة لتنزيل الإصدار المكسور. من تطبيق WeChat الذي تم قصفه.
(4) تثبيت yololib
يمكن لـ yololib حقن dylib في ملف WeChat الثنائي، بحيث يكون الخطاف الخاص بك فعالاً بعد التنزيل، وتجميعه والحصول على yololib
#####(1) إنشاء مكتبة ثابتة. تم تثبيت iOSOpendev في الخطوة السابقة. الآن افتح مشروعًا جديدًا في Xcode. سيظهر مشروع iOSOpendev في واجهة اختيار المشروع. نحتاج هنا إلى تحديد مشروع CaptainHook Tweak. يوجد مشروع واحد فقط تم إنشاؤه حديثًا.mm، نحتاج فقط إلى كتابة جميع أساليب الربط في هذا الملف.
نظرًا لأن الأجهزة التي لم يتم كسر الحماية فيها لا يمكنها تثبيت المكونات الإضافية المعدلة لربط التطبيقات الأصلية مثل الأجهزة التي تم كسر الحماية فيها، يستخدم CaptainHook آلية وقت التشغيل لتنفيذ وظائف مثل تعريف الفئة واستبدال الطريقة باستخدام أوامر الماكرو.
CHDeclareClass(WCContentItemViewTemplateNewSight);
يشير CHDeclareClass(ClassName)
إلى الفئة التي سيتم ربطها، ويتم كتابتها عادةً في بداية العملية على هذه الفئة.
CHDeclareMethod0(id, WCContentItemViewTemplateNewSight, SLSightDataItem){......}
CHDeclareMethod(count, return_type, class_type, name1, arg1, ....)
يعني إنشاء طريقة جديدة، count يعني عدد معلمات هذه الطريقة، return_type يعني نوع الإرجاع، class_type يملأ اسم الفئة لهذه الطريقة، name1 يعني اسم الطريقة، ويمثل arg1 المعلمة الأولى، وإذا لم يكن هناك معلمة، فاتركها فارغة، وهكذا.
CHOptimizedMethod0(self, void, WCContentItemViewTemplateNewSight, onLongTouch){
CHSuper(0, className, Method);//可选
......
}
CHOptimizedMethod(count, optimization, return_type, class_type, name1, type1, arg1)
يعني ربط الطريقة الأصلية (إذا كان CHSuper(0, className, Method)
تعني نسخ الطريقة الأصلية، CHSuper يعني استدعاء الطريقة الأصلية في الموضع الحالي)، العدد يعني عدد معلمات طريقة الربط، والتحسين يملأ بشكل عام الذات، وreturn_type هو نوع قيمة إرجاع الطريقة، ويملأ class_type اسم الفئة الفئة الحالية، name1 هو اسم الطريقة، arg1 هو المعلمة، إذا لم تكن هناك معلمات، املأ arg، وما إلى ذلك.
CHConstructor
{
@autoreleasepool
{
CHLoadLateClass(WCContentItemViewTemplateNewSight);
CHHook(0, WCContentItemViewTemplateNewSight, onLongTouch);
}
}
هذه هي وظيفة الإدخال الخاصة بـ CaptainHook، ويجب الإعلان عن جميع الفئات المرتبطة هنا ليتم تحميلها، ويجب الإعلان عن الطرق الموجودة في الفصل هنا.
ثم يمكنك كتابة التعليمات البرمجية في الفئات والأساليب، فالرمز طويل جدًا لذا لن أقوم بنشره على github باستخدام MMPlugin.
يتضمن هذا المشروع وظائف إعادة توجيه مقاطع الفيديو القصيرة، والتقاط المظاريف الحمراء تلقائيًا، وتعديل خطوات تمرين WeChat، ويمكن إيقاف تشغيل وظائف التقاط المظاريف الحمراء تلقائيًا، وتعديل خطوات تمرين WeChat يدويًا.
ملاحظة: إذا كنت تستخدم فئات النظام ، تذكر استيراد مكتبة الفئة المقابلة (مثل UIKIT) وملفات الرأس ، وإلا سيتم الإبلاغ عن خطأ أثناء التجميع.
بعد التجميع الناجح ، يمكنك العثور على المكتبة الثابتة المترجمة في مجلد المنتجات.
ابحث عنه في Finder ونسخه للاستخدام لاحقًا.
تتضمن المواد التي يجب أن يكون لديك في هذه المرحلة:
ابحث عن ملف WeChat الثنائي من تطبيق WeChat الأصلي ونسخه للاستخدام لاحقًا.
قم بتنفيذ الأمر التالي لحقن mmplugin.dylib في ملف WeChat الثنائي.
LeonLei-MBP:WeChat gaoshilei$ ./yololib WeChat MMPlugin.dylib
عند تنفيذ هذا الأمر ، تأكد من أن Yololib و WeChat و WeChat.app في نفس الدليل.
بعد الانتهاء ، نسخ mmplugin.dylib و WeChat إلى wechat.app الأصلي ، الكتابة فوق ملف WeChat الأصلي.
افتح علامة تطبيق iOS وحدد معلمات مختلفة كما هو موضح أدناه:
ما اخترته هنا شهادة على مستوى المؤسسة.
اتصل بهاتفك المحمول وقم بتنفيذ الأمر التالي للتحقق مما إذا كان IDeviceInstaller متصلاً بالهاتف المحمول:
LeonLei-MBP:WeChat gaoshilei$ ideviceinfo
إذا تمت طباعة الكثير من معلومات الهاتف المحمول ، فهذا يعني أن الاتصال ناجحًا ويمكنك تثبيت حزمة IPA. قم بتنفيذ الأمر التالي للتثبيت:
LeonLei-MBP:WeChat gaoshilei$ ideviceinstaller -i WeChat.ipa
WARNING: could not locate iTunesMetadata.plist in archive !
WARNING: could not locate Payload/WeChat.app/SC_Info/WeChat.sinf in archive !
Copying ' WeChat.ipa ' to device... DONE.
Installing ' com.xxxxxxxxxxxx '
- CreatingStagingDirectory (5%)
- ExtractingPackage (15%)
- InspectingPackage (20%)
- TakingInstallLock (20%)
- PreflightingApplication (30%)
- InstallingEmbeddedProfile (30%)
- VerifyingApplication (40%)
- CreatingContainer (50%)
- InstallingApplication (60%)
- PostflightingApplication (70%)
- SandboxingApplication (80%)
- GeneratingApplicationMap (90%)
- Complete
بعد اكتمال التثبيت ، افتح WeChat على هاتفك لتجربة الميزات الجديدة التي أضفناها! إذا كان هناك ارتباط معين ، فسيتم الإبلاغ عن خطأ. يرجى الاطلاع على العروض: