對于IOS來說,由于系統(tǒng)是封閉的,APP上架需要通過App Store,安全性來說相當(dāng)高。但是對于大廠和知名APP而言,別人給的安全保障永遠(yuǎn)沒有自己做的來得踏實。所以對于大廠、少部分企業(yè)級和金融支付類應(yīng)用來說加固是相當(dāng)重要的
字符串加密
對字符串加密的方式目前我所了解到掌握到的最可靠方式就是用腳本將代碼中的所有標(biāo)記需要加密的字符串進(jìn)行異或轉(zhuǎn)換,這樣代碼中就不存在明文字符串了。當(dāng)然第三方的字符串加密不可能這么簡單,具體怎么做的我也不太清楚。不過為了增加字符串加密的難度復(fù)雜性,我們可以先將字符串用加密工具轉(zhuǎn)換(例如AES、base64等)后的把加字符串放在工程中,并且把解密的鑰匙放在工程中,用異或轉(zhuǎn)換,把解密鑰匙和加密后的字符串轉(zhuǎn)換,這樣就有2層保障,增加了復(fù)雜度。
首先?我們創(chuàng)建任意一個工程,在工程中寫入下面的代碼,并在每句打上斷點,再選擇Xcode工具欄的Debug --> Debug Workflow --> Always Show Disassembly。這樣你就可以在斷點處進(jìn)入?yún)R編模式界面,最后運(yùn)行程序
/*?加密NSString字符串?*/
NSString*str?=@"Hello?World";
NSLog(@"%@",str);
/*?加密char*字符串?*/
char*?cStr?="Super?Man";
NSLog(@"%s",cStr);
斷點處進(jìn)入?yún)R編模式界面

你會發(fā)現(xiàn),你的字符串內(nèi)容暴露在了匯編模式中,這會導(dǎo)致別人在逆向分析你的工程時能看見你的字符串內(nèi)容,我們一般接口、域名、加解密鑰匙串、AppKey、AppId等比較重要的東西會放在客戶端用作字符串,這就很容易暴露出來。
步驟1?首先需要在工程代碼中進(jìn)行修改,把下面的宏和decryptConfusionCS,decryptConstString函數(shù)放入代碼中,用宏包含每個需要轉(zhuǎn)換的字符串。
/*?字符串混淆解密函數(shù),將char[]?形式字符數(shù)組和?aa異或運(yùn)算揭秘?*/
externchar*?decryptConfusionCS(char*?string)
{
char*?origin_string?=?string;
while(*string)?{
*string?^=0xAA;
string++;
}
returnorigin_string;
}
/*?解密函數(shù),返回的是NSString類型的?*/
externNSString*?decryptConstString(char*?string)
{
/*?先執(zhí)行decryptConfusionString函數(shù)解密字符串?*/
char*?str?=?decryptConfusionCS(string);
/*?獲取字符串的長度?*/
unsignedlonglen?=?strlen(str);
NSUIntegerlength?=?[[NSStringstringWithFormat:@"%lu",len]?integerValue];
NSString*resultString?=?[[NSStringalloc]initWithBytes:str?length:length?encoding:NSUTF8StringEncoding];
returnresultString;
}
/*
*?使用heyujia_confusion宏控制加密解密
*?當(dāng)heyujia_confusion宏被定義的時候,執(zhí)行加密腳本,對字符串進(jìn)行加密
*?當(dāng)heyujia_confusion宏被刪除或為定義時,執(zhí)行解密腳本,對字符串解密
*/
#define?heyujia_confusion
#ifdef?heyujia_confusion
/*?heyujia_confusion?宏被定義,那么就進(jìn)行執(zhí)行解密腳本?*/
/*?confusion_NSSTRING宏的返回結(jié)果是NSString?類型的?*/
#define?confusion_NSSTRING(string)?decryptConstString(string)
/*?confusion_CSTRING宏的返回結(jié)果是char*?類型的?*/
#define?confusion_CSTRING(string)?decryptConfusionCS(string)
#else
/*?heyujia_confusion?宏沒有被定義,那么就執(zhí)行加密腳本?*/
/*?加密NSString類型的?*/
#define?confusion_NSSTRING(string)?@string
/*?加密char?*類型的?*/
#define?confusion_CSTRING(string)?string
#endif
@interfaceViewController()
@end
@implementationViewController
-?(void)viewDidLoad?{
[superviewDidLoad];
//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.
/*?使用confusion_NSSTRING宏包含需要加密的NSString字符串?*/
NSString*str?=?confusion_NSSTRING("Hello?World");
NSLog(@"%@",str);
/*?使用confusion_NSSTRING宏包含需要加密的char*字符串?*/
char*?cStr?=?confusion_CSTRING("Super?Man");
NSLog(@"%s",cStr);
}
步驟2?使用終端cd 到需要加密的工程目錄下 執(zhí)行touch confusion.py 和 touch decrypt.py 命令,生產(chǎn)加密和解密腳本文件
步驟3?把下面代碼加入解密腳本confusion.py中
#!/usr/bin/env?python
#?encoding=utf8
#?-*-?coding:?utf-8?-*-
#?author?by?heyujia
#?腳本將會用于對指定目錄下的.h?.m源碼中的字符串進(jìn)行轉(zhuǎn)換
#?替換所有字符串常量為加密的char數(shù)組,形式((char[]){1,?2,?3,?0})
importimportlib
importos
importre
importsys
#?replace替換字符串為((char[]){1,?2,?3,?0})的形式,同時讓每個字節(jié)與0xAA異或進(jìn)行加密
#?當(dāng)然可以不使用0xAA?使用其他的十六進(jìn)制也行?例如0XBB、0X22、0X11
defreplace(match):
string?=?match.group(2)?+'\x00'
replaced_string?='((char?[])?{'+',?'.join(["%i"%?((ord(c)?^0xAA)ifc?!='\0'else0)forcinlist(string)])?+'})'
returnmatch.group(1)?+?replaced_string?+?match.group(3)
#?obfuscate方法是修改傳入文件源代碼中用confusion_NSSTRING標(biāo)記的所有字符串
#?使用replace函數(shù)對字符串進(jìn)行異或轉(zhuǎn)換
defobfuscate(file):
withopen(file,'r')asf:
code?=?f.read()
f.close()
code?=?re.sub(r'(confusion_NSSTRING\(|confusion_CSTRING\()"(.*?)"(\))',?replace,?code)
code?=?re.sub(r'//#define?ggh_confusion','#define?ggh_confusion',?code)
withopen(file,'w')asf:
f.write(code)
f.close()
#?openSrcFile方法是讀取源碼路徑下的所有.h和.m?文件
#?對每個文件執(zhí)行obfuscate函數(shù)
defopenSrcFile(path):
print("混淆的路徑為?"+?path)
#?this?folder?is?custom
forparent,dirnames,filenamesinos.walk(path):
#case?1:
#????????for?dirname?in?dirnames:
#????????????print(("?parent?folder?is:"?+?parent).encode('utf-8'))
#????????????print(("?dirname?is:"?+?dirname).encode('utf-8'))
#case?2
forfilenameinfilenames:
extendedName?=?os.path.splitext(os.path.join(parent,filename))
if(extendedName[1]?=='.h'orextendedName[1]?=='.m'):
print("處理源代碼文件:?"+?os.path.join(parent,filename))
obfuscate(os.path.join(parent,filename))
#這里需要修改源碼的路徑為自己工程的文件夾名稱
srcPath?='../daimahunxiao'
if__name__?=='__main__':
print("本腳本用于對源代碼中被標(biāo)記的字符串進(jìn)行加密")
iflen(srcPath)?>0:
openSrcFile(srcPath)
else:
print("請輸入正確的源代碼路徑")
sys.exit()
步驟4?把下面的解密代碼放入decrypt.py解密腳本中
#!/usr/bin/env?python
#?encoding=utf8
#?-*-?coding:?utf-8?-*-
#?author?by?heyujia
#?解密腳本
#?替換所有標(biāo)記過的加密的char數(shù)組為字符串常量,""
importimportlib
importos
importre
importsys
#?替換((char[]){1,?2,?3,?0})的形式為字符串,同時讓每個數(shù)組值與0xAA異或進(jìn)行解密
defreplace(match):
string?=?match.group(2)
decodeConfusion_string?=""
fornumberStrinlist(string.split(',')):
ifint(numberStr)?!=0:
decodeConfusion_string?=?decodeConfusion_string?+"%c"%?(int(numberStr)?^0xAA)
replaced_string?='\"'+?decodeConfusion_string?+'\"'
print("replaced_string?=?"+?replaced_string)
returnmatch.group(1)?+?replaced_string?+?match.group(3)
#?修改源代碼,加入字符串加密的函數(shù)
defobfuscate(file):
withopen(file,'r')asf:
code?=?f.read()
f.close()
code?=?re.sub(r'(confusion_NSSTRING\(|confusion_CSTRING\()\(\(char?\[\]\)?\{(.*?)\}\)(\))',?replace,?code)
code?=?re.sub(r'[/]*#define?ggh_confusion','//#define?ggh_confusion',?code)
withopen(file,'w')asf:
f.write(code)
f.close()
#讀取源碼路徑下的所有.h和.m?文件
defopenSrcFile(path):
print("解密路徑:?"+?path)
#?this?folder?is?custom
forparent,dirnames,filenamesinos.walk(path):
#case?1:
#????????for?dirname?in?dirnames:
#????????????print(("?parent?folder?is:"?+?parent).encode('utf-8'))
#????????????print(("?dirname?is:"?+?dirname).encode('utf-8'))
#case?2
forfilenameinfilenames:
extendedName?=?os.path.splitext(os.path.join(parent,filename))
#讀取所有.h和.m?的源文件
if(extendedName[1]?=='.h'orextendedName[1]?=='.m'):
print("已解密文件:"+?os.path.join(parent,filename))
obfuscate(os.path.join(parent,filename))
#源碼路徑
srcPath?='../daimahunxiao'
if__name__?=='__main__':
print("字符串解混淆腳本,將被標(biāo)記過的char數(shù)組轉(zhuǎn)為字符串,并和0xAA異或。還原代碼")
iflen(srcPath)?>0:
openSrcFile(srcPath)
else:
print("請輸入正確的源代碼路徑!")
sys.exit()
步驟5?根據(jù)自己的需求修改下腳本里面的代碼 和 文件路徑。
步驟6?把步驟1中的宏heyujia_confusion注釋了,然后執(zhí)行加密腳本,在終端中輸入python confusion.py,
(1.如果報錯,請查看下自己Mac電腦中的python版本,如果是python3就輸入python3 confusion.py.
(2.如果報Non-ASCII character '\xe8' in file confusion.py on line 2相關(guān)的錯,請確定腳本的前面3行是
#!/usr/bin/env?python
#?encoding=utf8
#?-*-?coding:?utf-8?-*-
必須有這三行代碼,才能在腳本中輸入中文
(3.如果報IndentationError: unexpected indent,請注意腳本中的每行代碼的換行符和縮進(jìn)格式必須標(biāo)準(zhǔn)
執(zhí)行完步驟6后的結(jié)果

此時字符串已被加密,運(yùn)行程序會發(fā)現(xiàn)一切正常

輸出結(jié)果

加密后匯編界面
加密后匯編界面看不見我們的字符串內(nèi)容了,但是我們用來解密的方法還是暴露在了匯編界面,所以我們后期還需要對方法名,變量名,類命等做混淆。
步驟7?把步驟1中的宏heyujia_confusion取消注釋,然后執(zhí)行解密腳本,在終端中輸入python decrypt.py

解密后
解密后文本又變回了原樣。
這里只是基本的異或轉(zhuǎn)換加密,讓代碼中的字符串變成看不懂的char [],實際操作中遠(yuǎn)遠(yuǎn)不止這么簡單
例如:
首先:我們先用加密工具例如:AES.Base64等把需要轉(zhuǎn)換的字符串先加密變成加密字符串
然后:在用異或轉(zhuǎn)換加密的腳本把加密字符串進(jìn)行轉(zhuǎn)換(包括解密用的鑰匙串)
在使用的時候:先異或解密字符串,然后根據(jù)解密鑰匙串把字符串在轉(zhuǎn)為可用的字符串
ps.還有一種保護(hù)字符串的方法,就是使用NSLocalizedString字符串本地化。
雖然跟著我的步驟你確實加密成功了,但是你卻無法實際驗證。所以要驗證最終的混淆結(jié)果是否達(dá)到效果,你還需要學(xué)習(xí)如何破殼解密IPA如何動態(tài)靜態(tài)逆向編程分析工程源碼,大家可以先看看我這篇文章。先掌握逆向分析后在來做代碼混淆,就能驗證混淆結(jié)果是否有效
變量、方法名,類名混淆
對于混淆這一塊,網(wǎng)上真的是千篇一律,基本都是copy的念大嬸的內(nèi)容,沒有一點自己的創(chuàng)新和思考。網(wǎng)上的方法我也用過,但是有缺陷,只能混淆方法名或者說自己固定的內(nèi)容去替換。第一不自動,對于大項目而言每個方法名自己添加,太麻煩。第二變量混淆有問題,因為只是單純的字符串替換,用宏代替。當(dāng)遇到使用_ 下劃線訪問變量時,就會出現(xiàn)錯誤。
對于變量、方法名,類名的混淆,其實跟字符串混淆差不多,都是加密混淆,然后解密混淆。不同的是,變量、方法名,類名的混淆目的是為了讓別人反編譯的時候不知道你的變量、方法,類是具體用來干什么的,不會想明文那樣一目了然。增加逆向難度?;煜膬?nèi)容不需要想字符串一樣,最后程序運(yùn)行時還要轉(zhuǎn)成中文正常使用。由于本人對shell腳本語言也不是非常熟悉,想要按照自己的思路寫一套完整的混淆腳本還不行。所以這部分也是在網(wǎng)上找的,算是目前最實用最完善的混淆
首先?打開終端cd到需要混淆的工程目錄下,輸入
touch obConfusion.sh (加密混淆腳本文件)
touch obDecrypt.sh(解密混淆腳本文件)
生成2個腳本文件
然后在工程目錄以外創(chuàng)建一個文件夾,用于保存加密時生成的加密文本內(nèi)容,該內(nèi)容會在解密是用到
最后是在obConfusion.sh和obDecrypt.sh文件中加入腳本內(nèi)容
下面是加密混淆腳本內(nèi)容
#!/bin/sh
##################################
#??(該腳本是在https://github.com/heqingliang/CodeObfus?上找到的)
#??代碼混淆腳本??heyujia?2018.03.15
#
##################################
#識別含有多字節(jié)編碼字符時遇到的解析沖突問題
exportLC_CTYPE=C
export
#配置項:
#項目路徑,會混淆該路徑下的文件
ProjectPath="/Users/xieyujia/Desktop/ios/學(xué)習(xí)項目/daimahunxiao"
#這個路徑是混淆成功后,原文本和替換文本解密對應(yīng)的文件存放路徑(該路徑不能在項目目錄或其子目錄),混淆成功后會在該路徑下生成一個解密時需要的文件,根據(jù)該文件的文本內(nèi)容把混淆后的內(nèi)容更換為原文本內(nèi)容,該文件名的組成由$(date?+%Y%m%d)"_"$(date?+%H%M)及日期_小時組成,每分鐘會不一樣。所以解密的時候需要每次更換文件路徑
SecretFile="/Users/xieyujia/Desktop/ios/學(xué)習(xí)項目/tihuan"$(date?+%Y%m%d)"_"$(date?+%H%M)
#第一個參數(shù)為項目路徑
if[[$1]]
then
if[[$1!="_"]];then
ProjectPath=$1
fi
fi
#第二個參數(shù)指定密鑰文件路徑及文件名
if[[$2]]
then
if[[$2!="_"]];then
SecretFile=$2
fi
fi
##############################################################################
#查找文本中所有要求混淆的屬性\方法\類,只會替換文本中ob_開頭和_fus結(jié)尾的字符串(區(qū)分大小寫,例如oB_就不會做混淆),如果注釋內(nèi)容有該類型的字符串,也會進(jìn)行替換。對于使用?_下劃線訪問的變量屬性,不會有影響,一樣會替換成對應(yīng)_的混淆內(nèi)容。
resultfiles=`grep'ob_[A-Za-z0-9_]*_fus'-rl$ProjectPath`
#查找結(jié)果為空則退出
if[[?-z$resultfiles]]
then
echo"項目沒有需要混淆的代碼"
exit
else
echo"開始混淆代碼..."
echo>$SecretFile
fi
x=$(awk'
BEGIN{srand();k=0;}
#隨機(jī)數(shù)生成函數(shù)
function?random_int(min,?max)?{
return?int(?rand()*(max-min+1)?)?+?min;
}
#隨機(jī)字符串生成函數(shù)
function?random_string(len)?{
result="UCS"k;
alpbetnum=split("a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z",?alpbet,?",");
for?(i=0;?iresult?=?result""alpbet[?random_int(1,?alpbetnum)?];
}
return?result;
}
/ob_[A-Za-z0-9_]*_fus/{
x?=?$0;
#匹配需要混淆的屬性變量方法
while?(match(x,?"ob_[A-Za-z0-9_]*_fus")?>?0)?{
tempstr=substr(x,?RSTART,?RLENGTH);
#判斷是否有之前已經(jīng)找過的重復(fù)字符串
for?(?i?=?0;?i?<?k;?i++?){
if?(strarr[i]?==?tempstr){break;}
}
if(i#重復(fù)字符串,直接刪除。所以不用擔(dān)心混淆內(nèi)容過多,可能會出現(xiàn)重復(fù)的混淆字符串
x=substr(x,?RSTART+RLENGTH);
continue;
}else{
#不是重復(fù)字符串,添加到替換數(shù)組
strarr[k++]=tempstr;
}
randomstr=random_string(20);
printf("%s:%s|",?tempstr,randomstr);
#替換隨機(jī)字符串
gsub(tempstr,randomstr,?x);
x?=?substr(x,?RSTART+RLENGTH);
}
}'
$resultfiles)
#加密對寫入密鑰文件
echo$x>$SecretFile
recordnum=1
while[[?1?==?1?]];do
record=`echo$x|cut?-d"|"-f$recordnum`
if[[?-z$record]]
then
break
fi
record1=`echo$record|cut?-d":"-f1`
echo"原項:"$record1
record2=`echo$record|cut?-d":"-f2`
echo"加密項:"$record2
#替換文件夾中所有文件的內(nèi)容(支持正則)
#單引號不能擴(kuò)展
sed?-i''"s/${record1}/${record2}/g"`grep$record1-rl$ProjectPath`
echo"第"$recordnum"項混淆代碼處理完畢"
let"recordnum?=$recordnum+?1"
done
#查找需要混淆的文件名并替換
filerecordnum=1
while[[?1?==?1?]];do
filerecord=`echo$x|cut?-d"|"-f$filerecordnum`
if[[?-z$filerecord]]
then
break
fi
filerecord1=`echo$filerecord|cut?-d":"-f1`
#echo?"原項:"$filerecord1
filerecord2=`echo$filerecord|cut?-d":"-f2`
#echo?"加密項:"$filerecord2
#改文件名
find$ProjectPath-name$filerecord1"*"|?awk'
BEGIN{frecord1="'
"$filerecord1"'";frecord2="'"$filerecord2"'";finish=1}
{
filestr=$0;
gsub(frecord1,frecord2,filestr);
print?"mv?"?$0?"?"?filestr";echo?第"finish"個混淆文件處理完畢";
finish++;
}'
|bash
let"filerecordnum?=$filerecordnum+?1"
done
下面是解密混淆腳本的內(nèi)容
#!/bin/sh
######################################
#
#??代碼還原腳本??RyoHo?2018.03.15
#
######################################
#識別含有多字節(jié)編碼字符時遇到的解析沖突問題
exportLC_CTYPE=C
export
#配置項:
#已經(jīng)混淆的項目路徑
ProjectPath="/Users/xieyujia/Desktop/ios/學(xué)習(xí)項目/daimahunxiao"
#這個是文件路徑而不是目錄,是混淆的時候生成的文本文件路徑,每次不一樣。所以每次加密后,解密時需要更換路徑
SecretFile="/Users/xieyujia/Desktop/ios/學(xué)習(xí)項目/tihuan20180315_1456"
#第一個參數(shù)為項目路徑
if[[$1]]
then
if[[$1!="_"]];then
ProjectPath=$1
fi
fi
#第二個參數(shù)指定密鑰文件路徑及文件名
if[[$2]]
then
if[[$2!="_"]];then
SecretFile=$2
fi
fi
##############################################################################
#內(nèi)容還原
x=`cat$SecretFile`
recordnum=1
while[[?1?==?1?]];do
record=`echo$x|cut?-d"|"-f$recordnum`
if[[?-z$record]]
then
break
fi
record1=`echo$record|cut?-d":"-f1`
echo"原項:"$record1
record2=`echo$record|cut?-d":"-f2`
echo"加密項:"$record2
#若項目中加密項與密鑰文件的加密項不符合則退出程序
searchresult=`grep$record2-rl$ProjectPath`
if[[?-z$searchresult]];then
echo"指定的密鑰文件不能還原"
exit
fi
#替換文件夾中所有文件的內(nèi)容(支持正則)
#單引號不能擴(kuò)展
sed?-i''"s/${record2}/${record1}/g"$searchresult
echo"第"$recordnum"項混淆代碼還原完畢"
let"recordnum?=$recordnum+?1"
done
#文件還原
filerecordnum=1
while[[?1?==?1?]];do
filerecord=`echo$x|cut?-d"|"-f$filerecordnum`
if[[?-z$filerecord]]
then
break
fi
filerecord1=`echo$filerecord|cut?-d":"-f1`
#echo?"原項:"$filerecord1
filerecord2=`echo$filerecord|cut?-d":"-f2`
#echo?"加密項:"$filerecord2
#改文件名
find$ProjectPath-name$filerecord2"*"|?awk'
BEGIN{
frecord1="'
"$filerecord1"'";
frecord2="'
"$filerecord2"'";
finish=1;
}
{
filestr=$0;
gsub(frecord2,frecord1,filestr);
print?"mv?"?$0?"?"filestr?";echo?第"finish"個混淆文件還原完畢"
finish++;
}'
|bash
let"filerecordnum?=$filerecordnum+?1"
done
應(yīng)大家需要把腳本源碼地址放出來
建議大家看看腳本內(nèi)容,有利于學(xué)習(xí)理解。該腳本是有針對性的混淆內(nèi)容,可以自己修改腳本中的正則表達(dá)式來確定混淆的內(nèi)容。腳本中只會替換文本中ob_開頭和fus結(jié)尾的字符串(區(qū)分大小寫,例如oB就不會做混淆),如果注釋內(nèi)容有該類型的字符串,也會進(jìn)行替換。對于使用 下劃線訪問的變量屬性,不會有影響,一樣會替換成對應(yīng)的混淆內(nèi)容。
提供一個shell腳本學(xué)習(xí)的網(wǎng)站
作者:樹下敲代碼的超人
鏈接:http://www.itdecent.cn/p/628a0c232c2a
本站內(nèi)容均為本站轉(zhuǎn)發(fā),已盡可能注明出處。因未能核實來源或轉(zhuǎn)發(fā)內(nèi)容圖片有權(quán)利瑕疵的,請及時聯(lián)系本站,本站會第一時間進(jìn)行修改或刪除。 QQ : 286254092