淺談 iOS Device ID 的修改

最近有一篇 文章 介紹了如何實(shí)現(xiàn) AppStore App 自動(dòng)下載育苟,筆者看后收獲良多黎棠。不過文中只介紹了如何去模擬用戶的操作來完成下載抖僵,并沒有涉及抹機(jī)鲤看、IP 更換等內(nèi)容。所以筆者打算在此分享一下自己對(duì)這些方面的經(jīng)驗(yàn)耍群。


FBI WARNING

  1. 以下內(nèi)容可能會(huì)引起很多人不適义桂,請(qǐng)讀者自酌。
  2. 18 歲以下請(qǐng)?jiān)诩议L(zhǎng)陪同下觀看蹈垢!
  3. 部分內(nèi)容可能違反你所在地相關(guān)法律慷吊,請(qǐng)謹(jǐn)慎模仿

為什么要修改 iOS Device ID ?

修改設(shè)備唯一可識(shí)別標(biāo)識(shí)可以做很多事前曹抬,比如防止根據(jù) UUID 的追蹤溉瓶,避免大數(shù)據(jù)「殺熟」等。但是在 iOS 設(shè)備上目前想做到修改的前提是越獄谤民,所以為了多領(lǐng)幾個(gè)美團(tuán)紅包而選擇承擔(dān)越獄的風(fēng)險(xiǎn)堰酿,是否值得還是要考慮清楚的。
不過在業(yè)界有大量應(yīng)用這種技術(shù)的產(chǎn)業(yè)张足,比如積分墻触创、ASO 刷榜…… 不過這些產(chǎn)業(yè)就屬于「灰黑產(chǎn)」了,涉及到了原力的黑暗面为牍,所以筆者不建議涉世不深的讀者繼續(xù)閱讀下去嗅榕。

當(dāng)你凝視深淵,深淵也在凝視著你吵聪。

現(xiàn)狀

在開始講如何做之前凌那,筆者決定先簡(jiǎn)單介紹一下業(yè)界現(xiàn)在已經(jīng)能做什么:


一款常見的改機(jī)軟件

如圖所示,這是一款在業(yè)內(nèi)非常常見的改機(jī)軟件吟逝。由于作者不可考(不過理應(yīng)如此帽蝶,畢竟為了自己的人生安全)、源碼遺失、以及 iOS 版本的多次更新励稳,現(xiàn)在已經(jīng)不值錢了佃乘。但是麻雀雖小五臟俱全,它能夠修改設(shè)備的五碼驹尼、機(jī)型趣避、配置 Apple ID 和一鍵越獄等。
前人的成功告訴了我們這是可行的新翎,剩下的只是模仿程帕,因此筆者深入逆向并研究了這款軟件,在當(dāng)我看到了一大堆用匯編寫的混淆之后…… 放棄了地啰。
所以下面的內(nèi)容都是筆者編的愁拭,大家有興趣看個(gè)開心就好,基本上可以點(diǎn)關(guān)閉按鈕了 (●°u°●) 」

如何破解一款程序亏吝?

筆者依稀記得 狗神 在他那本著名的 小黃書 中提到岭埠,逆向一款軟件最重要的不是最終成品的代碼,而是過程的分析與思路蔚鸥。所以經(jīng)诚郏可以看到一款軟件的破解代碼重要的也許只有兩三行,但是過程有多艱辛也許只有破解者才知道止喷。例如破解 Mac 版 QQ 音樂下載需要 VIP 權(quán)限的限制的代碼也許加上注釋也不到一百行:

/* How to Hook with Logos
Hooks are written with syntax similar to that of an Objective-C @implementation.
You don't need to #include <substrate.h>, it will be done automatically, as will
the generation of a class list and an automatic constructor.

%hook ClassName

// Hooking a class method
+ (id)sharedInstance {
    return %orig;
}

// Hooking an instance method with an argument.
- (void)messageName:(int)argument {
    %log; // Write a message about this call, including its class, name and arguments, to the system log.

    %orig; // Call through to the original function with its original arguments.
    %orig(nil); // Call through to the original function with a custom argument.

    // If you use %orig(), you MUST supply all arguments (except for self and _cmd, the automatically generated ones.)
}

// Hooking an instance method with no arguments.
- (id)noArguments {
    %log;
    id awesome = %orig;
    [awesome doSomethingElse];

    return awesome;
}

// Always make sure you clean up after yourself; Not doing so could have grave consequences!
%end
*/


%config(generator = internal)

#import <Foundation/Foundation.h>
#include <substrate.h>

%hook DownLoadTask

- (BOOL)checkHaveRightToDownload:(int)argument {
    return YES;
}

%end

unsigned int (*old_GetFlexBOOL)(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8);
unsigned int  new_GetFlexBOOL(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
  return 1;
}

%ctor {
    NSLog(@"!!!!!!inject success!!!!!!!");

    void * Symbol = MSFindSymbol(MSGetImageByName("/Applications/QQMusic.app/Contents/MacOS/QQMusic"), "_GetFlexBOOL");
    MSHookFunction(Symbol, &new_GetFlexBOOL, (void *)&old_GetFlexBOOL);
}

而真正重要的是找出思路和逆向分析的過程馆类,操作系統(tǒng)本質(zhì)上也是一個(gè)軟件,修改 Device ID 其實(shí)和破解一款音樂 VIP 限制本質(zhì)上是一樣的启盛,只是一個(gè)只需要把 checkHaveRightToDownload 的返回值改成 YES ,另一個(gè)則需要與操作系統(tǒng)斗智斗勇罷了技羔。

思路

綜上所述僵闯,在我們對(duì)操作系統(tǒng)下黑手之前應(yīng)該先理清思路。順便再說一次以下內(nèi)容皆是我瞎編的藤滥,如有雷同實(shí)屬巧合:

思路

如圖所示鳖粟,顯而易見,如果只是簡(jiǎn)簡(jiǎn)單單的修改某個(gè) App 中用到的 Device ID拙绊,極大幾率只需要勾住「再封裝的私有 API」就行了向图。

而在眾多私有 API 中,最著名的當(dāng)然是大名鼎鼎的 MGCopyAnswer标沪。

MGCopyAnswer

// Common form: MGCopyAnswer(CFStringRef string);
CFStringRef value = MGCopyAnswer(kMGDeviceColor);
NSLog(@"Value: %@", value);
CFRelease(value);

基本上平時(shí)從 UIDevice 還是其他大部分途徑獲取 Device ID榄攀,皆是通過調(diào)用 libMobileGestalt 中的 MGCopyAnswer 函數(shù)來獲取的。所以只需要勾住 MGCopyAnswer金句,使其返回的 Device ID 為我們所要的值即可檩赢,非常簡(jiǎn)單明了。

不過雖說思路很簡(jiǎn)單违寞,但是一個(gè)萌新想要勾 MGCopyAnswer 還是會(huì)繞很多彎路的贞瞒,比如最常見的就是「掛短鉤」偶房。

掛短鉤

在 ARM64 架構(gòu)下,直接對(duì) MGCopyAnswer 掛鉤的話會(huì)立即使進(jìn)程崩潰 invalid instruction军浆。如果通過反匯編手段分析 libMobileGestalt 庫:

01 00 80 d2        movz x1, #0
01 00 00 14        b    MGCopyAnswer_internal

易知 MGCopyAnswer 實(shí)際上在內(nèi)部調(diào)用了另一個(gè)私有無符號(hào)的函數(shù) MGCopyAnswer_internal 來實(shí)現(xiàn)其功能棕洋。因此 MGCopyAnswer 這個(gè)函數(shù)實(shí)際上非常短,只有 8 個(gè)字節(jié)乒融,而我們使用 Cydia Substrate 對(duì)一個(gè) C 函數(shù)掛鉤的話掰盘,它要求被勾函數(shù)至少有 16 個(gè)字節(jié)。因此直接勾住 MGCopyAnswer 時(shí)簇抵,MGCopyAnswer 函數(shù)地址開始的 16 個(gè)字節(jié)都會(huì)被改為 goto庆杜,從而破壞了相鄰函數(shù)的前 8 個(gè)字節(jié),使進(jìn)程崩潰碟摆。
因此晃财,當(dāng)我們吭哧吭哧讀完匯編之后,首先想到的方法自然是去勾這個(gè)被調(diào)用的子函數(shù) MGCopyAnswer_internal典蜕,雖說該函數(shù)并沒有符號(hào)断盛,但是在我們吭哧吭哧讀了匯編之后,發(fā)現(xiàn)其函數(shù)地址與 MGCopyAnswer 相差 8 字節(jié)愉舔。故可以很簡(jiǎn)單粗暴的寫出如下代碼:

static CFPropertyListRef (*orig_MGCopyAnswer_internal)(CFStringRef prop, uint32_t* outTypeCode);
CFPropertyListRef new_MGCopyAnswer_internal(CFStringRef prop, uint32_t* outTypeCode) {
    return orig_MGCopyAnswer_internal(prop, outTypeCode);
}

extern "C" MGCopyAnswer(CFStringRef prop);

static CFPropertyListRef (*orig_MGCopyAnswer)(CFStringRef prop);
CFPropertyListRef new_MGCopyAnswer(CFStringRef prop) {
    return orig_MGCopyAnswer(prop);
}

%ctor {
    uint8_t MGCopyAnswer_arm64_impl[8] = {0x01, 0x00, 0x80, 0xd2, 0x01, 0x00, 0x00, 0x14};
    const uint8_t* MGCopyAnswer_ptr = (const uint8_t*) MGCopyAnswer;
    if (memcmp(MGCopyAnswer_ptr, MGCopyAnswer_arm64_impl, 8) == 0) {
        MSHookFunction(MGCopyAnswer_ptr + 8, (void*)new_MGCopyAnswer_internal, (void**)&orig_MGCopyAnswer_internal);
    } else {
        MSHookFunction(MGCopyAnswer_ptr, (void*)new_MGCopyAnswer, (void**)&orig_MGCopyAnswer);
    }
}

顯然這段代碼除了簡(jiǎn)單粗暴钢猛、沒有任何框架檢測(cè)與異常處理之外完美實(shí)現(xiàn)了掛鉤任務(wù),但是基于相對(duì)偏移量來獲取函數(shù)地址也并不是很穩(wěn)轩缤。

好在張總在他的一篇博文中提到可以使用 Capstone Engine,一款基于 LLVM MC 的多平臺(tái)多架構(gòu)支持的反匯編框架來幫助我們找到 MGCopyAnswer_internal 的「符號(hào)」命迈。

static CFStringRef (*old_MGCA)(CFStringRef Key);
CFStringRef new_MGCA(CFStringRef Key) {
    CFStringRef Ret = old_MGCA(Key);
    NSLog(@"MGHooker:%@\nReturn Value:%@", Key, Ret);
    return Ret;
}

%ctor {
    void *Symbol = MSFindSymbol(MSGetImageByName("/usr/lib/libMobileGestalt.dylib"), "_MGCopyAnswer");
    NSLog(@"MG: %p", Symbol);
    csh           handle;
    cs_insn *     insn;
    cs_insn       BLInstruction;
    size_t        count;
    unsigned long realMGAddress = 0;
    // MSHookFunction(Symbol,(void*)new_MGCA, (void**)&old_MGCA);
    if (cs_open(CS_ARCH_ARM64, CS_MODE_ARM, &handle) == CS_ERR_OK) {
        /*cs_disasm(csh handle,
              const uint8_t *code, size_t code_size,
              uint64_t address,
              size_t count,
              cs_insn **insn);*/
        count = cs_disasm(handle, (const uint8_t *)Symbol, 0x1000, (uint64_t)Symbol, 0, &insn);
        if (count > 0) {
            NSLog(@"Found %lu instructions", count);
            for (size_t j = 0; j < count; j++) {
                NSLog(@"0x%" PRIx64 ":\t%s\t\t%s\n", insn[j].address, insn[j].mnemonic, insn[j].op_str);
                if (insn[j].id == ARM64_INS_B) {
                    BLInstruction = insn[j];
                    sscanf(BLInstruction.op_str, "#%lx", &realMGAddress);
                    break;
                }
            }

            cs_free(insn, count);
        }
        else {
            NSLog(@"ERROR: Failed to disassemble given code!%i \n", cs_errno(handle));
        }

        cs_close(&handle);

        // Now perform actual hook
        MSHookFunction((void *)realMGAddress, (void *)new_MGCA, (void **)&old_MGCA);
    }
    else {
        NSLog(@"MGHooker: CSE Failed");
    }
}

廢話不多說了,我們的正題并不在這里火的。

如何修改 iOS Device ID

接下來的東西我是真的就不會(huì)了壶愤,但是為了不太斧頭蛇尾,我就再瞎掰一段吧馏鹤。
談到修改的話征椒,我們首先要弄清楚的一點(diǎn)是我們打算要從哪一層修改?比如 ECID湃累,眾所周知它是燒在芯片上的勃救。講道理的話要修改 ECID 是要對(duì)硬件動(dòng)手的,但是我們一般不需要做的這么徹底治力,而是結(jié)合具體需求具體分析蒙秒。例如一個(gè)普通、簡(jiǎn)單的積分墻宵统,我們只需要對(duì)積分墻調(diào)用的 MGCopyAnswer 掛鉤税肪,就可以愉快的玩耍了。但是如果想對(duì) AppStore 或者 iTunes 下手呢?自然僅僅勾個(gè) MGCopyAnswer 是不行的益兄。
例如我們想讓手機(jī)連接 iTunes 時(shí)锻梳,iTunes 獲取的 Device ID 是偽造的,那么就需要勾住處理手機(jī)與電腦間 USB 通信的守護(hù)進(jìn)程——比如說 lockdownd净捅。因?yàn)?iTunes 并不會(huì)直接讀取手機(jī)的設(shè)備信息疑枯,而是從手機(jī)上運(yùn)行的守護(hù)進(jìn)程中請(qǐng)求數(shù)據(jù)。那么我們是不是只需要在這個(gè)守護(hù)進(jìn)程安裝一個(gè)鉤子即可蛔六?

typedef void *LockdownConnectionRef;
typedef int   kern_return_t;

typedef unsigned int              __darwin_natural_t;
typedef __darwin_natural_t        __darwin_mach_port_name_t;
typedef __darwin_mach_port_name_t __darwin_mach_port_t;
typedef __darwin_mach_port_t      mach_port_t;
typedef mach_port_t               io_object_t;
typedef io_object_t               io_registry_entry_t;

typedef char         io_name_t[128];
typedef unsigned int IOOptionBits;

static kern_return_t (*oldIORegistryEntryGetName)(io_registry_entry_t entry, io_name_t name);
kern_return_t newIORegistryEntryGetName(io_registry_entry_t entry, io_name_t name) {
    int ret = oldIORegistryEntryGetName(entry, name);
    NSLog(@"\n\nGetName:\n\tentry:%zd\n\tio_name_t%s\n\tret:%d", entry, name, ret);
    return ret;
}

static CFTypeRef (*oldIORegistryEntrySearchCFProperty)(
    io_registry_entry_t entry, const io_name_t plane, CFStringRef key, CFTypeRef allocator, IOOptionBits options);
CFTypeRef newIORegistryEntrySearchCFProperty(
    io_registry_entry_t entry, const io_name_t plane, CFStringRef key, CFTypeRef allocator, IOOptionBits options) {
    CFTypeRef ret = oldIORegistryEntrySearchCFProperty(entry, plane, key, allocator, options);
    NSLog(@"\n\nSearchCFProperty:\n\tkey:%@\n\tret:%@\n\t%lu", key, ret, CFGetTypeID(ret));
    return ret;
}

static CFPropertyListRef (*old_lockdown_copy_value)(LockdownConnectionRef connection,
                                                    CFStringRef           domain,
                                                    CFStringRef           key);
CFPropertyListRef new_lockdown_copy_value(LockdownConnectionRef connection, CFStringRef domain, CFStringRef Key) {
    CFPropertyListRef Ret = old_lockdown_copy_value(connection, domain, Key);
    NSLog(@"LDHooker:%@\nReturn Value:%@", Key, Ret);
    return old_lockdown_copy_value(connection, domain, Key);
}

% ctor {
    void *SymbolGN =
        MSFindSymbol(MSGetImageByName("/System/Library/Frameworks/IOKit.framework/IOKit"), "_IORegistryEntryGetName");
    NSLog(@"GName: %p", SymbolGN);
    MSHookFunction((void *)SymbolGN, (void *)newIORegistryEntryGetName, (void **)&oldIORegistryEntryGetName);

    void *SymbolSC = MSFindSymbol(MSGetImageByName("/System/Library/Frameworks/IOKit.framework/IOKit"),
                                  "_IORegistryEntrySearchCFProperty");
    NSLog(@"SPropertey: %p", SymbolSC);
    MSHookFunction(
        (void *)SymbolSC, (void *)newIORegistryEntrySearchCFProperty, (void **)&oldIORegistryEntrySearchCFProperty);
    }
    else {
        NSLog(@"MGHooker: CSE Failed");
    }
}

其實(shí)我想大家應(yīng)該猜到我下面想做什么了荆永,既然都已經(jīng)對(duì)守護(hù)進(jìn)程下手了,要不干脆我們自己也開一個(gè)守護(hù)進(jìn)程的了国章,加個(gè) root 權(quán)限具钥,對(duì)所有其他進(jìn)程安裝鉤子,如果調(diào)用了 Device ID 相關(guān)的 API液兽,把返回值魔改掉骂删,豈不美滋滋!代碼如下:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 紅紅火火恍恍惚惚
        NSLog(@"想不到吧四啰,這次我真的編不出來了??");
    }
    return 0;
}

那么今天的代碼就寫到這里了宁玫,下臺(tái)鞠躬!


注:以上所有代碼全是瞎掰柑晒,如能運(yùn)行欧瘪,純屬巧合。


如何實(shí)現(xiàn) AppStore App 的自動(dòng)下載

Hooking MGCopyAnswer Like A Boss

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匙赞,一起剝皮案震驚了整個(gè)濱河市佛掖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涌庭,老刑警劉巖芥被,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脾猛,居然都是意外死亡撕彤,警方通過查閱死者的電腦和手機(jī)鱼鸠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門猛拴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蚀狰,你說我怎么就攤上這事愉昆。” “怎么了麻蹋?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵跛溉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)芳室,這世上最難降的妖魔是什么专肪? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮堪侯,結(jié)果婚禮上嚎尤,老公的妹妹穿的比我還像新娘。我一直安慰自己伍宦,他們只是感情好芽死,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著次洼,像睡著了一般关贵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卖毁,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天揖曾,我揣著相機(jī)與錄音,去河邊找鬼势篡。 笑死翩肌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的禁悠。 我是一名探鬼主播念祭,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼碍侦!你這毒婦竟也來了粱坤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤瓷产,失蹤者是張志新(化名)和其女友劉穎站玄,沒想到半個(gè)月后濒旦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體株旷,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年尔邓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晾剖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梯嗽,死狀恐怖齿尽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灯节,我是刑警寧澤循头,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布绵估,位于F島的核電站,受9級(jí)特大地震影響卡骂,放射性物質(zhì)發(fā)生泄漏国裳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一全跨、第九天 我趴在偏房一處隱蔽的房頂上張望躏救。 院中可真熱鬧,春花似錦螟蒸、人聲如沸盒使。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽少办。三九已至,卻和暖如春诵原,著一層夾襖步出監(jiān)牢的瞬間英妓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工绍赛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔓纠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓吗蚌,卻偏偏與公主長(zhǎng)得像腿倚,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚯妇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容