[轉(zhuǎn)載]黑科技:把第三方 iOS 應(yīng)用轉(zhuǎn)成動態(tài)庫

前言

本文會介紹一個自己寫的工具剑刑,能夠把第三方iOS應(yīng)用轉(zhuǎn)成動態(tài)庫媳纬,并加載到自己的App中,文章最后會以支付寶為例施掏,展示如何調(diào)用其中的C函數(shù)和OC方法钮惠。

工具開源地址:
https://github.com/tobefuturer/app2dylib

有什么用

為什么要把第三方應(yīng)用轉(zhuǎn)成動態(tài)庫呢?與一般的注入動態(tài)庫+重簽名打包的手段有什么不一樣呢七芭?

好處主要有下面幾點:

  1. 可以直接調(diào)用別人的算法

    逆向分析別人的應(yīng)用時素挽,可能會遇到一些私有算法,如果搞不定的話狸驳,直接拿來用就好预明。

  2. 掌控程序的控制權(quán)

    程序的主體是自己的App,第三方應(yīng)用的代碼只是以動態(tài)庫的形式加載耙箍,主要的控制權(quán)還是在我們自己手里撰糠,所以可以直接繞過應(yīng)用的檢測代碼(文章最后有關(guān)于這部分攻防的討論)。

  3. 同個進程內(nèi)加載多個應(yīng)用

    重簽名打包畢竟只能是原來的應(yīng)用辩昆,但是如果是動態(tài)庫的話阅酪,可以同時加載多個應(yīng)用到進程內(nèi)了,比如你想同時把美圖秀秀和餓了么加載進來也是可以的(秀秀不餓汁针,想想去年大眾點評那個APPmixer的軟廣 - -! )术辐。

應(yīng)用和動態(tài)庫的異同

我們要把應(yīng)用轉(zhuǎn)成動態(tài)庫,首先要知道這兩者之前有什么相同與不同施无,有相同的才存在轉(zhuǎn)換的可能辉词,而不同之處就是我們要重點關(guān)注的了。

相同點:

![p://upload-images.jianshu.io/upload_images/8702968-b2484b42e3e7e035.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可執(zhí)行文件和動態(tài)庫都是標(biāo)準的 Mach-O 文件格式猾骡,兩者的文件頭部結(jié)構(gòu)非常類似较屿,特別是其中的代碼段(TEXT),和數(shù)據(jù)段(DATA)結(jié)構(gòu)完全一致,這也是后面轉(zhuǎn)換工作的基礎(chǔ)卓练。

不同點

不同點就是我們轉(zhuǎn)換工作的重點了,主要有:

  1. 頭部的文件類型
    一個是 MH_EXECUTE 可執(zhí)行文件购啄, 一個是 MH_DYLIB 動態(tài)庫襟企, 還有各種頭部的Flags,要特別留意下可執(zhí)行文件中Flags部分的 MH_PIE 標(biāo)志狮含,后面再詳細說顽悼。
    [圖片上傳中...(image-6d38ae-1543815911164-2)]

  2. 動態(tài)庫文件中多一個類型為 LC_ID_DYLIB 的 Load Command, 作用是動態(tài)庫的標(biāo)識符曼振,一般為文件路徑。路徑可以隨便填蔚龙,但是這部分必須要有冰评,是codesign的要求。


    image
  3. 可執(zhí)行文件會多出一個 PAGEZERO段木羹,動態(tài)庫中沒有甲雅。這個段開始地址為0(NULL指針指向的位置),是一個不可讀坑填、不可寫抛人、不可執(zhí)行的空間,能夠在空指針訪問時拋出異常脐瑰。這個段的大小妖枚,32位上是0x4000,64位上是4G苍在。這個段的處理也是轉(zhuǎn)換工作的重點之一绝页,之前有人嘗試轉(zhuǎn)換,不成功就是因為沒有處理好 PAGEZERO.


    image

實現(xiàn)細節(jié)

修改文件類型

第一步是修改文件的頭部信息寂恬,把文件類型從可執(zhí)行文件修改成動態(tài)庫续誉,同時把一些Flags修改好。

這里一個比較關(guān)鍵的Flag是可執(zhí)行文件中的 MH_PIE 標(biāo)志位掠剑,(position-independent executable)屈芜。

這個標(biāo)志位,表明可執(zhí)行文件能夠在內(nèi)存中任意位置正確地運行朴译,而不受其絕對地址影響的特性井佑,這一特性是動態(tài)庫所必須的一個特性。沒有這個標(biāo)志位的可執(zhí)行文件是沒有辦法轉(zhuǎn)換成動態(tài)庫的眠寿。iOS系統(tǒng)中躬翁,arm64架構(gòu)下,目前這個標(biāo)志位是必須的盯拱,不然程序無法運行(系統(tǒng)的安全性要求)盒发,但是armv7架構(gòu)下,可以沒有這個標(biāo)志位狡逢,所以支付寶armv7版本的可執(zhí)行文件是不能轉(zhuǎn)成動態(tài)庫的宁舰,就是這個原因。不過所有的arm64的應(yīng)用都是可以轉(zhuǎn)換的奢浑,后面演示時用的支付寶是arm64架構(gòu)的蛮艰。

頭部中添加 LC_ID_DYLIB

直接在文件頭部中按照文檔格式插入一個Load Command,并填入合適的數(shù)據(jù)雀彼。這里要注意下插入內(nèi)容的字節(jié)數(shù)必須是8字節(jié)對齊的壤蚜。

修改PAGEZERO段

這部分是最重要的一部分即寡,因為arm64上這個段的大小有4G,直接往內(nèi)存中加載袜刷,會提示沒有足夠的連續(xù)的地址空間聪富,所以必須要調(diào)整這個段的大小,而要調(diào)整 PAGEZERO 這個段的大小, 又會引起一連串的地址空間的變化著蟹,所以不能盲目的直接改墩蔓,必須結(jié)合dyld的源碼來對應(yīng)修改。(注意這里不能直接把 PAGEZERO 這個段給去掉草则,也不能直接把大小調(diào)成0钢拧,因為涉及到dyld的rebase操作,詳細看后面)

1. 所有段的地址都要重新計算

單純減少 PAGEZERO 段的占用空間炕横,作用不大源内,因為dyld加載動態(tài)庫的時候,要求是所有的段一起進行mmap(詳細可以查看dyld源碼的ImageLoaderMachO::assignSegmentAddresses函數(shù))份殿,所以必須把接下來所有的段的地址都重新計算一次膜钓。

同時要保證,前后兩個段沒有地址空間重疊卿嘲,并且每個段都是按0x4000對齊颂斜。因為 PAGEZERO 是所有段中的第一個,所以可以直接把 PAGEZERO 的大小調(diào)整到0x4000,然后后面每一個段都按順序依次減少同樣大小(0xFFFFC000 = 0x100000000 - 0x4000),同時能保證每個段在文件內(nèi)的偏移量不變章喉。

修改前:

image

修改后:

image

2. 對動態(tài)庫進行rebase操作

這里的rebase是系統(tǒng)為了解決動態(tài)庫虛擬內(nèi)存地址沖突,在加載動態(tài)庫時進行的基地址重定位操作司蔬。

這一步操作是整個流程里最重要的,因為按照前面的操作姨蝴,整個文件地址空間已經(jīng)發(fā)生了變化俊啼,如果dyld依然按照原來的地址進行rebase,必然會失敗左医。

那么rebase操作需要做哪些工作呢授帕?

相關(guān)的信息儲存在 Mach-O 文件的 LINKEDIT 段中, 并由 LC_DYLD_INFO_ONLY 指定 rebase info 在文件中的偏移量

image

詳細的rebase信息:

image

紅框里那些Pointer的意思是說,在內(nèi)存地址為 0x367C698 的地方有一個指針浮梢,這個指針需要進行rebase操作, 操作的內(nèi)容就是和前面調(diào)整地址空間一樣跛十,每個指針減去 0xFFFFC000。

image

3. 為什么不能直接去掉PAGEZERO這個段

這個原因要涉及到文件中rebase信息的儲存格式秕硝,上面的圖中偶器,可以看出rebase要處理的是一個個指針,但是實際上這些信息在文件中并不是以指針數(shù)組的形式存在,而是以一連串rebase opcode的形式存在屏轰,上面看到的一個個指針其實是 Mach O View 這個軟件幫我們將opcode整理得到的。

[圖片上傳失敗...(image-193e8b-1543815911165)]

這些opcode中有一種操作比較關(guān)鍵憋飞,REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB霎苗。

image

這個opcode的意思是, 接下去需要調(diào)整文件的中的第2個段,就是圖中segment(2)所表示的含義榛做。

所以說唁盏,如果把PAGEZERO這個段給去掉了,文件中各個段的序號也就都錯位了检眯,與rebase中的信息就對應(yīng)不上了厘擂。
而且把這個段大小改為0,也是不行的锰瘸,因為dyld在加載的過程中刽严,會重新自動過濾掉大小為0的段,也會導(dǎo)致同樣的段序號錯位的問題避凝。(有興趣的同學(xué)可以看下dyld的源碼舞萄,在ImageLoaderMachO類的構(gòu)造函數(shù)里)
這就是為什么必須要保留PAGEZERO這個段,同時大小不能為0管削。

修改符號表

正常的線上應(yīng)用是不存在符號表的,但是如果你之前用了我的另一個工具 restore-symbol 來恢復(fù)符號表的話崎弃,這個地方自然也需要做一些處理艇炎,處理方法同rebase類似,減去0xFFFFC000.

不過有一些符號需要單獨過濾,比如這個:

image

這個radr://5614542是個什么神奇的符號呢,google就能發(fā)現(xiàn)咒精,念茜的twitter上提過這個奇葩的符號鞋屈。(女神果然是女神, 棒~ ??)

image

實際效果

工具開源在github上权旷,用法:

1.下載源碼編譯:

git clone --recursive https://github.com/tobefuturer/app2dylib.git

cd app2dylib && make

./app2dylib

2.把支付寶arm64砸殼坤邪,然后提取可執(zhí)行文件,用上面的工具把支付寶的可執(zhí)行文件轉(zhuǎn)成動態(tài)庫

./app2dylib /tmp/AlipayWallet -o /tmp/libAlipayApp.dylib

3.用 Xcode 新建工程,并把新生成的dylib拖進去,調(diào)整好各項設(shè)置.


image

Run Script里的代碼(目的是為了對dylib進行簽名)

cd ${BUILT_PRODUCTS_DIR}

cd ${FULL_PRODUCT_NAME}

/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none libAlipayApp.dylib

4.怎么調(diào)用動態(tài)庫里的方法呢蹦肴?

為方便大家嘗試,這里選兩個分析起來比較簡單的函數(shù)調(diào)用演示給大家。

一個是OC的方法 +[aluSecurity rsaEncryptText:pubKey:], 可以直接用oc運行時調(diào)用。

另一個是C的函數(shù) int base64_encode(char * output, int * output_length, char * input, int input_length)
這個需要先確定 base64_encode 這個C函數(shù)的函數(shù)簽名和在dylib中的偏移地址(我這邊的9.9.3版本是0xa798e4)侦镇,可以用ida分析得到闹炉。

運行結(jié)果:


image

import <UIKit/UIKit.h>

import <dlfcn.h>

import <mach/mach.h>

import <mach-o/loader.h>

import <mach-o/dyld.h>

import <objc/runtime.h>

int main(int argc, char * argv[]) {

NSLog(@"\n===Start===\n");

NSString * dylibName = @"libAlipayApp";

NSString * path = [[NSBundle mainBundle] pathForResource:dylibName ofType:@"dylib"];

if (dlopen(path.UTF8String, RTLD_NOW) == NULL){

    NSLog(@"dlopen failed 养篓,error %s", dlerror());

    return 0;

};

//運行時 直接調(diào)用oc方法

NSString * plain = @"alipay";

NSString * pubkey = @"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZ6i9VNEGEaZaYE7XffA9XRj15cp/ZKhHYY43EEva8LIhCWi29EREaF4JjZVMwFpUAfrL+9gpA7NMQmaMRHbrz1KHe2Ho4HpUhEac8M9zUbNvaDKSlhx0lq/15TQP+57oQbfJ9oKKd+he4Yd6jpBI3UtGmwJyN/T1S0DQ0aXR8OQIDAQAB";

NSString * cipher = [NSClassFromString(@"aluSecurity") performSelector:NSSelectorFromString(@"rsaEncryptText:pubKey:") withObject:plain withObject:pubkey];

NSLog(@"\n-----------call oc method---------\n明文:%@\n密文: %@\n-----------------------------------", plain,cipher);

//確認dylib加載在內(nèi)存中的地址

uint64_t slide = 0;

for (int i = 0; i <  _dyld_image_count(); i ++)

    if ([[NSString stringWithUTF8String:_dyld_get_image_name(i)] isEqualToString:path])

        slide = _dyld_get_image_vmaddr_slide(i);

assert(slide != 0);

typedef int (*BASE64_ENCODE_FUNC_TYPE) (char * output, int * output_size , char * input, int input_length);

/** 根據(jù)偏移算出函數(shù)地址, 然后調(diào)用*/

long long base64_encode_offset_in_dylib = 0xa798e4;

BASE64_ENCODE_FUNC_TYPE base64_encode = (BASE64_ENCODE_FUNC_TYPE)(slide + base64_encode_offset_in_dylib);

char output[1000] = {0};

int length = 1000;

char * input = "alipay";

base64_encode(output, & length,  input, (int)strlen(input));

NSLog(@"\n-----------call c function---------\nbase64: %s -> %s\n-----------------------------------", input,  output);

}

ps:示例代碼中赂蕴,我刻意除掉了界面部分的代碼柳弄,因為支付寶的+load函數(shù)里swizzle了UI層的一些方法,會導(dǎo)致crash概说,如果想干掉那些+load方法的話碧注,看下面。

關(guān)于繞過檢測代碼

文章開頭的簡介中有提到席怪,以動態(tài)庫的形式加載应闯,能夠繞過應(yīng)用的檢測代碼,這說法不完全挂捻,因為如果把檢測代碼寫在類的+load方法里或者mod_init_func函數(shù)( 全局靜態(tài)變量的構(gòu)造函數(shù)和__attribute__((constructor))指定的函數(shù) )里碉纺,在dylib加載的時候也是可以得到調(diào)用的。

那么也就衍生出兩種配搭的對抗方案:

i)越獄機
+load方法的調(diào)用是在libobjc.dylib中的call_load_methods函數(shù), mod_init_func函數(shù)的調(diào)用是在dyld中的doModInitFunctions函數(shù)骨田,可以直接用CydiaSubstrate inline hook掉這兩個函數(shù)耿导,而且動態(tài)庫是由我們自己加載的,所以可以控制hook和加載dylib的時序态贤。

ii) 非越獄機
非越獄機上舱呻,沒有辦法inline hook,但是可以利用_dyld_register_func_for_add_image 這個函數(shù)注冊回調(diào)悠汽,這個回調(diào)是發(fā)生在動態(tài)庫加載到內(nèi)存后箱吕,+load方法和mod_init_func函數(shù)調(diào)用前,所以可以在這個回調(diào)里把+load方法改名柿冲,把mod_init_func段改名等等茬高,也就可以使得各種檢測函數(shù)沒法調(diào)用了。

總之假抄,主要的控制權(quán)還是在我們手中怎栽。

工具開源地址

https://github.com/tobefuturer/app2dylib

測試環(huán)境:
iPhone 6Plus 、iOS 9.3.1 宿饱、arm64
支付寶9.9.3

實際使用過程中熏瞄,可能會遇到各種奇葩問題,可以去github上提issue谬以,或者email(tobefuturer@gmail.com)强饮,提問時請描述清楚遇到的問題和已經(jīng)嘗試過的解決方法。

參考鏈接&致謝

  1. dyld的源碼:https://opensource.apple.com/source/dyld/
  2. 感謝狗哥的iOS逆向群里 @Ouroboros蛉签, @Misty胡陪,@張總 三位大神的激烈討論,還有幫我砸支付寶殼的 @{}
  3. 順便推廣下iOS逆向的論壇 http://iosre.com/

原文地址:http://blog.imjun.net/posts/convert-iOS-app-to-dynamic-library/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碍舍,一起剝皮案震驚了整個濱河市柠座,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌片橡,老刑警劉巖妈经,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捧书,居然都是意外死亡吹泡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門爆哑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舆吮,你說我怎么就攤上這事揭朝《蛹” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵潭袱,是天一觀的道長柱嫌。 經(jīng)常有香客問我,道長屯换,這世上最難降的妖魔是什么编丘? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮彤悔,結(jié)果婚禮上嘉抓,老公的妹妹穿的比我還像新娘。我一直安慰自己晕窑,他們只是感情好掌眠,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幕屹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪级遭。 梳的紋絲不亂的頭發(fā)上望拖,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音挫鸽,去河邊找鬼说敏。 笑死,一個胖子當(dāng)著我的面吹牛丢郊,可吹牛的內(nèi)容都是我干的盔沫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼枫匾,長吁一口氣:“原來是場噩夢啊……” “哼架诞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起干茉,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤谴忧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后角虫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沾谓,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年戳鹅,在試婚紗的時候發(fā)現(xiàn)自己被綠了均驶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡枫虏,死狀恐怖妇穴,靈堂內(nèi)的尸體忽然破棺而出爬虱,到底是詐尸還是另有隱情,我是刑警寧澤伟骨,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布饮潦,位于F島的核電站,受9級特大地震影響携狭,放射性物質(zhì)發(fā)生泄漏继蜡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一逛腿、第九天 我趴在偏房一處隱蔽的房頂上張望稀并。 院中可真熱鬧,春花似錦单默、人聲如沸碘举。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽引颈。三九已至,卻和暖如春境蜕,著一層夾襖步出監(jiān)牢的瞬間蝙场,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工粱年, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留售滤,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓台诗,卻偏偏與公主長得像完箩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拉队,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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