หลังจากเชี่ยวชาญเครื่องมือที่กล่าวมาข้างต้นแล้ว คุณสามารถเริ่มพัฒนาปลั๊กอินได้ ขั้นแรก คุณควรคิดถึงความต้องการที่ปลั๊กอินจำเป็นต้องนำไปใช้ และค้นหามุมมองและตัวควบคุมที่เกี่ยวข้อง ขั้นตอนแรกคือการใช้ class-dump เพื่อวิเคราะห์ APP ที่เชลล์ไว้ Class-dump สามารถวิเคราะห์ชื่อคลาสและการประกาศเมธอดทั้งหมดตามตารางสัญลักษณ์ในไฟล์ Mach-O
~ » ./class-dump -H -o /header/path WeChat
หลังจากส่งออกไฟล์ส่วนหัวแล้ว ให้ดึงไฟล์ส่วนหัวเหล่านี้ไปยังโปรเจ็กต์ XCode เพื่ออำนวยความสะดวกในการค้นหาในภายหลัง
หลังจากที่คุณมีไฟล์ส่วนหัวแล้ว คุณจะต้องล็อคมันไว้ที่คลาสคอนโทรลเลอร์ของอินเทอร์เฟซที่เกี่ยวข้อง ใช้ OpenSSH เพื่อเชื่อมต่อกับ iPhone ที่เจลเบรคแล้วด้วย Mac ใช้ Cycript เพื่อแทรกกระบวนการ เรียกเมธอด [[UIApp keyWindow] recursiveDescription]
เพื่อรับลำดับชั้นของมุมมองปัจจุบัน รับที่อยู่ของมุมมอง และเรียกซ้ำๆ ว่า [#0x2b2b2b00 nextResponder]
ในที่สุดคุณก็จะได้คลาสคอนโทรลเลอร์อินเทอร์เฟซ
ณ จุดนี้ โดยทั่วไปจะมีจุดเริ่มต้นสองจุดเพื่อทำการวิเคราะห์ย้อนกลับต่อไป
หากเราต้องการให้การดำเนินการ Hook ถูกกระตุ้นโดยเหตุการณ์การสัมผัสของมุมมองบนอินเทอร์เฟซ โดยทั่วไปมีสองวิธีในการค้นหาการดำเนินการ:
[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 และการดีบักการติดตามเบรกพอยต์ ณ ที่อยู่ของคำสั่งแอสเซมบลีบางอย่าง มันจะช่วยให้เข้าใจรายละเอียดของการใช้งานฟังก์ชัน
มีวิธีแก้ไขปัญหามากมายสำหรับฟังก์ชันเป้าหมาย Hook แต่โดยหลักการแล้วโซลูชันทั้งหมดนั้นขึ้นอยู่กับคุณลักษณะไดนามิกของ Objective-C และใช้ Method Swizzling เพื่อแทนที่การใช้งานดั้งเดิม ครั้งนี้เราจะแนะนำการใช้ไลบรารี่ CaptainHook อย่างละเอียด ไลบรารีนี้ถูกนำไปใช้งานตาม MSHookMessageEx()
ใน Cydia Substrate การประกาศของฟังก์ชันนี้คือ:
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
หลังจากติดตั้ง iOSOpenDev แล้ว คุณสามารถใช้เทมเพลต CaptainHook Tweak เพื่อสร้างโปรเจ็กต์ได้ ไลบรารี CaptainHook แนะนำไวยากรณ์ใหม่สำหรับการเขียนฟังก์ชัน Hook ขั้นแรก ให้โหลดคลาสที่มีฟังก์ชันที่จะ Hooked อยู่ใน 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 เพื่อประกาศฟังก์ชัน Hook และนำไปใช้ในโค้ดโดยตรง
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 ที่คอมไพล์แล้วลงในรายการ Load Commands ของไฟล์ปฏิบัติการ Mach-O
~ » ./yololib [binary] [dylib file]
โครงสร้างของไฟล์ Mach-O ส่วนใหญ่ประกอบด้วยสามส่วน ส่วนหน้าคือโครงสร้างส่วนหัว ซึ่งจะบันทึกประเภทแพลตฟอร์ม ประเภทไฟล์ จำนวน LoadCommands และข้อมูลอื่นๆ ของ Mach-O ตามด้วยส่วนหัวคือส่วนคำสั่งโหลด โดยการแยกวิเคราะห์ส่วนนี้ โครงสร้างเชิงตรรกะของไฟล์และ สามารถกำหนดตำแหน่งในไฟล์เสมือนได้ เครื่องมือ yololib จะเปลี่ยนข้อมูลในส่วน Load Commands เพื่อโหลด 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);
จากนั้นเปลี่ยนส่วน Load Commands และเพิ่มข้อมูลการโหลด 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 (รวมถึงส่วนขยาย APP ในโฟลเดอร์ปลั๊กอิน) ใช้คำสั่ง xcrun -sdk iphoneos PackageApplication -v
เพื่อรวมไลบรารีไดนามิกและไฟล์ทั้งหมดไว้ด้วยกัน คุณจะเข้าใจกระบวนการทั้งหมดของ หากมีใบรับรองระดับองค์กร แอปพลิเคชันที่ลงนามและแพ็กเกจจะสามารถติดตั้งบน iPhone ที่ไม่ได้เจลเบรคซึ่งเชื่อถือใบรับรองได้ ดารานักพายเรือ!