MobileSubstrate/th

From iPhone Development Wiki
Jump to: navigation, search
MobileSubstrate/th
Cydia Package
Developer saurik
Package ID mobilesubstrate
Latest Version 0.9.4001

Languages: Englishfrançais • ไทย

Cydia Substrate (หรือมักเรียกกันว่า MobileSubstrate) คือ de facto framework กล่าวคือ เป็นเฟรมเวิร์คที่มีอยู่จริงที่อนุญาตให้นักพัฒนาโปรแกรมอิสระใส่ patch แบบ run-time (“Cydia Substrate extensions”) ให้กับฟังก์ชันของระบบ เช่นเดียวกับ Application Enhancer ใน OS X

saurik ได้เขียน เว็บไซต์ที่อธิบายเกี่ยวกับ Substrate

Cydia Substrate ประกอบด้วย 3 องค์ประกอบหลัก อันได้แก่ MobileHooker, MobileLoader และ safe mode

MobileHooker

MobileHooker ใช้สำหรับการแทนที่ฟังก์ชันของระบบ กระบวนการนี้เป็นที่รู้จักในคำว่า hooking ซึ่งมันมี 2 API ที่เราจะใช้:

IMP MSHookMessage(Class class, SEL selector, IMP replacement, const char* prefix); // prefix ควรจะเป็น NULL
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP *result);
void MSHookFunction(void* function, void* replacement, void** p_original);

MSHookMessage() จะแทนที่ผลการทำงานของ Objective-C message -[class selector] โดย การแทนที่, และให้ผลการทำงานเดิมของมัน ในการ hook class method ให้ใช้ meta class ที่ได้จากการใช้ objc_getMetaClass ในการเรียกใช้ MSHookeMessage(Ex) และดูตัวอย่างด้านล่าง การแทนที่แบบไดนามิกนี้เป็น feature ใน Objective-C และสามาถทำได้โดยใช้ method_setImplementation. MSHookMessage() นั้นไม่ค่อยปลอดภัยและถูกแทนที่ด้วย MSHookMessageEx() แทน

MSHookFunction() นั้นเหมือนกับ MSHookMessage() แต่เอาไว่ใช้สำหรับฟังก์ชัน C/C++ การแทนที่จะต้องทำในระดับ assembly หลักการก็คือว่า MSHookFunction() จะเขียน instructions ไปยังฟังก์ชันที่ต้องการแทนที่ และจัดสรรไบต์บางส่วนในที่อยู่แบบปรับแต่งในหน่วยความจำ ซึ่งมี cut-out instructions เดิม และการไปยังฟังก์ชันที่ถูก hook การที่ memory page ของ iOS ไม่สามารถเขียนและอ่านได้พร้อมกัน ทำให้ต้องมีการ patch kernel เพื่อให้ MSHookFunction() ทำงานได้

ใน version ล่าสุดของ MobileSubstrate, MSHookMessage() อาศัยการ patch kernel สำหรับการ supercall closures เพื่อการ hook method ทั้งหมดได้อย่างปกติ

ตัวอย่างโค้ด

ใช้ MSHookFunction:

static void (*original_CFShow)(CFTypeRef obj);  // function pointer ที่เก็บ CFShow() เดิม
void replaced_CFShow(CFTypeRef obj) {           // การแทนที่ CFShow() ของเรา
  printf("Calling original CFShow(%p)...", obj);
  original_CFShow(obj);                         // เรียกใช้ CFShow เดิม
  printf(" done.\n");
}
...
// hook CFShow เพื่อผลการใช้งานของเรา
MSHookFunction(CFShow, replaced_CFShow, &original_CFShow);
// จากนี้ไปการเรียกใช้ CFShow ใดๆ จะผ่านทาง replaced_CFShow ทุกครั้ง
...
CFShow(CFSTR("test"));

ใช้ MSHookMessageEx:

static IMP original_UIView_setFrame_;
void replaced_UIView_setFrame_(UIView* self, SEL _cmd, CGRect frame) {  // พารามิเตอร์ self และ _cmd นั้นจำเป็นในที่นี้
  CGRect originalFrame = self.frame;
  NSLog("Changing frame of %p from %@ to %@", self, NSStringFromCGRect(originalFrame), NSStringFromCGRect(frame));
  original_UIView_setFrame_(self, _cmd, frame);    // จำไว้เสมอว่าต้องเรียกผ่าน self และ _cmd ด้วย
}
...
MSHookMessageEx([UIView class], @selector(setFrame:), (IMP)replaced_UIView_setFrame_, (IMP *)&original_UIView_setFrame_);
...
myView.frame = CGRectMake(0, 0, 100, 100);

จำไว้ว่าถ้าเกิดเราจะ hook class method เราจะต้องใส่ meta-class ใน class argument, ตัวอย่างเช่น

MSHookMessageEx(objc_getMetaClass("UIView"), @selector(commitAnimations), replaced_UIView_commitAnimations, (IMP *)&original_UIView_commitAnimations);

ใช้ MSHookFunction เพื่อ ฟังก์ชันลับ (ในกรณีนี้ คือ C++ Method):

#define WebKit "/System/Library/PrivateFrameworks/WebKit.framework/WebKit"
#define WebCore "/System/Library/PrivateFrameworks/WebCore.framework/WebCore"

NSURLRequest* (*X_ZNK7WebCore15ResourceRequest12nsURLRequestEv)(void* something);

void (*X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE)(void* something, void* loader, unsigned long identifier,  void* request, const void** response);


MSHook(void, X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE, void* something, void* loader, unsigned long identifier,  void* request, const void** response) {
        
    NSURLRequest *nsRequest = X_ZNK7WebCore15ResourceRequest12nsURLRequestEv(request);
    //ทำอะไรบางอย่าง
    _X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE(something, loader, identifier, request, response);
}

template <typename Type_>
static void nlset(Type_ &function, struct nlist *nl, size_t index) {
    struct nlist &name(nl[index]);
    uintptr_t value(name.n_value);
    if ((name.n_desc & N_ARM_THUMB_DEF) != 0)
        value |= 0x00000001;
    function = reinterpret_cast<Type_>(value);
}

...

 dlopen(WebKit, RTLD_LAZY | RTLD_NOLOAD);
 dlopen(WebCore, RTLD_LAZY | RTLD_NOLOAD);

struct nlist nl[2];
        bzero(&nl, sizeof(struct nlist) * 2);
        nl[0].n_un.n_name = (char*)"__ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE";
        
        if (nlist(WebKit, nl) < 0 || nl[0].n_type == N_UNDF) {
            fprintf(stderr, "\n nlist(%s, %s) failed\n",
                    "WebKit",
                    nl[0].n_un.n_name);
        } else {
            struct nlist nlsucker[2];
            bzero(&nlsucker, sizeof(struct nlist) * 2);
            nlsucker[0].n_un.n_name = (char*)"__ZNK7WebCore15ResourceRequest12nsURLRequestEv";
            
            if (nlist(WebCore, nlsucker) < 0 || nlsucker[0].n_type == N_UNDF) {
                fprintf(stderr, "\n nlist(%s, %s) failed\n",
                        "WebCore",
                        nlsucker[0].n_un.n_name);
            }
            else {
                
                nlset(X_ZNK7WebCore15ResourceRequest12nsURLRequestEv, nlsucker, 0);    
            nlset(X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE, nl, 0);
            MSHookFunction(X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE,
MSHake(X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE));
                }
        }

เพราะเราต้องการ pointer ไปยัง symbol ลับ เราจึงจำเป็นต้องใช้ nlist

MobileLoader

MobileLoader จะโหลด patching code อิสระในแอปพลิเคชันที่รันอยู่

MobileLoader จะโหลดตัวมันเองไปในแอปพลิเคชันที่รันอยู่ก่อนโดยใช้ DYLD_INSERT_LIBRARIES environment variable จากนั้นมันจะมองหา dynamic library ที่ถูกเก็บอยู่ใน /Library/MobileSubstrate/DynamicLibraries/, และ dlopen พวกมัน ส่วนเสริมดังกล่าวควรใช้ constructor code เพื่อการทำงาน, ตัวอย่างเช่น

...
// Attribute ทำให้ฟังก์ชันนี้ถูกเรียกใช้ตอนโหลดขึ้นมา
__attribute__((constructor))
static void initialize() {
  NSLog(@"MyExt: Loaded");
  MSHookFunction(CFShow, replaced_CFShow, &original_CFShow);
}

นักพัฒนาอาจเพิ่ม filter เพื่อเป็นตัวกำหนดว่าส่วนเสริมควรจะถูกโหลดหรือไม่ ซึ่ง filter เหล่านี้เป็นไฟล์ประเภท plist ที่อยู่ด้วยกันกับไฟล์ dylib จะต้องมีชื่อเดียวกัน เช่น ถ้า dylib ชื่อ foo.dylib แล้วตัว filter จะต้องมีชื่อว่า foo.plist ด้วย โดย filter ควรจะเป็น dictionary ที่มี key "Filter" ซึ่งมี dictionary ที่เก็บ key เหล่านี้:

  • CoreFoundationVersion (array): ส่วนเสริมถูกโหลดเมื่อ version ของ CoreFoundation.framework สูงกว่าค่าที่กำหนดเท่านั้น ซึ่งขณะนี้ มันจะตรวจสอบเพียง 2 ค่าแรกเท่านั้น
Firmware 2.0 2.1 2.2 3.0 3.1 3.2 4.0 4.1 4.2 4.3 5.0 5.1
SourceCache version 478.23 478.26.1 478.29 478.47.7 478.52 478.61 550.32 550.38.1 550.52 550.58.1  ?
dylib version 478.23 478.26.1 478.29 478.47.7 478.52 478.61 550.32 550.38.1 550.52 550.58.1 675.00 690.10
Firmware 6.0 6.1 7.0 7.1 8.0 8.1 8.2 8.3 8.4 9.0 9.1 9.2 9.3
SourceCache version  ?
dylib version 793.00 847.20 847.24 1140.10 1141.14 1142.16 1144.17 1145.15 1240.10 1241.11 1242.13 1280.38
Firmware 10.0 10.1 10.2
SourceCache version  ?
dylib version 1348.00 1348.22
  • Bundles (array): ส่วนเสริมจะถูกโหลดหาก bundle-ID ของแอปพลิเคชันที่รันอยู่ตรงกับรายการของมันนี้
  • Classes (array): ส่วนเสริมจะถูกโหลดใน objective-C class ในรายการ ที่มีอยู่ในแอปพลิเคชัน เท่านั้น
  • Executables (array): ส่วนเสริมถูกโหลดเมื่อชื่อของ executable ตรงกับแอปพลิเคชันที่รันอยู่ มันจำเป็นในการ hook สิ่งที่ไม่มี identifiable characteristics

ตัวอย่างเช่น ให้โหลดใน SpringBoard เท่านั้น, plist ควรจะเป็นเช่นนี้

Filter = {
  Bundles = (com.apple.springboard);
};

คุณยังสามารถใช้ method นี้เพื่อกำหนดให้โหลดในแอปพลิเคชันที่มี specific bundle เช่น UIKit ดังนี้

Filter = {
  Bundles = (com.apple.UIKit);
};


คุณสามาถใช้ CoreFoundationVersion key และกำหนดค่าต่ำและค่าสูงของ version ตัวแรกจะเป็นไปตามกฎ มากกว่า-หรือ-เท่ากับ และตัวที่สองตามกฏ น้อยกว่า ตัวอย่างนี้จะถูกโหลดใน firmware ตั้งแต่ 4.0 จนถึง 4.3 เท่านั้น:

Filter = {
  CoreFoundationVersion = (550.32, 675.00);
};

ในกรณีทั่วไปหากคุณใช้ filter มากกว่าหนึ่ง (ตัวอย่างเช่น: Executable และ Bundle) filter ทั้งหมดจะต้องตรงกัน คุณจะทำได้เมื่อใช้ Mode = "Any"

Filter = {
  Executables = ("mediaserverd");
  Bundles = ( "com.apple.MobileSMS"; "net.whatsapp.WhatsApp" );
  Mode = "Any";
};


เพิ่มเติมว่า MobileLoader ยัง hook nlist() เพื่อเพิ่มประสิทธิภาพ และกำหนดตัวรับมือกับสัญญาณมากมายสำหรับ safe mode

สำหรับแอปพลิเคชันแบบ setuid ซึ่งไม่อนุญาตให้เพิ่ม environment variables ได้ นักพัฒนาแอปพลิเคชันจะต้องทำการ dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib") เพื่อให้ MobileLoader รันขึ้นมาได้

Safe mode

เมื่อส่วนเสริมทำให้ SpringBoard แครช MobileLoader จะจัดการโดยทำให้อุปกรณ์เข้าสู่ safe mode ซึ่ง safe mode นี้จะปิดใช้ส่วนเสริมอิสระทั้งหมด

สัญญานต่อไปนี้จะทำให้เข้าสู่ safe mode:

  • SIGTRAP
  • SIGABRT
  • SIGILL
  • SIGBUS
  • SIGSEGV
  • SIGSYS