对 iOS app 进行安全加固

总所周知,运行在越狱设备上的 iOS app,非常容易遭到破解分析,这里我列举一些可以加大破解难度的方法,希望有所帮助。

一些实用手段

防止 tweak 依附

通常来说,我们要分析一个 app,最开始一般是砸壳,

$ DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /path/to/XXX.app/XXX

然后将解密之后的二进制文件扔给类似 hopper 这样的反编译器处理。直接将没有砸壳的二进制文件扔个 hopper 反编译出来的内容是无法阅读的(被苹果加密了)。所以说砸壳是破解分析 app 的第一步。对于这一步的防范,有两种方式。

  1. 限制二进制文件头内的段
    通过在 Xcode 里面工程配置 build setting 选项中将

    -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
    

    添加到 "Other Linker Flags"(注意这里我在项目中碰到了一个 问题,在 iPod touch iOS 9.3 的设备上,使用了 swift 的项目会导致莫名奇妙的 swift 标准库无法找到,而在 iOS 10 的设备上没有这个问题。之前并没有以为是因为添加了这个的原因,直到网上搜了所有解决方案,比如这个 SO Post 都没有效果的时候,我才发现是这个设置的原因)

  2. setuid 和 setgid (Apple 不接受调用这两个函数的 app,因为它可以通过查看符号表来判断您的二进制运行文件是否包含这两个函数)

具体原理可以查看参考资料1,2

检测越狱设备上是否有针对性 tweak

一般来说在越狱手机上,我们会使用 TheOS 创建 tweak 类型的工程。然后针对我们要分析的类,使用提供的 logify.pl 命令生成的 mk 文件来打印该类所有方法的入参和出参。这对分析 app 的运行方式有很大的帮助。当然,我们也可以自己创建某个类的 mk,来 hook 某个函数,让它以我们想要的方式运行,比如说对于一些做了证书绑定的 app,如果它用的框架是 AFNetWorking 的话,那么我们可以创建一个 mk 文件,hook AFSecurityPolicy 类的下列方法:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain

让这个方法永远返回 YES,那么大多数的应用所做的证书绑定也就失效了。用过 TheOS 的 tweak 模版的话,你会发现这种方式相当简单快速。

对于这一步的防范,可以在工程的 main 函数里面加入一层判断,首先读取 /Library/MobileSubstrate/DynamicLibraries 下所有的 plist 文件的内容,查看是否某个 plist 含有你的 app 的 bundle id,是的话,可以判定有人想利用 tweak 攻击你的 app,这时候你可以采取比如说将 app 给 crash 掉,或者限制某些功能等方式来应对。

具体原理可以查看参考资料4,简单来说,就是 MobileSubstrate 在 app 加载到内存的时候会先去检查 /Library/MobileSubstrate/DynamicLibraries 下面是否有需要加载的 tweak,有的话就加载,怎么判断有没有?就是根据 plist 里面的 bundle ID 判断的。

代码参考如下:

static __inline__ __attribute__((always_inline)) int anti_tweak()
{
    uint8_t lmb[] = {'S', 'u', 'b', 's', 't', 'r', 'a', 't', 'e', '/', 'D', 'y', 'n', 'a', 'm', 'i', 'c', 0, };
    NSString *dir = [NSString stringWithFormat:@"/%@/%@%s%@", @"Library", @"Mobile", lmb, @"Libraries"];
    NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:nil];
    NSArray *plistFiles = [dirFiles filteredArrayUsingPredicate:
                           [NSPredicate predicateWithFormat:
                            [NSString stringWithFormat:@"%@ %@%@ '.%@%@'",@"self", @"EN", @"DSWITH", @"pli", @"st"]]];
    int cnt = 0;
    for (NSString *file in plistFiles) {
        NSString *filePath = [dir stringByAppendingPathComponent:file];
        NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
        if (fileContent && [fileContent rangeOfString:[[NSBundle mainBundle] bundleIdentifier]].location != NSNotFound) {
            cnt ++;
        }
    }
    // 返回有针对本 app 的 tweak 数量,为 0 说明没有
    return cnt;
}

防 http 抓包

通常破解一个 app,我们会抓包。这样的话,我们的 app 所有接口,接口数据都会暴露在逆向人员的眼皮底下。这时候,我们可以限制 http 抓包。方式很简单,就是将 NSURLSessionConfiguration 的 connection​Proxy​Dictionary 设置成空的字典,因为这个属性就是用来控制会话的可用代理的。可用参见官方文档,也就是参考资料5。下面是对于 AFNetWorking 的使用方法:

// 继承 AFHTTPSessionManager,重写下列方法
- (instancetype)initWithServerHost:(PDLServerHost*)serverHost {
#ifdef DEBUG
    // debug 版本的包仍然能够正常抓包
    self = [super initWithBaseURL:serverHost.baseURL];
#else
// 由于使用 ephemeralSessionConfiguration session 发起的请求不带 cookie 和使用缓存等
    NSURLSessionConfiguration *conf = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    conf.connectionProxyDictionary = @{};
    self = [super initWithBaseURL:serverHost.baseURL sessionConfiguration:conf];
#endif
    return self;
}

但是由于 OC 方法很容易被 hook,避免抓包是不可能的,所以,个人认为最好的方式是对请求参数进行加密(最好是非对称加密,比如 RSA)

混淆(或者加密)硬编码的明文字符串

对于被砸壳的二进制文件,逆向分析人员分析代码有一条重要线索,也就是被硬编码的明文字符串。比如说,你的 app 被人抓包了,某些数据请求接口也被人发现了,那么很简单,逆向人员可以直接拷贝特征比较明显的字符串到 hopper 中搜索,通过查看该字符串被引用的地方,可以很快的找到相应的逻辑代码。

对于这一步的防范,需要做的就是对硬编码的明文进行加密或混淆。 有个开源代码可以用,UAObfuscatedString,但是这个开源混淆代码写出来的字符串是相当长的(也就是麻烦),同时不支持加密。最近我写了一个工具,可以在编译期间加密所有代码中的明文字符串,在 app 运行的时候解密字符串。这个工具的特点如下:

  1. 简单,开发人员可以硬编码明文字符串,所有的加密会在编译开始时自动处理
  2. 可以自定义加密或者混淆方式,(为了不影响 app 运行效率,需要提供一个简单快速的加密或混淆方式)提高解密难度

项目地址 MixPlainText

使用 Swift 开发

Swift 是目前比较新的开发 iOS 语言,由于 Swift 目前还不是很稳定,越狱开源社区对这个的支持也不是很即时,比如说 class-dump 工具目前就不支持含有 Swift 的二进制文件。 TheOS 也是最近才开始支持 Swift,但是还没有加到主分支上(可以参见 Features)。所以目前来看,至少 Swift 可能比纯 OC 的工程要安全一点点。当然,等 Swift 日趋稳定,以及越狱开源社区的逐渐支持,这一点优势可能就不明显了。

使用静态内连 C 函数

由于 OC 语言的动态性,导致 OC 的代码是最容易被破解分析的。在安全性上,更推荐使用 C 语言写成的函数。但是 C 语言的函数也是可以被 hook 的,主要有3种方式:

  1. 使用 Facebook 开源的 fishhook

  2. 使用 MobileSubstrate 提供的 hook C 语言函数的方法

    void MSHookFunction(void* function, void* replacement, void** p_original);
    
  3. 使用 mach_override,关于 mach_override 和 fishhook 的区别请看 mach_override 和 fishhook 区别

由于上面这三种方式可以 hook C 函数。要想不被 hook 解决方法是使用静态内联函数,这样的话需要被 hook 的函数没有统一的入口,逆向人员想要破解只能去理解该函数的逻辑。

使用 block

严格来说使用 block 并不能很大程度提高安全性,因为逆向人员只要找到使用该 block 的方法,一般来说在其附近就会有 block 内代码的逻辑。具体查找方法和原理可以看参考资料6。

但是个人认为使用 block 的安全性是比直接使用 oc 方法是要高的。在我的逆向分析 app 的经验中,对于使用了 block 的方法,目前我还不知道到怎么 hook (有知道的话,可以在 blog 上提个 issue 告诉我,先谢过 🙏),同时对于含有嵌套的 block 或者是作为参数传递的 block,处理起来就更加复杂了。所以,如果能将内敛 C 函数,嵌套 block , block 类型参数组合起来的话,安全性应该是会有一定提升。

2018年3月2日更新:block 的 hook 操作比较麻烦,但是也是可以的。请参见一下资料:

  1. MABlockClosure
  2. Hook Objective-C Block with Libffi
  3. Block 实现原理

代码混淆

代码混淆的方式有几种:

  1. 添加无用又不影响逻辑的代码片段,迷糊逆向人员
  2. 对关键的类、方法,命名成与真实意图无关的名称

对于第二种,目前有一些自动化工具,比如念茜提到的一个工具参见参考资料7。

个人认为最好的一个加密混淆工具是 ios-class-guard,不过目前这个项目已经停止维护了。但是这种方式的混淆我觉得才是最终极的方案。

其他方法

比如 ptrace 反调试等(不过据说已经可以很容易被绕过)

// see http://iphonedevwiki.net/index.php/Crack_prevention for detail
static force_inline void disable_gdb() {
#ifndef DEBUG
    typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#ifndef PT_DENY_ATTACH
#define PT_DENY_ATTACH 31
#endif
    // this trick can be worked around,
    // see http://stackoverflow.com/questions/7034321/implementing-the-pt-deny-attach-anti-piracy-code
    void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
    ptrace_ptr_t ptrace_ptr = dlsym(handle, [@"".p.t.r.a.c.e UTF8String]);
    ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
    dlclose(handle);
#endif
}

注意事项

通过了解 OC 的运行时特性和 mach-o 二进制文件的结构,借助现有的工具,你会发现 hook 方法 是很简单就能完成的。虽然上面我提到了一些提高安全性的几个方案,但是,所有这些方式只是增加了逆向人员的逆向难度,并不能让 app 变的坚不可摧。不过采取一定的措施肯定比什么措施都不采取来的安全,你说呢?

参考资料

  1. 防止 tweak 依附
  2. Blocking Code Injection on iOS and OS X
  3. Crack prevention
  4. Cydia Substrate
  5. connection​Proxy​Dictionary
  6. iOS符号表恢复&逆向支付宝
  7. iOS安全攻防(二十三):Objective-C代码混淆
  8. iOS安全攻防(二十四):敏感逻辑的保护方案(1)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容