Reverse Attack

Reverse Attack

  本文将整理关于iOS逆向开发的知识,由于工具多而散,所以随着时间的推移,此处内容将不断更新。

  本文内容如下:

  1. 砸壳与重签
  2. 逆向开发

1. 砸壳与重签

  在iOS的逆向开发中,需要先获得能运行在设备上的Mach-O文件,才能对App进行逆向探索。

  Mach-O文件是在Build的时候生成并存放于.app包内的,对.app文件显示包内容就能看到。

  App Store上下载下来的文件是.ipa文件,这是一个包含.app文件和Apple验证信息的包,改成.zip后解压可看到里面包含的文件(当然,此时.app包内的Mach-O文件是加密了的)。

  砸壳与重签其实是通过对Mach-O文件的解密提取和打包操作。

1.1 App加密原理

  首先,要对.ipa文件进行一系列操作的前提能了解它的原理机制。

  iOS App 签名的原理,这是一遍很好的介绍签名原理的文章,作者是JSPatch的作者,文章内也有阮一峰大神对非对称加密RSA算法原理文章的链接。这里就不卖弄了,若有相关知识点,提及的时候再关联补充。

1.2 砸壳

  在了解App的加密原理后就会发现,这时会遇到个难题:怎么获得解密后的Mach-O文件?就算是已经握在手上的设备中,已经存在的Apple公布出来的公钥都拿不到!

  所以,破解加密这种操作是做不到的了,那么就只能出绝招了:越狱。ps:现时,只有9.1以下的iOS系统能完美越狱。

  首先,Mach-O文件作为可执行文件,它是被dyld(存放与/usr/lib/dyld,iOS和macOS都是,代码是开源的,有兴趣可以看下)加载到内存中执行的。

  在无法解密的情况下,想要获得已经解密的文件,就是让设备自己解密。

1.2.1 静态砸壳

  静态砸壳,需要越狱设备,但并不需要让App加载到内存,而是调用设备本身的解密算法,将App解密后dump出来。

  Clutch可以做到静态砸壳,与上面的一条命令完成砸壳的动态砸壳方式相比,有好处,也有坏处,使用方法按照README.md走就行。

  好处是Clutch静态砸壳可以把从App Store上下载下来的.ipa中包含的多架构的Mach-O文件给dump出来,而动态砸壳方式因为系统只会加载适合当前架构的部分,所以只能dump出相应版本的Mach-o文件。

  坏处是Clutch对于framework会dump失败,需要手动拉取,某些App在dump的过程中会直接崩溃,另外默认还需要到/private/var/mobile/Documents/Dumped目录下把dump好的.ipa发回电脑。

1.2.2 动态砸壳

  动态砸壳,就是利用越狱的设备,向进程注入动态库,利用这个动态库,读取已被dyld加载到内存里的数据,dump出已经解密了的Mach-O文件(说是砸壳,其实有种提取的味道)。

  目前,最舒适方便的砸壳工具是AloneMonkeyfrida-ios-dump了,介绍:一条命令完成砸壳,是用frida做到的,搭好环境后一气呵成。需要提及的是frida有坑,设备运行久了,frida-server会挂掉,使用dump.py时遇见报错别慌,重启下设备端的frida-server能解决大部分问题。

1.3 重签

  了解了App加密原理,就可以手动对.app重签打包了。

1.3.1 准备
  1. 一个已被解密了的.app文件
  2. 一份允许App安装到真机上的开发者证书,这意味着已经有本地的公私钥了
  3. 一份App对应的Provisioning Profile,包含AppIDEntitlementDeviceID等信息
1.3.2 操作

  验证Mach-O文件是否已解密可以通过以下命令:

# cryptid 0 -> 已解密
# cryptid 1 -> 未解密
otool -l [MACH-O_FILE] | grep cryptid

  查看自己的开发者证书可以通过以下命令:

security find-identity -v -p codesigning

  将.app文件放置于Payload目录下(注意这一步是必须的,否则App安装时会报错)。

  从App开发者账号里创建一个对应App IDProvisioning Profile

  新建一个Xcode项目,利用刚创建的Provisioning Profile文件进行编译,编译后的.app文件内有一embedded.mobileprovision文件,检查其内容,保证App ID正确,然后将这个文件复制到解密了的.app下。

  查看embedded.mobileprovision(看Provisioning Profile文件也一样)文件中<key>Entitlements</key>内的的权限内容,可以通过以下命令:

security cms -D -i [Profile_Path]

  通过以下命令查看原App的权限设置,与自己创建的进行对比修改(为了重签后可以进行调试,可添加get-task-allowtask_for_pid-allow两个布尔类型的值)。

codesign -d --entitlements :- [MACH-O_FILE]
# 有ldid也可以用这个
ldid -e [MACH-O_FILE]

  复制<key>Entitlements</key>内的权限内容到一个新的.plist文件中。

  之后就可以通过以下命令进行签名了(注意这里是有坑的,各个App的.app文件内存在着不同的内容,有时需要注意Framework下的文件进行签名,而有些文件,直接删掉也问题不大):

# codesign_identity:开始时查看的自己的开发者证书ID
# entitlements.plist:通过查看权限后生成的权限plist文件
# .app:就是.app目标文件
codesign -fs [codesign_identity] --entitlements=[entitlements.plist] [.app]

  签名后,就可以打包成.ipa了,可以通过如下命令:

# 注意这里一定要把Payload目录包含进去,否则安装时会报错
zip -ry [.ipa] [Payload_Path]

  然后就可以将此.ipa包,通过Xcode安装到真机上了:Window->Devices and Simulators,拖进去就行了。

  如果签名打包成功,那么此时App已经装到设备上了。

2. 逆向开发

2.1 非越狱开发

  MonkeyDev,一个超方便的非越狱逆向开发工具,详细介绍点进去看就是了。

  本节将详解非越狱情况下逆向开发中需要用到的知识。

2.1.1 Xcode的使用

  Xcode作为macOS上的IDE,包含了很多开发时需要的工具,其中包括逆向开发时,可能会用上的功能。

  1. Always Show Disassembly,打开方式:Debug->Debug Workflow->Always Show Disassembly,用于在断点时,将已经编译为机器码的代码反汇编为汇编代码进行显示,用于分析逻辑。
  2. View Memory,打开方式:Debug->Debug Workflow->View Memory,快捷键:⇧⌘M,用于直接查看内存中的数据,通常配合指针地址或汇编代码中的地址使用。
  3. LLDB,断点时触发,下面列出一些逆向开发时比较有用的命令:
    # 读写寄存器值 read/write
    register
    # 读写当前进程的内存值 read/write
    memory
    # 读当前进程的内存值,memory read 的快捷方式
    x
    # 获取可执行文件的依赖库列表,也方便查看ASLR值(第一个就是)
    image list
    # 获取可执行文件相关信息
    image lookup
    # 打印当前线程的所有堆栈信息
    bt
    # 获取当前线程的当前堆栈信息
    frame info
    # 选择堆栈,后跟堆栈序号
    frame select
    # 选择前一条堆栈
    up
    # 选择后一条堆栈
    down
    # 比Xcode上UI的断点更灵活,详看breakpoint help
    breakpoint
    

2.1.2 命令行的使用

2.1.2.1 otool

  otool(object file displaying tool)是个非常强大的文件信息查看工具,下面列出些逆向开发时比较常用的命令。

# 查看Mach-O文件的header,-v可以查看比较直观的描述
# 最后的flag中可以发现有PIE (Position Independent Enable),就是说App启动时会产生ASLR 
# ASLR(Address Space Layout Randomization),地址空间随机布局,是为了防范对已知地址进行恶意攻击的,一些不完美越狱的机子就是因为这个原因
# ASLR用于App时,会使Mach-O加载到未知的地址,从而导致在App运行中某指令的内存地址与查看Mach-O内的内存地址不匹配的情况
# 但App内的内存地址还是固定的,只是多了一个偏移值,通过lldb打印image list就能查看这个偏移值,加上后就能匹配上了
otool -h -v [[MACH-O_FILE]]
# 查看Mach-O文件的load commands部分,想查看这个Mach-O文件有没有解密,如前所述,用管道筛选查看cryptid就行了
otool -l [MACH-O_FILE]
# 查看Mach-O文件的依赖库
otool -L [MACH-O_FILE]
2.1.2.2 lipo

  lipo是个对Mach-O文件的架构进行操作的命令,通常会在开始分析或进行打包时用到以下一些命令。

# 查看文件架构
file [FILE]
lipo -info [MACH-O_FILE]
# 分离架构
lipo -thin [architecture, armv7, arm64] [SOURCE_FILE] -output [TARGET_FILE]
# 合拼架构
lipo -create [SOURCE_FILES_DELIMITED_BY_WHITESPACE] -output [TARGET_FILE]
2.1.2.3 class-dump

  class-dump是个可以把Mach-O文件内的头文件导出来的命令,方便通过头文件进行分析和开发。

class-dump -H [MACH-O_FILE]
2.1.2.4 restore-symbol

  restore-symbol是用来恢复符号表的。在断点动态调试时,恢复了符号表的Mach-O文件能清楚看到堆栈调用信息,为逆向开发提供极大便利。

# -j 是用于恢复Block的符号表的,需要将/search_oc_block/ida_search_block.py放到IDA中执行生成的block_symbol.json文件作为参数传入
restore-symbol [SOURCE_FILE] -j [BLOCK_SYMBOL_JSON] -o [TARGET_FILE]
2.1.2.5 Hook

1. Method Swizzling

  Method Swizzling是利用语言的Runtime特性实现的,对其陌生的可以看下Runtime。通过前文所述,可以知道编译后的代码(就是Mach-O文件内的的部分内容)是通过dyld加载到内存中的,在ClassCategory加载到Runtime时就会触发+ (void)load;类方法,在这时,就可以通过Method Swizzling进行类中方法的Hook操作了(包括但不限于这种使用Method Swizzling的Hook方式)。

  对于+ (void)load;需要注意一些情况,只有新加载的ClassCategory会触发这个方法,而调用顺序如下:

1 项目中所有链接了的Framework的初始化调用时
2 项目中所有"+ (void)load;"方法调用时
3 项目中所有C++的静态初始化和C\C++的构造函数调用时
4 项目中所有依赖了项目的其他Framework的初始化调用时

  另外,还有细分两个顺序:

* Class的"+ (void)load;"会在其父类调用了"+ (void)load;"后再调用
* Category的"+ (void)load;"会在其Class调用"+ (void)load;"后再调用

  根据调用顺序可以发现,在+ (void)load;类方法中使用Method Swizzling进行Hook操作,在被注入代码后“基本”可以Hook住所有方法。

  但对于某些利用Category来Hook的方法,有可能因为加载Category的顺序不同,或又因为原代码中的方法经过Method Swizzle处理过,可能会导致Hook失败。需要具体问题具体分析。

  在用Method Swizzle进行Hook的时候,如果只是需要添加代码而不是修改时,通常会在Hook的时候保留原方法的IMP,然后在Hook了之后执行它,再添加自己的代码,下面是一个简单的例子:

//
//  MSHook.h
//  
//
//  Created by lzackx on 2018/4/5.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface MSHook : NSObject

@end
//
//  MSHook.m
//  
//
//  Created by lzackx on 2018/4/5.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "MSHook.h"
#import <objc/runtime.h>

@implementation MSHook

static Method originalMethod;
static Method swizzleMethod;
static IMP originalIMP;
static IMP swizzleIMP;

+ (void)load {
    
    originalMethod = class_getInstanceMethod(objc_getClass("AppDelegate"), @selector(application:didFinishLaunchingWithOptions:));
    originalIMP = method_getImplementation(originalMethod);
    swizzleMethod = class_getInstanceMethod(self, @selector(application:didFinishLaunchingWithOptions:));
    swizzleIMP = method_getImplementation(swizzleMethod);
    // 备注:这个函数的执行是不会改变两Method对象的地址的,只是将两个Method内的IMP互相调换了
    method_exchangeImplementations(originalMethod, swizzleMethod);
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   
	// 调用原方法
    BOOL returnValue = ((BOOL(*)(id, SEL, id, id))originalIMP)(self, _cmd, application, launchOptions);
    return returnValue;
}

@end

2. CaptainHook

  CaptainHook就是一个头文件,内有很多方便Hook的宏,原理也是基于Runtime的,文件并不大,有兴趣看一下就懂了。

3. fishhook

  fishhook是一个由Facebook提供的C库,只有两个文件,Hook的原理是修改Mach-O文件中对应函数的懒加载指针(__la_symbol_ptr)与非懒加载指针(__nl_symbol_ptr)来达到Hook的目的。

  fishhook.h中提供了一个结构体和两个函数:

struct rebinding {
  const char *name;		// 源函数名称
  void *replacement;	// 目标函数指针
  void **replaced;		// 源函数指针的指针(就是用一个指针变量指向了源函数指针,用于保存源函数)
};

// 顾名思义,用目标函数指针重绑源函数符号指向的地址
// 当调用相同的函数名时,由于符号指向的地址被绑定为目标函数指针,所以就会执行目标函数指针指向的函数,而不是源函数指针指向的函数
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
int rebind_symbols_image(void *header,
                         intptr_t slide,
                         struct rebinding rebindings[],
                         size_t rebindings_nel);

  在重绑定符号时,其实是通过源函数名称这个参数查找符号的,当找到匹配的符号时,才会进行重绑定。查找符号的具体原理可以看下Github上的介绍和流程图,这里以某Mach-ONSLog函数为例:

  • 首先找到__la_symbol_ptr中的指针地址,如下图,可以看到地址是00C1DB80

`__la_symbol_ptr`

  • 然后在Indirect Symbols中找到对应的符号,可以看到Data为187,这是一个对应Symbol中的序号。

Indirect Symbols

  • Symbols中,可以通过从Indirect Symbols中获得的序号找到相应的位置(对于MachOView中Symbols表不显示序号的情况,观察下每个符号的地址间隔可以发现他们的地址之间相差一个0x10,所以与序号相乘,即十六进制左移一位,这里的例子是0x1870,再加上Symbols表的初始偏移地址,就可以找到目标函数的符号地址了,当然这只适合于MachOView手动查找,其实右上角的搜索功能更方便……,在代码中的实现,其实就是数组通过下标查找的),然后可以发现Data列的值为2502,这是对应String Table中的偏移地址。如下图所示:

Symbols

  • 最后,在String Table中,将初始偏移地址与从Symbols表中获得的偏移地址相加,就能找到符号的字符串所在的地址了。如下图所示:

String Table

  在找到匹配的符号后,替换符号指向的源函数指针地址为目标函数函数指针地址,Hook就生效了。

备注fishhook并不是所有C函数都一定能Hook住的,意外情况通常发生在自定义的函数中。当自定义的函数在进行Hook前没有被调用过时,__la_symbol_ptr中没有找到与rebind_symbols函数中传递参数指定的符号时,fishhook会因为找不到对应的符号,所以不会进行指针的替换,进而Hook不住。

2.1.2.6 yololib

  yololib是个将动态库注入Mach-O文件的命令行工具,原理是把动态库信息写入Mach-O文件中,源码是只有200+的.m文件,很好理解。注入后,用MachOViewotool都能看见注入的部分。

# 注意DYNAMICALLY_LINKED_SHARED_LIBRARY_FILES传递的值,应与重打包后访问的动态库路径匹配,可参考otool中其他动态库的路径
yololib [MACH-O_FILE] [DYNAMICALLY_LINKED_SHARED_LIBRARY_FILES]

2.1.3 第三方工具的使用

2.1.3.1 MachOView

  MachOView是一个用于浏览Mach-O文件的工具。可以说是有UI界面的otool,在看Mach-O内的符号时比较直观。

2.1.3.2 IDA

  IDA是个很强大的工具,但也比较复杂,甚至有一本书来讲述它的使用,叫《The IDA PRO BOOK》,在逆向中,静态分析时用它比用Xcode要有效率得多。

2.2 越狱开发

2.2.1 ssh

  越狱后,就有了设备的root权限了,在通过Cydia安装了ssh相关工具后,就可以通过内网连上设备了,连设备跟连普通服务器没区别,甚至可以把设备当成服务器用。

  通常为了方便连接,会将经常连接的电脑与设备设置无密码登陆(生成时注意别填passphrase),设置也很简单(如果没有生效,注意文件和目录的权限)。

# 生成一对公私钥
ssh-keygen
# 将公钥发到设备上
ssh-copy-id -i [PUBLIC_ID_FILE] name@host -p port
# 通过验证无需密码即可
ssh -i [ID_FILE] name@host -p port

2.2.3 dyld

  在越狱的情况下,可以通过ssh链接到设备上了,这时就可以通过dyld动态加载库了。

DYLD_INSERT_LIBRARIES=[DYNAMICALLY_LINKED_SHARED_LIBRARY_FILES] [MACH-O_FILE]

  dyld还有很多环境变量,可以在电脑上man dyld查看。通过DYLD_INSERT_LIBRARIES动态加载的库并不会影响原Mach-O,只是让库加载到进程中而已(dumpdecrypted.dylib就是通过这种方式进行动态砸壳的)。

  由于dyld是开源的,看其代码,可以发现这种方式是可以被禁止的。

2.2.3 FLEXLoader

  FLEXLoader是一个BigBoss源上的插件,用于将FLEX库动态注入到App中,便于动态调试App中的UI。

  FLEX当然也可以在非越狱的情况下使用,直接在Hook中调用并注入库就可以了。

2.2.4 Keychain-Dumper

  Keychain-Dumper就是一个将钥匙串里的信息(路径:/private/var/Keychains)dump出来的工具。

  使用起来非常简单,将keychain_dumper通过scp传到越狱设备的/bin下,直接执行就可以看到Keychain的信息了。

2.2.5 cycript

  cycript是利用Javascript动态调试App的工具。

  cycript在macOS配置使用的时候是有坑的。通常因系统的ruby版本过高导致,这时可以机智地进入恢复模式(重启按住⌘R),关掉系统的SIP(System Integrity Protection ),将/System/Library/Frameworks/Ruby.framework内的目录复制一份并将版本号全部改成cycript要求的版本号就行,后面再打开SIP也不影响。

  越狱条件下使用时,可以通过Cydia装上cycript,通过ssh连接上设备后,通过如下命令直接动态调试App。

  非越狱条件下使用时,需要先给App集成Cycript.framework这个库,运行起来后,App中的Cycript.framework会通过设定的端口监听,只要与电脑连接在同以局域网内,就能通过安装在电脑上的cycript命令连接App,然后进行调试,调试Objective-C的语法可以看官网手册。

cycript [-c] [-p <pid|name>] [-r <host:port>] [<script> [<arg>...]]

  无论越狱和非越狱,都可以通过命令或在运行中通过import导入已经写好的脚本,能大大提升效率。不过在越狱情况下,通过指令导入的脚本能比import方式导入的脚本更方便,因为命令导入的脚本文件只要改动了,就能生效(毕竟Javascript是解释性语言),而通过import导入的,需要退出cycript后再进才有效。

2.2.6 Cydia Substrate

  Cydia Substrate前身是MobileSubstrate,包含3个主要组件:MobileHookerMobileLoadersafe mode,只能用于越狱设备,主页内的文档超齐全,这里就没必要搬运了。

2.2.7 theos

  theos是跨平台的不需要使用Xcode的用于管理、开发和发布iOS应用的工具,通常越狱设备上的tweaks就是用这个来开发的。

  可以按照Wiki进行安装使用,请严谨按照步骤,否则容易踩坑。

  在编译、打包、安装由theos生成的项目时,通常用到下面一些命令:

# 看到Makefile就不难理解了,再看里面的内容,可以发现所有模版的编译、打包都由相应的.mk文件负责
make
make package
# 在安装前,需要先配置好越狱设备的地址,可以通过Wi-Fi,也可以通过经过端口映射了的USB。
# export THEOS_DEVICE_IP=
# export THEOS_DEVICE_PORT=
make install
2.2.7.1 NIC(New Instance Creator)

  theos/bin/nic.pl是一个Perl脚本,用于创建theos项目模版,直接控制台执行就行。

nic.pl

  注意创建过程中,路径避免含有ASCII不能表示的字符,包名请以全小写命名。否则在打包过程中,会遇坑。

  遇坑也别慌,阅读theos/bin目录内编译项目和打包项目的脚本就能找到原因了。

2.2.7.2 Logos

  Logostheos中的一个组件,它有自己的语法,当代码写在.x.xm内后,编译时会用theos/bin/logos.pl进行预处理,生成.m.mm文件,以C/C++的形式调用函数或ObjC的方法(思路跟CaptainHook一样,都是为了方便Hook,甚至可以把Logos语法看成是另一种类型的宏)。语法用起来更自然,比CaptainHook效率高。

  Logos在预处理后就是一些C/C++代码了,所以与在越狱和非越狱环境无关,编译成动态库后,可以通过yololib注入,也可以通过dyld插入。

2.2.7.3 Logify

  theos/bin/logify.pl是一个能将.h头文件中的内容生成以Logos语法实现的.xm文件,一句命令就把整个类的方法都hook了,能在开始逆向代码逻辑时,提高观察逻辑调用顺序的效率(不用机械地抄方法来实现还是挺好的)。命令如下:

logify.pl header.h > source.xm

2.2.8 debugserver

  debugserverLLDB的一部分,Xcode中断点时就用到了它,在设备上的位置:/Developer/usr/bin/debugserver

  在逆向时,通常会用它在设备端attach进程,对App进行调试。

#Usage:
debugserver host:port [program-name program-arg1 program-arg2 ...]
debugserver /path/file [program-name program-arg1 program-arg2 ...]
debugserver host:port --attach=<pid>
debugserver /path/file --attach=<pid>
debugserver host:port --attach=<process_name>
debugserver /path/file --attach=<process_name>

  在电脑端,通过lldb启动LLDB后,连接设备端的debugserver进行调试:

process connect connect://host:port

  这种调试行为时可以被防御和反防御的,当然也有反反反防御和反反反反防御。。。攻防矛盾内容在之后另外叙述。

备注

  本文内容多而杂,但总归是逆向相关的归纳总结,后续会以此为骨架逐渐展开细节进行更新。


lZackx © 2022. All rights reserved.

Powered by Hydejack v9.1.6