ASLR
ASLR(Address Space Layout Randomization),地址空间布局随机化。
是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
iOS4.3
开始引入了ASLR技术。
在MachOView中查看LC_SEGMENT_64(_TEXT)
的VM_Address
是0x100000000
(__PAGEZERO VM Size),如果没有ASLR技术,MachO文件在内存中的物理地址就是0x100000000
查看MachO文件的在内存中的物理地址
0x00000001043bc000
(lldb) image list
[ 0] 802EFF39-2417-3E7E-97B8-E21AD7AFD27A 0x00000001043bc000 /Users/niujf/Library/Developer/Xcode/DerivedData/001--Demo-dantuskxyprjdbcmfkmwgrxzoxbt/Build/Products/Debug-iphoneos/001--Demo.app/001--Demo
[ 1] 571392A7-E1E6-369F-8805-C1A141F3C1C5 0x0000000104488000 /Users/niujf/Library/Developer/Xcode/iOS DeviceSupport/13.1.3 (17A878)/Symbols/usr/lib/dyld
...
可以看到ASLR随机产生的Offset
,也就是MachO文件的偏移值是0x1043bc000-0x100000000=0x43bc000
如何给一个函数设置内存断点?
随便写一个方法,在Hopper中查看方法在文件中的地址是00000001000065f0
,由于Hopper会自动加上一个__PAGEZERO VM Size 0x100000000
,所以实际方法在文件中的偏移值是0x65f0
在MachOView中查看
__PAGEZERO
,可以看到在文件中的File offset
和File Size
都是0
Hopper、IDA都是未使用ASLR的VM Address
方法在内存中的物理地址 = __PAGEZERO VM Size + File Offset + ASLR Offset = 0x100000000 + 0x65f0 + 0x43bc000
如下对方法的在内存中的物理地址下断点,可以看到下断点成功了。
(lldb) b -a 00000001000065f0+0x43bc000
Breakpoint 1: where = 001--Demo`-[ViewController eatWithObjc:] at ViewController.m:17, address = 0x00000001043c25f0
(lldb) breakpoint list
Current breakpoints:
1: address = 001--Demo[0x00000001000065f0], locations = 1, resolved = 1, hit count = 0
1.1: where = 001--Demo`-[ViewController eatWithObjc:] at ViewController.m:17, address = 0x00000001043c25f0, resolved, hit count = 0
如何查看一个全局变量在MachO文件中的位置?
查看一个int
类型的全局变量a
在内存的物理地址
int a = 10;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
a = 20; //打上断点
NSLog(@"%d",a);
}
(lldb) p a
(int) $0 = 10
(lldb) p &a
(int *) $1 = 0x0000000102ad4df0
可以看到全局变量a
在内存中的物理地址是0x0000000102ad4df0
(lldb) image list
[ 0] 802EFF39-2417-3E7E-97B8-E21AD7AFD27A 0x0000000102acc000 /Users/niujf/Library/Developer/Xcode/DerivedData/001--Demo-dantuskxyprjdbcmfkmwgrxzoxbt/Build/Products/Debug-iphoneos/001--Demo.app/001--Demo
MachO文件在内存中的物理地址是0x0000000102acc000
,全局变量a
在文件中的偏移值就是0x0000000102ad4df0
- 0x0000000102acc000
= 0x8df0
在MachOView中查看0x8df0
位置对应的数据是0xA0
,也就是十进制10
.
chisel
chisel一个LLDB命令集合,用于帮助调试iOS应用程序。
可以通过Homebrew安装,具体的安装命令在README里有
chisel在Xcode11上遇到的问题
error: module importing failed: Missing parentheses in call to 'print'. Did you mean print('Whoops! You are missing the <' + arg.argName + '> argument.')? (fblldb.py, line 98)
File "temp.py", line 1, in
莫慌,在issues里面已经给出了解决方案,有两种,lz
是在 /usr/local/Cellar/chisel/1.8.1
路径下替换了修改了源码兼容Python 3
的libexec
文件
chisel的Commands
如果对那个Commands不熟悉,可以通过(lldb) help Commands 查看,也可以看README
- pviews(打印当前页面的视图层级)
(lldb) pviews
; layer = >
| >
| | >
| | | >
| | | | >
| | | | | >
| | | | | | <_UILabelContentLayer: 0x283171be0> (layer)
(lldb) pviews self.view
>
| >
| | >
| | | <_UILabelContentLayer: 0x283171be0> (layer)
- pvc(打印当前页面的控制器)
(lldb) pvc
, state: appeared, view:
- presponder(打印响应者链)
(lldb) presponder 0x149d05b60 //按钮的在内存中的真实地址
>
| >
| |
| | | >
| | | | >
| | | | | ; layer = >
| | | | | | ; persistentIdentifier = 42D38740-A3B7-4D1F-BB16-34AA4FE0252A; activationState = UISceneActivationStateForegroundActive; settingsCanvas = ; windows = (
"; layer = >",
">"
)>
| | | | | | |
| | | | | | | |
- pactions(打印一个控件的响应方法和调用者)
(lldb) pactions 0x149d05b60
: btClicked
- pclass(打印class的继承关系)
(lldb) pclass 0x149d05b60
UIButton
| UIControl
| | UIView
| | | UIResponder
| | | | NSObject
- fvc(根据ViewController的Class名字查找VC)
(lldb) fvc ViewController
0x105002580 ViewController
(lldb) fvc -v 0x105002580
Found the owning view controller.
- pmethods(打印一个类的类和实例方法)
(lldb) pmethods 0x105002580
Class Methods:
No methods were found
Instance Methods:
- (void)btClicked
- (void)setBtn:(id)arg0
- (void)testMethod:(id)arg0
- (void)setTestArray:(id)arg0
- (id)btn
- (void).cxx_destruct
- (void)touchesBegan:(id)arg0 withEvent:(id)arg1
- (void)viewDidLoad
- (id)test
- (id)testArray
- (void)setTest:(id)arg0
- pinternals(打印一个对象内部的成员变量)
(lldb) pinternals 0x105002580
(ViewController) $16 = {
_test = nil
_testArray = nil
_btn = 0x0000000105107520
}
- taplog(我们需要先将程序暂停,输入taplog,程序会自己运行,这时候点击你需要查看的view,控制台上就会显示出你刚刚点击的view相关信息。)
(lldb) taplog
Process 669 resuming
>
要查看的view必须能接收点击事件,也就是他的userInteractionEnabled必须为YES才能被找到,UILabel和UIImageView默认userInteractionEnabled为NO。
- flicker(将view闪烁一下,以便于查找view的位置)
flicker 0x105107520 //view的在内存中的真实地址
- vs(在view层级中搜索view,并显示出来)
(lldb) vs 0x105107520
Use the following and (q) to quit.
(w) move to superview
(s) move to first subview
(a) move to previous sibling
(d) move to next sibling
(p) print the hierarchy
>
DerekSelander-LLDB
DerekSelander-LLDB是另一款比较好用的LLDB调试工具
README.md里面有安装步骤,这里lz
是把lldb_commands
文件夹,放到了跟chisel同级目录,/usr/local/opt
,然后在~/.lldbinit
里面加上 command script import /usr/local/opt/lldb_commands/dslldb.py 就ok
了
常用命令
- search(在堆中搜索某个类的所有活动实例,此类必须是动态的(继承NSObject / SwiftObject),目前不适用于NSString或NSNumber(标记的指针对象))
(lldb) search UIView
>
>
<_UINavigationBarLargeTitleView: 0x10b54eb70; frame = (0 0; 0 0); clipsToBounds = YES; hidden = YES; layer = >
...
- methods(打印所属类别的所有方法和成员变量)
(lldb) methods 0x2814fceb0
:
in WCAccountLoginControlLogic:
Properties:
@property (readonly) unsigned long hash;
@property (readonly) Class superclass;
@property (readonly, copy) NSString* description;
@property (readonly, copy) NSString* debugDescription;
Instance Methods:
- (void) startLogic; (0x10511d4e8)
- (void) stopLogic; (0x10511da50)
- (void) onOneClickLoginSwitchAccount; (0x10511e2e0)
- (void) onOneClickLoginProblem; (0x10511e314)
- (void) onOneClickLoginGoToSecurityCenter; (0x10511e34c)
- (void) onOneClickLoginGoToHelpCenter; (0x10511e384)
...
- sbt(恢复去符号的函数调用栈信息)
(lldb) b -a 0x10511dbcc //对一个函数方法下断点
(lldb) c
Process 19583 resuming
(lldb) bt //查看函数堆栈调用信息,frame #0是去符号的函数调用信息
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x000000010511dbcc WeChat`___lldb_unnamed_symbol142055$$WeChat
frame #1: 0x0000000193667a44 UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 96
(lldb) sbt //frame #0 恢复了去符号的函数调用信息
frame #0 : 0x10511dbcc WeChat`-[WCAccountLoginControlLogic onFirstViewLogin]
frame #1 : 0x193667a44 UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 96
README.md里面还有其他命令,可以自己去尝试
Cycript
Cycript是由Cydia
创始人Saurik推出的一款脚本语言,Cycript
混合了OC、JavaScript
语法的解释器,这意味着我们能够在一个命令中使用OC
或者JavaScript
,甚至两者并用。它能够挂钩正在运行的进程,能够在运行时修改很多东西。
Cycript的安装
1. 下载Cycript后,解压后直接把 cycript_0文件夹放到 /opt 目录下.
2. Terminal执行如下命令
$ cd /opt/cycript_0
$ ./cycript
如果出现
dyld: Library not loaded: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylib
Referenced from: /opt/cycript_0/./Cycript.lib/cycript-apl
Reason: image not found
Abort trap: 6
这个错误是因为电脑的ruby
版本太高导致
3. 查看电脑ruby版本
$ cd /System/Library/Frameworks/Ruby.framework/Versions/
$ ls
2.3 Current
4. 创建Ruby2.0版本文件,并将2.3版本文件拷贝到2.0中
sudo mkdir -p /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/
sudo ln -s /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/libruby.2.3.0.dylib /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylib
如果出现
mkdir: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/: Operation not permitted
5. 关闭系统的SIP
系统完整性保护(SIP)
- 通过重新启动计算机并在启动时按住Command+R键启动到恢复操作系统。
- 从 Utilities 菜单启动 Terminal。
- 输入以下命令:csrutil disable
- 重新启动您的计算机
6. 重新执行步骤4和步骤2,出现如下标志,安装成功!
cy#
control + D 退出 Cycript
Cycript配置环境变量
1. 编辑bash_profile文件
$ cd ~/
$ vim .bash_profile
2. 添加如下配置信息
#Cycript
export CY_PATH_ROOT=/opt/cycript_0
export PATH=$CY_PATH_ROOT:$PATH
3. 退出文件编辑并保存,执行如下命令查看,cycript环境变量配置成功
$ cycript
cy#
Cycript的简单使用
Cycript连接手机有两种方式:
- 通过ssh登录,需要越狱手机打开ssh端口
- 在非越狱手机上,我们可以使用MonkeyDev(iOS逆向-反Hook防护(VI)里面有详细介绍安装和使用)。它里面有句代码
CYListenServer(6666)
,代表在当前应用给Cycript
的Server
端申请了一个端口号6666
,我们可以在终端通过这个端口号在运行时给当前应用附加Cycript
(MonkeyDev已经帮我们注入了libcycript.dylib
)
$ cycript -r 172.20.0.240:6666
cy#
172.20.0.240是手机网络的ip
cy# UIWindow.keyWindow()
#"; layer = >"
cy# [UIApplication sharedApplication]
#""
cy# UIApp
#""
cy# var keyWindow = UIWindow.keyWindow()
#"; layer = >"
cy# keyWindow
#"; layer = >"
cy# keyWindow.rootViewController
#""
cy# #0x10b920a00 //根据地址获取对象
#""
cy# *#0x10b920a00 //获取对象的所有成员变量和值
{isa:MMUINavigationController,_responderFlags:@error,_overrideTransitioningDelegate:null,_view:#">",#">",#">"]
cy# keyWindow.recursiveDescription().toString() //打印所有的视图层级
`; layer = >
| ; layer = >
...
Shell脚本连接
1. 创建一个Shell脚本文件cylogin.sh, 路径如下
$ open ~/ 打开根目录
2. 添加可执行权限
$ chmod +x cylogin.sh
3. 配置环境变量
export NJSHELL=/Users/niujf/NJShell
export PATH=$CY_PATH_ROOT:$PATH:$NJSHELL
如果以前没有配置环境变量就是
export PATH=$NJSHELL
4. 执行Shell脚本文件cylogin.sh
$ sh cylogin.sh
Cycript的高级用法
cy# APPID
@"com.ttt.jinxiao"
cy# pviews
function (){return UIApp.keyWindow.recursiveDescription().toString()}
cy# pviews() //打印当前页面的view
`; layer = >
| ; layer = >
| | >
| | | >
| | | | >
| | | | | >
| | | | | | >
| | | | | >
| | | | | | >
| | | | | | | >
| | | | | | | | <_UILabelContentLayer: 0x283fa0ee0> (layer)
| | | | | | >
| | | | | | | >
| | | | | | | | <_UILabelContentLayer: 0x283fa0da0> (layer)
| | | | | >
| | | | | | >
| | | | | | | <_UILabelContentLayer: 0x283fa0d20> (layer)`
cy# pvcs() //打印当前页面的控制器
", state: appeared, view: \n | , state: appeared, view: "
cy# pactions(#0x11be35910) //获取对象的action
" onFirstViewLogin"
cy# rp(#0x11be35910) //获取对象的响应者
`>
>
>
>
>
; layer = >
; layer = >
; persistentIdentifier = 62B17F00-49F3-4938-80BC-83289F774DF4; activationState = UISceneActivationStateForegroundActive; settingsCanvas = ; windows = (
"; layer = >",
"; layer = >",
">"
)>
`
在MonkeyDev的MDConfig.plist
文件中,里面有链接,打开可以看到上面的命令其实都是已经封装好的
cy文件的封装
1. 在MonkeyDev中新建一个空的文件,命名test.cy
2. 在Build Phases->Copy Files中添加test.cy文件
3. 将test.cy文件类型改为 javaScript Source类型,撸代码,可以参考mjcript
4. 运行项目,终端调用
$ sh cylogin.sh
cy# @import test
{}
cy# MJAppId
@"com.ttt.jinxiao"
...
里面封装了很多命令就不一一演示了