1、动态调试举例

创建一个测试项目iOSTest,添加屏幕触摸方法,弹出一个Alert:
image.png
下面来尝试调试一下 touchesEnded:withEvent: 函数,首先搭建动态调试环境:
第一步:让debugserver附加到iOSTest进程
$ debugserver localhost:10011 -a iOSTest
第二步:lldb连接debugserver
(lldb) process connect connect://localhost:10011
第三步:给方法添加断点
通过Hopper查看 touchesEnded:withEvent: 函数的地址:截屏2022-08-04 18.01.33.png
通过函数地址地址给函数添加断点:
(lldb) breakpoint set -a 0x1000061d0

  1. warning: failed to set breakpoint site at 0x1000061c0 for breakpoint 1.1: error: 9 sending the breakpoint request

这里提示断点添加失败,原因是Hopper中的函数地址不是真正的地址,需要进行一定计算后才可以得到运行在内存中地址。

2、Mach-O的虚拟内存分段

通过查看iOSTest可执行文件的Load Commands信息可知,Mach-O在虚拟内存中的布局包括PAGEZERO、TEXT、DATA、LINKEDIT等分段:
截屏2022-08-04 18.05.34.png
查看各个分段的详细信息中VM Address和VM Size信息:截屏2022-08-04 18.06.10.png

  1. __PAGEZERO
  2. VM Address: 0x0
  3. VM Size: 0x100000000
  4. __TEXT
  5. VM Address: 0x100000000
  6. VM Size: 0x8000
  7. __DATA
  8. VM Address: 0x100008000
  9. VM Size: 0x4000
  10. __LINKEDIT
  11. VM Address: 0x10000C000
  12. VM Size: 0x8000
  13. ...

VM Address:Virtual Memory Address,内存地址,在内存中的位置 VM Size:Virtual Memory Size,内存大小,占用多少内存

可以分析出Mach-O文件装载到虚拟内存中的布局如下:
image.png
其中函数代码存放在TEXT段中、全局变量存放在DATA段中。可执行文件的内存地址是0x0,arm64架构下代码段的内存地址是0x100000000(8个0)

非arm64的代码段内存地址是 0x4000(3个0)

除了使用MachOView外,还可以使用 size 命令来查看Mach-O的内存分布,以iOSTest为例:
$ size -l -m -x iOSTest

  1. Segment __PAGEZERO: 0x100000000 (zero fill) (vmaddr 0x0 fileoff 0)
  2. Segment __TEXT: 0x8000 (vmaddr 0x100000000 fileoff 0)
  3. Section __text: 0x26c (addr 0x10000619c offset 24988)
  4. Section __stubs: 0x90 (addr 0x100006408 offset 25608)
  5. Section __stub_helper: 0xa8 (addr 0x100006498 offset 25752)
  6. Section __objc_methname: 0xe94 (addr 0x100006540 offset 25920)
  7. Section __cstring: 0x26 (addr 0x1000073d4 offset 29652)
  8. Section __objc_classname: 0x70 (addr 0x1000073fa offset 29690)
  9. Section __objc_methtype: 0xb29 (addr 0x10000746a offset 29802)
  10. Section __unwind_info: 0x6c (addr 0x100007f94 offset 32660)
  11. total 0x1e63
  12. Segment __DATA: 0x4000 (vmaddr 0x100008000 fileoff 32768)
  13. Section __got: 0x8 (addr 0x100008000 offset 32768)
  14. Section __la_symbol_ptr: 0x60 (addr 0x100008008 offset 32776)
  15. Section __cfstring: 0x60 (addr 0x100008068 offset 32872)
  16. Section __objc_classlist: 0x18 (addr 0x1000080c8 offset 32968)
  17. Section __objc_protolist: 0x20 (addr 0x1000080e0 offset 32992)
  18. Section __objc_imageinfo: 0x8 (addr 0x100008100 offset 33024)
  19. Section __objc_const: 0x1398 (addr 0x100008108 offset 33032)
  20. Section __objc_selrefs: 0x48 (addr 0x1000094a0 offset 38048)
  21. Section __objc_classrefs: 0x20 (addr 0x1000094e8 offset 38120)
  22. Section __objc_superrefs: 0x8 (addr 0x100009508 offset 38152)
  23. Section __objc_ivar: 0x4 (addr 0x100009510 offset 38160)
  24. Section __objc_data: 0xf0 (addr 0x100009518 offset 38168)
  25. Section __data: 0x188 (addr 0x100009608 offset 38408)
  26. total 0x178c
  27. Segment __LINKEDIT: 0x8000 (vmaddr 0x10000c000 fileoff 49152)
  28. total 0x100014000

3、ASLR

3.1、ASLR简介

iOS4.3开始引入了ASLR技术,全程Address Space Layout Randomization,地址空间化随即布局。
是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
假设ASLR随机生成的Offset(偏移)是0x5000,也就是可执行文件的内存地址,偏移之后的代码段内存地址是0x100005000:
image.png

3.2、函数的内存地址

重新搭建一下动态调试环境,通过 image list 命令查看加载的模块:
(lldb) image list -o -f

  1. [ 0] 0x0000000004660000 /private/var/containers/Bundle/Application/692946B7-9C4B-4C5B-BC05-27E5C5C2C11D/iOSTest.app/iOSTest(0x0000000104660000)
  2. [ 1] 0x0000000104924000 /Library/Caches/cy-Oe5O7p.dylib(0x0000000104924000)
  3. [ 2] 0x00000001046ac000 /usr/lib/substrate/SubstrateBootstrap.dylib(0x00000001046ac000)
  4. [ 3] 0x000000001f014000 /Users/mengxianliang/Library/Developer/Xcode/iOS DeviceSupport/14.7.1 (18G82)/Symbols/System/Library/Frameworks/Foundation.framework/Foundation
  5. ...

可以看到iOSTest的偏移地址是 0x4660000,代码段地址是0x104660000。那么函数的真实内存地址就是ASLR偏移地址 + 未使用ASLR的函数地址(hopper分析出的函数地址),touchesEnded:withEvent: 函数的真实地址就是0x4660000 + 0x1000061d0。重新给函数添加断点:
(lldb) breakpoint set -a 0x0x4660000+1000061d0

  1. Breakpoint 1: where = iOSTest`-[ViewController touchesEnded:withEvent:] at ViewController.m:22:5, address = 0x00000001046661d0

提示断点添加成功,再次点击屏幕,触发断点:

  1. Process 5425 stopped
  2. * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  3. frame #0: 0x00000001046661d0 iOSTest`-[ViewController touchesEnded:withEvent:](self=0x0000000105e08ea0, _cmd="touchesEnded:withEvent:", touches=0x0000000281995420, event=0x0000000282c98480) at ViewController.m:22:5 [opt]
  4. 19 }
  5. 20
  6. 21 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  7. -> 22 [self showAlert];
  8. 23 }
  9. 24
  10. 25 - (void)showAlert {
  11. Target 0: (iOSTest) stopped.

3.3、函数在Mach-O文件中的位置

首先看一下各个分段的File Offset和File Size信息:
截屏2022-08-05 15.41.06.png

  1. __PAGEZERO
  2. File Offset: 0x0
  3. File Size: 0x0
  4. __TEXT
  5. File Offset: 0x0
  6. File Size: 0x8000
  7. ...

File Offset:在Mach-O文件中的位置 File Size:在Mach-O文件中占据的大小

可以发现PAGEZERO段在Mach-O文件中是不占据大小的,而是从TEXT段开始的。因为Mach-O文件的内存是连续的,所以:函数在Mach-O文件中的地址 = 通过Hopper分析出的函数地址 - __PAGEZERO段在内存中占据的大小
那么 touchesEnded:withEvent: 方法在Mach-O文件中的地址就是 0x1000061d0 - 0x10000000 = 0x61d0,对照一下Mach-O文件的布局,Text,text段的内存地址是从0x619c开始到 0x6408结束:
截屏2022-08-05 15.40.15.png
从而证明了函数在Mach-O中是保存在Text,text段中的。

3.3、全局变量在Mach-O文件中的位置

调整一下iOSTest项目,添加三个全局变量,在触摸方法中打印其地址,并添加一个断点:
image.png

  1. 0x102e595a8 0x102e595ac 0x102e595b0

查看模块信息:(lldb) image list -o -f | grep iOSTest

  1. [ 0] 0x00000000000f8000 /Users/mengxianliang/Library/Developer/Xcode/DerivedData/iOSTest-fdjstgjrhqfjdhdegwkczvbccsbo/Build/Products/Release-iphoneos/iOSTest.app/iOSTest

可以看到ASLR偏移量是0xf8000,所以全局变量a在Mach-O文件中的内存地址是:0x1001015a8 - 0xf8000 - 0x100000000 = 0x95A8,再对照Mach-O中的分段信息:
截屏2022-08-05 15.35.23.png
从而证明了全局变量在Mach-O中是保存在DATA,data段中的。