对于iOS来说,因为零碎是关闭的,APP上架须要通过App Store,安全性来说相当高。然而对于大厂和出名APP而言,他人给的平安保障永远没有本人做的来得虚浮。所以对于大厂、少部分企业级和金融领取类利用来说加固是相当重要的。
上面是目前几个业余加固大厂提供的加固策略

  • 网易

  • 网易平安三板斧:
  1. 第一板斧是防动态剖析,这里包含字符串加密、符号混同、代码逻辑混同和游戏存档加密;

2.第二板斧是防动静调试、反调试和通信安全(数据加密);

  1. 第三板斧是外挂检测、减速挂、内存批改挂和主动工作挂等
  • 爱加密


  • safengine

  • 几维平安

  • 梆梆平安

本文将针对以上几点进行实现,对于一些不太容易实现的将会做方向性探讨

  • 字符串加密
  • 代码混同(办法命,类命,变量名,符号表)
  • 代码逻辑混同
  • 反调试
    • *

字符串加密

对字符串加密的形式目前我所理解到把握到的最牢靠形式就是用脚本将代码中的所有标记须要加密的字符串进行异或转换,这样代码中就不存在明文字符串了。当然第三方的字符串加密不可能这么简略,具体怎么做的我也不太分明。不过为了减少字符串加密的难度复杂性,咱们能够先将字符串用加密工具转换(例如AES、base64等)后的把加字符串放在工程中,并且把解密的钥匙放在工程中,用异或转换,把解密钥匙和加密后的字符串转换,这样就有2层保障,减少了复杂度。

  • 首先咱们创立任意一个工程,在工程中写入上面的代码,并在每句打上断点,再抉择Xcode工具栏的Debug --> Debug Workflow --> Always Show Disassembly。这样你就能够在断点处进入汇编模式界面,最初运行程序
/* 加密NSString字符串 */    NSString *str = @"Hello World";    NSLog(@"%@",str);    /* 加密char*字符串 */    char* cStr = "Super Man";    NSLog(@"%s",cStr);


你会发现,你的字符串内容裸露在了汇编模式中,这会导致他人在逆向剖析你的工程时能看见你的字符串内容,咱们个别接口、域名、加解密钥匙串、AppKey、AppId等比拟重要的货色会放在客户端用作字符串,这就很容易裸露进去。

  • 步骤1首先须要在工程代码中进行批改,把上面的宏和decryptConfusionCS,decryptConstString函数放入代码中,用宏蕴含每个须要转换的字符串。
/* 字符串混同解密函数,将char[] 模式字符数组和 aa异或运算揭秘 */extern char* decryptConfusionCS(char* string){    char* origin_string = string;    while(*string) {        *string ^= 0xAA;        string++;    }    return origin_string;}/* 解密函数,返回的是NSString类型的 */extern NSString* decryptConstString(char* string){    /* 先执行decryptConfusionString函数解密字符串 */    char* str = decryptConfusionCS(string);    /* 获取字符串的长度 */    unsigned long len = strlen(str);    NSUInteger length = [[NSString stringWithFormat:@"%lu",len] integerValue];     NSString *resultString = [[NSString alloc]initWithBytes:str length:length encoding:NSUTF8StringEncoding];    return resultString;}/* * 应用heyujia_confusion宏管制加密解密 * 当heyujia_confusion宏被定义的时候,执行加密脚本,对字符串进行加密 * 当heyujia_confusion宏被删除或为定义时,执行解密脚本,对字符串解密 */#define heyujia_confusion#ifdef heyujia_confusion/* heyujia_confusion 宏被定义,那么就进行执行解密脚本 *//* confusion_NSSTRING宏的返回后果是NSString 类型的 */#define confusion_NSSTRING(string) decryptConstString(string)/* confusion_CSTRING宏的返回后果是char* 类型的 */#define confusion_CSTRING(string) decryptConfusionCS(string)#else/* heyujia_confusion 宏没有被定义,那么就执行加密脚本 *//* 加密NSString类型的 */#define confusion_NSSTRING(string) @string/* 加密char *类型的 */#define confusion_CSTRING(string) string#endif@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // 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 到须要加密的工程目录下 执行touch confusion.pytouch decrypt.py命令,生产加密和解密脚本文件
  • 步骤3把上面代码退出解密脚本confusion.py中
#!/usr/bin/env python# encoding=utf8# -*- coding: utf-8 -*-# author by heyujia# 脚本将会用于对指定目录下的.h .m源码中的字符串进行转换# 替换所有字符串常量为加密的char数组,模式((char[]){1, 2, 3, 0})import importlibimport osimport reimport sys# replace替换字符串为((char[]){1, 2, 3, 0})的模式,同时让每个字节与0xAA异或进行加密# 当然能够不应用0xAA 应用其余的十六进制也行 例如0XBB、0X22、0X11def replace(match):    string = match.group(2) + '\x00'    replaced_string = '((char []) {' + ', '.join(["%i" % ((ord(c) ^ 0xAA) if c != '\0' else 0) for c in list(string)]) + '})'    return match.group(1) + replaced_string + match.group(3)# obfuscate办法是批改传入文件源代码中用confusion_NSSTRING标记的所有字符串# 应用replace函数对字符串进行异或转换def obfuscate(file):    with open(file, 'r') as f:        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)        with open(file, 'w') as f:            f.write(code)            f.close()# openSrcFile办法是读取源码门路下的所有.h和.m 文件# 对每个文件执行obfuscate函数def openSrcFile(path):    print("混同的门路为 "+ path)    # this folder is custom    for parent,dirnames,filenames in os.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        for filename in filenames:            extendedName = os.path.splitext(os.path.join(parent,filename))            if (extendedName[1] == '.h' or extendedName[1] == '.m'):                print("解决源代码文件: "+ os.path.join(parent,filename))                obfuscate(os.path.join(parent,filename))#这里须要批改源码的门路为本人工程的文件夹名称srcPath = '../daimahunxiao'if __name__ == '__main__':    print("本脚本用于对源代码中被标记的字符串进行加密")        if len(srcPath) > 0:        openSrcFile(srcPath)    else:        print("请输出正确的源代码门路")        sys.exit()
  • 步骤4 把上面的解密代码放入decrypt.py解密脚本中
#!/usr/bin/env python# encoding=utf8# -*- coding: utf-8 -*-# author by heyujia# 解密脚本# 替换所有标记过的加密的char数组为字符串常量,""import importlibimport osimport reimport sys# 替换((char[]){1, 2, 3, 0})的模式为字符串,同时让每个数组值与0xAA异或进行解密def replace(match):    string = match.group(2)    decodeConfusion_string = ""    for numberStr in list(string.split(',')):        if int(numberStr) != 0:            decodeConfusion_string = decodeConfusion_string + "%c" % (int(numberStr) ^ 0xAA)    replaced_string = '\"' + decodeConfusion_string + '\"'    print("replaced_string = " + replaced_string)        return match.group(1) + replaced_string + match.group(3)# 批改源代码,退出字符串加密的函数def obfuscate(file):    with open(file, 'r') as f:        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)        with open(file, 'w') as f:            f.write(code)            f.close()#读取源码门路下的所有.h和.m 文件def openSrcFile(path):    print("解密门路: "+ path)    # this folder is custom    for parent,dirnames,filenames in os.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        for filename in filenames:            extendedName = os.path.splitext(os.path.join(parent,filename))            #读取所有.h和.m 的源文件            if (extendedName[1] == '.h' or extendedName[1] == '.m'):                print("已解密文件:"+ os.path.join(parent,filename))                obfuscate(os.path.join(parent,filename))#源码门路srcPath = '../daimahunxiao'if __name__ == '__main__':    print("字符串解混同脚本,将被标记过的char数组转为字符串,并和0xAA异或。还原代码")    if len(srcPath) > 0:        openSrcFile(srcPath)    else:        print("请输出正确的源代码门路!")        sys.exit()
  • 步骤5依据本人的需要批改下脚本外面的代码 和 文件门路。
  • 步骤6把步骤1中的宏heyujia_confusion正文了,而后执行加密脚本,在终端中输出python confusion.py,
    (1.如果报错,请查看下本人Mac电脑中的python版本,如果是python3就输出python3 confusion.py.
    (2.如果报Non-ASCII character '\xe8' in file confusion.py on line 2相干的错,请确定脚本的后面3行是
#!/usr/bin/env python# encoding=utf8# -*- coding: utf-8 -*-

执行完步骤6后的后果

此时字符串已被加密,运行程序会发现一切正常


加密后汇编界面看不见咱们的字符串内容了,然而咱们用来解密的办法还是裸露在了汇编界面,所以咱们前期还须要对办法名,变量名,类命等做混同。

  • 步骤7把步骤1中的宏heyujia_confusion勾销正文,而后执行解密脚本,在终端中输出python decrypt.py

  • 解密后文本又变回了原样。

这里只是根本的异或转换加密,让代码中的字符串变成看不懂的char [],实际操作中远远不止这么简略
例如:

  • 首先:咱们先用加密工具例如:AES.Base64等把须要转换的字符串先加密变成加密字符串
  • 而后:在用异或转换加密的脚本把加密字符串进行转换(包含解密用的钥匙串)
  • 在应用的时候:先异或解密字符串,而后依据解密钥匙串把字符串在转为可用的字符串

ps.还有一种爱护字符串的办法,就是应用NSLocalizedString字符串本地化。


尽管跟着我的步骤你的确加密胜利了,然而你却无奈理论验证。所以要验证最终的混同后果是否达到成果,你还须要学习如何破壳解密IPA如何动静动态逆向编程剖析工程源码,大家能够先看看我这篇文章。先把握逆向剖析后在来做代码混同,就能验证混同后果是否无效

变量、办法名,类名混同

对于混同这一块,网上真的是千篇一律,根本都是copy的念大婶的内容,没有一点本人的翻新和思考。网上的办法我也用过,然而有缺点,只能混同办法名或者说本人固定的内容去替换。第一不主动,对于大我的项目而言每个办法名本人增加,太麻烦。第二变量混同有问题,因为只是单纯的字符串替换,用宏代替。当遇到应用_ 下划线拜访变量时,就会呈现谬误。

对于变量、办法名,类名的混同,其实跟字符串混同差不多,都是加密混同,而后解密混同。不同的是,变量、办法名,类名的混同目标是为了让他人反编译的时候不晓得你的变量、办法,类是具体用来干什么的,不会想明文那样高深莫测。减少逆向难度。混同的内容不须要想字符串一样,最初程序运行时还要转成中文失常应用。因为自己对shell脚本语言也不是十分相熟,想要依照本人的思路写一套残缺的混同脚本还不行。所以这部分也是在网上找的,算是目前最实用最欠缺的混同

  • 首先 关上终端cd到须要混同的工程目录下,输出
    touch obConfusion.sh(加密混同脚本文件)
    touch obDecrypt.sh(解密混同脚本文件)
    生成2个脚本文件
  • 而后在工程目录以外创立一个文件夹,用于保留加密时生成的加密文本内容,该内容会在解密是用到
  • 最初是在obConfusion.shobDecrypt.sh文件中退出脚本内容

上面是加密混同脚本内容

#!/bin/sh###################################  (该脚本是在https://github.com/heqingliang/CodeObfus 上找到的)#  代码混同脚本  heyujia 2018.03.15####################################辨认含有多字节编码字符时遇到的解析抵触问题export LC_CTYPE=Cexport LANG=C#配置项:#我的项目门路,会混同该门路下的文件ProjectPath="/Users/xieyujia/Desktop/ios/学习我的项目/daimahunxiao"#这个门路是混同胜利后,原文本和替换文本解密对应的文件寄存门路(该门路不能在我的项目目录或其子目录),混同胜利后会在该门路下生成一个解密时须要的文件,依据该文件的文本内容把混同后的内容更换为原文本内容,该文件名的组成由$(date +%Y%m%d)"_"$(date +%H%M)及日期_小时组成,每分钟会不一样。所以解密的时候须要每次更换文件门路SecretFile="/Users/xieyujia/Desktop/ios/学习我的项目/tihuan"$(date +%Y%m%d)"_"$(date +%H%M)#第一个参数为我的项目门路if [[ $1 ]]thenif [[ $1 != "_" ]]; thenProjectPath=$1fifi#第二个参数指定密钥文件门路及文件名if [[ $2 ]]thenif [[ $2 != "_" ]]; thenSecretFile=$2fifi###############################################################################查找文本中所有要求混同的属性\办法\类,只会替换文本中ob_结尾和_fus结尾的字符串(辨别大小写,例如oB_就不会做混同),如果正文内容有该类型的字符串,也会进行替换。对于应用 _下划线拜访的变量属性,不会有影响,一样会替换成对应_的混同内容。resultfiles=`grep 'ob_[A-Za-z0-9_]*_fus' -rl $ProjectPath`#查找后果为空则退出if [[ -z $resultfiles ]]thenecho "我的项目没有须要混同的代码"exitelseecho "开始混同代码..."echo  > $SecretFilefix=$(awk  'BEGIN{srand();k=0;}#随机数生成函数function random_int(min, max) {return int( rand()*(max-min+1) ) + min;}#随机字符串生成函数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; i<len; i++) {result = 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);#判断是否有之前曾经找过的反复字符串for ( i = 0; i < k; i++ ){if (strarr[i] == tempstr){break;}}if(i<k){#反复字符串,间接删除。所以不必放心混同内容过多,可能会呈现反复的混同字符串x=substr(x, RSTART+RLENGTH);continue;}else{#不是反复字符串,增加到替换数组strarr[k++]=tempstr;}randomstr=random_string(20);printf("%s:%s|", tempstr,randomstr);#替换随机字符串gsub(tempstr,randomstr, x);x = substr(x, RSTART+RLENGTH);}}' $resultfiles )#加密对写入密钥文件echo $x > $SecretFilerecordnum=1while [[ 1 == 1 ]]; dorecord=`echo $x|cut -d "|" -f$recordnum`if [[ -z $record ]]thenbreakfirecord1=`echo $record|cut -d ":" -f1`echo "原项:"$record1record2=`echo $record|cut -d ":" -f2`echo "加密项:"$record2#替换文件夹中所有文件的内容(反对正则)#单引号不能扩大sed -i '' "s/${record1}/${record2}/g" `grep $record1 -rl $ProjectPath`echo "第"$recordnum"项混同代码处理完毕"let "recordnum = $recordnum + 1"done#查找须要混同的文件名并替换filerecordnum=1while [[ 1 == 1 ]]; dofilerecord=`echo $x|cut -d "|" -f$filerecordnum`if [[ -z $filerecord ]]thenbreakfifilerecord1=`echo $filerecord|cut -d ":" -f1`#echo "原项:"$filerecord1filerecord2=`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++;}'|bashlet "filerecordnum = $filerecordnum + 1"done

上面是解密混同脚本的内容

#!/bin/sh########################################  代码还原脚本  RyoHo 2018.03.15########################################辨认含有多字节编码字符时遇到的解析抵触问题export LC_CTYPE=Cexport LANG=C#配置项:#曾经混同的我的项目门路ProjectPath="/Users/xieyujia/Desktop/ios/学习我的项目/daimahunxiao"#这个是文件门路而不是目录,是混同的时候生成的文本文件门路,每次不一样。所以每次加密后,解密时须要更换门路SecretFile="/Users/xieyujia/Desktop/ios/学习我的项目/tihuan20180315_1456"#第一个参数为我的项目门路if [[ $1 ]]thenif [[ $1 != "_" ]]; thenProjectPath=$1fifi#第二个参数指定密钥文件门路及文件名if [[ $2 ]]thenif [[ $2 != "_" ]]; thenSecretFile=$2fifi###############################################################################内容还原x=`cat $SecretFile`recordnum=1while [[ 1 == 1 ]]; dorecord=`echo $x|cut -d "|" -f$recordnum`if [[ -z $record ]]thenbreakfirecord1=`echo $record|cut -d ":" -f1`echo "原项:"$record1record2=`echo $record|cut -d ":" -f2`echo "加密项:"$record2#若我的项目中加密项与密钥文件的加密项不合乎则退出程序searchresult=`grep $record2 -rl $ProjectPath`if [[ -z $searchresult ]]; thenecho "指定的密钥文件不能还原"exitfi#替换文件夹中所有文件的内容(反对正则)#单引号不能扩大sed -i '' "s/${record2}/${record1}/g" $searchresultecho "第"$recordnum"项混同代码还原结束"let "recordnum = $recordnum + 1"done#文件还原filerecordnum=1while [[ 1 == 1 ]]; dofilerecord=`echo $x|cut -d "|" -f$filerecordnum`if [[ -z $filerecord ]]thenbreakfifilerecord1=`echo $filerecord|cut -d ":" -f1`#echo "原项:"$filerecord1filerecord2=`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++;}'|bashlet "filerecordnum = $filerecordnum + 1"done

应大家须要把脚本源码地址放进去

倡议大家看看脚本内容,有利于学习了解。该脚本是有针对性的混同内容,能够本人批改脚本中的正则表达式来确定混同的内容。脚本中只会替换文本中ob_结尾和_fus结尾的字符串(辨别大小写,例如oB_就不会做混同),如果正文内容有该类型的字符串,也会进行替换。对于应用 _下划线拜访的变量属性,不会有影响,一样会替换成对应_的混同内容。
提供一个shell脚本学习的网站