iOS安全與逆向-判斷APP被重簽名的一種方案

之前看到一篇防逆向的文章,大概就是去檢測包里是否有embedded.mobileprovision,然后解析描述文件的application-identifier來對比看是否包被重簽名了

于是我就自己測試了一下,本文就整理一下如何去檢測,以及如果去反檢測(分析一種簡單場景-將檢測的函數(shù)給hook掉)

1.檢測是否被重簽名

實(shí)現(xiàn)的思路就是:

  • 我們通過開發(fā)證書去重簽名一個應(yīng)用的時候,會有一個embeded.mobileprovision文件來描述應(yīng)用的信息、可以安裝的設(shè)備信息等
  • 當(dāng)我們?nèi)ブ睾灻臅r候,會生成一個embeded.mobileprovision來簽名砸殼之后的包,這個描述文件中則包含著簽名的證書的teamId以及殼APP的identifier等信息
  • 在程序啟動的時候去檢測是否有描述文件,然后獲取到關(guān)鍵的信息,跟原始的信息進(jìn)行比對,不一致則程序退出

一般我們開發(fā)調(diào)試以及逆向的人重簽名均有這個描述文件

代碼也比較簡單,就是解析描述文件,然后拿到application-identifier跟已知的簽名信息比對,不一致則退出程序

代碼實(shí)現(xiàn)如下:

void checkCodeSign(NSString *identifier, NSString *teamId) {
#if defined __x86_64__ || __i386__ // 模擬器不需要生成embeded.mobileprovision文件來做真機(jī)調(diào)試的配置
    // do nothing
#else
    // 描述文件路徑
    NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:embeddedPath]) {
        return;
    }
    // 讀取application-identifier  注意描述文件的編碼要使用:NSASCIIStringEncoding
    NSString *embeddedProvisioning = [NSString stringWithContentsOfFile:embeddedPath encoding:NSASCIIStringEncoding error:nil];
    NSArray<NSString *> *embeddedProvisioningLines = [embeddedProvisioning componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
    for (int i = 0; i < embeddedProvisioningLines.count; i++) {
        if ([embeddedProvisioningLines[i] rangeOfString:@"application-identifier"].location != NSNotFound) {
            NSString *identifierString = embeddedProvisioningLines[i + 1]; // 類似:<string>L2ZY2L7GYS.com.xx.xxx</string>
            NSRange fromRange = [identifierString rangeOfString:@"<string>"];
            NSInteger fromPosition = fromRange.location + fromRange.length;
            NSInteger toPosition = [identifierString rangeOfString:@"</string>"].location;
            NSRange range;
            range.location = fromPosition;
            range.length = toPosition - fromPosition;
            NSString *fullIdentifier = [identifierString substringWithRange:range];
            NSScanner *scanner = [NSScanner scannerWithString:fullIdentifier];
            NSString *teamIdString;
            [scanner scanUpToString:@"." intoString:&teamIdString];
            NSRange teamIdRange = [fullIdentifier rangeOfString:teamIdString];
            NSString *appIdentifier = [fullIdentifier substringFromIndex:teamIdRange.length + 1];
            // 對比簽名teamID或者identifier信息
            if (![appIdentifier isEqualToString:identifier] || ![teamId isEqualToString:teamIdString]) {
                // exit(0)
                asm(
                    "mov X0,#0\n"
                    "mov w16,#1\n"
                    "svc #0x80"
                    );
            }
            break;
        }
    }
#endif
}

2.逆向hook檢測的函數(shù)

針對檢測函數(shù)的類型有不同的hook方式

  • OC方法 - 直接使用runtime的method-swizzle
  • 動態(tài)庫的C方法 - fishhook去rebind symbols
  • 靜態(tài)的C方法 - Dobby去靜態(tài)hook

本文主要是測試一下Dobby去靜態(tài)hook,對于OC的函數(shù)以及動態(tài)庫的C方法比較簡單,就不多說了;
如果發(fā)現(xiàn)你逆向的app有一些檢測,那么一般的思路就是繞過檢測,接下來就使用Dobby來看如果繞過上面說的checkCodeSign檢測

2.1 Dobby

由于checkCodeSign函數(shù)是個靜態(tài)的C函數(shù),它沒有動態(tài)符號同時也不走OC的消息機(jī)制,沒法通過runtime的方式以及fishhook rebind符號的方式去hook它,不過還好有Dobby這個工具支持靜態(tài)的hook

你可以直接下載最新的release包,也可以自己去編譯生成包;如何編譯生成包直接參照Getting Started With iOS去生成一個工程,然后Xcode編譯一下framework

通過cmake來生成對應(yīng)平臺的工程

cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \
-DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
-DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.Obj

該工具提供了很對配置參數(shù),比如架構(gòu)、是否支持bitcode等,如果你的項目支持bitcode那么就-DENABLE_BITCODE=1

這里需要注意下cmake .. 是在build目錄下執(zhí)行的,如果你跟我一樣有創(chuàng)建子目錄,那么需要修改下命令

圖片.png

我這里是多了一級子目錄,我在iOS_arm64目錄執(zhí)行的cmake,那么就將cmake ..修改為cmake ../..就能找到cmake配置目錄了

執(zhí)行完后在build目錄下就有對于的工程文件了

圖片.png

通過Xcode來打包Framework

圖片.png

運(yùn)行一下,然后將打包好的Framework拷貝出來就可以了

2.2 使用Dobby靜態(tài)hook函數(shù)

第一次用Dobby,就先試了下直接在主APP中去hook試試效果

先看看hook函數(shù)的定義

int DobbyHook(void *function_address, void *replace_call, void **origin_call);

三個參數(shù):

function_address -- 需要hook的函數(shù)的地址
replace_call -- 新函數(shù),也就是我們hook的實(shí)現(xiàn)
origin_call -- 用來保存原有的函數(shù)實(shí)現(xiàn)的指針地址

那么我們就定義一個新函數(shù)hookCheckCodeSign,一個函數(shù)指針*originCheckCodeSign用來存儲原始實(shí)現(xiàn)的指針地址,實(shí)現(xiàn)起來也簡單,代碼如下:

void checkCodeSign(NSString *identifier, NSString *teamId); // 原來的方法
void (*originCheckCodeSign)(NSString *identifier, NSString *teamId); // 保留原始的方法實(shí)現(xiàn)的指針地址
void hookCheckCodeSign(NSString *identifier, NSString *teamId); // hook的方法

int main(int argc, char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        NSString * appDelegateClassName = NSStringFromClass([AppDelegate class]);
        BOOL isEncrypt = isEncrypted();
        NSLog(@"check is encrypt: %d", isEncrypt);
        // int DobbyHook(void *function_address, void *replace_call, void **origin_call);
        DobbyHook(checkCodeSign, hookCheckCodeSign, (void *)&originCheckCodeSign);
        checkCodeSign(@"hc.RuntimeLearning.demo", @"9D7EH8PVAX"); // security find-identity -v -p CodeSigning 可以獲取到,也可以在導(dǎo)出ipa包的plist中查看
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
}

void hookCheckCodeSign(NSString *identifier, NSString *teamId) {
    // do nothing
    NSLog(@"%s", __FUNCTION__);
}

運(yùn)行查看效果,hook成功了執(zhí)行了hook之后的函數(shù)

RuntimeLearning[8921:1837017] hookCheckCodeSign
2.3 注入的方式hook主包中的函數(shù)

我們分析別人的APP,重簽名做代碼注入的時候,顯然是沒法直接在主包的源碼中去寫hook的代碼的,可以通過動態(tài)注入的方式,注入代碼來達(dá)到修改源程序的功能

注入的思路:

  • 創(chuàng)建一個動態(tài)庫編寫注入代碼
  • 將動態(tài)庫注入到主APP的macho中
  • 在合適的時機(jī)執(zhí)行注入的代碼;關(guān)于這個合適的時機(jī)可以利用_objc_init的流程來在程序初始化的時候去執(zhí)行注入的代碼邏輯

代碼注入時機(jī):

  • C全局初始化方法__attribute__((constructor(1)))
  • 庫的load函數(shù)中

這些方法都會在main之前調(diào)用,可以保證我們在程序執(zhí)行檢查函數(shù)之前就將其hook掉

2.3.1 編寫注入代碼

準(zhǔn)備工作:

通過MachOView去獲取需要hook的函數(shù)的地址(偏移地址) 0x1000164A0

圖片.png

新建了一個動態(tài)庫HCInjecttarget,然后編寫代碼:

由于安全性的需要,iOS在加載代碼鏡像到內(nèi)存的時候會隨機(jī)生成一個鏡像文件的起始地址,函數(shù)的真實(shí)地址則是這個隨機(jī)值A(chǔ)SLR+函數(shù)對于該鏡像的offset值

我這里hook的是主程序中的函數(shù),所以_dyld_get_image_vmaddr_slide(0)的參數(shù)傳的是0

lldb調(diào)試下也可以直接通過image list來查看所有加載的鏡像的ASLR

圖片.png

最終實(shí)現(xiàn)代碼如下:

#import "HCHook.h"
#import <Dobby/dobby.h>
#import <mach-o/dyld.h>

void (*originCheckCodeSign)(NSString *identifier, NSString *teamId); // 保留原始的方法實(shí)現(xiàn)的指針地址
void hookCheckCodeSign(NSString *identifier, NSString *teamId); // hook的方法

@implementation HCHook

+ (void)load {
    static uintptr_t checkCodeSignOffset = 0x1000164A0; // 這個偏移地址可以通過MachOView去查看
    uintptr_t mainASLR = _dyld_get_image_vmaddr_slide(0); // 獲取主程序的aslr,因為checkCodeSign函數(shù)在主程序
    uintptr_t checkCodeSignAddress = mainASLR + checkCodeSignOffset;
    DobbyHook((void *)checkCodeSignAddress, hookCheckCodeSign, (void *)&originCheckCodeSign);
}

void hookCheckCodeSign(NSString *identifier, NSString *teamId) {
    // do nothing
    NSLog(@"%s", __FUNCTION__);
}

@end

2.3.2 注入代碼到主程序

注入的思路就是修改主程序的MachO文件,我習(xí)慣使用yololib工具去修改MachO的Load Commonds將我們寫的動態(tài)庫注入進(jìn)去,這里我直接使用shell腳本的方式,腳本如下:

TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "app路徑:$TARGET_APP_PATH"
# 拿到MachO文件的路徑
APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#上可執(zhí)行權(quán)限
chmod +x "$TARGET_APP_PATH/$APP_BINARY"
TARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks"
# 拷貝包到framework里面去
INJECT_FRAMEWORK_BUILD_PATH="$SRCROOT/HCInject.framework"
cp -rf "$INJECT_FRAMEWORK_BUILD_PATH" "$TARGET_APP_FRAMEWORKS_PATH"
#簽名:如果不簽名,那么dyld load的時候簽名校驗不過就會加載失敗 報 image not found
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$TARGET_APP_FRAMEWORKS_PATH/HCInject.framework"
#注入:修改MachO文件
yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HCInject.framework/HCInject"

在Xcode的Build Phases菜單下添加一個執(zhí)行腳本的工作流

圖片.png

執(zhí)行腳本


圖片.png

拷貝庫文件到APP的Frameworks目錄


圖片.png

運(yùn)行查看效果:


圖片.png

嗯,成功了;至此已經(jīng)實(shí)現(xiàn)了通過注入代碼的方式去hook主程序中的C函數(shù)了

3. 總結(jié)

  • 通過一個APP加固的小方案以及如何去逆向它,學(xué)習(xí)了一下Dobby工具的使用
  • APP沒有絕對的安全,安全是相對的,做的一切加固的方案,只是為了增加逆向的成本
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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