Objective-C 代码混淆

前言

终于开始做攻防相关的操作了,我会一边攻一边防,这篇文章先介绍一个简单的代码混淆的防备操作。文章会从为何要加密、加密市场市场现状、加密思路、方法、工具、操作等方面进行阐述,也作为我个人的记录以便日后的回顾。

为什么要做混淆加密

先说安卓,是一定要做混淆加密的。因为 apk 非常容易反编译,比如用 smail2java,apk 文件反编译过后都会变成 smail 文件,通过这个工具能看到里面的 Java 代码,请注意,看到的是源码!没有混淆的代码一旦经过反编译全部完完全全的展现给别人,知识产权一点保障也没有。如果写了什么敏感信息,也就全部暴露了。而加固后,比如用360加固保后,反编译出来基本什么也看不到了。

再说 iOS,使用 classdump 对原程序进行 dump,可以 dump 出所有源程序的函数所有信息,包括源程序所有函数类型,变量全部泄露。又比如用 IDA 或者 Hopper Disassembler, 虽然不能像安卓一样反编译出源代码,但还是可以看到汇编或者汇编与 OC 运行时的结合体,还是可以比较容易看出方法的具体实现,让攻击者,也就是黑客们了解了程序结构方便逆向,所以也需要做混淆加密。

加密市场

国内市场上的现状是安卓的加固烂大街,并且免费,比如360加固保。对于安卓来说,自己写混淆加密意义不大,当然基于了解与学习的角度出发,Get 一项新技能完全没毛病。

而 iOS 市场上很少有人做混淆,因为不能像安卓基于平台化的提交应用包,只能使用第三方提供的集成了加密插件的 Xcode,代替官方原版 Xcode 来打包。国内首家做 iOS 加密的爱加密,据我的深度了解购买授权需要八万块,这可是一笔很大的成本。对于安全级别本身就比较高的苹果应用来说,其实不需要再做太多太多的安全防范操作了。自己学会较简单的混淆加密原理与操作,就可以省下一笔钱了。接下来就只说 iOS 的混淆加密。

iOS 混淆加密

混淆思路

  • 花代码花指令
  • 易读字符替换

花代码花指令即随意往程序中加入迷惑人的代码指令,但是个人觉得这个方法还是不太好,如果开发阶段加入迷惑人的代码,容易连开发者自己都被迷惑;如果上架前加入迷惑人的代码,以后的版本迭代也会比较恶心,所以个人不推荐用这个方法。

易读字符替换是防止 class-dump 出可读信息的非常有效的办法,例如将方法名混淆。

混淆的时机

  • 开发时一直保留清晰可读的程序代码,方便自己。
  • 发布时编译出来的二进制包含乱七八糟的混淆后的程序代码,恶心他人。

因此,我们可以在 Build Phrase 中设定在编译之前进行方法名的字符串替换。

混淆的方法

方法名混淆其实就是字符串替换,利用 #define 的方法来完成,可以把混淆结果合并在一个 .h 中,在工程 Prefix.pch 的最前面 #import 这个 .h。不导入也可以编译、导入则实现混淆。

单段的 selector,如 func: ,可以通过 #define func 来实现字符串替换。
多段的 selector,如 a:b:c: ,可以通过分别 #define a 、b、c 来实现字符串替换。

混淆工具

写一个的混淆脚本,主要思路是把敏感方法名集中写在一个名叫 func.list 的文件中,逐一 #define 成随机字符,追加写入 .h。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env bash
TABLENAME=symbols
SYMBOL_DB_FILE="symbols"
STRING_SYMBOL_FILE="func.list"
HEAD_FILE="$PROJECT_DIR/$PROJECT_NAME/Classes/Other/DKCodeObscure.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 放到工程目录下

    1
    mv confuse.sh to/project/path/
  2. 创建混淆头文件 DKCodeObscure.h

    可以直接在 xcode 中新建一个 Header 文件,根据项目架构分层找一个适当的位置放,我把它放在了 /Classes/Other/下,要与 confuse.sh 里的 HEAD_FILE 保持一致。

    1
    HEAD_FILE="$PROJECT_DIR/$PROJECT_NAME/Classes/Other/DKCodeObscure.h"
  3. 修改 pch,添加混淆头文件

    1
    2
    3
    #ifdef __OBJC__
    #import "DKCodeObscure.h"
    #endif
  4. 配置 Build Phase,添加执行脚本操作

    新建一个 Run Script,输入$PROJECT_DIR/confuse.sh

    confuse.png

  5. 创建函数名列表 func.list,根据规则写入待混淆的方法名

    例如:

    1
    2
    /** 获取立即支付前的准备数据 */
    + (void)fetchPreDataWithProductId:(NSString *)productId quantity:(NSInteger)quantity callback:(void(^)(DKPayPreData *preData, NSError *error))callback;

    可写成:

    1
    2
    3
    fetchPreDataWithProductId
    quantity
    callback

    一般情况下,需要混淆加密的方法应该只是一些比较敏感的,比如与支付相关的 API Service 等。当然,如果需要写入混淆的方法名太多了,一个一个写也是很麻烦,后面我会写一个根据规则自动生成所有方法名列表的脚本。

  6. 将 func.list 文件放置于与 confuse.sh 脚本同级

    1
    mv func.list to/project/path/
  7. 编译查看结果

    直接 build,混淆脚本会在编译前运行,进行字符随机替换,并且每次 build 的随机字符不同,如图:

    obscure.png

    同时也会将混淆记录写在 DKCodeObscure.h 中

    DKCodeObscure.h

补充

如果 Build 的时候报了如下一个错误:

1
2
3
4
Shell Script Invocation Error
/Users/bingo/Library/Developer/Xcode/DerivedData/SF-cprhxwzvzucuawewgvpjhoffdayp/Build/Intermediates/SF.build/Debug-iphoneos/SF.build/Script-D71BDF821E5C99F4003BF126.sh:
line 2: /Users/bingo/Desktop/iOS/iOS: No such file or directory

这是路径有问题,比如包含空格,我的 PROJECT_DIR 是 /Users/bingo/Desktop/iOS/iOS\ Project/SF,“iOS Project” 中间有空格,但是 Script 不会自动转义,所以要保证路径不能有空格,例如我改成了 “iOS_Project”。(中文路径还没测试,有可能没问题,但我的看法作为一个有逼格的 developer,用中文你还是别 do 了吧。)

后话

至此,一个 iOS 方法名混淆加密就完成了,但还是很简单的,后面会再一步步优化,现在的 ramdomString() 方法每次都是生成随机字符,后面可能会考虑改成 md5 加密,还有写个脚本一键生成 func.list,还考虑将创建文件等操作也改成用脚本来生成等等。

  • 本文作者: Bingo
  • 本文链接: https://blog.bingo.ren/17.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!