開源地址
首先拋出GitHub地址吧~多多支持指點,謝謝。
AYTikTokPod
簡述
iOS逆向工程指的是軟件層面上進行逆向分析的過程。
在一般的軟件開發流程中,都是過程導向結果。在逆向中,你首先拿到的是結果,然後是去分析實現這個結果的過程。理清過程之後,纔開始進行逆向的代碼編寫,在整個流程中,分析過程的佔比是90%,代碼書寫的過程只佔10%。所以本篇更多的講的是一個思路,代碼其實很日常
前期準備
- 一臺Mac
- 一臺iPhone
- frida-ios-dump
- Hopper Disassembler
- class-dump
- MonkeyDev
- Reveal
frida-ios-dump
用於脫殼,脫殼是逆向的第一步。直接AppStore上下載的應用都有帶殼,導致我們無法對他進行任何操作。脫殼的ipa文件,也可以直接去一些越獄商店下載,但是可能版本上比較舊。
如果有一臺已越獄的機器,按照frida-ios-dump的wiki來操作很簡單。
Hopper Disassembler
Hopper Disassembler
是Mac上的一款二進制反彙編器,基本上滿足了工作上的反彙編的需要,包括僞代碼以及控制流圖(Control Flow Graph),支持ARM指令集並針對Objective-C的做了優化。
class-dump
class-dump
是一款可以導出頭文件的命令行工具,改程序用於檢查Objective-C運行時信息存儲在Mach-O文件,它生成類的聲明,類別和協議。
MonkeyDev
MonkeyDev
的前身是iOSOpenDev
,在iOSOpenDev
的基礎上增加CaptainHook Tweak
、Logos Tweak
、Command-line Tool
。
MonkeyDev爲我們做的事情:
- 創建dylib,通過hook修改類的屬性或方法
- 將dylib注入到App中
- 重簽名ipa文件
靜態分析
前期準備
拿到TikTok的脫殼ipa文件
由於自己的6s痛失越獄環境,於是脫殼這一步,是拜託了我的好哥們完成的。
只要有越獄手機,砸殼並不複雜,按照網上的教程步驟來就行。
class-dump導出頭文件
通過命令
class-dump -H XXX.app -o /DumpHeaderClass
- -H後跟的是脫殼後的app文件路徑
- -o是頭文件輸出的文件夾路徑
如圖所示爲class-dump之後的項目中所有頭文件,單從這裏,我們就能看出,TikTok項目中,使用的幾個第三方庫:AFNetWorking
、YYKit
、FaceBook的SDK
。
tips: 快速搜索對應的頭文件或方法,可以新建個工程,將頭文件文件夾拖入項目中。有什麼工具能比Xcode檢索更方便檢索代碼呢?
Hopper靜態分析
直接將脫殼後的二進制可執行文件拖入Hopper,等待一段時間後,Hopper會完成反編譯。
左邊的展示的是對應的類和方法列表,通過搜索框可以快速定位到方法。
紅色框框起來的是模式切換:分別是
彙編模式
、控制流圖模式
、僞代碼模式
、十六進制模式
通常我們用的最多的就是控制流圖
和僞代碼
。
Reveal查看界面
MonkeyDev
會爲我們自動注入RevealService.framework
。RevealService.framework
需要和對應版本Reveal
使用。否則請更新替換注入的RevealService.framework
。
Reveal
能讓我們快速定位到我們需要的控制器或視圖。
如圖,首頁的ViewController就是AWEFeedTableViewController
。
問題&處理問題
Question1
Q1:
發現從其他區的AppStore下載的TikTok打開後什麼都沒有?
T1:
初步懷疑是網絡問題。
A1:
全局代理之後打開還是一片漆黑,基本排除是網絡的問題導致的。
Question2
Q2:
如果不是網絡問題,那問題會不會出現在請求參數上?
T2:
使用Charles
抓包看看
A2:
刷新feed,拿到url
/aweme/v1/feed/?version_code=4.3.0&language=zh&pass-region=1&app_name=trill&vid=B196D171-B020-453E-A19C-9AAD845151BE&app_version=4.3.0&carrier_region=CN&is_my_cn=1&channel=App%20Store&mcc_mnc=46001&device_id=6631689375623284225&tz_offset=28800&account_region=&sys_region=CN&aid=1180&screen_width=750&openudid=63ceee2a26c0fd4501ebcf1f47a2311c5551f6e0&os_api=18&ac=WIFI&os_version=12.0&app_language=en&tz_name=Asia/Shanghai&device_platform=iphone&build_number=43004&device_type=iPhone8,1&iid=6635504889546049282&idfa=BFBB2BCA-9743-451B-95CC-F01292FC02F6&ad_user_agent=Mozilla%2F5.0%20%28iPhone%3B%20CPU%20iPhone%20OS%2012_0%20like%20Mac%20OS%20X%29%20AppleWebKit%2F605.1.15%20%28KHTML%2C%20like%20Gecko%29%20Mobile%2F16A366&count=6&feed_style=0&filter_warn=0&max_cursor=0&min_cursor=0&pull_type=1&type=0&volume=0.25&mas=01050af0364af36a45501b82b389f379ef3f8bda89739cc55924e8&as=a1157001fc8b2c2ce65057&ts=1544948924
其中有幾個字段引起了懷疑:
key | value |
---|---|
is_my_cn | 1 |
language | zh |
account_region | CN |
carrier_region | CN |
mcc_mnc | 46001 |
tz_name | Asia/Shanghai |
sys_region | CN |
分析:
-
is_my_cn
字面意思,是否是中國,很可能通過標記來判斷是否是國內用戶。 -
language
語言類型,通過這個來判斷可能性比較低,誤傷機率很高。外區也可以設置語言中文,但你不可能去影響他使用吧。這麼做,是不合理的。 -
account_region
、carrier_region
、sys_region
,賬號、運營商和系統的地區,可能通過所屬地區來進行封鎖。 -
mcc_mnc
,mcc
指的是移動國家碼
,mnc
指的是移動網絡碼
。 -
tz_name
時區。
驗證:
使用Charles
的Rewrite或者Breakpoints來改變URL中傳遞的params
。
結果:
通過各種組合實驗,發現真正產生作用的
key | value |
---|---|
carrier_region | CN |
這裏得到了第一個結論:說明TikTok服務器,是通過運營商來封鎖用戶的。既然是運營商,那就把mcc_mnc
這個字段也一起處理。
key | value |
---|---|
mcc_mnc | 46001 |
Question3
Q3:
怎麼處理carrier_region
和mcc_mnc
?
T3:
上面是通過Charles
完成了,可以正常觀看TikTok的視頻,勉強算是完成了部分修改,但侷限性很大。
比如:
- 無法評論、關注等操作,因爲只
Rewrite
了部分接口,其他接口沒有Rewrite
。 - 離開特定的WiFi就無法觀看,無法通過蜂窩網觀看視頻。(PS:可以通過Thor這個軟件的攔截器實現,和Charles的原理一致)
- 如果後續更新添加了接口簽名校驗,那這種方法就會失效。
A3:
方案一:
通過Hook第三方網絡庫AFNetWorking
或內部封裝的NetService類來修改carrier_region
字段。
這個方案基本可行,通過HookAFHTTPRequestSerializer
類的requestWithMethod: URLString: parameters: error:
方法。獲取parameters
,然後修改carrier_region
的值。
優點:
- 方案簡單,不需要過多的內部實現分析。
- 能完成所有接口的Hook。
缺點:
- 遇到接口簽名校驗將失效。
- 所有網絡接口都被Hook,如果Hook函數裏存在複雜耗時的操作,會嚴重影響性能。
方案二:
iOS系統的CoreTelephony.framework
的CTCarrier
類提供了carrier_region
、mnc
和mcc
的獲取。通過Hook他們來實現土突破地區限制。
/*
* isoCountryCode
*
* Discussion:
* Returns an NSString object that contains country code for
* the subscriber's cellular service provider, represented as an ISO 3166-1
* country code string
*/
@property (nonatomic, readonly, retain, nullable) NSString* isoCountryCode __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
/*
* mobileCountryCode
*
* Discussion:
* An NSString containing the mobile country code for the subscriber's
* cellular service provider, in its numeric representation
*/
@property (nonatomic, readonly, retain, nullable) NSString *mobileCountryCode __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
/*
* mobileNetworkCode
*
* Discussion:
* An NSString containing the mobile network code for the subscriber's
* cellular service provider, in its numeric representation
*/
@property (nonatomic, readonly, retain, nullable) NSString *mobileNetworkCode __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
代碼編寫:
// MARK: - Hook CTCarrier
CHDeclareClass(CTCarrier)
CHMethod0(NSString *, CTCarrier, isoCountryCode) {
NSDictionary *areaDic = [UserDefaults valueForKey:HookArea];
NSString *code = [areaDic objectForKey:@"code"];
return code;
}
CHMethod0(NSString *, CTCarrier, mobileCountryCode) {
NSDictionary *areaDic = [UserDefaults valueForKey:HookArea];
NSString *mcc = [areaDic objectForKey:@"mcc"];
return mcc;
}
CHMethod0(NSString *, CTCarrier, mobileNetworkCode) {
NSDictionary *areaDic = [UserDefaults valueForKey:HookArea];
NSString *mnc = [areaDic objectForKey:@"mnc"];
return mnc;
}
CHConstructor {
CHLoadLateClass(CTCarrier);
CHHook0(CTCarrier, isoCountryCode);
CHHook0(CTCarrier, mobileCountryCode);
CHHook0(CTCarrier, mobileNetworkCode);
}
CaptainHook爲我們提供了完善的Hook宏。
-
CHDeclareClass
作用是聲明需要Hook的類 -
CHMethod
作用是對應的方法Hook的實現 -
CHConstructor
作用是用於加載Hook的方法和所在的類 -
CHLoadLateClass
加載Hook類 -
CHHook
註冊Hook方法
這個framework底層通過runtime
接口實現對應功能,比如
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
method_getImplementation(Method _Nonnull m)
method_getTypeEncoding(Method _Nonnull m)
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
結果:
到這裏區域限制的突破已經完成了。
Question4
Q4:
使用過程中發現其他地區TikTok都能下載視頻,日區TikTok不能😓
T4:
使用的是同一部手機,只Hook了carrier_region
和mcc_mnc
,出現了下載限制問題,那肯定是地區版權策略導致的(11區對版權的重視,佩服了)。
A4:
點開分享按鈕
發現判斷是否有下載權限是發生在按鈕點擊之前的。考慮是在請求返回的JSON數據中存儲的flag,然後把這個flag傳給AWEAwemeShareViewController
。
使用Reveal
對界面分析,發現TableView的Cell類名是AWEFeedViewCell
,然後查找class-dump出的AWEFeedViewCell.h
,有一個可疑的方法是- (void)configWithModel:(id)arg1;
使用MDMethodTrace
進行方法跟蹤,確認了方法被調用,同時arg1
的類型是AWEAwemeModel
,這個Model裏又發現了可疑的屬性@property(nonatomic, assign) BOOL preventDownload;
,意思是禁止下載。
代碼編寫:
// MARK: - AWEAwemeModel
CHDeclareClass(AWEAwemeModel)
CHMethod1(void, AWEAwemeModel, setPreventDownload, BOOL, arg1) {
arg1 = ![UserDefaults boolForKey:HookDownLoad];
CHSuper1(AWEAwemeModel, setPreventDownload, arg1);
}
CHConstructor {
CHLoadLateClass(AWEAwemeModel);
CHHook1(AWEAwemeModel, setPreventDownload);
}
效果:
下載按鈕沒被禁用了!懷着激動的心情點下去!
WTF !!!
繼續:
對比日區和其他區的AWEAwemeModel
。發現AWEAwemeModel
的某一個數據結構是這個樣的
@interface AWEURLModel
@property(retain, nonatomic) NSArray *originURLList;
@end
@interface AWEVideoModel
@property(readonly, nonatomic) AWEURLModel *playURL;
@property(readonly, nonatomic) AWEURLModel *downloadURL;
@end
@interface AWEAwemeModel
@property(nonatomic, assign) BOOL preventDownload;
@property(retain, nonatomic) AWEVideoModel *video;
@end
一頓分析得到日區的downloadURL
只有兩個接口,不包含視頻地址。其他能下載的地區,downloadURL
有四個接口,前兩個爲視頻地址。進一步發現playURL
和downloadURL
的參數一直。直接嘗試將playURL
賦值給downloadURL
。
代碼編寫:
// MARK: - AWEAwemeModel
CHDeclareClass(AWEAwemeModel)
CHMethod1(void, AWEAwemeModel, setPreventDownload, BOOL, arg1) {
arg1 = ![UserDefaults boolForKey:HookDownLoad];
CHSuper1(AWEAwemeModel, setPreventDownload, arg1);
}
CHMethod1(void, AWEAwemeModel, setVideo, AWEVideoModel *, arg1) {
BOOL isHookDownLoad = [UserDefaults boolForKey:HookDownLoad];
if (isHookDownLoad) {
arg1.downloadURL.originURLList = arg1.playURL.originURLList;
}
CHSuper1(AWEAwemeModel, setVideo, arg1);
}
CHConstructor {
CHLoadLateClass(AWEAwemeModel);
CHHook1(AWEAwemeModel, setPreventDownload);
CHHook1(AWEAwemeModel, setVideo);
}
再次運行,成功下載日區TikTok視頻。
Question5
Q5:
視頻down下來發現有水印?
T5:
對比原地址,發現原視頻是沒有水印的,那麼水印就是在下載完成後添加了的。
目錄搜索watermark,驗證了猜想。
在頭文件中,發現了帶watermark名稱的類。
最終發現AWEDynamicWaterMarkExporter
這個類的+ (id)watermarkLogoImageArray;
返回了對應的水印圖片。
代碼編寫
#pragma mark WaterMark
CHDeclareClass(AWEDynamicWaterMarkExporter)
CHOptimizedClassMethod0(self, NSArray *, AWEDynamicWaterMarkExporter, watermarkLogoImageArray) {
BOOL isHookWaterMark = [UserDefaults boolForKey:HookWaterMark];
if (isHookWaterMark) {
return @[];
}
return CHSuper0(AWEDynamicWaterMarkExporter, watermarkLogoImageArray);
}
CHConstructor {
CHLoadLateClass(AWEDynamicWaterMarkExporter);
CHClassHook0(AWEDynamicWaterMarkExporter, watermarkLogoImageArray);
}
總結
整個逆向過程中,完整的Hook代碼並不複雜,開發工作也是站在巨人的肩膀上完成的,草草幾行就能完成功能逆向。
他是令人振奮,因爲最終證明了你的逆向想法是對的,通往成功的路不只有一條,切入點可能不一樣,思路可能不一樣,方法可能不一樣,但是都能成功。
刷了幾天日韓小姐姐之後,身體越來越差了。