iOS逆向 - 运行时分析(二)Cycript
# 一、Cycript
Cycript
是一款混合了 OC
和 JS
语法解释器的脚本语言(即可以使用 OC
和 JS
的语法来写脚本),其主要用来注入运行时程序进行调试,在程序重启后,所有通过 Cycript
进行的程序修改内容都会失效,对原生程序和代码毫无副作用。
官网地址:http://www.cycript.org (opens new window)
# 二、安装与简单使用
在 Cydia
中搜索 Cycript
,找到安装即可。
在 iOS
设备上输入 cycript
,会显示 cy#
提示符
lxf-iPad:~ root# cycript
cy#
这是一个 JS
控制台,输入的内容都由 JS
内核运行。
cy# var name = 'lxf'
"lxf"
cy# print(name)
lxf
常用快捷键
快捷键 | 作用 |
---|---|
ctrl + c | 取消当前输入或中断执行中的命令 |
ctrl + d | 退出 Cycript |
ctrl + l | clear 清屏 |
使用 -p
参数注入指定程序,前提是你的程序得在运行中。
下面展示一下注入 Twitter
后,查看其沙盒路径
lxf-iPad:~ root# cycript -p Twitter
cy# NSHomeDirectory()
@"/var/mobile/Containers/Data/Application/2FA6975C-9DB0-4B08-9FE9-365473E86748"
cy#
# 三、实战
目标:如上图所示,我们来将
linxunfeng.top
改成fullstackaction.com
要修改它就得先找到它,可以使用 recursiveDescription
函数可以递归打印任意视图的层次结构
[[UIApp keyWindow] recursiveDescription].toString()
如果我们不清楚完整的函数名时,可以使用敲击 tab
键列出所有匹配项
# cy# [[UIApp keyWindow] recursive【敲击tab键】
cy# [[UIApp keyWindow] recursive
recursiveDescription recursivelyForceDisplayIfNeeded
看着上面图中打印出来的内容,任谁看了都觉得很烦恼,这里可以使用简化版的方法 _autolayoutTrace
[[UIApp keyWindow] _autolayoutTrace].toString()
相比上面的 recursiveDescription
来对,会清晰很多
往下翻,可以找到该段落
\u2022T1ProfileHeaderViewContro...:0x1164bac50
UIView:0x1164bae30
| T1ProfileUserInfoViewCont...:0x117748050
| ProfileHeaderBio:0x116498070
| T1TranslateButton:0x116474cc0
| ProfileHeaderTranslatedBi...:0x11773ed70
| ProfileTranslationActivit...:0x11773f170
| UIImageView:0x11773f3f0
| ProfileHeaderLocation:0x11641f090
| UIButtonLabel:0x11641f3b0
| ProfileHeaderWebSite:0x113526840
| | UIImageView:0x1164e3b60
| | UIButtonLabel:0x11355bbe0'linxunfeng.top'
| ProfileHeaderBirthday:0x11355bee0
| | UIButtonLabel:0x11355c200
| ProfileHeaderCreatedDate:0x11355c500
| | UIImageView:0x1164e4100
| | UIButtonLabel:0x11355c820'2017\u5e748\u6708 \u52a0\u5165'
| TFNAttributedTextView:0x11641f6b0
| | T1AccessibilityProxyView:0x11641af00
T1ProfileFriendsFollowing...:0x1164dc870
| TFNSolidColorView:0x1164dca50
| T1FlexibleLayoutView:0x1135df410
| | TFNAttributedTextView:0x1135df810
| | | T1AccessibilityProxyView:0x1164e06a0
| | T1UserFacepileView:0x1164df730
| | TFNAttributedTextView:0x1164dfb50
| T1ProfileFriendsFollowingHighlightView:0x1164dff50
这里说明一下,Cycript
对中文的显示不太友好
可以将上面的输出内容拷贝到一些 Unicode转中文
的工具里,直接转换输出
•T1ProfileHeaderViewContro...:0x1164bac50
UIView:0x1164bae30
| T1ProfileUserInfoViewCont...:0x117748050
| ProfileHeaderBio:0x116498070
| T1TranslateButton:0x116474cc0
| ProfileHeaderTranslatedBi...:0x11773ed70
| ProfileTranslationActivit...:0x11773f170
| UIImageView:0x11773f3f0
| ProfileHeaderLocation:0x11641f090
| UIButtonLabel:0x11641f3b0
| ProfileHeaderWebSite:0x113526840
| | UIImageView:0x1164e3b60
| | UIButtonLabel:0x11355bbe0'linxunfeng.top'
| ProfileHeaderBirthday:0x11355bee0
| | UIButtonLabel:0x11355c200
| ProfileHeaderCreatedDate:0x11355c500
| | UIImageView:0x1164e4100
| | UIButtonLabel:0x11355c820'2017年8月 加入'
| TFNAttributedTextView:0x11641f6b0
| | T1AccessibilityProxyView:0x11641af00
T1ProfileFriendsFollowing...:0x1164dc870
| TFNSolidColorView:0x1164dca50
| T1FlexibleLayoutView:0x1135df410
| | TFNAttributedTextView:0x1135df810
| | | T1AccessibilityProxyView:0x1164e06a0
| | T1UserFacepileView:0x1164df730
| | TFNAttributedTextView:0x1164dfb50
| T1ProfileFriendsFollowingHighlightView:0x1164dff50
如果内容较少,如
UIButtonLabel:0x11355c820'2017\u5e748\u6708 \u52a0\u5165'
可以使用 echo -e
进行转换输出
echo -e '2017\u5e748\u6708 \u52a0\u5165'
2017年8月 加入
回到正题,我们已经找到了目的控件,接下来就是修改它
UIButtonLabel:0x11355bbe0'linxunfeng.top'
在
Cycript
中,如果我们知道一个对象的内存地址,就可以通过#
操作符来获取该对象。
不过它的类型是:UIButtonLabel
,我们可以看看它的父控件
| ProfileHeaderWebSite:0x113526840
| | UIImageView:0x1164e3b60
| | UIButtonLabel:0x11355bbe0'linxunfeng.top'
打印 ProfileHeaderWebSite:0x113526840
这个对象
cy# #0x113526840
#"<UIButton: 0x113526840; frame = (20 12.186; 123 18); clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x282f56df0>; layer = <CALayer: 0x2822821c0>>"
可以看到是个 UIButton
,那怎么改标题不是很明白了吗?
[#0x113526840 setTitle:@"fullstackaction.com" forState:UIControlStateNormal]
但是并没有任何效果~,没事,那就换设置富文本试试
var siteAttr = [[NSAttributedString alloc] initWithString:@"fullstackaction.com" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:10],NSForegroundColorAttributeName:[UIColor blueColor]}];
[#0x113526840 setAttributedTitle:siteAttr forState:UIControlStateNormal]
大功告成😃
# 四、高级用法
# 1、choose
- 查找对象
用法:choose(类名)
有时候上面提到的 recursiveDescription
和 _autolayoutTrace
,对我们来说输出内容太多了,且我们知道目标对象的类名,此时就可以通过 choose
这个函数,获取符合条件(当前类及子类)的所有记录,从而快速拿到对象的地址来操作对象
cy# choose(UILabel)
[#"<T1AccessibleBadgesLabel: 0x11773a1d0; baseClass = UILabel; frame = (265.5 0; 100.24 20.2871); text = '\xe2\x81\xa8LinXunFeng\xe2\x81\xa9'; clipsToBounds = YES; alpha = 0; gestureRecognizers = <NSArray: 0x282f254d0>; layer = <_UILabelLayer: 0x2803585a0>>",#"<T1ScrollingHorizontalLabelCellLabel: 0x117740a70; baseClass = UILabel; frame = (4 0; 35 20.5); text = '\xe5\xaa\x92\xe4\xbd\x93'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x28035a8a0>>",#"<T1ScrollingHorizontalLabelCellLabel: 0x117742180; baseClass = UILabel; frame = (4 0; 87 20.5); text = '\xe6\x8e\xa8\xe6\x96\x87\xe5\x92\x8c\xe5\x9b\x9e\xe5\xa4\x8d'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x28035ad50>>",#"<T1ScrollingHorizontalLabelCellLabel: 0x117743890; baseClass = UILabel; frame = (4 0; 35 20.5); text = '\xe6\x8e\xa8\xe6\x96\x87'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x28035b110>>",#"<T1ScrollingHorizontalLabelCellLabel: 0x116438ef0; baseClass = UILabel; frame = (4 0; 35 20.5); text = '\xe5\x96\x9c\xe6\xac\xa2'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x28033e580>>",#"<T1AccessibleBadgesLabel: 0x116484790; baseClass = UILabel; frame = (0 0; 139.113 28.6406); text = '\xe2\x81\xa8LinXunFeng\xe2\x81\xa9'; clipsToBounds = YES; userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x282f27f60>; layer = <_UILabelLayer: 0x28035d9f0>>"]
# 2、分类拓展
在 Cycript
中也可以使用 OC
的 Category
,为类拓展方法,如
cy# @implementation NSString (LXF)
+ website { return "fullstackaction.com"; }
@end
cy# [NSString website]
@"fullstackaction.com"
# 3、封装函数
在实战过程中,往往会遇到一些层次很深的情况,如果靠我们自己手动去循环的查找,会变成非常麻烦,如查找当前最顶层的控制器,这时,我们可以利用 Cycript
来编写自定义函数解决这个问题
cy# function topVc(vc) {
if (vc.presentedViewController) {
return _topVc(vc.presentedViewController);
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return _topVc(vc.selectedViewController);
} else if ([vc isKindOfClass:[UINavigationController class]]) {
return _topVc(vc.visibleViewController);
} else {
var count = vc.childViewControllers.count;
for (var i = count - 1; i >= 0; i--) {
var childVc = vc.childViewControllers[i];
if (childVc && childVc.view.window) {
vc = _topVc(childVc);
break;
}
}
return vc;
}
}
打印最顶层的控制器
cy# topVc(UIApp.keyWindow.rootViewController)
#"<T1TabBarViewController: 0x107864270>"
每次都在终端里写一次,那岂不是也非常麻烦?我们可以将这些代码写到一个 .cy
文件中
我们可以在 /usr/lib/cycript0.9/com/saurik/substrate/
找到一个名为 MS.cy
的文件
(function(exports) {
exports.getImageByName = MSGetImageByName;
exports.findSymbol = MSFindSymbol;
exports.hookFunction = function(func, hook, old) {
...
};
exports.hookMessage = function(isa, sel, imp, old) {
...
};
})(exports);
参考它写一个即可。附上自己封装的 cy
文件:https://github.com/LinXunFeng/lxf_cycript (opens new window)
下载下来后,将 lxf.cy
拷贝至 /usr/lib/cycript0.9/com/lxf/
路径下
然后在 cycript
环境下执行:
cy# @import com.lxf.lxf; 0
注:分号后面的0
是为了隐藏脚本导入后的脚本内容输出
导入后,使用 lxf.函数名()
即可
cy# lxf.topVc()
#"<T1TabBarViewController: 0x107864270>"
如果不太记得函数名字,可以使用 tab键
查看
cy# lxf.【敲击tab键】
__defineGetter__ __lookupSetter__ appPath classMethods findVc isString keyWindow methods rootVc toLocaleString valueOf
__defineSetter__ __proto__ cachesPath constructor hasOwnProperty ivarNames loadFramework printIvars subViews toString
__lookupGetter__ appId classMethodNames documentPath isPrototypeOf ivars methodNames propertyIsEnumerable subViewsSimple topVc
cy# lxf.
- 01
- Flutter - 子部件任意位置观察滚动数据11-24
- 02
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 03
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21