为什么要做混淆?
我们先来看看一个没有混淆过的iOS包,使用class-dump拿到的头文件是怎么样的。
先看看一个简单的demo,下面是源码
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self timerStart];
// Do any additional setup after loading the view.
}
-(void)timerStart{
__block int count = 0;
NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate now] interval:1 repeats:true block:^(NSTimer * _Nonnull timer) {
self.titleLabel.text = [NSString stringWithFormat:@"%d",count];
NSLog(@"%d",count);
count++ ;
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[timer fire];
}这个demo在页面加载的时候,调用了私有函数timerStart,启动一个计时器。
接下来,我们dump出头文件,看看dumpHeader下面有什么,
class-dump -H ShellTestTwo -o dumpHeader
这里把工程里面左右的头文件都拿到了,接下来看看ViewController里面是什么
class-dump
对比原工程里面的头文件

触目惊心,dump把我们的私有函数和变量都读出来了,- (void)timerStart;一眼就看出这个函数是做什么的了。
我们在用hopper去看App的函数调用:

这里面有四个类,我们主要看ViewController里面的

是不是很惊喜,如果这是个登录接口,如果是涉及到一些私密信息的接口......
接下就是本文的重头戏,我们如何对他做混淆
- 创建一个shell脚本,这是核心部分,命名confuse.sh,代码如下
#!/usr/bin/env bash
TABLENAME=symbols
SYMBOL_DB_FILE="symbols"
STRING_SYMBOL_FILE="$PROJECT_DIR/ShellTest/func.list"
HEAD_FILE="$PROJECT_DIR/ShellTest/codeObfuscation.h"
####这里是全局混淆要查找的内容路径,不需要全局混淆的时候要注释掉
#CONFUSE_FILE="$PROJECT_DIR/MobileOA"
export LC_CTYPE=C
####这里是全局混淆要查找的条件,不需要全局混淆的时候要注释掉
#取以.m或.h结尾的文件以+号或-号开头的行 |去掉所有+号或-号|用空格代替符号|n个空格跟着<号 替换成 <号|开头不能是IBAction|用空格split字串取第二部分|排序|去重复|删除空行|删掉以init开头的行>写进func.list
## |sed -n "/^sk_/p" 是特定问方法名的开头,不需要的话可以删掉
#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 "/^sk_/p" >$STRING_SYMBOL_FILE
#维护数据库方便日后作排重
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`
userName="LuckWork_"
echo $line $userName$ramdom
insertValue $line $userName$ramdom
echo "#define $line $userName$ramdom" >> $HEAD_FILE
fi
done
echo "#endif" >> $HEAD_FILE
sqlite3 $SYMBOL_DB_FILE .dump
2.然后创建一个空文件func.list,里面写入我们需要混淆的函数名,如下
testDemo
timerStart
3.给TARGETS加入预编译脚本
$PROJECT_DIR/ShellTest/confuse.sh

4.给confuse.sh文件添加权限
$ chmod 755 confuse.sh
5.重新编译一下,工程目录下会创建一个新文件codeObfuscation.h,我们把他加到工程里,看看里面是什么

这个头文件里生成了两个宏定义,对一func.plist里面的两个函数名。接下来我们需要吧codeObfuscation.h导入到pch文件,全局使用。
#ifndef PrefixHeader_pch
#define PrefixHeader_pch
#import "codeObfuscation.h"
#endif /* PrefixHeader_pch */
6.再回到工程的ViewController里面看看我们的timerStart函数,这个时候的timerStart已经指向刚刚生产的宏定义了

Run一下,么有报错,功能正常,整个混淆流程就做完了,接下来我们要验证一下,到底是不是整的混淆成功了。
验证混淆过后的ipa
- 首先看一下混淆后的头文件
// // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2015 by Steve Nygard. // #import <UIKit/UIViewController.h> @class UILabel; @interface ViewController : UIViewController { UILabel *_titleLabel; } @property(nonatomic) __weak UILabel *titleLabel; // @synthesize titleLabel=_titleLabel; - (void).cxx_destruct; - (void)LuckWork_lPOLykrz:(id)arg1 with:(id)arg2; - (void)LuckWork_JIHZIzCT; - (void)viewDidLoad; @end我们可以看到,头文件里面捕获的是有函数名都变成了我们刚刚混淆后的了,startTimer 变成了LuckWork_JIHZIzCT,别人逆向你的头文件的时候就会一脸懵逼,这是个啥????
- 接下来看一下hopper的函数栈

同样是混淆过后的函数名,再次懵逼!!!!
到这里,整个混淆的流程已经讲完了,对于混淆规则,你可以自定义很多种!