使用gdb和cycript越过iOS应用的越狱检测 Email:root@obaby.org.cn 翻译:obaby e9bK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3R3@1j5$3E0Q4x3X3g2G2M7X3N6Q4x3X3g2U0L8R3`.`. 我已经在现实生活中给安全测试人员和开发者(安全编程指导)讲述“iOS应用安全和审计”有一段时间了,但是始终有一个问题会不断的出现,那就是对于内置在程序中的反盗版代码(Anti-Piracy)真的有效么?安全测试人员想要知道如果这些程序内置了这种保护措施,那么他们能否发现问题。而开发者则正好相反,他们想要知道如果内置了这样的保护能否让他们睡个安心觉。 最直接的答案就是“不”,如果你的代码运行在一个由攻击者完全控制的平台上,并且他有足够的技能。那么他会很容的发现怎么去破坏你的程序。当运行在一个越狱的设备上的时候这会变得更加简单,因为攻击者可以更容易地做任何事情。 我已经看到安全测试人员的笑容了,如果你知道怎么使用cycript和gdb进行运行时分析,那么你就能够破解任何保护。但是,由于和其他程序(web和network)的安全审计不同,并且是与性在一个arm平台上需要一定的逆向分析基础,那么这个工作还是有一定的挑战性的,当然非常有意思。 这篇文章是我将要发布的现在开发者通用的检测越狱技术的一系列文章的第一篇,包含了怎么检测越狱,以及如何破解这个越狱检测。 为了实现这个目标,我们需要一个小程序来辅助,我已经创建了一个简单的iOS设备上的反盗版演示程序,程序中使用了我的一些在线iOS代码。你可以猛击此处来下载这个IPA。需要说明的是,这是一个自签名的应用,你需要有一个越狱的设备(iPhone/iPad)来运行这个程序。 你可以通过像下面的方法用installipa来安装这个应用: 程序在iOS5.1.1和6.1.2上进行过测试。一旦你运行程序你会看到一个非常简单的界面和一个检测越狱的按钮。 点击这个按钮,并且确认应用是运行在一个已经越狱的设备上。如果是在现实生活中,那么应用可能已经推出或者发送一个报告到他的服务器上来通知这个行为了(侵犯隐私了么)。 目标:越过iOS应用的越狱检测 第一步:找到应用程序的安装目录和进程PID。这个可以非常简单的通过ps命令来获取,并且使用grep来过滤结果。 第二步:进入应用目录,并且找到真正的二进制文件 第三步:原生的iOS应用都是使用Object-c来写的。这是一个动态类型语言,要求所有的类信息在运行时都是可用的,并且要嵌入到二进制文件中。我们可以通过class-dump-z来获取这些类信息 第四步:查看生成的类信息文件,信息量是灰常大滴:) 第五步:我们要找到当前窗体的rootViewController,这个东西可以通过一个叫做cycript的工具来实现,它通过Mobile Substrate来挂钩到任何执行的程序中。可以找到当前的rootViewController如下图所示: 第六步:回到class-dump-z在第四步中生成的文件,在AntiPiracyViewController中找到@section节。 第七步:在这里可以看到一个“checkPiracy”方法,更有意思的是还有另外一个方法叫做isJailbroken,并且返回来一个BOOL值,也即是意味着这个东西来检测越狱状态。 我们可以使用两种不同的技术来越过这个检测-- 1. 使用gdb进行运行时修改 2. 使用cycript进行方法替换(Method Swizzling) 首先使用第一种方法使用gdb进行运行时修改 第一步:使用gdb附加到AntiPiracyDemo的pid上 第二步:对isJailbroken设置一个断点 第三步:继续运行程序,并且点击Am I Pirated按钮来看下是否到达了设置的断点 第四步:反汇编代码,如果你对arm指令不够熟悉的话,那么就做好见识这些陌生面孔的准备吧 第五步:iOS使用的是arm架构的处理器,因而你看到的是arm汇编指令。如果你是从x86架构过来的,那么在处理arm汇编指令的时候,你只需要记住一点即可—所有的参数都是由R0,R1,R2,R3寄存器来传送的。如果参数超过四个,那么另外的参数将通过堆来传送。这里有一个ABI文档,如果你感兴趣可以看下。 第六步:在第四步的反汇编中可以看到大量的类似"blx 0x98fe4 <dyld_stub_objc_msgSend>"指令,BLX是指"Branch with Link",通常会通过调用objc_msgSend来结束。Objc_msgSend定义如下: 上图来自于苹果开发中心,objc_msgSend是iOS应用中所有消息的承载者(carrier)。通过这个ABI我们可以知道: 1. theReceiver将会保存由R0指定 2. theSelector将会由R1指定 3. 第一个参数指向R2 第七步:我们可以给objc_msgSend设置断点,但是我更倾向于对于所有的objc_msgSend调用设置断点。 第八步:继续执行程序,当程序中断在第二个断点之后我们来查看R0/R1的内容.这将有助于我们来理解消息的receiver和各自不同的selector NSSString alloc没有任何的意思,我们继续上面的操作来处理其他的断点。下面是我们中断在第四和第五个断点的时候的数据 断点四告诉我们,程序使用了NSFileManager,并且断点五告诉我们它检测了"FileExistsAtPath:" for "/private/var/lib/apt"是否存在,这就非常有意思了。 APT可能是所有的越狱设备在越狱之后安装的第一个二进制程序,通过它可以来管理cydia中的包。看起来程序是通过检测这个文件是否存在来判断设备是否越狱的。 第九步:现在该怎么办?返回值保存在R0中,如果你去查看下NSFileManager FileExistsAtPath会发现发返回了一个BOOL值。这样就是说如果没有越狱那么返回0,如果越狱了就会返回1. 在这个情况下,我们的设备已经越狱了,它将会返回1.我们可以在下面一行代码检测R0的时候设置一个断点,如下: 第十步:最简单的版饭就是将R0的值设为0.来让应用认为APT不存在。这个可以通过下面的代码非常简单的实现 第十一步:如果现在来检测,那么很高兴他会告诉我们,设备没有越狱 当然,我们现在没有对二进制程序进行补丁,所以你每次都需要这么做,我将会在另外一篇文章中介绍程序修改的相关内容。 现在让我们来看第二种方法:使用cycript进行方法替换(Method Swizzling) 第一步:使用cycript附加到应用程序上 第二步:方法替换允许你将给定的方法的实现函数映射到你自己的实现代码上。为了得到所有消息的列表需要使用isa.messages。 上面的命令会给你海量的输出内容。你可以清楚的看到isJailbroken已经高亮的显示在那里了。 神马是isa.messages? 如果你查看Object-C 运行时实现原理,就会发现,isa是类结构自身的一个指针。 实现文件: 1ceK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3!0H3k6h3&6K6L8%4g2J5j5$3g2Q4x3X3g2S2M7s2m8D9k6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4G2N6i4u0U0k6g2)9J5c8X3!0T1K9X3x3@1i4K6u0r3L8$3u0B7j5K6c8Q4x3X3b7#2x3K6u0Q4x3V1k6J5N6h3&6@1K9h3#2W2i4K6u0r3M7Y4g2F1N6r3W2E0k6g2)9J5k6h3S2Q4x3U0k6F1j5Y4y4H3i4K6y4n7K9i4y4S2" target="_blank">53aK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3!0H3k6h3&6K6L8%4g2J5j5$3g2Q4x3X3g2S2M7s2m8D9k6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4G2N6i4u0U0k6g2)9J5c8X3!0T1K9X3x3@1i4K6u0r3L8$3u0B7j5K6c8Q4x3X3b7#2x3K6u0Q4x3V1k6J5N6h3&6@1K9h3#2W2i4K6u0r3M7Y4g2F1N6r3W2E0k6g2)9J5k6h3R3`. isa从来不会直接暴露给程序员,但是通过cycript我们可以访问到他。下面内容引用自苹果的官方网站: 如果你是一个刚接触面向对象的程序员,它可以帮助你想到这是一个与方法想关联的对象。这个与实际的情况相差不多,尤其是在运行时。 每个Objective-C对象都隐藏了第一个成员结构(实例变量),这就是isa指针(其余的成员都由对象类或者父类定义)。Isa顾名思义,指向对象的类,它是从类定义编译来的与一个对象。类对象维护一个调度表,包括指针和实现方法。它同样拥有一个父类的指针,父类同样拥有自己的调度表和父类指针。通过这条链子一个对象可以访问他自己的类和实现方法以及父类(公开的或者受保护的实力变量在内)。Isa指针对消息调度和Cocoa对象的动态性都是至关重要的。 更多内容参考下面的链接: ec9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2S2M7s2m8D9k6g2)9J5k6h3y4G2L8g2)9J5c8X3I4A6j5Y4u0S2M7Y4W2Q4x3V1k6E0j5h3y4Q4x3V1k6Q4x3U0y4V1L8$3y4#2L8h3g2F1N6r3q4@1K9h3!0F1i4K6u0r3b7$3!0U0L8$3q4Q4x3V1k6o6L8$3&6U0k6i4m8@1N6h3q4D9i4K6u0r3b7$3!0U0L8$3q4r3N6h3&6V1j5h3#2W2L8Y4c8S2L8s2y4Q4x3V1k6o6L8$3y4G2j5f1!0T1K9X3g2U0N6s2y4Q4x3V1k6o6L8$3y4G2j5f1!0T1K9X3g2U0N6s2y4Q4x3X3g2Z5N6r3#2D9 上面引用的最后一行显示了isa在消息调度中的重要性。这里还有关于isa的更多的信息: 消息的关键在于编译器为每个类和对象生成的结构,每个类结构包含两个基本的元素: 一个指向父类的指针 一个调度表。这个表包含了方法选择与方法类特定地址的条目列表详细信息参考这里: 6b8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2S2M7s2m8D9k6g2)9J5k6h3y4G2L8g2)9J5c8X3I4A6j5Y4u0S2M7Y4W2Q4x3V1k6E0j5h3y4Q4x3V1k6Q4x3U0y4V1L8$3y4#2L8h3g2F1N6r3q4@1K9h3!0F1i4K6u0r3b7$3!0U0L8$3q4Q4x3V1k6o6L8$3&6U0k6i4m8@1N6h3q4D9i4K6u0r3e0$3u0B7b7#2u0#2L8Y4c8A6L8h3g2s2N6h3W2V1k6g2)9J5c8V1q4J5N6r3W2U0L8r3g2K6i4K6u0r3L8$3y4J5N6p5S2G2N6@1#2W2M7%4y4S2k6$3W2F1k6#2N6G2M7X3E0K6i4K6u0W2K9s2c8E0L8l9`.`. 第三步:如果上面的东西感觉无意义,那也还好,至少知道了这个东西是怎么运行的。现在我们来修改isJailbroken的实现代码: 第四步:现在当你尝试点击Am I Pirated的时候,你得到始终是"NO" :) W00t :) 希望你喜欢这篇文章,我将会不断的编写更多的关于检测的文章,例如二进制检测,Bundle检测,哈希校验等等。敬请关注! 原文链接: f67K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3S2S2j5$3E0G2k6Y4c8Z5k6h3c8S2P5g2)9J5k6i4y4W2j5%4g2J5K9i4c8&6N6s2g2T1k6g2)9J5k6h3&6W2N6q4)9J5c8U0t1H3x3e0y4Q4x3V1j5H3y4q4)9J5c8X3u0&6M7r3q4K6M7$3W2F1k6#2)9J5k6r3A6S2K9h3I4T1M7X3!0C8k6h3&6Q4x3X3c8U0K9r3g2U0K9%4y4Q4x3X3c8A6L8W2)9J5k6r3W2G2M7#2)9J5k6h3S2@1L8h3H3`.注:本帖由看雪论坛志愿者PEstone 重新将PDF整理排版,若和原文有出入,以原作者附件为准 相关ipa AntiPiracyDemo.rar PDF附件: 使用gdb和cycript越过iOS应用的越狱检测.pdf
[培训]科锐逆向工程师培训第53期2025年7月8日开班!