- int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
- int retval = prepend_rebindings(rebindings, rebindings_nel);
- if (retval < 0) {
- return retval;
- }
- // If this was the first call, register callback for image additions (which is also invoked for
- // existing images, otherwise, just run on existing images
- if (!rebindings_head->next) {
- _dyld_register_func_for_add_image(rebind_symbols_for_image);
- } else {
- uint32_t c = _dyld_image_count();
- for (uint32_t i = 0; i < c; i++) {
- rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
- }
- }
- return retval;
- }
- #import <dlfcn.h>
- #import <UIKit/UIKit.h>
- #import "AppDelegate.h"
- #import "fishhook.h"
- static int (*orig_close)(int);
- static int (*orig_open)(const charchar *, int, ...);
- void save_original_symbols() {
- orig_close = dlsym(RTLD_DEFAULT, "close");
- orig_open = dlsym(RTLD_DEFAULT, "open");
- }
- int my_close(int fd) {
- printf("Calling real close(%d)\n", fd);
- return orig_close(fd);
- }
- int my_open(const charchar *path, int oflag, ...) {
- va_list ap = {0};
- mode_t mode = 0;
- if ((oflag & O_CREAT) != 0) {
- // mode only applies to O_CREAT
- va_start(ap, oflag);
- mode = va_arg(ap, int);
- va_end(ap);
- printf("Calling real open('%s', %d, %d)\n", path, oflag, mode);
- return orig_open(path, oflag, mode);
- } else {
- printf("Calling real open('%s', %d)\n", path, oflag);
- return orig_open(path, oflag, mode);
- }
- }
- int main(int argc, charchar * argv[])
- {
- @autoreleasepool {
- save_original_symbols();
- //fishhook用法
- rebind_symbols((struct rebinding[2]){{"close", my_close}, {"open", my_open}}, 2);
- // Open our own binary and print out first 4 bytes (which is the same
- // for all Mach-O binaries on a given architecture)
- int fd = open(argv[0], O_RDONLY);
- uint32_t magic_number = 0;
- read(fd, &magic_number, 4);
- printf("Mach-O Magic Number: %x \n", magic_number);
- close(fd);
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
- }
- }
- rebind_symbols((struct rebinding[2]){{"close", my_close}, {"open", my_open}}, 2);
不得不说,facebook忒NB。
iOS安全攻防(十八):数据保护API
开篇先扯几句题外话,许多朋友都问我怎么不写防啊,我确实有点犹豫。
hackers总是想象如果自己是开发者会怎么写,然后才能找到入手点。同理,开发者们也要想象自己是hackers会怎么做,才能采取相应的防御措施。然后,就是一场递归的博弈。
拿越狱检测这件事来说,起初大家只需判断有无安装Cydia就好了,hackers们说好,那我就不安装Cydia也可以动手脚。开发者们又说,那你一定得用的上MobileSubstrate,bash,ssh吧,我去检测手机有没有安装这些工具。可是又有什么用呢?你判断什么我绕过去什么。
当class-dump大肆流行,函数符号都被暴露,开发者想尽办法藏起自己的敏感函数代码。hackers们也知道class-dump的死穴在哪里,于是新的检索办法油然而生。也就说,当一个防御手段成为流行,它就不会再是个让hackers大骂“真特么费劲”的防御手段了。比如之前介绍的一个小技巧:内存数据擦除 ,hackers知道开发者都去擦数据了,那我hook memset在你擦之前去读就好了。开发者说:我直接写硬盘上然后删除!hackers说:难道你没听说过文件恢复?
OK,贫的有点多了,本文介绍一下防御相关的话题————iOS的数据保护API。
数据保护API
文件系统中的文件、keychain中的项,都是加密存储的。当用户解锁设备后,系统通过UDID密钥和用户设定的密码生成一个用于解密的密码密钥,存放在内存中,直到设备再次被锁,开发者可以通过Data Protection API 来设定文件系统中的文件、keychain中的项应该何时被解密。
1)文件保护
- /* 为filePath文件设置保护等级 */
- NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
- forKey:NSFileProtectionKey];
- [[NSFileManager defaultManager] setAttributes:attributes
- ofItemAtPath:filePath
- error:nil];
- //文件保护等级属性列表
- NSFileProtectionNone //文件未受保护,随时可以访问 (Default)
- NSFileProtectionComplete //文件受到保护,而且只有在设备未被锁定时才可访问
- NSFileProtectionCompleteUntilFirstUserAuthentication //文件收到保护,直到设备启动且用户第一次输入密码
- NSFileProtectionCompleteUnlessOpen //文件受到保护,而且只有在设备未被锁定时才可打开,不过即便在设备被锁定时,已经打开的文件还是可以继续使用和写入
2)keychain项保护
- /* 设置keychain项保护等级 */
- NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
- (__bridge id)kSecAttrGeneric:@"MyItem",
- (__bridge id)kSecAttrAccount:@"username",
- (__bridge id)kSecValueData:@"password",
- (__bridge id)kSecAttrService:[NSBundle mainBundle].bundleIdentifier,
- (__bridge id)kSecAttrLabel:@"",
- (__bridge id)kSecAttrDescription:@"",
- (__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleWhenUnlocked};
- OSStatus result = SecItemAdd((__bridge CFDictionaryRef)(query), NULL);
应用实例
把一段信息infoStrng字符串写进文件,然后通过Data Protection API设置保护。
- NSString *documentsPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
- NSString *filePath = [documentsPath stringByAppendingPathComponent:@"DataProtect"];
- [infoString writeToFile:filePath
- atomically:YES
- encoding:NSUTF8StringEncoding
- error:nil];
- NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
- forKey:NSFileProtectionKey];
- [[NSFileManager defaultManager] setAttributes:attributes
- ofItemAtPath:filePath
- error:nil];
设备锁屏(带密码保护)后,即使是越狱机,在root权限下cat读取那个文件信息也会被拒绝。
iOS安全攻防(十九):基于脚本实现动态库注入
MobileSubstrate可以帮助我们加载自己的动态库,于是开发者们谨慎的采取了对MobileSubstrate的检索和防御措施。
那么,除了依靠MobileSubstrate帮忙注入dylib,还有别的攻击入口吗?
理理思路,条件、目的很明确:
1)必须在应用程序启动之前,把dylib的环境变量配置好
2)dylib的位置必须能被应用程序放问到
3)最后再启动应用程序
再点击应用程序图标-->程序启动这个过程中,在我们看来程序是被动执行的。为了让特定功能的脚本被执行,我们可以把脚本改成应用程序二进制的名字伪装成应用程序,让系统调用启动。在脚本中,配置好dylib,然后再手动启动真的应用程序,假装什么也没发生,挥一挥衣袖不带走一片云彩~
将真的支付宝程序改名为oriPortal:
- mv Portal oriPortal
将待执行的脚本改名为支付宝:
- mv Portal.sh Portal
脚本代码:
- #!/bin/bash
- #得到第一个参数
- C=$0
- #第一个参数是二进制的绝对路径 比如 :
- #/private/var/mobile/Applications/4763A8A5-2E1D-4DC2-8376-6CB7A8B98728/Portal.app/
- #截取最后一个 / 之前的内容
- C=${C%/*}
- #库和二进制放在一起
- export DYLD_INSERT_LIBRARIES=${C:-.}/wq.dylib
- #执行原来APP $@ 别忘了把原来的参数保留
- exec "${C:-.}"/oriPortal "$@"
结果不尽人意,失败了……
错误信息如下:
在打开某个加密信息时出了错误,大概猜一下应该是类似加密签名校验的步骤,但是我们无法去了解其中详细的操作到底是什么样的,没关系,那么就把原始的可执行文件环境全部给他造出来,因为检验文件属性肯定不会带着路径信息的。
备份一份Portal.app目录Portal_ori.app,修改脚本为:
- #!/bin/bash
- C=$0
- C=${C%/*}
- export DYLD_INSERT_LIBRARIES=${C:-.}/wq.dylib
- exec "${C:-.}"/../Portal_ori.app/Portal "$@"
运行支付宝app验证一下,
好消息是,在iOS6上,成功加载了动态库wq.dylib
坏消息是,在iOS7上,失败了,错误信息如下:
应该是因为iOS7的沙盒机制升了级,把我们这套小把戏拦在门外了……
那又怎么样,面包总会有的~