利用fishhook如何实现iOS攻防注入

我们知道在Windows中实现HOOK,动态注入代码实现修改系统的API行为很容易,但是我们有没有考虑过怎么在IOS开发中利用这些特性呢?很多IOS开发者大多是业务层面的研究技术,很少涉及到底层的攻防,因为一般情况下这些“黑科技”我们也用不上,不过作为一名程序员,其“钻研精神”应该继续下去,那就是剖根问底,寻找最基本的原理。其实这些高深的技术就是对基本原理的理解,反而上层的业务开发者对基本原理不需要知道多少。

接下来我将以我学习到的IOS攻防技术,做一个简单的总结。首先说明我利用的技术框架:开源的Fishhook。基于此我们探讨一下数据保护相关API以及基于脚本实现动态库注入。

利用fishhook如何实现iOS攻防注入
       本文参考:iOS安全攻防(十七):Fishhook
      上一节,我们介绍了IOS的一种运行时HOOK的机制: object-C黑科技之Runtime机制利用
  其主要原理是利用C函数实现Method Swizzle进行API替换。
      那么现在,我们就探讨一下依然是基于C函数的hook方案:fishhook 
      fishhook介绍:
      fishhook是facebook开源发布的一个用于动态修改链接(Mach-O符号表)的开源工具。
      什么是Mach-O呢?可以查看先前写的这篇文章:IOS开发底层:Mach-o文件结构分析
      Mach-O为Mach Object文件格式的缩写,也是iOS的可执行文件,目标代码,动态库,内核转储的文件格式。Mach-O有自己的dylib规范。
      fishhook的实现原理:(也可以去官方查看How it works)
      dyld链接2种符号,lazy和non-lazy,fishhook可以重新链接/替换本地符号。
利用fishhook如何实现iOS攻防注入
如图所示,__DATA区有两个section和动态符号链接相关:__nl_symbol_ptr 、__la_symbol_ptr。__nl_symbol_ptr为一个指针数组,直接对应non-lazy绑定数据。__la_symbol_ptr也是一个指针数组,通过dyld_stub_binder辅助链接。<mach-o/loader.h>的section头提供符号表的偏移量。
图示中,1061是间接符号表的偏移量,*(偏移量 间接符号地址)=16343,即符号表偏移量。符号表中每一个结构都是一个nlist结构体,其中包含字符表偏移量。通过字符表偏移量最终确定函数指针。
fishhook就是对间接符号表的偏移量动的手脚,提供一个假的nlist结构体,从而达到hook的目的。
fishhook替换符号函数:
  1. int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
  2.   int retval = prepend_rebindings(rebindings, rebindings_nel);
  3.   if (retval < 0) {
  4.     return retval;
  5.   }
  6.   // If this was the first call, register callback for image additions (which is also invoked for  
  7.   // existing images, otherwise, just run on existing images  
  8.   if (!rebindings_head->next) {
  9.     _dyld_register_func_for_add_image(rebind_symbols_for_image);
  10.   } else {
  11.     uint32_t c = _dyld_image_count();
  12.     for (uint32_t i = 0; i < c; i ) {
  13.       rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
  14.     }
  15.   }
  16.   return retval;
  17. }
关键函数是 _dyld_register_func_for_add_image,这个函数是用来注册回调,当dyld链接符号时,调用此回调函数。 rebind_symbols_for_image 做了具体的替换和填充。
fishhook替换Core Foundation函数的例子
以下是官方提供的替换Core Foundation中open和close函数的实例代码
  1. #import   
  2.   
  3. #import <UIKit/UIKit.h>  
  4.   
  5. #import "AppDelegate.h"  
  6. #import "fishhook.h"  
  7. static int (*orig_close)(int);
  8. static int (*orig_open)(const charchar *, int, ...);
  9. void save_original_symbols() {
  10.   orig_close = dlsym(RTLD_DEFAULT, "close");
  11.   orig_open = dlsym(RTLD_DEFAULT, "open");
  12. }
  13. int my_close(int fd) {
  14.   printf("Calling real close(%d)\n", fd);
  15.   return orig_close(fd);
  16. }
  17. int my_open(const charchar *path, int oflag, ...) {
  18.   va_list ap = {0};
  19.   mode_t mode = 0;
  20.   if ((oflag & O_CREAT) != 0) {
  21.     // mode only applies to O_CREAT  
  22.     va_start(ap, oflag);
  23.     mode = va_arg(ap, int);
  24.     va_end(ap);
  25.     printf("Calling real open(\'%s\', %d, %d)\n", path, oflag, mode);
  26.     return orig_open(path, oflag, mode);
  27.   } else {
  28.     printf("Calling real open(\'%s\', %d)\n", path, oflag);
  29.     return orig_open(path, oflag, mode);
  30.   }
  31. }
  32. int main(int argc, charchar * argv[])
  33. {
  34.   @autoreleasepool {
  35.     save_original_symbols();
  36.     //fishhook用法  
  37.     rebind_symbols((struct rebinding[2]){{"close", my_close}, {"open", my_open}}, 2);
  38.     // Open our own binary and print out first 4 bytes (which is the same  
  39.     // for all Mach-O binaries on a given architecture)  
  40.     int fd = open(argv[0], O_RDONLY);
  41.     uint32_t magic_number = 0;
  42.     read(fd, &magic_number, 4);
  43.     printf("Mach-O Magic Number: %x \n", magic_number);
  44.     close(fd);
  45.     return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  46.   }
  47. }
注释//fishhook用法
  1. rebind_symbols((struct rebinding[2]){{"close", my_close}, {"open", my_open}}, 2);
传入rebind_symbols的第一个参数是一个结构体数组,大括号中为对应数组内容。

不得不说,facebook忒NB。


iOS安全攻防(十八):数据保护API

开篇先扯几句题外话,许多朋友都问我怎么不写防啊,我确实有点犹豫。

 

hackers总是想象如果自己是开发者会怎么写,然后才能找到入手点。同理,开发者们也要想象自己是hackers会怎么做,才能采取相应的防御措施。然后,就是一场递归的博弈。

 

拿越狱检测这件事来说,起初大家只需判断有无安装Cydia就好了,hackers们说好,那我就不安装Cydia也可以动手脚。开发者们又说,那你一定得用的上MobileSubstrate,bash,ssh吧,我去检测手机有没有安装这些工具。可是又有什么用呢?你判断什么我绕过去什么。

 

当class-dump大肆流行,函数符号都被暴露,开发者想尽办法藏起自己的敏感函数代码。hackers们也知道class-dump的死穴在哪里,于是新的检索办法油然而生。也就说,当一个防御手段成为流行,它就不会再是个让hackers大骂“真特么费劲”的防御手段了。比如之前介绍的一个小技巧:内存数据擦除  ,hackers知道开发者都去擦数据了,那我hook memset在你擦之前去读就好了。开发者说:我直接写硬盘上然后删除!hackers说:难道你没听说过文件恢复?

利用fishhook如何实现iOS攻防注入

OK,贫的有点多了,本文介绍一下防御相关的话题————iOS的数据保护API。

 

数据保护API

文件系统中的文件、keychain中的项,都是加密存储的。当用户解锁设备后,系统通过UDID密钥和用户设定的密码生成一个用于解密的密码密钥,存放在内存中,直到设备再次被锁,开发者可以通过Data Protection API 来设定文件系统中的文件、keychain中的项应该何时被解密。

 

1)文件保护

  1. /* 为filePath文件设置保护等级 */
  2. NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
  3.                                                        forKey:NSFileProtectionKey];
  4. [[NSFileManager defaultManager] setAttributes:attributes
  5.                                  ofItemAtPath:filePath
  6.                                         error:nil];
  1. //文件保护等级属性列表  
  2. NSFileProtectionNone                                    //文件未受保护,随时可以访问 (Default)  
  3. NSFileProtectionComplete                                //文件受到保护,而且只有在设备未被锁定时才可访问  
  4. NSFileProtectionCompleteUntilFirstUserAuthentication    //文件收到保护,直到设备启动且用户第一次输入密码  
  5. NSFileProtectionCompleteUnlessOpen                      //文件受到保护,而且只有在设备未被锁定时才可打开,不过即便在设备被锁定时,已经打开的文件还是可以继续使用和写入  

2)keychain项保护

  1. /* 设置keychain项保护等级 */
  2. NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
  3.                         (__bridge id)kSecAttrGeneric:@"MyItem",
  4.                         (__bridge id)kSecAttrAccount:@"username",
  5.                         (__bridge id)kSecValueData:@"password",
  6.                         (__bridge id)kSecAttrService:[NSBundle mainBundle].bundleIdentifier,
  7.                         (__bridge id)kSecAttrLabel:@"",
  8.                         (__bridge id)kSecAttrDescription:@"",
  9.                         (__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleWhenUnlocked};
  10. OSStatus result = SecItemAdd((__bridge CFDictionaryRef)(query), NULL);

 

应用实例

把一段信息infoStrng字符串写进文件,然后通过Data Protection API设置保护。

  1. NSString *documentsPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  2. NSString *filePath = [documentsPath stringByAppendingPathComponent:@"DataProtect"];
  3. [infoString writeToFile:filePath
  4.              atomically:YES
  5.                encoding:NSUTF8StringEncoding
  6.                   error:nil];
  7. NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
  8.                                                        forKey:NSFileProtectionKey];
  9. [[NSFileManager defaultManager] setAttributes:attributes
  10.                                  ofItemAtPath:filePath
  11.                                         error:nil];
利用fishhook如何实现iOS攻防注入

 

设备锁屏(带密码保护)后,即使是越狱机,在root权限下cat读取那个文件信息也会被拒绝。

利用fishhook如何实现iOS攻防注入

 iOS安全攻防(十九):基于脚本实现动态库注入

MobileSubstrate可以帮助我们加载自己的动态库,于是开发者们谨慎的采取了对MobileSubstrate的检索和防御措施。

那么,除了依靠MobileSubstrate帮忙注入dylib,还有别的攻击入口吗?

利用fishhook如何实现iOS攻防注入

理理思路,条件、目的很明确:

1)必须在应用程序启动之前,把dylib的环境变量配置好

2)dylib的位置必须能被应用程序放问到

3)最后再启动应用程序

利用fishhook如何实现iOS攻防注入

再点击应用程序图标-->程序启动这个过程中,在我们看来程序是被动执行的。为了让特定功能的脚本被执行,我们可以把脚本改成应用程序二进制的名字伪装成应用程序,让系统调用启动。在脚本中,配置好dylib,然后再手动启动真的应用程序,假装什么也没发生,挥一挥衣袖不带走一片云彩~

 

将真的支付宝程序改名为oriPortal:

  1. mv Portal oriPortal

将待执行的脚本改名为支付宝:

  1. mv Portal.sh Portal

脚本代码:

  1. #!/bin/bash  
  2.   
  3. #得到第一个参数  
  4. C=$0
  5.   
  6. #第一个参数是二进制的绝对路径 比如 :  
  7. #/private/var/mobile/Applications/4763A8A5-2E1D-4DC2-8376-6CB7A8B98728/Portal.app/  
  8. #截取最后一个 / 之前的内容  
  9. C=${C%/*}
  10.   
  11. #库和二进制放在一起  
  12. export DYLD_INSERT_LIBRARIES=${C:-.}/wq.dylib
  13. #执行原来APP $@ 别忘了把原来的参数保留  
  14. exec "${C:-.}"/oriPortal "$@"
利用fishhook如何实现iOS攻防注入

结果不尽人意,失败了……

错误信息如下:

利用fishhook如何实现iOS攻防注入

在打开某个加密信息时出了错误,大概猜一下应该是类似加密签名校验的步骤,但是我们无法去了解其中详细的操作到底是什么样的,没关系,那么就把原始的可执行文件环境全部给他造出来,因为检验文件属性肯定不会带着路径信息的。

备份一份Portal.app目录Portal_ori.app,修改脚本为:

  1. #!/bin/bash  
  2. C=$0
  3. C=${C%/*}
  4. export DYLD_INSERT_LIBRARIES=${C:-.}/wq.dylib
  5. exec "${C:-.}"/../Portal_ori.app/Portal "$@"

运行支付宝app验证一下,

好消息是,在iOS6上,成功加载了动态库wq.dylib

坏消息是,在iOS7上,失败了,错误信息如下:

利用fishhook如何实现iOS攻防注入

应该是因为iOS7的沙盒机制升了级,把我们这套小把戏拦在门外了……

那又怎么样,面包总会有的~

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: