iOS逆向 – 运行时分析(二)Cycript

欢迎关注微信公众号:FSA全栈行动 ?

一、Cycript

Cycript 是一款混合了 OCJS 语法解释器的脚本语言(即可以使用 OCJS 的语法来写脚本),其主要用来注入运行时程序进行调试,在程序重启后,所有通过 Cycript 进行的程序修改内容都会失效,对原生程序和代码毫无副作用。

官网地址:www.cycript.org

二、安装与简单使用

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()
复制代码

_autolayoutTrace

相比上面的 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 中也可以使用 OCCategory,为类拓展方法,如

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 文件:github.com/LinXunFeng/…

下载下来后,将 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.
复制代码

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享