(四) iOS静态、动态注入库
1. 动态库
1.1 为什么是动态库?
系统核心可以让一个动态库供多个进程使用。比如大家都用同一套UIKit
,就只需要加载一份到内存。既然是动态库,我们就可以在运行时加载库,在运行时用LLDB
进行探索并执行代码。
1.2 静态地检查一个可执行的动态库
这些动态库在运行时加载,并通过动态加载器dyld
加载到内存。如果一个必需要加载的框架加载失败了,dyld
会关闭这个程序;但一个可选的框架加载失败了,程序完全可以运行起来,只不过那个库的代码就没法执行了。
在项目中加入Callkit.framework
(可选)和Social.framework
(必选),然后进行build
。
![](https://img.haomeiwen.com/i3344530/209687061f32ad4b.png)
找到Demo.app
文件夹。
![](https://img.haomeiwen.com/i3344530/ca8bdf9145be3ad7.png)
显示包内容。
![](https://img.haomeiwen.com/i3344530/75185cad5845a017.png)
用终端切到这个文件夹下,并输入
Demo.app> otool -L Demo
Demo:
/System/Library/Frameworks/CallKit.framework/CallKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Social.framework/Social (compatibility version 1.0.0, current version 87.0.0)
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1673.126.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1673.126.0)
/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 61000.0.0)
我们就用otool
命令把Demo
用到的所有动态库列出来了。之前加入的CallKit
和Social
也在列表里面。我们注意到,它们的读取路径都是这两个开头的。
/System/Library/Frameworks/
/usr/lib/
我们继续研究,把之前的L
缓存l
,再执行一次。
Demo.app> otool -l Demo
...
Load command 13
cmd LC_LOAD_WEAK_DYLIB
cmdsize 80
name /System/Library/Frameworks/CallKit.framework/CallKit (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1.0.0
compatibility version 1.0.0
Load command 14
cmd LC_LOAD_DYLIB
cmdsize 80
name /System/Library/Frameworks/Social.framework/Social (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 87.0.0
compatibility version 1.0.0
...
对比两个库的cmd
操作,我们会发现可选库使用LC_LOAD_WEAK_DYLIB
加载,而必选库使用LC_LOAD_DYLIB
加载。
1.3修改加载命令
install_name_tool
就是用来修改加载命令的。
我们把工程运行起来,并点击暂停,用image
找到加载的CallKit
。
(lldb) image list CallKit
[ 0] B4ECBAE6-A49A-3ED6-94FF-457D6D015513 0x00007fff230dc000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CallKit.framework/CallKit
我们在终端新建一个tab,找到Demo的完整地址。
pgrep -fl Demo
~> pgrep -fl Demo
4323 /Users/xxx/Library/Developer/CoreSimulator/Devices/85225EEE-8D5B-4091-A742-5BEBAE1C4906/data/Containers/Bundle/Application/FD25F38B-E73C-4CBD-8CFE-605E9B208E70/Demo.app/Demo
利用install_name_tool
来改变加载的库。
// 用NotificationCenter替换CallKit库
~> install_name_tool -change \
> /System/Library/Frameworks/CallKit.framework/CallKit \
> /System/Library/Frameworks/NotificationCenter.framework/NotificationCenter \
> /Users/xxx/Library/Developer/CoreSimulator/Devices/85225EEE-8D5B-4091-A742-5BEBAE1C4906/data/Containers/Bundle/Application/FD25F38B-E73C-4CBD-8CFE-605E9B208E70/Demo.app/Demo
// 查看是否生效
~> otool -L /Users/xxx/Library/Developer/CoreSimulator/Devices/85225EEE-8D5B-4091-A742-5BEBAE1C4906/data/Containers/Bundle/Application/FD25F38B-E73C-4CBD-8CFE-605E9B208E70/Demo.app/Demo
/Users/xxx/Library/Developer/CoreSimulator/Devices/85225EEE-8D5B-4091-A742-5BEBAE1C4906/data/Containers/Bundle/Application/FD25F38B-E73C-4CBD-8CFE-605E9B208E70/Demo.app/Demo:
/System/Library/Frameworks/NotificationCenter.framework/NotificationCenter (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Social.framework/Social (compatibility version 1.0.0, current version 87.0.0)
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1673.126.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1673.126.0)
/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 61000.0.0)
我们点击模拟器上的Demo
应用。
~> lldb -n Demo
(lldb) process attach --name "Demo"
Process 4708 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff523b625a libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
-> 0x7fff523b625a <+10>: ret
0x7fff523b625b <+11>: nop
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x7fff523b625c <+0>: mov r10, rcx
0x7fff523b625f <+3>: mov eax, 0x1000020
Target 0: (Demo) stopped.
//查看加载的库有没有CallKit
(lldb) image list CallKit
error: no modules found that match 'CallKit'
//查看加载的库有没有NotificationCenter
(lldb) image list NotificationCenter
[ 0] F4B21D50-3426-3D36-B510-482468F77301 0x00007fff295be000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/NotificationCenter.framework/NotificationCenter
1.4 运行时加载动态库
开始之前,先在~/.lldbinit
中加入
command regex ls 's/(.+)/po @import Foundation; [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"%1" error:nil]/'
因为现在LLDB
已经运行起来了,需要重新载入配置。再查找UIKit
,并根据目录地址打印framework目录下所有模拟器可用的公开动态库。
(lldb) command source ~/.lldbinit
(lldb) image list -d UIKit
[ 0] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework
//打印framework目录下所有可用的动态库
(lldb) ls /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/
这时我们可以动态地加载一个动态库,比如Speech
。
(lldb) process load /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Speech.framework/Speech
Loading "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Speech.framework/Speech"...ok
Image 0 loaded.
(lldb) image list -d Speech
[ 0] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Speech.framework
因为dyld
默认会搜索一些目录来查找你输入的动态库,你甚至不需要输入动态库的完整路径。
(lldb) process load MessageUI.framework/MessageUI
Loading "MessageUI.framework/MessageUI"...ok
Image 1 loaded.
1.5 探索动态库
探索动态库是逆向工程的基础。虽然动态库需要把二进制代码编译到一个位置无关的可执行文件,但是你依然可以查询到关于动态库的信息(即使编译器去掉了动态库的调试符号)。二进制代码是位置无关的,因为编译器并不不知道代码应该在内存中的什么位置,而这些都是dyld
完成的。
添加这样一个命令到你的~/.lldbinit
文件中:
command regex dump_stuff "s/(.+)/image lookup -rn '\+\[\w+(\(\w+\))?\ \w+\]$' %1 /"
它接受一个或多个动态库作为输入,导出所有的没有参数的OC类方法。
(lldb) command source ~/.lldbinit
(lldb) dump_stuff Social
69 matches found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Social.framework/Social:
Address: Social[0x0000000000001704] (Social.__TEXT.__text + 0)
Summary: Social`+[SLInternalComposeServiceHostContext _extensionAuxiliaryVendorProtocol] Address: Social[0x0000000000001770] (Social.__TEXT.__text + 108)
Summary: Social`+[SLInternalComposeServiceHostContext _extensionAuxiliaryHostProtocol] Address: Social[0x00000000000018d4] (Social.__TEXT.__text + 464)
Summary: Social`+[SLInternalComposeServiceVendorContext _extensionAuxiliaryVendorProtocol] Address: Social[0x0000000000001940] (Social.__TEXT.__text + 572)
...
还有一些好用的命令
// 导出继承NSObject的实例的所有ivars
command regex ivars 's/(.+)/expression -l objc -O -- [%1 _ivarDescription]/'
// 导出出继承NSObject的实例,或NSObject类的所有方法
command regex methods 's/(.+)/expression -l objc -O -- [%1 _shortMethodDescription]/'
// 递归导出继承NSObject的类的实例的所有方法
command regex lmethods 's/(.+)/expression -l objc -O -- [%1 _methodDescription]/'
你可以自己玩一下以下命令。
(lldb) command source ~/.lldbinit
(lldb) ivar [UIView new]
(lldb) methods UIView
(lldb) lmethods UIView
1.6 在真机上加载动态库
唯一的区别是System/Library路径!
- 模拟器的framework路径
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/
你可能注意到了,之前我们用
otool -L
输出的时候是/System/Library/Frameworks,可没有这么长!
之前说过,dyld
会搜索一些特殊的文件路径。模拟器使用的是模拟器版本的dyld_sim
。所以,如果在真机上运行,动态库将会在/System/Library/Frameworks/。
- iOS系统不是有沙盒限制么?
iOS内核对不同目录有着不同的限制。在iOS 13或更早的版本上,/System/Library目录对于我们的进程是可读的!因为程序在进程空间内需要调用一些公开或私有的动态库,需要这个访问权限,这很合理。如果沙盒机制限制了这些目录的读取,那么app就不能够正常加载导致启动失败。
(lldb) ls /
<__NSArrayM 0x28052ced0>(
.HFS+ Private Directory Data
,
.Trashes,
.ba,
.file,
.mb,
Applications,
Developer,
Library,
System,
bin,
cores,
dev,
etc,
private,
sbin,
tmp,
usr,
var
)
(lldb) ls /System/Library/