ios混淆初探

298 阅读3分钟

为什么要做混淆?

我们先来看看一个没有混淆过的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里面的


是不是很惊喜,如果这是个登录接口,如果是涉及到一些私密信息的接口......

接下就是本文的重头戏,我们如何对他做混淆

  1. 创建一个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

  1. 首先看一下混淆后的头文件


    //
    //     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,别人逆向你的头文件的时候就会一脸懵逼,这是个啥????

  2. 接下来看一下hopper的函数栈


    同样是混淆过后的函数名,再次懵逼!!!!

到这里,整个混淆的流程已经讲完了,对于混淆规则,你可以自定义很多种!