iOS应用加固

完整性校验

通过检测SignerIdentity判断是Mach-O文件否被篡改

原理是:SignerIdentity的值在info.plist中是不存在的,开发者不会加上去,苹果也不会,只是当ipa包被反编译后篡改文件再次打包,需要伪造SignerIdentity。

    NSBundle *bundle = [NSBundle mainBundle];
    NSDictionary *info = [bundle infoDictionary];
    if ([info objectForKey:@"SignerIdentity"] != nil)
    {
        return YES;
    }
    return NO;

参考:https://www.jianshu.com/p/91aa49c45677

越狱检测

参照念茜大神的方法:iOS安全攻防(二十):越狱检测的攻与防,很详细的讲述了检测的办法,不过经过测试,发现有的方法在未越狱的设备上也会检测成越狱,可以在使用的时候过滤掉这些方法。

双击home键后app缩略视图模糊处理

有时候双击home键,app展示启动app记录,会有可能暴露敏感信息,可以在app退至后台时做一个模糊处理,然后进入前台后移除模糊效果。
实现如下:

@interface AppDelegate ()
@property(nonatomic,strong)UIVisualEffectView *effectView;
@end

@implementation AppDelegate

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [self.window addSubview:self.effectView];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [self.effectView removeFromSuperview];
}

-(UIVisualEffectView *)effectView
{
    if (!_effectView)
    {
        UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
        _effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
        _effectView.frame = self.window.bounds;
    }
    return _effectView;
}
...
@end

方法名混淆

也是参照念茜大神的方法:iOS安全攻防(二十三):Objective-C代码混淆.

明文字符串混淆

参考这篇文章:iOS字符串硬编码混淆.

可能作者的步骤描述的不是很清楚,我在这里简单描述一下:
1.在项目的.pch文件中,添加如下代码

#include "GolobalCFile.h"


//字符串混淆加密 和 解密的宏开关
//#define ggh_confusion
#ifdef ggh_confusion
#define confusion_NSSTRING(string) [NSString stringWithUTF8String:decryptConstString(string)]
#define confusion_CSTRING(string) decryptConstString(string)
#else
#define confusion_NSSTRING(string) @string
#define confusion_CSTRING(string) string
#endif

其中decryptConstString定义在c文件GolobalCFile中

/*
 *  字符串混淆解密函数,将char[] 形式字符数组和 aa异或运算揭秘
 *  如果没有经过混淆,请关闭宏开关
 */
extern char* decryptConstString(char* string)
{
    char* origin_string = string;
    while(*string) {
        *string ^= 0xAA;
        string++;
    }
    return origin_string;
}

2.打开终端Terminal,cd到你的工程根目录;
3.执行如下混淆脚本(执行之前将脚本里面的工程名字改为你的工程名字):

python .../confusion.py

将如下脚本写在一个文件里confusion.py。在终端执行即可。

#!/usr/bin/env python
# encoding=utf8
# -*- coding: utf-8 -*-
# 本脚本用于对源代码中的字符串进行加密
# 替换所有字符串常量为加密的char数组,形式((char[]){1, 2, 3, 0})

import importlib
import os
import re
import sys


# 替换字符串为((char[]){1, 2, 3, 0})的形式,同时让每个字节与0xAA异或进行加密
def replace(match):
    string = match.group(2) + '\x00'
    replaced_string = '((char []) {' + ', '.join(["%i" % ((ord© ^ 0xAA) if c != '\0' else 0) for c in list(string)]) + '})'
    return match.group(1) + replaced_string + match.group(3)


# 修改源代码,加入字符串加密的函数
def obfuscate(file):
    with open(file, 'r') as f:
        code = f.read()
        f.close()
        code = re.sub(r'(confusion_NSSTRING\(|confusion_CSTRING\()"(.*?)"(\))', replace, code)
        code = re.sub(r'//#define ggh_confusion', '#define ggh_confusion', code)
        with open(file, 'w') as f:
            f.write(code)
            f.close()


#读取源码路径下的所有.h和.m 文件
def openSrcFile(path):
    print("开始处理路径: "+ path +"  下的所有.h和.m文件")
    # this folder is custom
    for parent,dirnames,filenames in os.walk(path):
        #case 1:
        #        for dirname in dirnames:
        #            print((" parent folder is:" + parent).encode('utf-8'))
        #            print((" dirname is:" + dirname).encode('utf-8'))
        #case 2
        for filename in filenames:
            extendedName = os.path.splitext(os.path.join(parent,filename))
            if (extendedName[1] == '.h' or extendedName[1] == '.m'):
                print("处理源代码文件: "+ os.path.join(parent,filename))
                obfuscate(os.path.join(parent,filename))


#源码路径
srcPath = '../StringDecodeDemo'

if __name__ == '__main__':
    print("本脚本用于对源代码中被标记的字符串进行加密")
    
    if len(srcPath) > 0:
        openSrcFile(srcPath)
    else:
        print("请输入正确的源代码路径")
        sys.exit()

执行完成后查看你的代码,会发现用confusion_NSSTRING和confusion_CSTRING 写的明文字符串都被编码。这样就达到了混淆的效果。

4.由于代码被混淆不利于以后项目迭代,所以需要解码。方法同编码,在终端执行
如下脚本:

#!/usr/bin/env python
# encoding=utf8
# -*- coding: utf-8 -*-
# 本脚本用于对源代码中的字符串进行解密
# 替换所有加密的char数组为字符串常量,""

import importlib
import os
import re
import sys


# 替换((char[]){1, 2, 3, 0})的形式为字符串,同时让每个数组值与0xAA异或进行解密
def replace(match):
    string = match.group(2)
    decodeConfusion_string = ""
    for numberStr in list(string.split(',')):
        if int(numberStr) != 0:
            decodeConfusion_string = decodeConfusion_string + "%c" % (int(numberStr) ^ 0xAA)

# replaced_string = '\"' + "".join(["%c" % ((int© ^ 0xAA) if int© != 0 else '\0') for c in string.split(',')]) + '\"'
    replaced_string = '\"' + decodeConfusion_string + '\"'
    print("replaced_string = " + replaced_string)
    
    return match.group(1) + replaced_string + match.group(3)


# 修改源代码,加入字符串加密的函数
def obfuscate(file):
    with open(file, 'r') as f:
        code = f.read()
        f.close()
        code = re.sub(r'(confusion_NSSTRING\(|confusion_CSTRING\()\(\(char \[\]\) \{(.*?)\}\)(\))', replace, code)
        code = re.sub(r'[/]*#define ggh_confusion', '//#define ggh_confusion', code)
        with open(file, 'w') as f:
            f.write(code)
            f.close()


#读取源码路径下的所有.h和.m 文件
def openSrcFile(path):
    print("开始处理路径: "+ path +"  下的所有.h和.m文件")
    # this folder is custom
    for parent,dirnames,filenames in os.walk(path):
        #case 1:
        #        for dirname in dirnames:
        #            print((" parent folder is:" + parent).encode('utf-8'))
        #            print((" dirname is:" + dirname).encode('utf-8'))
        #case 2
        for filename in filenames:
            extendedName = os.path.splitext(os.path.join(parent,filename))
            #读取所有.h和.m 的源文件
            if (extendedName[1] == '.h' or extendedName[1] == '.m'):
                print("处理代码文件:"+ os.path.join(parent,filename))
                obfuscate(os.path.join(parent,filename))


#源码路径
srcPath = '../StringDecodeDemo'
if __name__ == '__main__':
    print("字符串解混淆脚本,将被标记过的char数组转为字符串,并和0xAA异或。还原代码")
    if len(srcPath) > 0:
        openSrcFile(srcPath)
    else:
        print("请输入正确的源代码路径!")
        sys.exit()

即可解码明文。

为了不那么麻烦,可以打包前将工程拷贝一份,这样就可以只需要编码,不用解码。

以上脚本和代码均在文末Demo中。

反编译

build生成的app

在混淆了函数名以后,当然要检验一下成果了,这里就需要反编译我们的app了,有个很方便的工具Class-dump,这里有篇文章详细的描述了安装和使用方法:Objective-C Class-dump 安装和使用方法(原创).

另外还有一个反编译、反汇编和调试神器:Hopper。可以查看源码。

疑问

1.执行脚本文件时报如下错误

confusion.py: Permission denied

解决方法:
1、打开终端。
2、cd到目标文件夹。
3、输入 chmod 755 你的文件名.sh。

Demo

本文demo:iOS_CodeEncrypt

推荐阅读更多精彩内容

  • 前提 众所周知,iOS系统安全性非常高,很少出现漏洞,几乎不会中毒。大家认为苹果系统的封闭性会使iOS APP安全...
    小枫123阅读 741评论 0 10
  • 前提 众所周知,iOS系统安全性非常高,很少出现漏洞,几乎不会中毒。大家认为苹果系统的封闭性会使iOS APP安全...
    在_宥阅读 20,238评论 12 54
  • 再道欺凌 1 中关村二小的校园欺凌,被炒的沸沸扬扬,各个公众号各抒己见,可谓百家争鸣。 胡子宏先生说:“他打你,你...
    lovingyourself阅读 111评论 0 1