首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
iOS安全
发新帖
26
14
从微信扔骰子看iOS应用安全与逆向分析
发表于: 2021-4-11 14:22
96559
从微信扔骰子看iOS应用安全与逆向分析
evilpan
13
2021-4-11 14:22
96559
看了下看雪的帖子分布,Android 帖子比 iOS 的帖子多了五六倍啊。本人之前也是在 Android 平台下进行分析比较多,因此这也算面向 Android 安全工程师的一个 iOS 入门介绍吧。本文以微信扔骰子小游戏为例,详细记录了一次完整 iOS 逆向分析的过程,希望能对大家有所帮助。 # Jailbreak 本文主要讨论的是应用安全,因此关于越狱实现的部分不做深入介绍。关于XNU内核漏洞的分析和利用网上有很多相关的文章或书籍,比如: - <a href="elink@b2bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2W2P5s2m8D9L8$3W2@1i4K6u0V1k6r3u0Q4x3X3g2U0L8$3#2Q4x3V1k6V1L8$3y4K6i4K6u0r3k6h3&6Y4L8r3W2K6K9q4)9J5c8U0b7K6z5e0b7#2i4K6u0V1K9X3q4A6L8r3u0J5k6h3q4C8K9h3&6Y4i4K6u0V1K9h3!0K6i4K6u0V1x3e0q4Q4x3X3f1I4i4K6u0W2x3W2)9J5k6r3q4F1i4K6u0V1j5h3c8$3k6h3&6@1N6i4u0W2i4K6u0V1K9h3&6@1L8#2)9J5k6s2c8Z5k6g2)9J5k6s2S2F1N6g2)9J5k6r3E0W2M7X3&6W2L8q4)9J5k6i4m8V1k6R3`.`.">An adventure into the XNU kernel</a> - <a href="elink@ac8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5k6h3u0D9L8$3N6K6M7r3!0@1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9I4z5g2)9J5c8U0p5J5i4K6u0r3M7$3!0U0K9%4m8#2M7s2m8W2N6q4)9J5k6s2N6S2L8r3E0@1K9s2u0G2N6h3N6Z5i4K6u0V1L8$3k6Q4x3X3c8C8k6i4u0F1k6h3I4Q4x3X3g2Z5N6r3#2D9">Project Zero - SockPuppet: A Walkthrough of a Kernel Exploit for iOS 12.4</a> - <a href="elink@f1aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5k6h3u0D9L8$3N6K6M7r3!0@1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9J5x3q4)9J5c8U0l9$3i4K6u0r3j5g2)9J5k6s2y4#2M7Y4k6W2P5g2)9J5k6r3!0X3i4K6u0V1M7X3g2U0k6h3&6@1i4K6u0V1K9h3!0K6i4K6u0V1K9$3g2J5L8X3g2D9i4K6u0V1k6i4S2H3L8r3!0A6N6s2y4Q4x3X3g2Z5N6r3#2D9">Project Zero - A survey of recent iOS kernel exploit</a> - <a href="elink@abaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6k6h3y4X3j5i4g2D9N6q4)9J5k6s2y4W2j5%4g2J5K9i4c8&6i4K6u0W2j5$3!0E0i4K6u0r3j5X3I4G2k6#2)9J5c8X3y4Z5j5h3W2F1x3#2)9J5k6h3S2@1L8h3H3`.">Secfault Security - Writing an iOS Kernel Exploit from Scratch</a> - 《MacOS and iOS Internals》(*OS Internals) - ... 看一些公开的漏洞利用代码对加深印象也很有帮助,比如<a href="elink@d07K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3N6K6i4K6u0W2j5$3S2J5L8$3#2A6N6h3#2Q4x3X3g2G2M7X3N6Q4x3V1k6H3i4K6u0r3M7s2u0G2K9X3g2U0N6q4)9J5k6s2A6W2M7X3!0Q4x3V1k6A6M7%4y4#2k6i4y4Q4x3V1k6V1k6i4c8S2K9h3I4Q4x3@1k6A6k6q4)9K6c8o6p5&6z5o6j5`.">oob_timestamp</a>的利用。对于本文而言只需要站在巨人的肩膀上使用这些封装好的利用即可。iOS越狱和Android root的的一个很大不同是前者系统封闭性高,碎片化较低,因此提权的方法也相对单一,不支持刷机,大部分都是通过漏洞去获取更高的权限(tfp0)。既然是专注应用层安全,就抓大放小,使用<a href="elink@70eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Y4N6e0S2Q4x3X3g2U0L8$3#2Q4x3V1k6B7j5h3W2D9j5Y4u0W2j5h3E0Q4x3V1j5`.">现有的越狱工具</a>,站在前人肩膀上即可。 # 砸壳 越狱之后总算可以通过 ssh 进入到系统中,也就是相当于安卓世界的 `adb shell` 而已。既然是逆向分析,下一步就是获取应用的安装包,这在安卓中是一条`adb pull`命令,但苹果里要复杂一些。 iOS 中大部分应用都从 Apple Store 即应用商店下载,而从应用商店下载的 app 是通过苹果签名和加密保护的,这是苹果 FairPlay DRM 数字版权保护的重要部分。为了能使用逆向工具进行分析,需要先对其进行解密,即俗称的砸壳。网上关于砸壳的资料和工具都很多,比如: - <a href="elink@f91K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K6N6r3g2X3j5h3&6W2M7%4y4W2M7W2)9J5c8X3c8#2L8i4m8V1k6h3y4J5P5i4m8@1k6h3b7`.">stefanesser/dumpdecrypted</a>:手动将动态库注入进程获取解密后的文件,不包括动态库 - <a href="elink@640K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6U0L8$3&6J5j5h3c8W2N6W2)9J5c8X3c8#2L8i4m8V1k6h3y4J5P5i4m8@1k6h3b7`.">conradev/dumpdecrypted</a>:更新版,支持对每个模块进行解密 - <a href="elink@762K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6w2d9V1y4J5j5h3y4C8M7#2)9J5c8V1y4D9N6i4c8U0K9l9`.`.">Clutch</a>:通过posix_spawnp创建进程并解密文件,支持重新打包为ipa (iOS12之后这类静态砸壳方法基本上都不行了) - <a href="elink@c1bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6m8L8r3!0F1k6f1#2G2L8X3E0W2P5g2)9J5c8X3k6J5K9h3c8S2i4K6u0V1K9h3!0K6i4K6u0V1k6s2g2E0M7l9`.`.">frida-ios-dump</a>:使用frida进行动态解密,并支持重新打包为ipa - <a href="elink@fc9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6o6K9r3W2o6K9r3!0#2i4K6u0r3j5X3q4Y4j5X3q4C8">bagbak</a>:和前者类似,不过是基于 nodejs 的 frida binding > 这里面有些工具已经年代久远年久失修了,比如我就注意到 dumpdecrypted 在文件沙盒的处理上有点问题导致无法保存文件,需要经过简单 patch 。 上面的这些工具大部分都是基于内存 dump,也就是需要通过 exec 执行目标程序才实现砸壳,这可能会让一些 App 通过在初始化函数中自我检测来对抗砸壳。如果出现这种情况,可以通过基于 [mremap_encrypted][remap] 的方式进行解密,直接调用内核接口,无需启动目标程序实现静态砸壳。 # 抓手 狱也越了,壳也砸了,接下来呢?直接丢到 IDA 里分析?不是不可以,但 iOS 的应用主体是一个巨大的 [mach-o][macho] 文件,直接分析是很难的。还是以微信为例,主程序解密后单 arm64 架构的可执行文件就有 218 MB,即便在逆向工具里分析也会让人无从下手。这时候就需要一个入手点,用互联网的黑话来说,就是需要一个抓手。 一个最常见的入手点就是 UI 界面。在安卓应用分析中一般通过 Android Studio 提供的`uiautomatorviewer`可以进行目标应用的 UI 分析,并通过 UI 的 ID 在反编译的代码中查找引用。不过这个方法我一般不用,而是直接获取顶层 Activity 然后找对应的类去分析。 在 iOS 中,UI 分析却是一个有效的入手点,因为 iOS 应用都是基于 MVC 结构,View 中触发的事件由对应的 Controller 去实现。不管是基于 Springboard 的拖拖拽拽还是通过代码布局,MVC 的基调是不变的。在 Xcode 中有视图层级调试功能(`Debug View Hierarchy`),但需要目标开启调试。除此之外,更为常用的是一些视图调试框架,比如 Reveal 或者开源的 FLEX。 用 [FLEX][flex] 直接注入到进程中可实现 UI 的分析: ![flex.gif][flex.gif] 这里用到了 cycript,后文再细说。或者可以用 Reveal 在电脑端分析,当然还是需要将 `RevealServer.dylib` 注入到目标进程中并调用`[IBARevealLoader startServer]`,参考官方的 lldb 脚本,启动后 PC 端界面如下: ![Reveal][reveal.png] 点击发送骰子的区域是一个 UIImageView,其本身是没有响应点击事件的,因此要找对应的 ViewController 或者 Gesture Recognizer。 # 动态分析 根据 UI 信息能知道的是当前界面的 Controller 类是 `BaseMsgContentViewController`,所以我们可以通过脚本去跟踪发送骰子时该类所有的函数调用,如下所示: ```sh [-] 13:04:24.188 Hooked: -[BaseMsgContentViewController didRotateFromInterfaceOrientation:] [-] 13:04:24.188 Hooked: -[BaseMsgContentViewController previewingContext:viewControllerForLocation:] [-] 13:04:24.188 Hooked: -[BaseMsgContentViewController previewingContext:commitViewController:] [-] 13:04:24.188 Hooked: -[BaseMsgContentViewController previewActionItems] [-] 13:04:24.189 Hooked: -[BaseMsgContentViewController m_delegate] [!] 13:04:24.291 Failed to hook: -[BaseMsgContentViewController willShow] [-] 13:04:24.291 Hooked: -[BaseMsgContentViewController canPasteImage] [-] 13:04:24.292 Hooked: -[BaseMsgContentViewController setTableFooterView:] [-] 13:04:24.292 Hooked: -[BaseMsgContentViewController documentInteractionControllerViewControllerForPreview:] [-] 13:04:24.292 Hooked: -[BaseMsgContentViewController willAppear] [-] 13:04:24.292 Hooked: -[BaseMsgContentViewController showLoadingView] [-] 13:04:24.293 Hooked: -[BaseMsgContentViewController initTableView] [-] 13:04:24.293 Hooked: 614 methods for class BaseMsgContentViewController # 点击骰子图标发送表情 [-] 13:04:35.075 ENTER -[BaseMsgContentViewController useTransparentNavibar] [-] 13:04:35.076 ENTER -[BaseMsgContentViewController useTransparentNavibar] [-] 13:04:35.078 ENTER -[BaseMsgContentViewController shouldInteractivePop] [-] 13:04:35.078 ENTER -[BaseMsgContentViewController toolView] [-] 13:04:35.132 ENTER -[BaseMsgContentViewController SendEmoticonMesssageToolView:] [-] 13:04:35.148 ENTER -[BaseMsgContentViewController findNodeDataByLocalId:] [-] 13:04:35.148 ENTER -[BaseMsgContentViewController addMessageNode:layout:addMoreMsg:] [-] 13:04:35.148 ENTER -[BaseMsgContentViewController findNodeDataByLocalId:] [-] 13:04:35.148 ENTER -[BaseMsgContentViewController getCurContentSizeHeight] ... ``` ## frida 其中`SendEmoticonMesssageToolView`是个值得关注的函数,可以直接用 frida 打印函数堆栈: ```js import { kObjC } from "../../agent/objc"; import { kNative } from "../../agent/native"; // kObjC.traceClass("BaseMsgContentViewController"); kObjC.traceMethod("BaseMsgContentViewController", "- SendEmoticonMesssageToolView:", { onEnter: function(args) { log.i("SendEmoticonMesssageToolView", args[0], args[1]); kNative.printStackTrace(this.context); } }); ``` 输出结果如下: ```sh [-] 13:10:32.079 Hooked: -[BaseMsgContentViewController SendEmoticonMesssageToolView:] [+] 13:10:37.212 SendEmoticonMesssageToolView 0x11f8a1800 0x10bf6e8ba [-] 13:10:37.636 backtrace: 0x104e06de4 WeChat!0x2676de4 0x10294cde8 WeChat!0x1bcde8 0x1041bb930 WeChat!0x1a2b930 0x1b8331e34 UIKitCore!-[UICollectionView _selectItemAtIndexPath:animated:scrollPosition:notifyDelegate:deselectPrevious:] 0x1b83464b0 UIKitCore!-[UICollectionView _cellForItemAtIndexPath:includePrefetchedCells:] 0x1b8359e10 UIKitCore!-[UICollectionView touchesEnded:withEvent:] 0x1b46ef730 libsystem_malloc.dylib!nanov2_calloc 0x1b46f4100 libsystem_malloc.dylib!calloc 0x1b48ddae4 CoreFoundation!-[__NSSetM addObject:] 0x1b473926c libobjc.A.dylib!objc_autoreleasePoolPop 0x1b4a14320 CoreFoundation!_CFAutoreleasePoolPop 0x1b48dd594 CoreFoundation!-[__NSSetM enumerateObjectsWithOptions:usingBlock:] 0x1b498bc7c CoreFoundation!__NSSetM_new 0x1bb64e208 QuartzCore!CA::Layer::retain_parent(CA::Transaction*) const 0x1b8b5d9e8 UIKitCore!forwardTouchMethod 0x1b8b5dae4 UIKitCore!-[UIResponder touchesEnded:withEvent:] ``` 在 WeChat 部分的回溯没有符号,因为已经去掉了。 ## lldb 在 lldb 中也可以看到类似的结果: ```sh (lldbinit) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000105334f04 WeChat`___lldb_unnamed_symbol201841$$WeChat frame #1: 0x0000000104b32f80 WeChat`___lldb_unnamed_symbol170264$$WeChat + 160 frame #2: 0x0000000104e06de4 WeChat`___lldb_unnamed_symbol182196$$WeChat + 2600 frame #3: 0x000000010294cde8 WeChat`___lldb_unnamed_symbol8796$$WeChat + 248 frame #4: 0x00000001041bb930 WeChat`___lldb_unnamed_symbol129751$$WeChat + 624 frame #5: 0x00000001b8331e34 UIKitCore`-[UICollectionView _selectItemAtIndexPath:animated:scrollPosition:notifyDelegate:deselectPrevious:] + 952 frame #6: 0x00000001b8359e10 UIKitCore`-[UICollectionView touchesEnded:withEvent:] + 572 frame #7: 0x00000001b8b5d9e8 UIKitCore`forwardTouchMethod + 332 frame #8: 0x00000001b8b5dae4 UIKitCore`-[UIResponder touchesEnded:withEvent:] + 64 frame #9: 0x00000001b8b5d9e8 UIKitCore`forwardTouchMethod + 332 frame #10: 0x00000001b8b5dae4 UIKitCore`-[UIResponder touchesEnded:withEvent:] + 64 frame #11: 0x00000001b8b5d9e8 UIKitCore`forwardTouchMethod + 332 frame #12: 0x00000001b8b5dae4 UIKitCore`-[UIResponder touchesEnded:withEvent:] + 64 frame #13: 0x00000001b86e6478 UIKitCore`_UIGestureEnvironmentUpdate + 6992 frame #14: 0x00000001b86e48dc UIKitCore`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 380 frame #15: 0x00000001b86e4698 UIKitCore`-[UIGestureEnvironment _updateForEvent:window:] + 248 frame #16: 0x00000001b8b6d654 UIKitCore`-[UIWindow sendEvent:] + 3512 frame #17: 0x00000001b8b48840 UIKitCore`-[UIApplication sendEvent:] + 348 frame #18: 0x0000000103de4920 WeChat`___lldb_unnamed_symbol111457$$WeChat + 404 frame #19: 0x00000001ebaaa87c UIKit`-[UIApplicationAccessibility sendEvent:] + 100 ``` 有符号更好,没有也无所谓,我们还是可以通过地址在逆向工具中查看代码。不过考虑到 ASLR 的存在,需要确认一下镜像的加载地址: ```sh (lldbinit) vmmap /WeChat 000102790000 - 00010d6d4000 af44000 R-X /private/var/containers/Bundle/Application/2A5D623F-5F8E-4A99-96C8-8CBD00D8B6BE/WeChat.app/WeChat 0 __TEXT 00010d6d4000 - 00010fc40000 256c000 RW- /private/var/containers/Bundle/Application/2A5D623F-5F8E-4A99-96C8-8CBD00D8B6BE/WeChat.app/WeChat af44000 __DATA 00010fc40000 - 000110ac4000 e84000 R-- /private/var/containers/Bundle/Application/2A5D623F-5F8E-4A99-96C8-8CBD00D8B6BE/WeChat.app/WeChat cb68000 __LINKEDIT ``` > `vmmap` 是我自己定义的一个 lldb 命令,参考了 gef 和 pwndbg 在 gdb 中的实现 这里加载地址是 `0x000102790000`,而在 IDA 中 text 段的加载地址是 `0x100004000`,可以在后者进行 rebase,但是因为函数不多,所以先手动查找。另外用 lldb 也可以直接查看对应函数地址的偏移: ```sh (lldbinit) image lookup -a 0x0000000105334f04 Address: WeChat[0x0000000102ba4f04] (WeChat.__TEXT.__text + 45747972) Summary: WeChat`___lldb_unnamed_symbol201841$$WeChat (lldbinit) image lookup -a 0x0000000104b32f80 Address: WeChat[0x00000001023a2f80] (WeChat.__TEXT.__text + 37351296) Summary: WeChat`___lldb_unnamed_symbol170264$$WeChat + 160 (lldbinit) image lookup -a 0x0000000104e06de4 Address: WeChat[0x0000000102676de4] (WeChat.__TEXT.__text + 40316388) Summary: WeChat`___lldb_unnamed_symbol182196$$WeChat + 2600 (lldbinit) image lookup -a 0x000000010294cde8 Address: WeChat[0x00000001001bcde8] (WeChat.__TEXT.__text + 1805800) Summary: WeChat`___lldb_unnamed_symbol8796$$WeChat + 248 (lldbinit) image lookup -a 0x00000001041bb930 Address: WeChat[0x0000000101a2b930] (WeChat.__TEXT.__text + 27425072) Summary: WeChat`___lldb_unnamed_symbol129751$$WeChat + 624 ``` ## 符号恢复 根据 lldb 中每个函数地址在 TEXT 段中的偏移,可以在 IDA 中直接跳转到对应函数(返回值),整理对应的调用函数分别是: - `-[BaseMsgContentViewController SendEmoticonMesssageToolView:]` (45747972) - `-[MMInputToolView didSelectorSelfDefinedEmotcion:]` (37351296) - `-[EmoticonBoardView onTapEmoticonWrap:atIndex:maxCountPerLine:fromSection:]` (40316388) - `-[EmoticonBoardCrossCollectionController onEmoticonPageCellTapEmoticonWrap:atIndex:pid:maxCountPerLine:]` (1805800) - `-[EmoticonBoardCrossCollectionEmoticonPageCell collectionView:didSelectItemAtIndexPath:]` (27425072) 既然 IDA 可以识别出对应 OC 函数的符号,那么理论上这些符号也是可以还原的,如果经常需要进行动态分析,那么可以通过一些方法自动化恢复对应的符号,可以参考 [iOS符号表恢复][rs2] 以及 [restore-symbol][rs3] 等项目。 # 静态分析 通过动态分析找到了着手点,以及通过回溯调用栈找到了一些相关函数,其中并没有一目了然的设置骰子点数的函数 `setDiceValue`,所以要想改点数还需要对上面的函数进行进一步分析。到这里,逆向工程是不可避免了,一般而言我是能躺着绝不坐着,能坐着绝不站着,不到万不得已是不去逆向的。 花开两朵,各表一枝,在前面砸完壳的第一时间我就预感到需要逆向,所以早早地把 200MB 的 MachO 丢进了 IDA,在文章写到这里时已经过去了四个小时,期间看了两集动漫,但是 IDA 还是没有全部分析完。所以在等待的时间里,先来简单介绍些 ObjectiveC 的底层实现。 ## ObjectiveC 101 在学习 OC 的时候,总会看到说 OC 语言实现面向对象是通过发送消息,具体是怎么发送呢?因此在实际逆向之前,我们先来自己写一个程序来进行分析,以便了解 OC 的调用原理。 以下面简单的 helloworld 为例: ```objc #import <Foundation/Foundation.h> @interface MyClass: NSObject - (void)insMethod:(const char *)a1 arg2:(int)a2; + (void)clsMethod:(const char *)a1 arg2:(int)a2; @end @implementation MyClass - (void)insMethod:(const char *)a1 arg2:(int)a2 { NSLog(@"insMethod called, self=%p, a1=%s", self, a1); } + (void)clsMethod:(const char *)a1 arg2:(int)a2 { NSLog(@"clsMethod called, self=%p, a1=%s", self, a1); } @end int main() { MyClass *c = [[MyClass alloc] init]; [MyClass clsMethod:"hello" arg2:1]; [c insMethod:"world" arg2:2]; return 0; } ``` 主要调用了两个方法,一个类方法和一个成员方法,编译后查看其汇编代码如下: ```asm (lldbinit) disassemble main @ test_hello: 0x100003e90: push rbp 0x100003e91: mov rbp, rsp 0x100003e94: sub rsp, 0x10 0x100003e98: mov dword ptr [rbp - 0x4], 0x0 -> 0x100003e9f: mov rax, qword ptr [rip + 0x424a] ; (void *)0x0000000100008120: MyClass 0x100003ea6: mov rdi, rax 0x100003ea9: call 0x100003f04 ; symbol stub for: objc_alloc_init 0x100003eae: mov qword ptr [rbp - 0x10], rax 0x100003eb2: mov rax, qword ptr [rip + 0x4237] ; (void *)0x0000000100008120: MyClass 0x100003eb9: mov rsi, qword ptr [rip + 0x4220] ; "clsMethod:arg2:" 0x100003ec0: mov rdi, rax 0x100003ec3: lea rdx, [rip + 0xa8] ; "hello" 0x100003eca: mov ecx, 0x1 0x100003ecf: call qword ptr [rip + 0x12b] ; (void *)0x00007fff20439d00: objc_msgSend 0x100003ed5: mov rax, qword ptr [rbp - 0x10] 0x100003ed9: mov rsi, qword ptr [rip + 0x4208] ; "insMethod:arg2:" 0x100003ee0: mov rdi, rax 0x100003ee3: lea rdx, [rip + 0x8e] ; "world" 0x100003eea: mov ecx, 0x2 0x100003eef: call qword ptr [rip + 0x10b] ; (void *)0x00007fff20439d00: objc_msgSend 0x100003ef5: xor eax, eax 0x100003ef7: add rsp, 0x10 0x100003efb: pop rbp 0x100003efc: ret ``` 且不管初始化部分,后面两个函数调用最终都进入了 [objc_msgSend][send] 函数,在苹果官网可以看到其函数原型: ```c id objc_msgSend(self, op, ...); ``` 其中各个参数为: 1. self: 消息的接收方,对于类方法为指向类的指针,对于对象方法而言为对象指针; 2. op: 为消息的标识符,也称为 selector,实际上是一个表示对应方法名称的字符串; 3. ... 其他方法参数; 回到上面的例子,第一次类方法调用的参数为: ```c (lldbinit) p/x $rdi (unsigned long) $29 = 0x0000000100008120 (lldbinit) x/1s $rsi 0x100003f86: "clsMethod:arg2:" (lldbinit) x/1s $rdx 0x100003f72: "hello" (lldbinit) p/x $rcx (unsigned long) $32 = 0x0000000000000001 ``` 第二次成员方法的调用参数为: ```sh (lldbinit) p/x $rdi (unsigned long) $54 = 0x0000000100208340 (lldbinit) x/1s $rsi 0x100003f96: "insMethod:arg2:" (lldbinit) x/1s $rdx 0x100003f78: "world" (lldbinit) p/x $rcx (unsigned long) $57 = 0x0000000000000002 ``` 程序的输出如下: ```sh 2021-04-10 22:54:19.487148+0800 test_hello[99332:5120374] clsMethod called, self=0x100008120, a1=hello 2021-04-10 22:54:54.565229+0800 test_hello[99332:5120374] insMethod called, self=0x100208340, a1=world ``` 所以,ObjectiveC 在实现上还是挺接近 C 语言的,这对于我们逆向而言方便很多。 ## 头铁逆向 了解了简单的 OC 逆向之后,IDA 也跑的差不多了。根据栈回溯对应 OC 函数的名称,逐级往上看。首先是 `didSelectorSelfDefinedEmotcion`这个函数,表示选中了某个自定义表情,里面只是一些发送操作,所以并不是我们想要的; 然后是 `-[EmoticonBoardView onTapEmoticonWrap:atIndex:maxCountPerLine:fromSection:]` ,该函数中主要是判断所选择的表情是否为自拍表情或者自定义表情,如果是的话就进行异步上传。其调用`didSelectorSelfDefinedEmotcion` 的参数为 a3,即一个对象指针,虽然还不知道是哪个对象,但知道其包含这些属性(方法): - m_emojiInfo - m_isAsyncUpload - `attachObject:forKey:` 在 class-dump 导出的头文件中搜索,可以发现是`CEmoticonWrap` 类: ```objc #import <objc/NSObject.h> #import "PBCoding-Protocol.h" @class EmojiInfoObj, NSData, NSString; @interface CEmoticonWrap : NSObject <PBCoding> { _Bool m_bCanDelete; _Bool m_isAsyncUpload; _Bool m_isRemoteRecommed; _Bool m_isLastSended; unsigned int m_uiType; unsigned int m_uiGameType; unsigned int m_lastUsedTime; unsigned int m_extFlag; NSString *m_nsAppID; NSString *m_nsThumbImgPath; NSString *m_query; EmojiInfoObj *m_emojiInfo; NSData *_m_imageData; } ``` 该函数主要是执行上传并记录一些信息,更新一些 emojiInfo 的字段,所以还是要继续往上走; 接着是`-[EmoticonBoardView onTapEmoticonWrap:atIndex:maxCountPerLine:fromSection:]`,该函数主要是作为 delegate 进行转发,没有什么实际功能。但是从名字可以猜测自定义表情栏是通过不同的行实现,而每行中每个表情又对应一个 Cell。 按照回溯的堆栈都看完了,也没有发现和骰子相关的代码。不管怎样,骰子的点数总归是在设置骰子图片之前确定的,而根据上面的逆向可以知道,自定义表情的图片应该是定义在 `[CEmoticonWrap m_emojiInfo]`中,该类的属性如下: ```objc @interface EmojiInfoObj : MMObject <PBCoding, NSCopying> { _Bool _disableExtern; NSString *md5; NSString *url; NSString *thumbUrl; NSString *designerId; NSString *encryptUrl; NSString *aesKey; NSString *productId; NSString *externUrl; NSString *attachedText; NSString *externMd5; NSString *activityId; NSString *attachedTextColor; NSString *lensId; NSString *linkId; NSString *_tpUrlString; NSString *_authkey; } ``` 接下来还是要通过结合动态分析来确定骰子图片加载的时机。 ## 动静结合 还是回到`SendEmoticonMesssageToolView`函数,通过逆向可以得知其参数为`CEmoticonWrap`类型,修改前面打印栈 callback 的脚本,令其打印参数的各个字段,如下: ```sh [+] 21:50:18.958 Called SendEmoticonMesssageToolView: [+] 21:50:18.958 emotion: <CEmoticonWrap: self.m_uiType=1, self.m_bCanDelete=1, self.m_uiGameType=2, self.m_nsAppID=(null), self.m_extFlag=0, self.m_nsThumbImgPath=(null), self.m_lastUsedTime=0, self.m_isAsyncUpload=0, self.m_isRemoteRecommed=0, self.m_isLastSended=0, self.m_query=(null), self.m_emojiInfo=<EmojiInfoObj: 0x2813c77b0> { activityId = <nil>; aesKey = <nil>; attachedText = <nil>; attachedTextColor = <nil>; authkey = <nil>; designerId = <nil>; disableExtern = 0; encryptUrl = <nil>; externMd5 = <nil>; externUrl = <nil>; lensId = <nil>; linkId = <nil>; md5 = "dice_emoticon_md5"; productId = "custom_emoticon_pid"; thumbUrl = <nil>; tpUrlString = <nil>; url = <nil> }> ``` 也就是说进入函数之时这些参数都是空的,**m_emojiInfo** 的 md5 为 **“dice_emoticon_md5”**,此时骰子图像并没有真正生成,所以印证了我们之前的猜想,即实际的操作还在后面。那么栈回溯是不是白看了?也不尽然,至少至少了参数格式和调用流程(逆向嘛,总是要学会安慰自己)。 回过头来继续看这个函数: ```c void __cdecl -[BaseMsgContentViewController SendEmoticonMesssageToolView:](BaseMsgContentViewController *self, SEL a2, id emotion) { id v4; // x19 id v5; // x20 id *v6; // x21 id v7; // x24 unsigned __int8 v8; // w23 id *v9; // x21 id v10; // x0 id *v11; // x21 void *v12; // x20 id v13; // x24 unsigned int v14; // w22 id *v15; // x21 v4 = objc_retain(emotion); v5 = objc_loadWeakRetained((id *)&self->m_delegate); if ( ((unsigned int)objc_msgSend(v5, "respondsToSelector:", "CanSendEmoticonMessage") & 1) == 0 ) goto LABEL_6; v7 = objc_loadWeakRetained(v6); v8 = (unsigned __int8)objc_msgSend(v7, "CanSendEmoticonMessage"); objc_release(v7); objc_release(v5); if ( (v8 & 1) != 0 ) { v10 = objc_loadWeakRetained(v9); if ( v10 ) { v12 = v10; v13 = objc_loadWeakRetained(v11); v14 = (unsigned int)objc_msgSend(v13, "respondsToSelector:", "SendEmoticonMessage:"); objc_release(v13); objc_release(v12); if ( v14 ) { v5 = objc_loadWeakRetained(v15); objc_msgSend(v5, "SendEmoticonMessage:", v4); LABEL_6: objc_release(v5); goto LABEL_7; } } } LABEL_7: objc_release(v4); } ``` 又是一个 delegate 模式,直接打印出实例: ```js kObjC.traceMethod("BaseMsgContentViewController", "- SendEmoticonMesssageToolView:", { onEnter(args) { log.i("Called", args[1].readCString()); const vc = new ObjC.Object(args[0]); const emotion = new ObjC.Object(args[2]); log.i("vc:", vc); log.i("m_delegate:", vc.valueForKey_("m_delegate")); } }); ``` 结果如下: ``` [+] 19:15:24.203 Called SendEmoticonMesssageToolView: [+] 19:15:24.204 vc: <BaseMsgContentViewController: 0x1200b8e00> [+] 19:15:24.206 m_delegate: <WeixinContentLogicController: 0x11f340a90> ``` 是个 WeixinContentLogicController,那就继续找它的`SendEmoticonMessage:`方法,没有的话就在父类或者 Category 里找。因为程序太大,我的破电脑在 IDA 里搜索比较卡,所以有个小技巧是直接通过函数名跳转,对应的 IDA 函数为名为 `-[WeixinContentLogicController SendEmoticonMessage:]`,当然实际并没有这个函数,而是在父类中定义的 `-[BaseMsgContentLogicController SendEmoticonMessage:]`。 ![ida.png][ida.png] 在上面高亮的一行,IDA 反编译的结果有点问题,显示 V7 是未初始化的,遇到这种情况直接看汇编即可,汇编是不会骗人的: ![asm][asm.png] 即第一个参数实际上是 PluginUtil 类的地址,这里调用的则是类方法`+[PluginUtil isPluginUserName:]`。其中 `m_uiGameType` 前面已经通过 frida 打印出来了,其值是 2,继续动态分析得知进入 else 分支。按照这种动静结合的策略,最终分析得到向前的调用过程: - `-[BaseMsgContentLogicController SendEmoticonMessage:]` - `+[GameController GameEmoticonMsgForEmoticonWrap:]` - `+[GameController SetGameContentForMsgWrap:]` (参数类型是 **CMessageWrap**) 最终设置内容如下: ![random][random.png] 看到这逻辑应该很明显了,如果游戏类型为 1,则有: ```c content = 1 + rand - rand / 3 * 3 = 1 + rand % 3 ``` 如果游戏类型为 2,则有: ```c content = 4 + rand - rand / 6 * 6 = 4 + rand % 6 ``` 由于我们选的是扔骰子,游戏类型为 2,骰子的 content 取值范围为 `4 ~ 9`,正好对应 6 个点数;有理由猜测前面一个 3 个点数的应该就对应剪刀石头布游戏。 这里有两个值得注意的点,一是 random 函数调用了两次,但是后面一次的结果并没有使用,因此目的可能通过减少随机数连续性增加破解伪随机序列的难度;另外一个点是随机后的内容经过了 md5,所以修改点数的时候可能要一并修改哈希,否则会导致校验失败,或者直接修改 random 函数的返回值也是可以的。 ```js kNative.trace(null, "random", { onLeave(retval) { log.i("random:", retval.toInt32()); retval.replace(ptr(5)); } }); ``` 实现效果如下: ![dice][dice.gif] 至此,修改骰子点数的核心功能就分析完毕了。 # 再谈 Hook 说实话最近听 hook 这个词已经听到腻了,不管是 plt hook 还是 inline hook,其本质都是在运行时修改现有的代码,进而达到修改控制流的目的。对于二进制汇编代码的修改大同小异,不过对于 iOS 程序而言其实也有其他选择,下面逐一介绍: ## Inline Hook 这是最为通用的 hook 方式,即在指定地址前增加跳转指令,保存现场并跳转到用户指令,执行完后还原现场并返回执行。inline hook 可以实现汇编指令级别的 trace,也是大部分动态跟踪工具实现的基础。下图为 frida-gum 实现 inline hook 的的大致流程: ![frida][frida.png] 当然原理归原理,要基于公式实现核弹在工程上还要做很多工作,比如代码段通常是不可修改的,因此需要 mmap 拷贝一份到自己的地盘去进行修改;另外保存和恢复现场的操作大多只能用汇编写,因此要实现跨平台还要针对不同指令集进行测试等等。这一切繁重的工作别人都已经为我们做好了,我们需要做的就只是调用一下 `Interceptor.attach` ,不得不感慨生活真幸福。 ## Method Swizzling 前面我们说过,Objective-C 实现面向对象是基于消息的发送,那么针对 Objc 的调用,我们其实可以动态的修改某个 selector 对应的实现,这个过程就称之为 method swizzling。苹果官方也提供了对应的 API: ```c void method_exchangeImplementations(Method m1, Method m2); ``` 该方法相当于下面指令的原子实现: ```c IMP imp1 = method_getImplementation(m1); IMP imp2 = method_getImplementation(m2); method_setImplementation(m1, imp2); method_setImplementation(m2, imp1); ``` 还是那句老话,原理是简单的,实现是复杂的。现实中很少这样直接交换两个方法实现,而是在之前进行一些必要的检查,如下所示: ```objc + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class aClass = [self class]; SEL originalSelector = @selector(method_original:); SEL swizzledSelector = @selector(method_swizzle:); Method originalMethod = class_getInstanceMethod(aClass, originalSelector); Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); BOOL didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } ``` 直接交换方法是危险的,因为如果类中没有实现该方法,那么 **class_getInstanceMethod** 会返回某个父类中的 Method 对象,实际交换的则是父类的方法,这样其他父类和子类的调用就会出现意想不到的问题。因此上面的代码会判断 selector 方法不存在的情况。 另外还要注意的是 selector 命名冲突问题、重复 swizzling 问题以及 `_cmd` 被修改的问题等等,因此也就有了 <a href="elink@9f1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5j5h3u0G2N6X3W2C8i4K6u0r3f1W2y4e0N6$3W2*7P5X3I4W2">RSSwizzle</a> 或者 <a href="elink@751K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5k6h3&6@1P5Y4y4U0K9q4)9J5c8X3A6J5M7%4N6A6P5Y4A6D9k6b7`.`.">jrswizzle</a> 这些完善的实现方案。对于开发人员来说这是个需要仔细考虑的问题,毕竟这影响了程序的稳定性。 值得一提的是,frida 中对于 Objc 也提供了基于 method swizzling 的 hook 方案: ```js const myMethod_ = ObjC.classes.MyClass.myMethod_; myMethod_.implementation = ObjC.implement(myMethod_, function (handle, selector, arg0) { log.i(`self=${handle}, selector=${selector}, arg0=${arg0}`); }); ``` ## NSProxy 在 Java 中,我们可以通过 <a href="elink@9eeK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1L8$3y4K6i4K6u0W2L8%4u0S2j5$3I4W2i4K6u0W2j5$3!0E0i4K6u0r3K9X3q4$3j5i4y4W2i4K6u0r3y4#2)9J5c8X3c8G2j5%4y4Q4x3V1k6S2M7r3W2Q4x3V1k6B7j5i4k6S2i4K6u0r3L8r3q4F1k6#2)9J5c8Y4u0W2k6X3I4W2j5%4c8Q4x3V1k6b7M7X3!0^5P5g2)9J5k6h3S2@1L8h3H3`.">java.lang.reflect.Proxy</a> 基于反射机制来实现方法的动态代理,这种方式在很多地方被应用于 AOP 即切面编程。而 Objective-C 也有类似的方式,即 NSProxy,该类的定义如下: ```objc /* NSProxy.h Copyright (c) 1994-2019, Apple Inc. All rights reserved. */ #import <Foundation/NSObject.h> @class NSMethodSignature, NSInvocation; NS_ASSUME_NONNULL_BEGIN NS_ROOT_CLASS @interface NSProxy <NSObject> { __ptrauth_objc_isa_pointer Class isa; } + (id)alloc; + (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; + (Class)class; - (void)forwardInvocation:(NSInvocation *)invocation; - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available"); - (void)dealloc; - (void)finalize; @property (readonly, copy) NSString *description; @property (readonly, copy) NSString *debugDescription; + (BOOL)respondsToSelector:(SEL)aSelector; - (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos); - (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos); // - (id)forwardingTargetForSelector:(SEL)aSelector; @end NS_ASSUME_NONNULL_END ``` 可以看到其遵守 NSObject protocol,并且第一个 ivar 是 isa 指针,因此可以当成是一个 NSObject 或者其派生类使用,但是它并不是一个 NSObject 的子类。使用上只需要实现两个方法: ```objc - (void)forwardInvocation:(NSInvocation *)invocation; - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel; ``` **NSInvocation** 封装了一个方法调用的全部信息,包括调用参数和返回值。一个示例实现如下: ```objc #import <Foundation/Foundation.h> @interface MyProxy : NSProxy { id victim_; } + (id)hookObject:(id)obj; @end @implementation MyProxy + (id)hookObject:(id)obj { MyProxy *p = [MyProxy alloc]; p->victim_ = obj; return p; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [victim_ methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { if ([victim_ respondsToSelector:invocation.selector]) { NSString *selectorName = NSStringFromSelector(invocation.selector); NSLog(@"ENTER %@", selectorName); [invocation invokeWithTarget:victim_]; NSLog(@"LEAVE %@", selectorName); } } @end int main(int argc, char *argv[]) { dispatch_semaphore_t sem = dispatch_semaphore_create(0); NSURL *url = [MyProxy hookObject:[NSURL URLWithString:@"https://evilpan.com"]]; NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_semaphore_signal(sem); }]; [task resume]; dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); return 0; } ``` 输出: ```sh $ ./test_proxy 2021-04-11 11:18:55.193 test_proxy[15011:5637371] ENTER absoluteURL 2021-04-11 11:18:55.194 test_proxy[15011:5637371] LEAVE absoluteURL ``` 直接用我们的 MyProxy 对象替换了原始的 NSURL 对象,这样 NSURL 对象中所调用的所有方法都由我们的 Proxy 对象来进行代理,因此可以在执行原始函数之前以及返回之前执行我们自己的操作,从而实现 hook 的功能。 # 其他 补充一些前面没有提到的东西。 ## 瘦身 Mach-O程序是支持多架构的,例如同一个程序既能运行在arm32位也能运行在64位机器上。多架构(multiarch)的应用通常比原程序体积要大,但是比两个单架构的程序要小,因为多架构应用会共享资源。 iOS应用开发者为了保证兼容性通常会选择同时支持armv7和arm64,但对于逆向分析却不必要。因此,我们可以先对其进行瘦身(thin),只保留arm64架构即可。首先查看应用是否为多架构: ```sh # 通过file命令查看 $ file pp2048/Payload/2048.app/2048 pp2048/Payload/2048.app/2048: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64] pp2048/Payload/2048.app/2048 (for architecture armv7): Mach-O executable arm_v7 pp2048/Payload/2048.app/2048 (for architecture arm64): Mach-O 64-bit executable arm64 # 或者通过lipo命令查看 $ lipo -detailed_info pp2048/Payload/2048.app/2048 Fat header in: pp2048/Payload/2048.app/2048 fat_magic 0xcafebabe nfat_arch 2 architecture armv7 cputype CPU_TYPE_ARM cpusubtype CPU_SUBTYPE_ARM_V7 offset 16384 size 8838944 align 2^14 (16384) architecture arm64 cputype CPU_TYPE_ARM64 cpusubtype CPU_SUBTYPE_ARM64_ALL offset 8863744 size 10265808 align 2^14 (16384) ``` 只保留arm64,可以使用lipo或者ditto,二者都是苹果自带的: ```sh # 使用lipo工具 $ lipo -thin arm64 pp2048/Payload/2048.app/2048 -output thin_2048 # 或者使用ditto工具 $ ditto --arch arm64 pp2048/Payload/2048.app/2048 thin_2048 ``` 输出的文件是一样的。 ## Cycript 前文中有用到 cycript 来注入动态库进行 UI 界面分析,这里再补充一下。[cycript][cycript] 由 saurik 大神开发,在 JavaScript REPL 中支持 ObjectiveC 的混合语法,使用起来比较直观。比如: ```sh cy# [[UIApplication sharedApplication] keyWindow] #"<iConsoleWindow: 0x11581f120; baseClass = UIWindow; frame = (0 0; 1194 834); gestureRecognizers = <NSArray: 0x2800d0570>; layer = <UIWindowLayer: 0x280eb1800>>" ``` 支持直接调用 c 函数,并且直接使用 enum 常量: ```sh cy# dlopen (extern "C" void *dlopen(char const*, int)) cy# RTLD_NOW 2 cy# dlopen("/usr/lib/libFLEX.dylib", RTLD_NOW); (typedef void*)(0x11a23e180) ``` 前面使用 FLEX 进行 UI 分析就是直接通过 dlopen 注入动态库实现的(其实注入 Reveal 也是)。cycript 的功能很强大,但是文档不多,主要是参考官方的 manual。一些参考网站如下: - <mark class="encrypted">6d3K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3y4&6j5%4u0A6M7s2c8Q4x3X3g2G2M7X3N6Q4x3V1k6E0j5h3&6#2j5h3I4Q4x3V1j5`.</mark> - <mark class="encrypted">ef0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6A6M7r3S2G2L8X3g2V1k6i4k6%4K9h3E0A6i4K6u0W2L8X3g2@1i4K6u0r3K9h3&6V1k6i4S2Q4x3X3g2H3K9s2m8Q4x3V1k6o6P5h3y4J5K9i4m8@1i4K6g2X3g2s2u0A6j5$3E0K6</mark> - <mark class="encrypted">9b2K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6A6L8%4y4J5k6g2)9J5k6h3y4G2L8g2)9J5c8Y4c8Q4x3V1k6U0P5h3y4J5K9i4m8@1i4K6u0r3y4K6f1#2y4l9`.`.</mark> [cycript]: <mark class="encrypted">e3dK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3y4&6j5%4u0A6M7s2c8Q4x3X3g2G2M7X3N6Q4x3V1j5`.</mark> ## Tweak 在越狱 iOS 中有类似于 Andoid Xposed 的 patch 框架 <a href="elink@091K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6A6M7r3S2G2L8X3g2V1k6i4k6%4K9h3E0A6i4K6u0W2L8X3g2@1i4K6u0r3K9h3&6V1k6i4S2Q4x3X3g2H3K9s2m8Q4x3V1k6o6P5h3c8A6j5g2)9#2k6W2y4#2j5Y4y4@1M7X3q4@1k6b7`.`.">Cydia Substrate</a>,其提供了 <a href="elink@fe5K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3y4&6k6r3W2S2M7%4g2T1M7%4c8J5j5i4c8W2i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8A6i4K6u0r3j5#2)9J5c8V1#2e0d9r3!0G2K9@1#2W2M7%4y4S2k6$3g2q4P5q4)9J5c8R3`.`.">C API</a> 给越狱 APP 的开发者来实现对特定方法的动态修改,还提供了基于 plist 的声明文件,让开发者可以选择指定的 APP 进行 hook,同时也很好地处理了同一个方法被 Hook 多次的问题。基于 Substrate 开发的 dylib 称为 Tweak,由 Substrate 根据 plist 在应用启动时选择进行注入。 一个 hook 示例如下: ```objc NSString *(*oldDescription)(id self, SEL _cmd); // implicit self and _cmd are explicit with IMP ABI NSString *newDescription(id self, SEL _cmd) { NSString *description = (*oldDescription)(self, _cmd); description = [description stringByAppendingString:@"!"]; return description; } MSHookMessageEx( [NSObject class], @selector(description), &newDescription, &oldDescription ); ``` 如果觉得这样写还是太麻烦,可以使用 <a href="elink@b16K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3W2H3K9r3!0F1k6h3c8W2N6Y4N6A6K9$3W2Q4x3X3g2F1k6i4c8Q4x3V1k6A6L8X3c8W2P5q4)9J5k6i4m8Z5M7q4)9J5c8V1I4G2k6$3!0K6">Logos</a> 来简化代码,这是个基于预处理库的组件,包含一系列以`%`开头的宏方便编写 hook 代码,示例如下: ```objc %hook SBApplicationController -(void)uninstallApplication:(SBApplication *)application { NSLog(@"Hey, we're hooking uninstallApplication:!"); %orig; // Call the original implementation of this method return; } %end ``` Logos 现在是 <a href="elink@c61K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6@1K9r3g2G2M7#2)9J5c8Y4c8Z5k6h3!0K6">Theos</a> 组件的一部分,可以通过 NIC 工程模板系统快速地创建一个基于 Makefile 的 Tweak 项目。初学者可以通过参考一些 <a href="elink@64bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3j5h3y4W2M7Y4c8G2M7%4g2K6f1X3g2H3L8#2)9J5c8V1!0H3k6h3&6Q4x3X3c8e0L8%4g2J5j5$3g2Q4x3X3c8f1N6$3g2S2K9%4x3`.">开源的 Tweaks</a> 来熟悉开发流程。 ## LLDB LLDB是个功能十分强大的调试器,在有些场景下比GDB还要好用。关于GDB命令可以参考[GDB调试笔记][p2],而GDB命令对应的LLDB命令可以参考[GDB and LLDB Command Examples][db],这里只介绍一些常用的例子: 在一个ObjectiveC类中的所有方法下设置断点(正则表达式): ``` (lldb) breakpoint set -r '\[ClassName .*\]$' ``` 打印 Objective-C 对象: ``` (lldb) po addr ``` 查看各个模块的映射地址(target module list): ``` (lldb) image list -h -f ``` 设置`zero flag`,常用于修改跳转指令: ``` (lldb) register write rflags `$rflags|0x40` ``` 更详细的功能以及 lldb 脚本的编写可以参考下面的链接: - <mark class="encrypted">b7dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9L8r3c8T1i4K6u0W2L8r3I4$3L8g2)9J5k6h3!0J5k6#2)9J5c8Y4g2K6k6g2)9J5c8Y4c8#2N6r3!0J5K9h3q4D9i4K6u0W2K9s2c8E0L8l9`.`.</mark> - <mark class="encrypted">d68K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9L8r3c8T1i4K6u0W2L8r3I4$3L8g2)9J5k6h3!0J5k6#2)9J5c8Y4g2K6k6g2)9J5c8Y4m8&6N6r3S2G2L8W2)9J5k6s2u0W2k6X3g2J5k6h3&6U0k6g2)9J5k6h3S2@1L8h3H3`.</mark> - <mark class="encrypted">a57K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4A6L8W2)9J5c8X3I4D9k6r3u0Q4x3V1k6W2P5r3q4E0M7r3I4W2M7#2)9J5c8Y4m8&6N6r3S2G2L8R3`.`.</mark> # 后记 本文以微信扔骰子小游戏为例,介绍了一次完整的 iOS 逆向分析过程。以最初的越狱开始,分别介绍了砸壳、UI 分析、动态分析和静态分析的具体操作,最后还介绍了 iOS 中常见的 hook 方案以及一些越狱开发相关的工具。之前分析过 Android 中微信的扔骰子,里面大部分代码都经过了混淆,相比之下 iOS 中逆向代码就直观很多。现今很多应用都是同时支持两个平台,在一个平台中受阻可以切换到另一个平台说不定有意想不到的收获,比如对于 `Unity3D` 中 **C#** 代码的逆向,Android 平台就比 iOS 要方便点 (因为 **IL2CPP**)。因此,对于有志深造的逆向工程师而言,保持灵活和开放的心态也是至关重要的。 # 参考资料 - <a href="elink@968K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6A6N6W2u0G2k6s2u0A6k6%4g2W2P5V1y4m8i4K6u0r3f1V1g2Q4x3X3c8A6e0#2y4Q4x3X3c8m8M7s2m8K6">RE-iOS-Apps</a> - <mark class="encrypted">1abK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6A6M7r3S2G2L8X3g2V1k6i4k6%4K9h3E0A6i4K6u0W2L8X3g2@1i4K6u0r3K9h3&6V1k6i4S2Q4x3X3g2H3K9s2l9`.</mark> [p1]: <mark class="encrypted">670K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2N6X3W2D9M7r3q4F1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9I4z5g2)9J5c8U0l9@1i4K6u0r3x3o6N6Q4x3V1k6A6L8%4y4Q4x3X3c8J5k6i4k6W2M7Y4y4W2i4K6u0V1j5X3q4K6K9h3y4K6i4K6u0r3</mark> [p2]: <mark class="encrypted">3e9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2N6X3W2D9M7r3q4F1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9I4y4g2)9J5c8U0p5H3i4K6u0r3x3U0S2Q4x3V1k6s2c8p5u0Q4x3X3c8f1K9i4m8K6i4K6u0r3</mark> [oob]: <mark class="encrypted">85eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3N6K6i4K6u0W2j5$3S2J5L8$3#2A6N6h3#2Q4x3X3g2G2M7X3N6Q4x3V1k6H3i4K6u0r3M7s2u0G2K9X3g2U0N6q4)9J5k6s2A6W2M7X3!0Q4x3V1k6A6M7%4y4#2k6i4y4Q4x3V1k6V1k6i4c8S2K9h3I4Q4x3@1k6A6k6q4)9K6c8o6p5&6z5l9`.`.</mark> [rs2]: <mark class="encrypted">60aK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0D9L8$3N6Q4x3X3g2A6L8h3A6#2L8W2)9J5k6h3&6W2N6q4)9J5c8Y4m8G2M7%4c8K6i4K6u0r3M7X3g2K6N6r3!0J5k6g2)9J5k6s2y4&6L8h3u0G2L8q4)9J5k6r3!0X3i4K6u0V1K9f1!0e0i4K6u0V1j5i4m8H3i4K6u0r3</mark> [rs3]: <mark class="encrypted">e97K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6@1L8$3u0W2k6Y4g2@1N6i4u0W2M7W2)9J5c8Y4u0W2M7%4c8G2M7X3g2Q4x3X3c8K6P5h3#2T1L8$3H3`.</mark> [db]: <mark class="encrypted">690K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2S2M7s2m8D9k6g2)9J5k6h3y4G2L8g2)9J5c8X3I4A6j5Y4u0S2M7Y4W2Q4x3V1k6S2M7X3y4Z5K9i4k6W2i4K6u0r3k6r3!0U0N6h3#2W2L8Y4c8S2N6r3W2G2L8W2)9J5c8V1W2p5c8i4y4Q4x3V1k6o6L8$3&6U0k6i4m8@1N6h3q4D9i4K6u0r3k6$3c8T1i4K6g2X3N6r3!0Q4y4h3k6D9L8r3c8T1i4K6g2X3N6s2u0S2L8Y4y4A6N6r3W2G2L8W2)9#2k6X3N6#2K9h3c8W2i4K6u0r3k6r3!0U0N6h3#2W2L8Y4c8Q4x3V1k6D9L8r3c8T1i4K6u0V1j5$3!0E0L8h3q4F1k6q4)9J5k6r3g2^5j5h3#2H3L8r3g2K6i4K6u0W2K9s2c8E0L8l9`.`.</mark> [remap]: <mark class="encrypted">587K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2D9K9h3&6C8k6h3c8A6L8W2)9J5k6h3y4G2L8g2)9J5c8Y4m8#2L8s2y4W2i4K6u0r3k6r3g2U0M7Y4W2H3N6r3W2F1k6#2)9J5k6r3q4H3M7s2y4Q4x3X3c8A6L8%4y4Q4x3X3c8B7L8$3S2F1i4K6u0V1j5$3!0S2N6r3g2K6i4K6u0r3</mark> [macho]: <mark class="encrypted">057K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2N6X3W2D9M7r3q4F1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9J5x3q4)9J5c8U0l9&6i4K6u0r3x3o6k6Q4x3V1k6E0j5h3y4Z5L8#2)9J5k6r3W2F1M7$3W2V1k6g2)9J5k6r3!0#2N6q4)9J5c8R3`.`.</mark> [flex]: <mark class="encrypted">93eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6r3e0p5g2j5g2r3!0G2L8q4)9J5c8V1k6x3c8g2R3`.</mark> [send]: <mark class="encrypted">629K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2S2M7s2m8D9k6g2)9J5k6h3y4G2L8g2)9J5c8X3c8G2j5%4g2E0k6h3&6@1j5i4c8A6L8$3&6Q4x3V1k6G2j5X3A6W2j5%4c8A6N6X3g2U0i4K6u0r3x3e0b7#2y4U0M7I4x3W2)9J5k6r3!0T1K9X3y4Q4y4h3k6E0M7$3N6K6k6h3&6V1</mark> [flex.gif]: upload/attach/202104/844554_5V9WV6F67FQ62P5.jpg "使用 cycript 注入 libflex" [reveal.png]: upload/attach/202104/844554_X439D3KXVEJVSK7.jpg [ida.png]: upload/attach/202104/844554_DX964VQ5ZBJDK6U.jpg [asm.png]: upload/attach/202104/844554_SN974DWZVX9B22C.jpg [random.png]: upload/attach/202104/844554_R6RVCWNZDPV7MEB.jpg [dice.gif]: upload/attach/202104/844554_CPFYM769XK8X7D9.jpg [frida.png]: upload/attach/202104/844554_RW42DG52UXHYKEU.jpg "OSDC 2015: The engineering behind the reverse engineering"
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#逆向分析
#基础理论
#HOOK注入
收藏
・
26
点赞
・
14
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
zhongdong
为你点赞!
2026-6-19 08:21
镜中人24
感谢你的贡献,论坛因你而更加精彩!
2026-1-29 10:41
一路南寻
为你点赞!
2025-3-21 02:37
東陽不列山
为你点赞!
2025-3-20 06:17
Gift1a
为你点赞~
2024-3-1 15:13
令狐双
为你点赞~
2024-2-6 14:46
xxRea
为你点赞~
2023-11-24 09:31
wx_益达
为你点赞~
2022-12-13 21:58
PLEBFE
为你点赞~
2022-7-27 21:46
飘零丶
为你点赞~
2022-7-17 02:30
Mco令海
为你点赞~
2021-4-16 15:49
尐进
为你点赞~
2021-4-11 21:08
hellokami
为你点赞~
2021-4-11 16:13
wmsuper
为你点赞~
2021-4-11 16:11
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
7
)
v0id_
雪 币:
8659
活跃值:
(5431)
能力值:
( LV4,RANK:45 )
在线值:
发帖
7
回帖
243
粉丝
36
关注
私信
v0id_
2
楼
很清楚,学习了
2021-4-11 15:47
0
尐进
雪 币:
5724
活跃值:
(3847)
能力值:
( LV5,RANK:60 )
在线值:
发帖
10
回帖
187
粉丝
163
关注
私信
尐进
3
楼
写的很好 mark
2021-4-11 21:08
0
qyc
雪 币:
707
活跃值:
(1301)
能力值:
( LV9,RANK:190 )
在线值:
发帖
28
回帖
550
粉丝
30
关注
私信
qyc
4
4
楼
谢谢大佬。学习了,系统学习的话还是建议大家找专业的书 比如:《iOS应用逆向与安全之道》
c0bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6A6N6r3g2E0i4K6u0W2K9X3c8Q4x3X3g2U0L8$3#2Q4x3V1j5I4x3U0R3H3x3o6b7J5y4W2)9J5k6h3S2@1L8h3I4Q4x3U0k6F1j5Y4y4H3i4K6y4n7
2021-4-26 11:15
0
skyun
雪 币:
763
活跃值:
(1840)
能力值:
( LV9,RANK:160 )
在线值:
发帖
14
回帖
146
粉丝
37
关注
私信
skyun
3
5
楼
对照Android做分析,写的真棒
2021-4-28 10:02
0
fei3ei
雪 币:
184
活跃值:
(1379)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
27
粉丝
0
关注
私信
fei3ei
8
楼
写的真好
2026-1-26 18:56
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
evilpan
13
21
发帖
37
回帖
595
RANK
关注
私信
他的文章
[原创] Android 反序列化漏洞攻防史话
36439
iptables 在 Android 抓包中的妙用
12172
[原创] Linux 内核监控在 Android 攻防中的应用
57679
[原创] ART 在 Android 安全攻防中的应用
43923
[原创] JNDI 注入漏洞的前世今生
7420
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部