iOS安全系列-代码安全

前段时间由于工作需要,接触到了App安全相关的冰山一角,在此整理一下以备后续需要。

分析二进制文件

首先先介绍两款分析二进制源码的工具。

Class-Dump

Class-Dump是一款从OC运行时成的Mach-O文件中导出工程头文件的命令行工具。
下载地址:http://stevenygard.com/projects/class-dump/
下载完成后,打开.dmg文件,将里面的class-dump文件移动至/usr/local/bin,这时就可以使用class-dump命令了。
如导出微信App的所有头文件,运行以下命令:

class-dump -H /Users/mac/Desktop/WeChat.app -o /Users/mac/Desktop/WeChatHeaders 

-H 后面为.app的路径(可以通过Mac OS 自带的“归档实用工具”打开.ipa包,从而查看里面的.app文件)
-o 后面为将要存储的头文件列表的路径

Hopper

Hopper Disassembler,强大的逆向分析工具,你不仅仅可以查看方法列表,还可以查看代码逻辑以及常量。
下载地址:https://www.hopperapp.com
使用的时候将.app文件拖入Hopper,Hopper就自动分析了。

代码混淆

混淆分许多思路,比如:
1)花代码花指令,即随意往程序中加入迷惑人的代码指令
2)易读字符替换
最简单的方法就是使用#define来替换易读的字符串,原理可以参考念茜的博客.
但是使用念茜女神的方法需要自己把需要替换的字符串手动的写入单独的文件,工作量太大,这里推荐一个比较好用的OC工程代码混淆工具:codeobscure,原理是通过运行ruby脚本来遍历工程中的属性、方法、类名进行混淆,亲测可用。
使用前请先阅读codeobscure的作者对他的介绍:codeobscure使用文档,这儿介绍一下我自己使用codeobscure的过程中踩过的坑:

  • 三方库、以及其他不需要混淆的文件注意分文件夹管理,以备后续通过配置忽略路径来忽略这些不需要混淆的文件。
  • 由于codeobscure不会混淆静态字符串如@"text",因此以下几类不能混淆:
    • 与网络请求相关的模型类的属性,并且您使用了MJExtension等直接将json对象转换为模型的三方库。
    • 使用了NSClassFromString(@"classNameA")方法将静态字符串转换为Class的该类的类名不能混淆。
    • 被键值观察的属性不能被混淆, 如以下canExchange这个属性就不能被混淆:
[self.viewModel addObserver:self forKeyPath:@"canExchange" options:NSKeyValueObservingOptionNew context:nil];

阻止动态调试

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

Xcode 中的编译器发展历程:GCC -> LLVM, 调试器的发展历程: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]));
    }
}

模拟器检测

OC提供了宏TARGET_OS_SIMULATOR来检查你的App是否运行在模拟器环境。

 if (TARGET_OS_SIMULATOR) {
        // 模拟器
    } else {
        // 非模拟器
    }

越狱检测

  • 判断设备是否安装了越狱常用工具:
    一般安装了越狱工具的设备都会存在以下文件:
    /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;
}

防止二次打包

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();//退出应用
    }
}

可以在多个重要的地方加入此代码,防止第三方攻击人员发现某一处检测代码后绕过检测。

参考

https://www.splinter.com.au/2014/09/16/storing-secret-keys/
https://www.jianshu.com/p/91aa49c45677
代码签名探析

猜你喜欢

转载自blog.csdn.net/u011656331/article/details/81120420
今日推荐