代码混淆
对于iOS应用的安全保护,代码混淆是常用手段。除了手动实现,开发者也可以借助专业工具如IpaGuard来简化混淆流程,它支持代码混淆、资源文件混淆等多种功能,且无需源码即可操作,提升开发效率。
一、添加脚本文件sh与要混淆的名称list文件
1、打开终端,转入cd+项目目录名
2、在终端输入:touch testCodeConfush.sh(名称可随意取)
3、在终端输入:touch testFuns.list(名称可随意取)
4、修改testCodeConfush.sh权限,在终端输入:chmod a+x +(testCodeConfush.sh所在目录名)
注意:第4点不操作,可能会编辑不过,会报”Permission denied“的错误
二、编辑脚本文件(testCodeConfush.sh)
将以下内容输入到testCodeConfush.sh文件中:
#!/usr/bin/env bash
TABLENAME=symbols
SYMBOL_DB_FILE="symbols"
#此处为混淆名称列表
STRING_SYMBOL_FILE="testFuns.list"
#此处为自动导出的头文件,导出目录与.pch文件同级目录,注意此路径也要与.pch文件一致,否则编辑不过
HEAD_FILE="$PROJECT_DIR/$PROJECT_NAME/Configuration/testNameUpsetDefine.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 testNameUpsetDefine_h
// #define testNameUpsetDefine_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、配置脚本:项目-TARGETS-BUILD PHASES 点击左上角“+“加号,添加 New Run Script Phase - Run Script 输入:$PROJECT_DIR/testCodeConfush.sh
注意:添加后,先编辑运行下,生成testNameUpsetDefine.h文件
2、添加pch文件并在pch中添加头文件testNameUpsetDefine.h(与脚本输出的文件同名) 修改Build Setting 中的属性Precompile Prefix Header改为 YES 注意:如果报错,可能是未生成 testNameUpsetDefine.h,可以在.pch同级目录下查看下
3、添加pch文件配置:在Build Settings - Prefix Header 输入:$(SRCROOT)/testConfusionDemo/Configuration/ConfusionPrefix.pch
四、自动导出类名与函数名,添加到混淆文件中(testFuns.list)
直接上代码:
/**
* 说明:获取文件夹下所有文件名称及.h文件中的函数名
* @param strRootPath : 要检索的文件夹全路径
*
* 注意:因此方法为递归方法,将数据添加到外部定义的字典中
*/
- (void)PE_GetFileAndFunNameWithFolder:(NSString *)strRootPath {
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isExist = [fileManager fileExistsAtPath:strRootPath];
if ( isExist ) {
NSEnumerator *childFileEnumerator = [[fileManager subpathsAtPath:strRootPath] objectEnumerator];
NSString *strFileName = @"";
while ( (strFileName = [childFileEnumerator nextObject] ) != nil ) {
NSString *strFilePath = [strRootPath stringByAppendingPathComponent:strFileName];
BOOL bDeir;
if ( [fileManager fileExistsAtPath:strFilePath isDirectory:&bDeir] ) {
if ( bDeir ) {
[self PE_GetFileAndFunNameWithFolder:strFilePath];
} else {
NSArray *arrDatas = [strFileName componentsSeparatedByString:@"/"];
NSString *strClassName = [arrDatas lastObject];
if ( [strClassName rangeOfString:@".h"].length > 0 ) {
NSString *strFunData = [self PE_GetContentWithFilePath:strFilePath];
[self PE_GetFunNameWithStrData:strFunData WithHeaderName:strClassName];
}
if ( [strClassName rangeOfString:@".m"].length > 0 ) {
NSString *strTempName = [strClassName stringByReplacingOccurrencesOfString:@".m" withString:@""];
NSLog(@"文件类名:%@", strTempName);
[self.pLock lock];
[self.muDictDatas setValue:strFileName forKey:strTempName];
[self.pLock unlock];
}
}
} else {
NSLog(@"错误");
}
}
} else {
NSLog(@"错误");
}
}
/**
* 说明:获取指定文件数据中的函数名(成员函数、类函数、内联函数)
* @param strContentData : 文件内容数据
* @param strFFName : 文件类名
*/
- (void)PE_GetFunNameWithStrData:(NSString *)strContentData WithHeaderName:(NSString *)strFFName {
// 文件数据分行处理
[strContentData enumerateLinesUsingBlock:^(NSString * _Nonnull line, BOOL * _Nonnull stop) {
NSInteger nType = [self PE_GetFunTypeWith:line];
if ( 1 == nType || 2 == nType ) {
NSArray *arrKuoHao = [line componentsSeparatedByString:@")"];
if ( [arrKuoHao count] > 1 ) {
NSString *strFunName = [arrKuoHao objectAtIndex:1];
// 如果有参数,则取参数前面函数名
NSArray *arrMaoHao = [strFunName componentsSeparatedByString:@":"];
strFunName = [arrMaoHao firstObject];
// 去除分号
NSArray *arrFenHao = [strFunName componentsSeparatedByString:@";"];
strFunName = [arrFenHao firstObject];
// 避免头文件中定义有内联函数
NSArray *arrDaKuoHao = [strFunName componentsSeparatedByString:@"{"];
strFunName = [arrDaKuoHao firstObject];
// 排除函数中有返回代理
if ( [strFunName rangeOfString:@"("].length <= 0 &&
[strFunName rangeOfString:@" "].length <= 0 ) {
if ( 1 == nType ) {
NSLog(@"%@成员函数名:%@", strFFName, strFunName);
} else if ( 2 == nType ) {
NSLog(@"%@ 类函数名:%@", strFFName, strFunName);
}
[self.pLock lock];
[self.muDictDatas setValue:strFFName forKey:strFunName];
[self.pLock unlock];
}
}
}
}];
}
/**
* 说明:根据内容判断内容为成员函数或类函数或非函数
* @param strContent : 要判断的是否为函数的内容数据
* 返回值:0-非函数 1-成员函数 2-类函数
*/
- (NSInteger)PE_GetFunTypeWith:(NSString *)strContent {
if ( [strContent length] > 9 ) {
NSString *strFirst = [strContent substringToIndex:3];
if ( [strFirst isEqualToString:@"- ("] ) {
return 1;
} else if ( [strFirst isEqualToString:@"+ ("] ) {
return 2;
}
strFirst = [strContent substringToIndex:2];
if ( [strFirst isEqualToString:@"-("] ) {
return 1;
} else if ( [strFirst isEqualToString:@"+("] ) {
return 2;
}
}
return 0;
}
/**
* 说明:根据文件全路径名获取文件内容数据
* @param strFilePath : 文件名全路径
* 返回值:返回文件内容
*/
- (NSString *)PE_GetContentWithFilePath:(NSString *)strFilePath {
return [NSString stringWithContentsOfFile:strFilePath encoding:NSUTF8StringEncoding error:nil];
}
#pragma mark - getter
- (NSMutableDictionary *)muDictDatas {
if ( _muDictDatas ) {
return _muDictDatas;
}
_muDictDatas = [[NSMutableDictionary alloc] init];
return _muDictDatas;
}
- (NSLock *)pLock {
if ( _pLock ) {
return _pLock;
}
_pLock = [[NSLock alloc] init];
return _pLock;
}
手动实现代码混淆可以增强应用安全性,但过程较为繁琐。对于需要快速、全面混淆的项目,可以考虑使用IpaGuard等专业工具,它提供图形化界面和自动化的混淆方案,支持多种开发平台,无需手动编写脚本即可实现高效的代码保护。