(轉載) Objective-C程式碼混淆(轉載自念茜)
class-dump可以很方便的匯出程式標頭檔案,不僅讓攻擊者瞭解了程式結構方便逆向,還讓著急趕進度時寫出的欠完善的程式給同行留下笑柄。
所以,我們迫切的希望混淆自己的程式碼。
混淆的常規思路
混淆分許多思路,比如:
1)花程式碼花指令,即隨意往程式中加入迷惑人的程式碼指令
2)易讀字元替換
等等
防止class-dump出可讀資訊的有效辦法是易讀字元替換。
Objective-C的方法名混淆
混淆的時機
我們希望在開發時一直保留清晰可讀的程式程式碼,方便自己。
同時,希望編譯出來的二進位制包含亂七八糟的混淆後的程式程式碼,噁心他人。
因此,我們可以在Build Phrase 中設定在編譯之前進行方法名的字串替換。
混淆的方法
方法名混淆其實就是字串替換,有2個方法可以,一個是#define,一個是利用tops。
利用#define的方法有一個好處,就是可以把混淆結果合併在一個.h中,在工程Prefix.pch的最前面#import這個.h。不匯入也可以編譯、匯入則實現混淆。
單段的selector,如func: ,可以通過#define func 來實現字串替換。
多段的selector,如a:b:c: ,可以通過分別#define a 、b、c 來實現字串替換。
我的混淆工具
我寫了個簡易的混淆指令碼,主要思路是把敏感方法名集中寫在一個名叫func.list的檔案中,逐一#define成隨機字元,追加寫入.h。
指令碼如下:
#!/usr/bin/env bash TABLENAME=symbols SYMBOL_DB_FILE="symbols" STRING_SYMBOL_FILE="func.list" HEAD_FILE="$PROJECT_DIR/$PROJECT_NAME/codeObfuscation.h" export LC_CTYPE=C #維護資料庫方便日後作排重 createTable() { echo "create table $TABLENAME(src text, des text);" | sqlite3 $SYMBOL_DB_FILE } insertValue() { echo "insert into $TABLENAME values('$1' ,'$2');" | sqlite3 $SYMBOL_DB_FILE } query() { echo "select * from $TABLENAME where src='$1';" | sqlite3 $SYMBOL_DB_FILE } ramdomString() { openssl rand -base64 64 | tr -cd 'a-zA-Z' |head -c 16 } rm -f $SYMBOL_DB_FILE rm -f $HEAD_FILE createTable touch $HEAD_FILE echo '#ifndef Demo_codeObfuscation_h #define Demo_codeObfuscation_h' >> $HEAD_FILE echo "//confuse string at `date`" >> $HEAD_FILE cat "$STRING_SYMBOL_FILE" | while read -ra line; do if [[ ! -z "$line" ]]; then ramdom=`ramdomString` echo $line $ramdom insertValue $line $ramdom echo "#define $line $ramdom" >> $HEAD_FILE fi done echo "#endif" >> $HEAD_FILE sqlite3 $SYMBOL_DB_FILE .dump
操作步驟
1.將混淆指令碼confuse.sh放到工程目錄下
mv confuse.sh your_proj_path/
2.修改Prefix.pch
開啟Xcode,修改XXX-Prefix.ch ,新增混淆標頭檔案:
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
//新增混淆作用的標頭檔案(這個檔名是指令碼confuse.sh中定義的)
#import "codeObfuscation.h"
#endif
3.配置Build Phase 在工程Build Phase中新增執行指令碼操作,執行confuse.sh指令碼,如圖:
4.建立函式名列表func.list,寫入待混淆的函式名,如:
-(void)sample;
-(void)seg1:(NSString *)string seg2:(NSUInteger)num;
就這樣寫:
sample
seg1
seg2
並將檔案放置於與confuse.sh指令碼同級
mv func.list your_proj_path/
5.編譯檢視結果
直接build,混淆指令碼會在編譯前執行,進行字元隨機替換,並且每次build的隨機字元不同,如圖:
實際驗證了下 可用。但是要注意兩點
#取以.m或.h結尾的檔案以+號或-號開頭的行 |去掉所有+號或-號|用空格代替符號|n個空格跟著<號 替換成 <號|開頭不能是IBAction|用空格split字串取第二部分|排序|去重複|刪除空行|刪掉以init開頭的行>寫進func.list
grep -h -r -I "^[-+]" $CONFUSE_FILE --include '*.[mh]' |sed "s/[+-]//g"|sed "s/[();,: *\^\/\{]/ /g"|sed "s/[ ]*</</"| sed "/^[ ]*IBAction/d"|awk '{split($0,b," "); print b[2]; }'| sort|uniq |sed "/^$/d"|sed -n "/^hsk_/p" >$STRING_SYMBOL_FILE
最終我的confuse.sh是這樣
#!/usr/bin/env bash
TABLENAME=symbols
SYMBOL_DB_FILE="symbols"
STRING_SYMBOL_FILE="func.list"
TARGET_PATH="$PROJECT_DIR/ios"
HEAD_FILE="$TARGET_PATH/codeObfuscation.h"
echo $HEAD_FILE
export LC_CTYPE=C
#維護資料庫方便日後作排重
createTable()
{
echo "create table $TABLENAME(src text, des text);" | sqlite3 $SYMBOL_DB_FILE
}
insertValue()
{
echo "insert into $TABLENAME values('$1' ,'$2');" | sqlite3 $SYMBOL_DB_FILE
}
query()
{
echo "select * from $TABLENAME where src='$1';" | sqlite3 $SYMBOL_DB_FILE
}
ramdomString()
{
openssl rand -base64 64 | tr -cd 'a-zA-Z' |head -c 16
}
writeToFile()
{
echo $1 >> $HEAD_FILE;
}
rm -f $SYMBOL_DB_FILE
rm -f $HEAD_FILE
createTable
touch $HEAD_FILE
touch $CONFIG_FILE
writeToFile '#ifndef Demo_codeObfuscation_h'
writeToFile '#define Demo_codeObfuscation_h'
writeToFile "//confuse string at `date`"
#echo "//confuse string at `date`" >> $CONFIG_FILE
writeToFile "#define CONFUSE_FUNC_MAP_START "
writeToFile "#define CONFUSE_FUNC_MAP_END "
writeToFile "CONFUSE_FUNC_MAP_START "
cat "$TARGET_PATH/$STRING_SYMBOL_FILE" | while read -ra line; do
if [[ ! -z "$line" ]]; then
ramdom=`ramdomString`
echo $line $ramdom
insertValue $line $ramdom
writeToFile "#define $line $ramdom"
fi
done
writeToFile "CONFUSE_FUNC_MAP_END "
writeToFile "#endif"
sqlite3 $SYMBOL_DB_FILE .dump
跟原始的區別在於: 需要注意如果你配置在xcode裡編譯報錯了,他是不能繼續執行shell的
因為每一次執行func.list 生成.h檔案,再進行編譯連結生成的話,生成太慢了,所以我只是直接手動生成func.list一次,執行confuse.sh,生成了.h檔案新增進去就ok了,並不配置到xcode的build phase裡面,因為那樣每一次都會重新生成,慢的hei skr。
還有兩個注意的。
一個是連結的外部庫檔案,不能將庫檔案的.h匯出。比如百度sdk 或者微信sdk的.h檔案裡面的函式,你抽取出來混淆了,就會導致執行時crash,除非你能拿到庫檔案原始碼把混淆的codeObfucation.h編譯進去. 不然庫檔案裡函式呼叫還是用的原來的函式,就會崩啊.
還有一個,如果用到了lua呼叫,要進行函式名替換. 我是直接改的 LuaObjcBridge, 初始化的時候直接讀取codeObfucation.h檔案 維護一個map, 在LuaObjcBridge 中NSSelectorFromString 前進行替換.