iOS开发中手动实现代码混淆的完整步骤与示例

17 阅读4分钟

代码混淆

对于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等专业工具,它提供图形化界面和自动化的混淆方案,支持多种开发平台,无需手动编写脚本即可实现高效的代码保护。