为什么要代码混淆?
主要是为了给App加固
为什么要给App加固?
为了增加应用的安全性,防止应用被破解、盗版、二次打包、注入、反编译等
常见的加固方式
- 1.数据加密(字符串、网络数据、敏感数据等)
- 2.应用加壳(二进制加密)
- 3.代码混淆(类名,方法名,代码逻辑等)
不同平台有不同的做法
代码混淆
iOS程序可以通过class-dump,Hopper,IDA等获取类名,方法名,以及分析程序的执行逻辑
如果进行了代码混淆,可以加大别人的分析难度
示例:使用class-dump导出可执行文件的头文件
以网上以前写的一个demo为示例MVPDemo-> Product->MVPDemo.app 右键Show in Finder ->MVPDemo.app 右键显示包内容,找到可执行文件MVPDemo
把这个文件拷贝到桌面,启动终端,cd到桌面目录 执行
class-dump -H MVPDemo -o Headers
可以看到头文件的信息非常清晰
把可执行文件MVPDemo
放到Hopper Disassembler
同样可以查看到方法名,类名
iOS代码混淆方案
一.源码的混淆
- 1.类名
- 2.方法名
- 3.协议名 …
二.LLVM中间代码IR的混淆(容易产生bug)
1.自己编写Pass 2.ollvm:https://github.com/obfuscator-llvm/obfuscator
使用宏定义混淆方法名,类名 以示例应用的Model为演示,可以先看下之前导出的头文件,我们可以看到哪些关键信息
#import "NSObject.h"
#import "ModelProtocol.h"
@class NSString;
@interface Model : NSObject <ModelProtocol>
{
NSString *_titleString;
NSString *_subTitleString;
}
@property(copy, nonatomic) NSString *subTitleString; // @synthesize subTitleString=_subTitleString;
@property(copy, nonatomic) NSString *titleString; // @synthesize titleString=_titleString;
- (void).cxx_destruct;
- (double)height;
- (id)identifier;
// Remaining properties
@property(readonly, copy) NSString *debugDescription;
@property(readonly, copy) NSString *description;
@property(readonly) unsigned long long hash;
@property(readonly) Class superclass;
@end
很明显有属性,方法,协议相关重要信息
@interface Model : NSObject <ModelProtocol>
@property(copy, nonatomic) NSString *subTitleString;
@property(copy, nonatomic) NSString *titleString;
- (double)height;
- (id)identifier;
这里使用pch文件,定义混淆的宏定义
#ifndef PrefixHeader_pch
#define PrefixHeader_pch
#define ModelProtocol DXPdllsdsc
#define titleString ddsdlls
#define subTitleString sccdlsd
#define testTitle dsdhowe
#define subTitles dsdsdfd
#endif /* PrefixHeader_pch */
编译后,使用class-dump重新生成头文件
查看model代码
#import "NSObject.h"
#import "DXPdllsdsc.h"
@class NSString;
@interface Model : NSObject <DXPdllsdsc>
{
NSString *_ddsdlls;
NSString *_sccdlsd;
}
@property(copy, nonatomic) NSString *sccdlsd; // @synthesize sccdlsd=_sccdlsd;
@property(copy, nonatomic) NSString *ddsdlls; // @synthesize ddsdlls=_ddsdlls;
- (void).cxx_destruct;
- (double)height;
- (id)identifier;
- (void)dsdhowe:(id)arg1 dsdsdfd:(id)arg2;
// Remaining properties
@property(readonly, copy) NSString *debugDescription;
@property(readonly, copy) NSString *description;
@property(readonly) unsigned long long hash;
@property(readonly) Class superclass;
@end
协议名字DXPdllsdsc
属性
@property(copy, nonatomic) NSString *sccdlsd;
@property(copy, nonatomic) NSString *ddsdlls;
方法名
- (void)dsdhowe:(id)arg1 dsdsdfd:(id)arg2;
把可执行文件放到Hopper查看
关键属性,方法名,协议,也无法查看到
注意点
- 1.不能混淆系统方法
- 2.不能混淆init开头的等初始化方法
- 3.混淆属性时需要额外注意set方法
- 4.如果xib、storyboard中用到了混淆的内容,需要手动修正
- 5.可以考虑把需要混淆的符号都加上前缀,跟系统自带的符号进行区分
- 6.混淆过多可能会被AppStore拒绝上架,需要说明用途
建议:给需要混淆的符号加上了一个特定的前缀,防止替换了其他人或者系统的或者第三方的方法
字符串加密
很多时候,可执行文件中的字符串信息,对破解者来说,非常关键,是破解的捷径之一
为了加大破解、逆向难度,可以考虑对字符串进行加密
字符串的加密技术有很多种,可以根据自己的需要自行制定算法
这里举一个简单的例子,如下
- (void)viewDidLoad {
[super viewDidLoad];
NSString *token = @"abcdefg123456";
NSLog(@"%@",token);
}
使用Hopper查看可执行文件 关键字符串token完全是明文.
对每个字符进行异或(^)处理 需要使用字符串时,对异或(^)过的字符再进行一次异或(^),就可以获得原字符
比如,字符串abcd123
,我们先对每个字符进行按位异或^处理
const char *str2 = "abcd123";
for (int i = 0; i<strlen(str2); i++) {
// {100,103,102,97,52,55,54};
NSLog(@"%d",str2[i]^5);
}
这样拿到的相对应的字符是{100,103,102,97,52,55,54};
现在对上面的C数组字符,封装一个方法,对每一个字符再次异或,那么就得到了原字符abcd123
,方法如下
- (NSString *)getString{
// 最后加上0表示结束
char str[] = {100,103,102,97,52,55,54,0};
NSMutableString *strM = [NSMutableString string];
for (int i = 0; i<strlen(str); i++) {
[strM appendFormat:@"%c",str[i]^5];
}
return strM;
}
第三方的混淆,比如https://github.com/CoderMJLee/MJCodeObfuscation 使用起来也是很方便