iOS逆向--chisel、LLDB、Cycript、MonkeyDev

1,819 阅读7分钟

安装chisel

我们要安装一下chisel

安装chisel:

brew install chisel

chisel安装的位置是/usr/local/Cellar/chisel,在这里面有很多.py文件,我们需要的是fblldb.py

这个脚本我们需要配置一下,上一篇文章我知道LLDB启动时候默认会加载.lldbinit文件,我们需要把fblldb.py配置到这个文件里面去

command script import /usr/local/opt/chisel/libexec/fblldb.py

pviews指令

我们来做一个Demo测试一下,首先我们创建一个Demo,在控制器里面实现touchesbegan方法,然后在实现里面添加一个断点

运行,点击一下屏幕,授信我们可以通过p命令来打印一下view的子view

(lldb) pviews self.view
<UIView: 0x100d0c660; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x2813be400>>
(lldb) 

我们在view上添加一个按钮,并且上面指令没有层级关系,我们输入pviews指令试一下

(lldb) pviews self.view
<UIView: 0x10130eb40; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x282751360>>
   | <UIButton: 0x101304620; frame = (100 200; 100 100); opaque = NO; layer = <CALayer: 0x282751220>>
(lldb) pviews
<UIWindow: 0x10130b9d0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x282916580>; layer = <UIWindowLayer: 0x282743520>>
   | <UITransitionView: 0x101505e40; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x282746ba0>>
   |    | <UIDropShadowView: 0x101506b90; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x282747c00>>
   |    |    | <UIView: 0x10130eb40; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x282751360>>
   |    |    |    | <UIButton: 0x101304620; frame = (100 200; 100 100); opaque = NO; layer = <CALayer: 0x282751220>>

我们发现有层级关系了,而pviews这个指令就是上面lldb脚步加载时候添加的

pvc指令

(lldb) pvc
<ViewController 0x10130b280>, state: appeared, view: <UIView 0x10130eb40>
(lldb) 

如果有多层控制器会都打印出来的

假如我们在操作过程时候才配置了这个脚本,怎么办呢,这个时候就需要另外一个指令来重新加载一下:

(lldb) command source ~/.lldbinit
Executing commands in '/Users/zhaojing/.lldbinit'.
command script import /usr/local/opt/chisel/libexec/fblldb.py
command script import /opt/LLDB/lldb_commands/dslldb.py
target stop-hook add -o "frame variable"
Stop hook #1 added.
(lldb) 

也就是command source + 路径

我们来对自己ipa进行动态调试一下,假装我们没有源码,怎么进行调试呢?

首先可以通过Debug view查看页面的层级:

查看按钮的响应者和响应方法:pactions + 按钮内存地址

查看一个按钮的响应者和响应方法:

我们也可以用指令来查看:pactions + 按钮内存地址

(lldb) pactions 0x1063621a0
<BYHomeNotLoginedView: 0x106356b20; frame = (0 0; 414 938.4); layer = <CALayer: 0x2817031a0>>: by_clickLoginBtn
(lldb) 

查看响应者链条:presponder + 按钮内存地址

也可以查看按钮的响应链条:presponder + 内存地址

(lldb) presponder 0x1063621a0
<UIButton: 0x1063621a0; frame = (46.92 644.736; 320.16 55.2); opaque = NO; layer = <CALayer: 0x2817032e0>>
   | <BYHomeNotLoginedView: 0x106356b20; frame = (0 0; 414 938.4); layer = <CALayer: 0x2817031a0>>
   |    | <UIScrollView: 0x1070ee600; frame = (0 0; 414 813); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x281973420>; layer = <CALayer: 0x281707680>; contentOffset: {0, 0}; contentSize: {414, 1400.384}; adjustedContentInset: {0, 0, 0, 0}>
   |    |    | <BYNewHomeView: 0x10634d490; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x281902be0>; layer = <CALayer: 0x281701320>>
   |    |    |    | <BYNewHomeViewController: 0x10b60d640>
   |    |    |    |    | <UIViewControllerWrapperView: 0x10646f5c0; frame = (0 0; 414 896); layer = <CALayer: 0x2817d3680>>
   |    |    |    |    |    | <UINavigationTransitionView: 0x10b60fe30; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x2817305a0>>
   |    |    |    |    |    |    | <UILayoutContainerView: 0x10b60c730; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x2819a4900>; layer = <CALayer: 0x28172d0c0>>
   |    |    |    |    |    |    |    | <BaseNavigationController: 0x10780f600>
   |    |    |    |    |    |    |    |    | <UIViewControllerWrapperView: 0x10a30cc70; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x28173c580>>
   |    |    |    |    |    |    |    |    |    | <UITransitionView: 0x10b60d2b0; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x28172e700>>
   |    |    |    |    |    |    |    |    |    |    | <UILayoutContainerView: 0x10b60c930; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x28172f8e0>>
   |    |    |    |    |    |    |    |    |    |    |    | <BYTabBarViewController: 0x107811800>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIDropShadowView: 0x10644d8c0; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x281739e20>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UITransitionView: 0x1064214e0; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x281739e00>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIWindow: 0x1063154c0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x2819d6730>; layer = <UIWindowLayer: 0x2817e95c0>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIWindowScene: 0x106316160; scene = <FBSScene: 0x2839a7680; identifier: sceneID:com.baoyinxiaofei.MonkeyDemo-default>; persistentIdentifier = 88293E55-C492-4396-AAED-56139F72B747; activationState = UISceneActivationStateForegroundActive; settingsCanvas = <UIWindowScene: 0x106316160>; windows = [
<UIWindow: 0x1063154c0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x2819d6730>; layer = <UIWindowLayer: 0x2817e95c0>>
]>
   |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIApplication: 0x106311fd0>
(lldb) 

查看继承关系:pclass

我们也可以查看一下类的继承关系:

(lldb) pclass 0x107811800
BYTabBarViewController
   | UITabBarController
   |    | UIViewController
   |    |    | UIResponder
   |    |    |    | NSObject
(lldb) 

查看类有哪些方法:pmethods

查看这个类有哪些方法:

(lldb) pmethods 0x107174a00
Class Methods:
No methods were found

Instance Methods:
- (id)bgView
- (void)setBgView:(id)arg0 
- (void)setImgView:(id)arg0 
- (id)imgView
- (void)setFuncNameLabel:(id)arg0 
- (id)funcNameLabel
- (id)item
- (id)initWithStyle:(long long)arg0 reuseIdentifier:(id)arg1 
- (void).cxx_destruct
- (void)setIndicator:(id)arg0 
- (id)indicator
- (void)layoutSubviews
- (void)setItem:(id)arg0 
- (void)initViews
(lldb) 

查看有哪些成员变量:pinternals

查看有哪些属性:

(lldb) pinternals 0x107174a00
(BMCCenterNewPersonalTableViewCell) $116 = {
  UITableViewCell = {
    UIView = {
      UIResponder = {
        NSObject = {
          isa = BMCCenterNewPersonalTableViewCell
        }
      }
    }
  }
  _item = 0x0000000283434320
  _bgView = 0x000000010648f630
  _imgView = 0x000000010648fb00
  _funcNameLabel = 0x0000000106487920
  _indicator = 0x0000000106487b90
}

通过类名找视图:fv

通过类名找视图:

(lldb) fv BMCCenterNewPersonalTableViewCell
0x107174a00 BMCCenterNewPersonalTableViewCell
0x1070a7200 BMCCenterNewPersonalTableViewCell
0x1070c6200 BMCCenterNewPersonalTableViewCell
0x107015400 BMCCenterNewPersonalTableViewCell
0x107069c00 BMCCenterNewPersonalTableViewCell
(lldb) 

动态调试 -- taplog

首先我们运行好程序,通过pause断住程序,然后输入taplog指令

这个指令就是断住程序,用来动态调试使用的,当点击能够响应的控件时候就会断住程序,然后显示按钮的内存地址

通过内存地址我们可以通过指令flicker + 内存地址,在断点模式下调用该指令会闪烁 view , 非常方便调试 , 以及确定该内存地址是否为我们想找到的那个视图 .

还有一个vc指令,

  • w: 移动到控件的父控件上
  • s:移动到子控件的第一个控件
  • a:移动到同级关系的前一个
  • d:移动到同级关系的后一个控件
  • p:打印控件的层级关系

退出 vs 调试模式 , q 指令 .

LLDB插件

LLDB官网是:官网

配置过程就是,先在LLDB下载地址,下载下来,然后放到自定义的工具目录/opt/中(这个目录可以改的,看自己兴趣),然后在.lldbinit文件中奖LLDB中的LLDB/lldb_commands/dslldb.py 文件配置进去,配置命令是:

command script import /opt/LLDB/lldb_commands/dslldb.py

前面的opt是自定义的路径

通过类名在项目中查找类在哪使用:search + 类名

配置完成后,在XCodel中再次加载一下.lldbinit文件,这个时候我们就能使用LLDB插件中配置的指令了,例如通过类名在项目中查找类在哪使用search + 类名

假如我们想对哪个按钮下断点怎么操作呢?

首先我们打开Debug View打开页面调试,找到按钮的类名:

虽然上面已经显示了按钮的内存地址,我们也可以通过fv指令来查看按钮的内存地址:

(lldb) fv FixTitleColorButton
0x12ea16170 FixTitleColorButton
0x12ea54b00 FixTitleColorButton
(lldb) 

我们发现有两个按钮,其中一个跟DebugView显示的一样

有了按钮的内存地址,我们就能通过methods指令定位相应的方法地址:

方法后面就是内存地址,我们就可以通过breakpoint指令来对方法进行下断点了,这里因为方法太多,所以找不到,就换一个方法进行下断点

(lldb) b -a 0x1054d02b0
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol346528$$WeChat, address = 0x00000001054d02b0
(lldb) 

我们发现断点下成功了

测试一下,点击下按钮,断住了,然后我们看一下堆栈信息,

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00000001b14c3dd0 libsystem_kernel.dylib`mach_msg_trap + 8
    frame #1: 0x00000001b14c3184 libsystem_kernel.dylib`mach_msg + 76
    frame #2: 0x000000018553acf8 CoreFoundation`__CFRunLoopServiceMachPort + 380
    frame #3: 0x0000000185534ea8 CoreFoundation`__CFRunLoopRun + 1216
    frame #4: 0x00000001855344bc CoreFoundation`CFRunLoopRunSpecific + 600
    frame #5: 0x000000019bfb9820 GraphicsServices`GSEventRunModal + 164
    frame #6: 0x0000000187ed8734 UIKitCore`-[UIApplication _run] + 1072
    frame #7: 0x0000000187edde10 UIKitCore`UIApplicationMain + 168
    frame #8: 0x0000000102aa0be4 credit`main(argc=<unavailable>, argv=<unavailable>) at main.m:19:16 [opt]
    frame #9: 0x00000001851fbe60 libdyld.dylib`start + 4
(lldb)

可能发现有的方法没有,这是因为我们没有恢复符号,这个时候我们就需要工具恢复macho符号,不过这个地方我们可以用sbt来查看,这个指令可以通过方法名称来给我们恢复一部分

(lldb) sbt
frame #0 : 0x1b14c3dd0 libsystem_kernel.dylib`mach_msg_trap + 8
frame #1 : 0x1b14c3184 libsystem_kernel.dylib`mach_msg + 76
frame #2 : 0x18553acf8 CoreFoundation`__CFRunLoopServiceMachPort + 380
frame #3 : 0x185534ea8 CoreFoundation`__CFRunLoopRun + 1216
frame #4 : 0x1855344bc CoreFoundation`CFRunLoopRunSpecific + 600
frame #5 : 0x19bfb9820 GraphicsServices`GSEventRunModal + 164
frame #6 : 0x187ed8734 UIKitCore`-[UIApplication _run] + 1072
frame #7 : 0x187edde10 UIKitCore`UIApplicationMain + 168
frame #8 : 0x102aa0be4 credit`main + 88
frame #9 : 0x1851fbe60 libdyld.dylib`start + 4

(lldb) 

section指令

  • Section 指令可以让我们快速查看 Mach-O 有哪些 Section 段
(lldb) section
[0x00000000000000-0x00000100000000] 0x0100000000 credit`__PAGEZERO
[0x000001028e0000-0x0000010329c000] 0x00009bc000 credit`__TEXT
[0x0000010329c000-0x00000103570000] 0x00002d4000 credit`__DATA
[0x00000103570000-0x00000103bdc000] 0x000066c000 credit`__LINKEDIT
[0xffffffffffffffff-0x100000000011fcfff] 0x00011fd000 credit`__DWARF

(lldb) 
  • Section 可添加其他指令来查看 Mach-O 具体内容 .
(lldb) section
[0x00000000000000-0x00000100000000] 0x0100000000 credit`__PAGEZERO
[0x000001028e0000-0x0000010329c000] 0x00009bc000 credit`__TEXT
[0x0000010329c000-0x00000103570000] 0x00002d4000 credit`__DATA
[0x00000103570000-0x00000103bdc000] 0x000066c000 credit`__LINKEDIT
[0xffffffffffffffff-0x100000000011fcfff] 0x00011fd000 credit`__DWARF

(lldb) section UIKit
[0x000001b79d2000-0x000001b79d3000] 0x0000001000 UIKit`__TEXT
[0x000001d815a0b0-0x000001d815a0b8] 0x0000000008 UIKit`__DATA_CONST
[0x000001e5ef8000-0x000001e5ef9000] 0x0000001000 UIKit`__LINKEDIT

(lldb) section UIKit __TEXT
[0x000001b79d2fc8-0x000001b79d2fc8] 0x0000000000 UIKit`__TEXT.__text
[0x000001b79d2fc8-0x000001b79d3000] 0x0000000038 UIKit`__TEXT.__const

(lldb) section UIKit __TEXT.__cstring -l
Traceback (most recent call last):
  File "/opt/LLDB/lldb_commands/section.py", line 74, in handle_command
    output = parseSection(sections, options, target)
  File "/opt/LLDB/lldb_commands/section.py", line 90, in parseSection
    name = ds.getSectionName(section)
  File "/opt/LLDB/lldb_commands/ds.py", line 73, in getSectionName
    name = section.name
AttributeError: 'NoneType' object has no attribute 'name'
(lldb) section  __TEXT.__entitlements
Traceback (most recent call last):
  File "/opt/LLDB/lldb_commands/section.py", line 74, in handle_command
    output = parseSection(sections, options, target)
  File "/opt/LLDB/lldb_commands/section.py", line 90, in parseSection
    name = ds.getSectionName(section)
  File "/opt/LLDB/lldb_commands/ds.py", line 73, in getSectionName
    name = section.name
AttributeError: 'NoneType' object has no attribute 'name'
(lldb) section  __DATA.__la_symbol_ptr -l
(lldb) 

上面我们知道了lldb可以进行调试,但是调试前提需要把程序断住才能,下面我们介绍一种不用断住程序就能调试的工具--Cycript

Cycript

  • 编译型语言(OC)

    需要将源码经过编译器编译,生成对应架构的可执行文件(二进制),编译型语言就相当于一个英语娴熟的人看一本英文书籍,不需要借助别的工具了。代价就是学英语这个过程很慢(编译时间长)。

  • 解释型语言(Python)

    源码不需要编译器提前编译,而是在运行的时候,经过一套对应的解释器,临时将源码翻译成二进制让CPU识别。

解释型语言就相当于一个不会英语的人看一本英文书籍,这时候就需要借助工具(字典)才能正常阅读。好处的就是没有学习(编译)这么个耗时的过程。

理论上解释型语言在运行效率上会比解释型语言慢很多,但实际上现在有很多牛逼的解释器,在执行特定的代码的时候效率也非常快。原理就是在执行的的时候会将之前解释过的代码缓存起来,之后就不需要重复解释了。如:pypy3、这片文章的主角Cycript就是解释型语言!

Cycript 是由 Cydia ( 熟悉越狱的同学应该都很清楚 ) 创始人 Saurik 推出的一款脚本语言,Cycript 混合了 OC、 JavaScript 语法的解释器,这意味着我们能够在一个命令中使用 OC 或者 JavaScript,甚至两者并用。

它能够挂钩 正在运行的进程,能够在运行时修改很多东西。

到官网Cycript点击 Download SDK 即可下载 .

将下载后的文件夹放入 /opt/ 里即可 , 也可以自行选择位置

配置环境变量

想使用Cycript需要我们在.bash_profile文件里面配置:export + 文件路径

export /opt/cycript_0.9.594/

重启 iTerm , 输入 cycript , 即可查看

如果有遇到 ruby 环境不对的同学 , 去下载对应版本的即可 . /System/Library/Frameworks/Ruby.framework/Versions

cycript使用

在使用cycript之前我们需要安装一个MonkeyDev,它可以自动帮我们集成cycript和fishHook等工具

MonkeyDev的安装

在学习Cycript之前我们要先按照以下MonkeyDev,安装方式是:MonkeyDev,里面很详细,可以直接按照教程来

按照过程中可能会出现下面错:

连接不上服务器
curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused
Failed to download https://raw.githubusercontent.com/AloneMonkey/frida-ios-dump/3.x/dump.py to /opt/MonkeyDev/bin/dump.py

这个用VPN做一下安全上网就行了

找不到MacOSX Package Types.xcspec文件
Creating symlink to Xcode templates...
Modifying Bash personal initialization file...
File /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX Package Types.xcspec not found

这是因为我的是XCode12,XCode里面没有这个文件了,解决办法就是从XCode11,里面复制一份过来放在相应路径就行了,我这放百度网盘一份,需要的可以下载:

链接: pan.baidu.com/s/1KXjFH6AQ…
密码: vf5p

Homebrew

使用方式在官网:

Homebrew官网

安装命令:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

查看安装的工具:

brew list

我们创建一个monkey工程:

在非越狱环境下 , 就要借助 Cycript 提供的 iOS Framework , 注入进去了,而且在 MonkeyDev 中 , 是默认已经做好了 Cycript 的注入的

而且添加了 默认 6666 端口号的监听(这个地方用6666端口我手机连接不上,所以自己把它改成6688端口了):

也就是说只要用 MonkeyDev 来重签跑起来的程序进程 , 这个端口就可以附加使用了

通过cycript连接到手机:

  • 1 . 保证电脑和手机在同一个局域网内 ( 因为要做端口映射 )
  • 2 . 运行 MonkeyDev 重签名程序 / 或者直接打开以前重签好的工程 , 无须 Xcode 运行也可以
  • 3 . 终端输入 cycript -r 192.168.0.116:6688

换成你自己的手机 ip 地址,另外 , 请不要将应用程序放到后台 , 会影响附加.

连接到手机

 ✘  ~   master ●✚  cycript -r 10.1.109.121:6688
cy# 

说明连接成功,如果出现下面情况说明连接失败:

如果我们不想用这种方式来开启调试,我们把这条指令放到一个sh脚本里面,每次运行一下脚本就能连接到手机了

其实我们工作中有很多常用的脚本,我们可以吧脚本放到一个统一文件夹下,然后在bash_profile配置一下环境变量就行了,这样我们每次启动iTerm时候,想做哪个功能,运行一下脚本就行了,

例如我在根目录的opt里面创建一个ZJJShell目录。里面放我自己写的脚本

然后在.zshrc里面配置一下环境变量:

配置好了在iTerm中重新运行一下就行了

source .zshrc

下面我们可以用一些命令来测试一下cycript是否真的连接上了:

查看当前 Window :UIWindow.keyWindow()
cy# UIWindow.keyWindow()
#"<iConsoleWindow: 0x10fe632a0; baseClass = UIWindow; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28112d710>; layer = <UIWindowLayer: 0x281e877a0>>"

拿到UIApplication:[UIApplication sharedApplication ]

cy# [UIApplication sharedApplication]
#"<UIApplication: 0x110e04bf0>"
cy# #0x110e04bf0
#"<UIApplication: 0x110e04bf0>"

也可以通过# + 内存地址 来打印,其中#相当于po指令

还有* + 对象,可以打印出这个对象所有成员变量

自定义变量
  • 指令 : 自己 var 一个对象 , 而后我们就可以使用
  • 另外注意 : 只要 APP 进程没有挂 , 这个变量是一直存在的
cy# [UIApplication sharedApplication]
#"<UIApplication: 0x147e15f40>"
cy# APP = [UIApplication sharedApplication];
#"<UIApplication: 0x147e15f40>"
cy# APP
#"<UIApplication: 0x147e15f40>"
cy# APP.delegate
#"<MicroMessengerAppDelegate: 0x281777900>"
cy#
  • 地址可以直接使用
  • 对象地址 可以直接调用对象的方法
cy# #0x147e15f40
#"<UIApplication: 0x147e15f40>"
cy# #0x147e15f40.delegate
#"<MicroMessengerAppDelegate: 0x281777900>"
cy#
查看视图层级:UIWindow.keyWindow().recursiveDescription().toString()
cy# UIWindow.keyWindow().recursiveDescription().toString()
`<iConsoleWindow: 0x147f43f80; baseClass = UIWindow; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x2824485a0>; layer = <UIWindowLayer: 0x282b96700>>
   | <UILayoutContainerView: 0x147f32ae0; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x282449710>; layer = <CALayer: 0x282b949a0>>
   |    | <UINavigationTransitionView: 0x147f46d90; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x282b94cc0>>
   |    |    | <UIViewControllerWrapperView: 0x147f585d0; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x282a7dbc0>>
   |    |    |    | <UIView: 0x147f47b00; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x282ba88c0>>
   |    |    |    |    | <UIView: 0x147e2d430; frame = (0 48; 414 961); autoresize = W; layer = <CALayer: 0x282ba0280>>
   |    |    |    |    |    | <UIImageView: 0x147f253c0; frame = (0 -48; 414 896); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x282ba9720>>
   |    |    |    |    | <UIView: 0x147f1bf60; frame = (0 777; 414 65); autoresize = W+TM; layer = <CALayer: 0x282baaf20>>
   |    |    |    |    |    | <FixTitleColorButton: 0x147f4d2e0; baseClass = UIButton; frame = (20 15; 177 47); clipsToBounds = YES; opaque = NO; autoresize = RM; layer = <CALayer: 0x282bab3c0>>
   |    |    |    |    |    |    | <UIButtonLabel: 0x147f2d7d0; frame = (70 12.5; 37 22); text = '\u767b\u5f55'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x280838d70>>
   |    |    |    |    |    | <FixTitleColorButton: 0x147f58e50; baseClass = UIButton; frame = (217 15; 177 47); clipsToBounds = YES; opaque = NO; autoresize = LM; layer = <CALayer: 0x282b8bb40>>
   |    |    |    |    |    |    | <UIButtonLabel: 0x147f59170; frame = (70 12.5; 37 22); text = '\u6ce8\u518c'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x280839130>>
   |    |    |    |    | <UIButton: 0x147f59400; frame = (326 48; 88 49); opaque = NO; autoresize = LM; layer = <CALayer: 0x282b8bd00>>
   |    |    |    |    |    | <UIButtonLabel: 0x147f60900; frame = (15 16; 58 17); text = '\u7b80\u4f53\u4e2d\u6587'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x280839220>>`

通过这个指令我们可以拿到页面所有控件的内存地址,并且可以针对性的对控件进行动态调试

我们可以拿到页面中的一个控件的内存地址来修改这个控件的属性

修改后的结果:

获取页面上所有控件:choose(UIButton)
cy# choose(UIButton)
[#"<FixTitleColorButton: 0x10ed29390; baseClass = UIButton; frame = (20 15; 177 47); clipsToBounds = YES; opaque = NO; autoresize = RM; layer = <CALayer: 0x2802ba460>>",#"<FixTitleColorButton: 0x10ed72710; baseClass = UIButton; frame = (217 15; 177 47); clipsToBounds = YES; opaque = NO; autoresize = LM; layer = <CALayer: 0x2802ab060>>",#"<UIButton: 0x10ed72cc0; frame = (326 48; 88 49); opaque = NO; autoresize = LM; layer = <CALayer: 0x2802ab220>>"]
cy#

choose里面可以放label、imageView

隐藏 / 显示状态栏

[UIApp setStatusBarHidden:YES]

  • APP角标 [UIApp setApplicationIconBadgeNumber: 99]

  • 获取Bundle ID

APPID 结果 :

@"com.libin.LBMonkeyApp"

  • 页面层级

pviews()

pvcs()

  • 根据按钮地址获取按钮 target & Action
cy# pactions(#0x10ed29390)
"<WCAccountLoginControlLogic: 0x282111d60> onFirstViewLogin"
cy#
  • 根据按钮地址获取响应链

rp(#0x10b29da40)

  • 退出cy 调试模式

control + d

我们知道微信可以检测到程序是否可以被调试,其实微信是通过检测是否有被注入的库来检测APP是否被调试的,而cycript是我们通过monkey注入到ipa包里的,

自定义 cy 指令

pviews / pvc / pactions / rp 这些指令是 Monkey 在 MDConfig.plist 中额外封装了自定义的 cy 源的 . 也就是说使用越狱环境原本的 cycript 插件是没有这些指令可用的 .

这两个地方配置的url就是cy文件的路径,也就是这两个cy文件封装了上面pviews等功能

我们来自定义一个.cy文件,然后把它加载到项目中:

//IIFE 匿名函数自执行表达式

(function(exports){

     ZJJAPPID = [NSBundle mainBundle].bundleIdentifier,
     APPPATH = [NSBundle mainBundle].bundlePath,

     //如果有变化,就用function去定义!!
     LBRootvc = function(){
        return UIApp.keyWindow.rootViewController;
     };

     LBKeyWindow = function(){
        return UIApp.keyWindow;
     };

    LBGetCurrentVCFromRootVc = function(rootVC){
        var currentVC;
        if([rootVC presentedViewController]){
            rootVC = [rootVC presentedViewController];
        }

        if([rootVC isKindOfClass:[UITabBarController class]]){
            currentVC = LBGetCurrentVCFromRootVc(rootVC.selectedViewController);
        }else if([rootVC isKindOfClass:[UINavigationController class]]){
            currentVC = LBGetCurrentVCFromRootVc(rootVC.visibleViewController);
        }else{
            currentVC = rootVC;
        }

        return currentVC;
    };

    LBCurrentVC = function(){
        return LBGetCurrentVCFromRootVc(LBRootvc());
    };
 
})(exports);

然后我们将文件移到项目中,并在Build Phases - Copy Files中 , 引入这个文件

  • 重新运行工程

  • 输入我们自定义的指令 例如 : LBCurrentVC()

  • 提示没找到指令 , 因为我们还没有引入 , monkey 那两个在 config 中会自动引入.

  • @import zjj

  • 再次输入 LBCurrentVC() , 得到结果.

  • 使用 cycript 某一个控件 / 对象 内存地址时 , 要注意其生命周期 , 例如用一个 label 的内存地址调试 , 你页面退出 , 又重进 , 是重新创建了对象的 , 以前的内存地址也会无法再使用 . 不要忘记这点

  • cycript 使用需要同局域网每次要连接 , 或者其他我们需要自定义的初始化动作 , 我们可以将其写到一个脚本中 , 配置到 zsh / bash 环境变量中即可 , 大家可以玩一玩 , 有问题大家一起探讨一下 .

  • 逆向过程中 , View Debug 和 cycript 调试界面是非常常用的手段 , 因此 , 希望大家能熟练掌握这些技巧 .