1、动态调试举例
创建一个测试项目iOSTest,添加屏幕触摸方法,弹出一个Alert:
下面来尝试调试一下 touchesEnded:withEvent: 函数,首先搭建动态调试环境:
第一步:让debugserver附加到iOSTest进程
$ debugserver localhost:10011 -a iOSTest
第二步:lldb连接debugserver
(lldb) process connect connect://localhost:10011
第三步:给方法添加断点
通过Hopper查看 touchesEnded:withEvent: 函数的地址:
通过函数地址地址给函数添加断点:
(lldb) breakpoint set -a 0x1000061d0
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等分段:
查看各个分段的详细信息中VM Address和VM Size信息:
__PAGEZERO:
VM Address: 0x0
VM Size: 0x100000000
__TEXT:
VM Address: 0x100000000
VM Size: 0x8000
__DATA:
VM Address: 0x100008000
VM Size: 0x4000
__LINKEDIT:
VM Address: 0x10000C000
VM Size: 0x8000
...
VM Address:Virtual Memory Address,内存地址,在内存中的位置 VM Size:Virtual Memory Size,内存大小,占用多少内存
可以分析出Mach-O文件装载到虚拟内存中的布局如下:
其中函数代码存放在TEXT段中、全局变量存放在DATA段中。可执行文件的内存地址是0x0,arm64架构下代码段的内存地址是0x100000000(8个0)
非arm64的代码段内存地址是 0x4000(3个0)
除了使用MachOView外,还可以使用 size 命令来查看Mach-O的内存分布,以iOSTest为例:
$ size -l -m -x iOSTest
Segment __PAGEZERO: 0x100000000 (zero fill) (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x8000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x26c (addr 0x10000619c offset 24988)
Section __stubs: 0x90 (addr 0x100006408 offset 25608)
Section __stub_helper: 0xa8 (addr 0x100006498 offset 25752)
Section __objc_methname: 0xe94 (addr 0x100006540 offset 25920)
Section __cstring: 0x26 (addr 0x1000073d4 offset 29652)
Section __objc_classname: 0x70 (addr 0x1000073fa offset 29690)
Section __objc_methtype: 0xb29 (addr 0x10000746a offset 29802)
Section __unwind_info: 0x6c (addr 0x100007f94 offset 32660)
total 0x1e63
Segment __DATA: 0x4000 (vmaddr 0x100008000 fileoff 32768)
Section __got: 0x8 (addr 0x100008000 offset 32768)
Section __la_symbol_ptr: 0x60 (addr 0x100008008 offset 32776)
Section __cfstring: 0x60 (addr 0x100008068 offset 32872)
Section __objc_classlist: 0x18 (addr 0x1000080c8 offset 32968)
Section __objc_protolist: 0x20 (addr 0x1000080e0 offset 32992)
Section __objc_imageinfo: 0x8 (addr 0x100008100 offset 33024)
Section __objc_const: 0x1398 (addr 0x100008108 offset 33032)
Section __objc_selrefs: 0x48 (addr 0x1000094a0 offset 38048)
Section __objc_classrefs: 0x20 (addr 0x1000094e8 offset 38120)
Section __objc_superrefs: 0x8 (addr 0x100009508 offset 38152)
Section __objc_ivar: 0x4 (addr 0x100009510 offset 38160)
Section __objc_data: 0xf0 (addr 0x100009518 offset 38168)
Section __data: 0x188 (addr 0x100009608 offset 38408)
total 0x178c
Segment __LINKEDIT: 0x8000 (vmaddr 0x10000c000 fileoff 49152)
total 0x100014000
3、ASLR
3.1、ASLR简介
iOS4.3开始引入了ASLR技术,全程Address Space Layout Randomization,地址空间化随即布局。
是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
假设ASLR随机生成的Offset(偏移)是0x5000,也就是可执行文件的内存地址,偏移之后的代码段内存地址是0x100005000:
3.2、函数的内存地址
重新搭建一下动态调试环境,通过 image list 命令查看加载的模块:
(lldb) image list -o -f
[ 0] 0x0000000004660000 /private/var/containers/Bundle/Application/692946B7-9C4B-4C5B-BC05-27E5C5C2C11D/iOSTest.app/iOSTest(0x0000000104660000)
[ 1] 0x0000000104924000 /Library/Caches/cy-Oe5O7p.dylib(0x0000000104924000)
[ 2] 0x00000001046ac000 /usr/lib/substrate/SubstrateBootstrap.dylib(0x00000001046ac000)
[ 3] 0x000000001f014000 /Users/mengxianliang/Library/Developer/Xcode/iOS DeviceSupport/14.7.1 (18G82)/Symbols/System/Library/Frameworks/Foundation.framework/Foundation
...
可以看到iOSTest的偏移地址是 0x4660000,代码段地址是0x104660000。那么函数的真实内存地址就是ASLR偏移地址 + 未使用ASLR的函数地址(hopper分析出的函数地址),touchesEnded:withEvent: 函数的真实地址就是0x4660000 + 0x1000061d0。重新给函数添加断点:
(lldb) breakpoint set -a 0x0x4660000+1000061d0
Breakpoint 1: where = iOSTest`-[ViewController touchesEnded:withEvent:] at ViewController.m:22:5, address = 0x00000001046661d0
提示断点添加成功,再次点击屏幕,触发断点:
Process 5425 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00000001046661d0 iOSTest`-[ViewController touchesEnded:withEvent:](self=0x0000000105e08ea0, _cmd="touchesEnded:withEvent:", touches=0x0000000281995420, event=0x0000000282c98480) at ViewController.m:22:5 [opt]
19 }
20
21 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
-> 22 [self showAlert];
23 }
24
25 - (void)showAlert {
Target 0: (iOSTest) stopped.
3.3、函数在Mach-O文件中的位置
首先看一下各个分段的File Offset和File Size信息:
__PAGEZERO:
File Offset: 0x0
File Size: 0x0
__TEXT:
File Offset: 0x0
File Size: 0x8000
...
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结束:
从而证明了函数在Mach-O中是保存在Text,text段中的。
3.3、全局变量在Mach-O文件中的位置
调整一下iOSTest项目,添加三个全局变量,在触摸方法中打印其地址,并添加一个断点:
0x102e595a8 0x102e595ac 0x102e595b0
查看模块信息:(lldb) image list -o -f | grep iOSTest
[ 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中的分段信息:
从而证明了全局变量在Mach-O中是保存在DATA,data段中的。