2019年iOS逆向最新学习资料(三):强大的断点调试工具

1、强大的lldb

上文我们说到了调试。在iOS逆向中,很多人推荐debugserver + lldb 其实调试只需要lldb就够了。
debugserver配置的文章有很多,从14年到18年不等,但大部分都过时了。所以我也没用。那 只用lldb该怎么断点调试三方app呢?我们先来简单看下lldb的断点命令。

1.1 LLDB断点命令:

1.1.1 设置断点 下断点命令我们只需要先会两个就够了:

a: 给指定内存地址下断点: br set -a 0x00000000全拼我记不住
b: 给某方法下断点:b -n "[ClassName methodName]" 全拼同样记不住
关于a,怎么定位方法内存,一会再讲。
关于b,很多app打App Store包的时候,是去掉了符号表的编译配置项的。所以,我们暂时打不了三方app的方法断点。不过不用急,后面我们会教大家如何还原三方app的符号表。

1.1.2 查看断点

br list这个全拼我能记住:breakpoint list

1.1.3 删除断点
br del n 这里的n是上一步br list列出的断点的序号。根据对应的序号删除想删的断点。当然你也可以直接br del,然后lldb会问你是否要删除全部断点。[Y/n] ? 输入Y即可。

1.2 如何定位函数地址

好 我们先来看看如何找到想断点的函数的地址。为了降低被黑客攻击的风险,操作系统大都采用ASLR(地址空间布局随机化) 技术: 详细解释请看这 这个人的内存管理讲的不错,一共7篇,建议大家有时间看看。

ASLR是一种避免类似攻击的有效保护。进程每一次启动时,地址空间都会被简单地随机化:进行整体的地址偏移,而不是搅乱。通过内核将整个进程的内存空间“平移”某个随机数,进程的基本内存布局如程序文本、数据、库等相对位置仍然是一样的,但其具体的地址都不同了。
简单来说,ASLR会在进程启动时候随机一个基地址
lldb中的查看命令是image listimage list -o -f


如过命令后面不加参数,它打印的就是每个image(镜像)的虚拟内存地址。
如果加了-o参数,打印的就是image的偏移量(相对于谁,暂时还没研究)。

具体区别如下:
image list 打印的基地址: 0x102a94000
image list -o -f的地址: 0x002a94000

看出区别了么?第9位差了一个1
这个1正好跟IDA工具中的0x 1 0000 0000对上了。
原因终于找到了,在:杨潇玉大哥的这篇文章。main()调用之前会调用exec()exec() 是一个系统调用。系统内核把应用映射到新的地址空间,且每次起始位置都是随机的(因为使用 ASLR)。并将起始位置到 0x000000 这段范围的进程权限都标记为不可读写不可执行。如果是 32 位进程,这个范围至少是 4KB;对于 64 位进程则至少是4GB。NULL 指针引用和指针截断误差都是会被它捕获。4GB = 232,用二进制表示就是:第32+1位为1,剩下的32位为0。而4位二进制位可以表示一个十六进制位,所以十六进制表示就是:0x 1 0000 0000。这样就满足了64位进程至少4GB的最小偏移范围了。

这里我们提到的地址是虚拟地址,为什么不是物理地址?
原因是:操作系统将我们(程序猿)跟物理内存划分了一个安全界限,我们程序猿只需要用虚拟内存跟操作系统打交道即可,操作系统调用底层硬件api,跟物理内存打交道。如果程序猿直接跟物理内存交流,恐怕不是那么安全的。程序一旦出了bug,可能会导致系统瘫痪。

基地址有了,在前面ASLR技术中我们提到内核将整个进程的内存“平移”,所以数据段、代码段等等内存的偏移量其实是固定的,他们相对于mach-o header部分的偏移量是个常亮,那既然如此,我们的IDA工具又派上用场了:


这里列出的偏移地址,就是函数相对于mach-o的起始地址的偏移量,所以:
虚拟地址 = 基地址 + 偏移地址
那么,我们就可以在 lldbbr set -a 0x102a94000下断点了。
在这里在交给大家两个好用的命令:
读内存命令: x 0x102a94000 全拼是: memory read 0x102a94000
反汇编命令: dis -s 0x102a94000 dis是dissamble的简写。

我们调试别人的app,是看不到源码的,只能看汇编。所以这里建议大家学一下汇编。我知道很多人听到汇编就头大,不过不要紧,找对了教材,汇编真的可以通俗易懂。比如这本:《汇编语言第3版》王爽著。学习时长两三天就够。不信你试试。
我是从网上下的影印版PDF,阅读效果不太好,大家可以买正版书或者正版PDF来学习。

学完了汇编,CPU工作原理你就基本了解了,再跟内存交流起来就方便多了。不过你可能还是看不懂xcode中出现的上古语言。因为两者的汇编指令集不一样,书里的CPU是古老的8086,是地址总线20位,数据总线16位的16位机器。而iPhone 5s之后都是arm64 CPU,命令不太一样,总线位数也不一样,不过如果你理解了CPU的工作原理,arm64其实只是换了一种语法而已。而且CPU寻址操作也变得更简单。毕竟arm64数据总线跟地址总线位数相同(皆为64位),CPU不再需要地址加法器计算地址了。

这些都掌握了之后,我们可以看一下runtime源码,重点看一下objc/message部分。
目前苹果官方最新的是objc4-762版本,为了方便调试,我从github上下载了objc4-750版本,就差一个版本,不影响我们理解原理。
runtime 非官方Git地址
可以结合这篇文章进行理解。作者梳理的很好,从objc_msgSend的汇编代码入口开始,梳理到最终的runtime消息转发机制。

有了这些基础,下面我们再进行断点调试的时候,就会方便很多。

比如说,我们给没有隐藏符号表的app下断点:


2019年iOS逆向最新学习资料(三):强大的断点调试工具_第1张图片
断点viewDidAppear:

未隐藏符号表的程序,断点的时候,函数名也会显示出来。

接下来我们将用到一个进阶命令:register read 查看寄存器信息:


前面如果你研究了runtime的objc_msgSend函数,你就会知道,该函数有两个默认参数 (receiver, cmd),第一个参数是消息接收者,第二个是函数地址。在 iOS arm64 CPU中,通用寄存器中的 x0~x7寄存器 用于参数传递。所已 x0寄存器的值就是我们该函数的第一个参数:消息接收者,也就是DJHomeViewController类型的一个对象。结合po命令,我们就可以查看很多东西了。
第二个参数是函数地址,对应 x1寄存器。
objc_msgSend中第三个参数( x2寄存器),就是viewDidAppear:后面的传参了,我们看到这个值是0. 回头看看 class-dump,可以看到这个参数要求传一个布尔值,那么 我们就可以猜出该函数入参是false。
2019年iOS逆向最新学习资料(三):强大的断点调试工具_第2张图片

有了对象和参数地址,你就拥有了一切。
2019年iOS逆向最新学习资料(三):强大的断点调试工具_第3张图片

比如,我们通过class-dump 看到该类有这么一个属性,那我们就可以直接访问!
2019年iOS逆向最新学习资料(三):强大的断点调试工具_第4张图片

我们可以用 p命令 输出一下对象,该功能有点类似于 expression命令。
此时会生成一个 $符号开头的变量,这个变量可以在后续 lldb中使用。
那么接下来我们就可以利用 kvc进行访问任何内容了。也可以直接像写 OC代码一样,在 lldb中写方法调用。例如: po [$10 class];
lldb常用命令可以 看这里、 或这里。网上有很多这类文章,大家可以自行查找。

到这里,我们可以看到,假如调试的时候能看到函数名,那我们逆向就没有任何阻碍。所以,符号表是我们的必争之地!
如何还原符号表,请看下集:iOS逆向资料(四):还原符号表,再无障碍。

你可能感兴趣的:(2019年iOS逆向最新学习资料(三):强大的断点调试工具)