iOS如何做代码加固

虽然公司的项目目前还不算健壮,安全问题对于大部分小公司来说似乎并没什么必要,不过要攻击的话,我有十足的把握,我们是无法承受冲击的。嘿嘿嘿~不过带着一颗入坑iOS的心思,搜集了一下资料后,还是做了一些尝试。

iOS App安全防范总结:

1.防止抓包篡改数据
2.防止反编译
3.阻止动态调试
4.防止二次打包

关键检测:越狱检测

  • OK,下面是正文开始。

1.防止抓包篡改数据

对于抓包,利用神器charles的操作会在另外的文章单独介绍。如果不懂以下为利用charles抓包。
charles抓包教程

若别人真想抓你程序包,该如何防范呢?我目前只能说,let it go ~ let it go~随他抓,随他抓。因为基本上只要想抓取程序访问的数据,基本上是能抓取到的。对于iOS来说,目前我是做了两种操作。

1)判断是否设置了代理
对于抓包,现在的手段基本是设置代理,所以我们可以通过判断是否设置了代理的方式来进行下一步的防范。

#在网络请求前插入这个方法,再根据需求做相应的防范
+ (BOOL)getDelegateStatus
{
    NSDictionary *proxySettings = CFBridgingRelease((__bridge CFTypeRef _Nullable)((__bridge NSDictionary *)CFNetworkCopySystemProxySettings()));
    NSArray *proxies = CFBridgingRelease((__bridge CFTypeRef _Nullable)((__bridge NSArray *)CFNetworkCopyProxiesForURL((__bridge CFURLRef)[NSURL URLWithString:@"http://www.google.com"], (__bridge CFDictionaryRef)proxySettings)));
    NSDictionary *settings = [proxies objectAtIndex:0];
    NSLog(@"host=%@", [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
    NSLog(@"port=%@", [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
    NSLog(@"type=%@", [settings objectForKey:(NSString *)kCFProxyTypeKey]);
    if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"])
    {
        //没有设置代理
        return NO;
        
    } else {
        //设置代理了
        return YES;
    }
}

2)RSA
通过与后台的配合,设置公钥与私钥,对请求数据和返回数据进行加密。这里另外起一篇单独介绍。

2.防止反编译(防止class-dump、hopper反编)

越狱检测

一般能拿到自己ipa包都需要有一台越狱的手机

  • 判断设备是否安装了越狱常用工具:
    一般安装了越狱工具的设备都会存在以下文件:
    /Applications/Cydia.app
    /Library/MobileSubstrate/MobileSubstrate.dylib
    /bin/bash
    /usr/sbin/sshd
    /etc/apt
  • 判断设备上是否存在cydia应用
  • 是否有权限读取系统应用列表
    没有越狱的设备是没有读取所有应用名称的权限
  • 检测当前程序运行的环境变量 DYLD_INSERT_LIBRARIES
    非越狱手机DYLD_INSERT_LIBRARIES获取到的环境变量为NULL。

综上所述,检查设备是否越狱

+ (BOOL)isJailbroken {
    // 检查是否存在越狱常用文件
    NSArray *jailFilePaths = @[@"/Applications/Cydia.app",
                               @"/Library/MobileSubstrate/MobileSubstrate.dylib",
                               @"/bin/bash",
                               @"/usr/sbin/sshd",
                               @"/etc/apt"];
    for (NSString *filePath in jailFilePaths) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
            return YES;
        }
    }

    // 检查是否安装了越狱工具Cydia
    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){
        return YES;
    }

    // 检查是否有权限读取系统应用列表
    if ([[NSFileManager defaultManager] fileExistsAtPath:@"/User/Applications/"]){
        NSArray *applist = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/User/Applications/"
                                                                               error:nil];
        NSLog(@"applist = %@",applist);
        return YES;
    }

    //  检测当前程序运行的环境变量
    char *env = getenv("DYLD_INSERT_LIBRARIES");
    if (env != NULL) {
        return YES;
    }

    return NO;
}

代码混淆

这里生成混淆代码的方法我们通过shell脚本来实现,同时我们需要一个文档来写入我们需要进行混淆的方法名或是变量名。

  • 打开终端,cd到文件所在目录,使用

touch confuse.sh
touch func.list

此时将目录中的.sh和.list文件拖入项目中

  • 写入shell脚本

在项目中找到刚刚拖进来的.sh文件,在confuse.sh中写入脚本

#!/bin/bash

# 这是Shell脚本,如果不懂shell,自行修炼:http://www.runoob.com/linux/linux-shell.html

# 以下使用sqlite3进行增加数据,如果不了解sqlite3命令,自行修炼:http://www.runoob.com/sqlite/sqlite-tutorial.html

#数据表名
TABLENAME="CodeObfuscationOC"

#数据库名
SYMBOL_DB_FILE="CodeObfuscation.db"

#要被替换的方法列表文件
STRING_SYMBOL_FILE="$PROJECT_DIR/ConfusionDemo/func.list"

#被替换后的宏定义在此文件里
HEAD_FILE="$PROJECT_DIR/$PROJECT_NAME/CodeObfuscation.h"

#维护数据库方便日后做bug排查
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
}

#生成随机16位名称
randomString()
{
openssl rand -base64 64 | tr -cd 'a-zA-Z' | head -c 16
}

#删除旧数据库文件
rm -f $SYMBOL_DB_FILE

#删除就宏定义文件
rm -f $HEAD_FILE

#创建数据表
createTable

#touch命令创建空文件,根据指定的路径
touch $HEAD_FILE
echo '#ifndef CodeObfuscation_h
#define CodeObfuscation_h' >> $HEAD_FILE
echo "//confuse string at `date`" >> $HEAD_FILE

#使用cat将方法列表文件里的内容全部读取出来,形成数组,然后逐行读取,并进行替换
cat "$STRING_SYMBOL_FILE" | while read -ra line;
do
if [[ ! -z "$line" ]]
then
random=`randomString`
echo $line $random

#将生成的随机字符串插入到表格中
insertValue $line $random

#将生成的字符串写入到宏定义文件中,变量是$HEAD_FILE
echo "#define $line $random" >> $HEAD_FILE
fi
done
echo "#endif" >> $HEAD_FILE
sqlite3 $SYMBOL_DB_FILE .dump
  • 添加run script命令

640.jpg
  • 然后添加$PROJECT_DIR/ConfusionDemo/confuse.sh

  • 给脚本授权

接下来还是在我们项目的文件夹下,通过终端给我们的脚本赋予最高权限

chmod 777 confuse.sh
  • 添加预编译文件PCH

6401.jpg

然后配置PCH文件

6402.jpg
  • 添加$PROJECT_DIR/ConfusionDemo/PrefixHeader.pch

  • 生成CodeObfuscation.h文件

这时候我们编译一下代码,会发现项目中多出了一个CodeObfuscation.h文件(如果没有,可到项目文件夹中找,我的就是在文件夹里找到的- -,然后拖进项目)。这个文件就是替换方法名的文件,我们在PCH文件中引入他。

  • 在func.list中添加准备替换的方法名

在项目中点开之前拖进来的func.list文件,然后在里面加入自己想要混淆的方法名

//此处方法名为项目中自己编码的方法名,不可混淆系统方法名
viewControllerTestMethodA
viewControllerTestMethodB
viewControllerTestMethodC
viewControllerMethodWithParameter
testString
testArray
testMutDic
  • 结果

编译之后

#ifndef CodeObfuscation_h
#define CodeObfuscation_h

#define viewControllerTestMethodA CTBxmOXAbJYekhnH
#define viewControllerTestMethodB RnPEjnXygFXLdikO
#define viewControllerTestMethodC IzHlDYOpaAFYFTXa
#define viewControllerMethodWithParameter nWqyalBcfoUSRVpc
#define testString MNPoVYdmCcklAnCO
#define testArray kHMRxPlGXGeqekxL
#define testMutDic hphPSODIvbBFSTHX
#endif

看到 CodeObfuscation有这种变化,恭喜你,已经代码混淆成功。即使通过class-dump反编出来的,也只是一堆乱码。

  • 需要注意的几点

    不可以混淆iOS中的系统方法;

    不可以混淆iOS中init等初始化方法;

    不可以混淆xib的文件,会导致找不到对应文件;

    不可以混淆storyboard中用到的类名;

    混淆有风险,有可能会被App Store以2.1大礼包拒掉。

3.阻止动态调试

GDB、LLDB是Xcode内置的动态调试工具。使用GDB、LLDB可以动态的调试你的应用程序(通过下断点、打印等方式,查看参数、返回值、函数调用流程等)。

为了阻止hackers使用调试器 GDB、LLDB来攻击你的App,你可以在main.m文件中插入以下代码:

#import <dlfcn.h>
#import <sys/types.h>

typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif  // !defined(PT_DENY_ATTACH)

void disable_gdb() {
    void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
    ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
    ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
    dlclose(handle);
}

int main(int argc, char *argv[]) {
    // Don't interfere with Xcode debugging sessions.
    #if !(DEBUG) 
        disable_gdb();
    #endif

    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil,
            NSStringFromClass([MyAppDelegate class]));
    }
}

4.防止二次打包

iOS 和 OS X 的应用和框架包含了二进制代码和所需要的资源文件(如:图片、不同的语言文件、XIB/Storyboard文件、profile文件等),在通过开发者私钥签名程序包时,对于可执行文件( Mach-O ),会将签名直接写入到该文件中,而对于其他的资源文件,会统一写到 _CodeSignature 文件下的 CodeResources 文件中,它仅仅是一个 plist 格式文件。

这个列表文件中不光包含了文件和它们的签名的列表,还包含了一系列规则,这些规则决定了哪些资源文件应当被设置签名。伴随 OS X 10.10 DP 5 和 10.9.5 版本的发布,苹果改变了代码签名的格式,也改变了有关资源的规则。如果你使用10.9.5或者更高版本的 codesign 工具,在 CodeResources 文件中会有4个不同区域,其中的 rules 和 files 是为老版本准备的,而 files2 和 rules2 是为新的第二版的代码签名准备的。最主要的区别是在新版本中你无法再将某些资源文件排除在代码签名之外,在过去你是可以的,只要在被设置签名的程序包中添加一个名为 ResourceRules.plist 的文件,这个文件会规定哪些资源文件在检查代码签名是否完好时应该被忽略。但是在新版本的代码签名中,这种做法不再有效。所有的代码文件和资源文件都必须 设置签名,不再可以有例外。在新版本的代码签名规定中,一个程序包中的可执行程序包,例如扩展 (extension),是一个独立的需要设置签名的个体,在检查签名是否完整时应当被单独对待。

有些hacker可能会通过篡改你的程序包(包括资源文件和二进制代码)加入一些广告或则修改你程序的逻辑,然后重新签名打包,由于第三方hacker获取不到签名证书的私钥,因此会替换掉程序包中签名相关的文件embedded.mobileprovision,我们可以直接检查此文件是否被修改,来判断是否被二次打包,如果程序被篡改,则退出程序。
检测embedded.mobileprovision是否被篡改:

// 校验值,可通过上一次打包获取
#define PROVISION_HASH @"w2vnN9zRdwo0Z0Q4amDuwM2DKhc="
static NSDictionary * rootDic=nil;

void checkSignatureMsg()
{
    NSString *newPath=[[NSBundle mainBundle]resourcePath];

    if (!rootDic) {

        rootDic = [[NSDictionary alloc] initWithContentsOfFile:[newPath stringByAppendingString:@"/_CodeSignature/CodeResources"]];
    }

    NSDictionary*fileDic = [rootDic objectForKey:@"files2"];

    NSDictionary *infoDic = [fileDic objectForKey:@"embedded.mobileprovision"];
    NSData *tempData = [infoDic objectForKey:@"hash"];
    NSString *hashStr = [tempData base64EncodedStringWithOptions:0];
    if (![PROVISION_HASH isEqualToString:hashStr]) {
        abort();//退出应用
    }
}

备注:网络请求的url可以加密

 

发帖时间: iOS

发表评论

电子邮件地址不会被公开。 必填项已用*标注