歡迎光臨
每天分享高質量文章

TikTok(抖音國際版)逆向,全球的小姐姐們,我來啦!

作者:AYJk

連結:https://juejin.im/post/5c19a38ae51d453e0a209256

開源地址

首先丟擲GitHub地址吧~多多支援指點,謝謝。

AYTikTokPod:https://github.com/AYJk/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為我們做的事情:


1、建立dylib,透過hook修改類的屬性或方法

2、將dylib註入到App中

3、重簽名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∾=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


其中有幾個欄位引起了懷疑:

分析:

1、is_my_cn字面意思,是否是中國,很可能透過標記來判斷是否是國內使用者。

2、language語言型別,透過這個來判斷可能性比較低,誤傷機率很高。外區也可以設定語言中文,但你不可能去影響他使用吧。這麼做,是不合理的。

3、account_region、carrier_region、sys_region,賬號、運營商和系統的地區,可能透過所屬地區來進行封鎖。

4、mcc_mnc,mcc指的是移動國家碼,mnc指的是行動網路碼。

5、tz_name時區。

驗證:

使用Charles的Rewrite或者Breakpoints來改變URL中傳遞的params。


結果:

透過各種組合實驗,發現真正產生作用的

這裡得到了第一個結論:說明TikTok伺服器,是透過運營商來封鎖使用者的。既然是運營商,那就把mcc_mnc這個欄位也一起處理。

Question3

Q3:怎麼處理carrier_region和mcc_mnc?

T3:上面是透過Charles完成了,可以正常觀看TikTok的影片,勉強算是完成了部分修改,但侷限性很大。

比如:

1、無法評論、關註等操作,因為只Rewrite了部分介面,其他介面沒有Rewrite。

2、離開特定的WiFi就無法觀看,無法透過蜂窩網觀看影片。(PS:可以透過Thor這個軟體的攔截器實現,和Charles的原理一致)

3、如果後續更新添加了介面簽名校驗,那這種方法就會失效。

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 (nonatomicreadonlyretainnullableNSString* 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 (nonatomicreadonlyretainnullableNSString *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 (nonatomicreadonlyretainnullableNSString *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(retainnonatomicNSArray *originURLList;
@end

@interface AWEVideoModel
@property(readonlynonatomic) AWEURLModel *playURL;
@property(readonlynonatomic) AWEURLModel *downloadURL;
@end

@interface AWEAwemeModel
@property(nonatomicassignBOOL preventDownload;
@property(retainnonatomic) 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(selfNSArray *, AWEDynamicWaterMarkExporter, watermarkLogoImageArray) {
    BOOL isHookWaterMark = [UserDefaults boolForKey:HookWaterMark];
    if (isHookWaterMark) {
        return @[];
    }
    return CHSuper0(AWEDynamicWaterMarkExporter, watermarkLogoImageArray);
}
CHConstructor {
    CHLoadLateClass(AWEDynamicWaterMarkExporter);
    CHClassHook0(AWEDynamicWaterMarkExporter, watermarkLogoImageArray);
}

總結

整個逆向過程中,完整的Hook程式碼並不複雜,開發工作也是站在巨人的肩膀上完成的,草草幾行就能完成功能逆向。

他是令人振奮,因為最終證明瞭你的逆向想法是對的,通往成功的路不只有一條,切入點可能不一樣,思路可能不一樣,方法可能不一樣,但是都能成功。



編號373,輸入編號直達本文

贊(1)

分享創造快樂