非越狱环境下的iOS逆向工程:IPAPatch + Reveal
![](https://img.haomeiwen.com/i1741551/8a1bf26aabf390d7.jpg)
要是不需要越狱,就可以用Reveal实时查看他人APP的UI、阅读并修改他们APP的代码、甚至使用Method Swizzling掉包他人APP的方法,那还要啥自行车?
事实上这真的是可以做到的,而且非常简单,因为我们拥有大杀器IPAPatch。
效果展示
(仅供学习参考,请支持正版,亲测做程序员真的很辛苦)
Lovedays
是一款不错的交公粮记日器,界面简约美观,尤其底部的广告栏更是美如画。
![](http://upload-images.jianshu.io/upload_images/1741551-0d3a17c73a9b7435.png)
当然,App内部提供了请咖啡去广告的功能,不过咖啡喝多了不健康,为了开发者的身体考虑,笔者决定帮他省下这杯咖啡钱。
![](http://upload-images.jianshu.io/upload_images/1741551-23c85fb9793cf3aa.png)
准备工作
首先你需要一台越狱手机
基础工具:MacOS,XCode,非越狱iPhone
Hopper Disassembler
超级强大的反编译软件,不仅可以把机器码解析成汇编,还能解析出相似与Objc的伪代码。总之就是太强大了,强大到我们几乎连class-dump
都用不上了。本文使用了史蒂芬周的破解版本,把Hopper Disassembler v4.app
拖进HopperV4Patcher
即可破解。
Reveal
Mac下的UI调试工具,后来因为XCode自带了UI调试工具,所以Reveal逐渐转入了地下使用,主要用来调试别人的UI(滑稽)。网盘下载。安装只需把Reveal.app
拖到/Applications
目录下即可。
逆向分析
首先明确我们的目标——去除广告。
那就必须找到设置广告的代码所在,既然在设置
页面有买咖啡去广告的选项,那我们可以尝试从设置页入手。
下载IPAPatch到桌面上。
准备好lovedays.ipa
,网盘下载。
特别说明
.ipa文件是iOS的安装包文件,类似于.dmg文件,可以当做.zip文件解压。
从App Store下载的.ipa文件是加密过的,需要砸壳才能进行逆向开发,但砸壳这一操作目前需要越狱手机才能进行。
幸运的是很多第三方助手提供砸壳后的ipad安装包(因为他们要签上自己的名然后安装到用户的手机上),本文使用的Lovedays.ipa是PP助手下载的版本。
![](http://upload-images.jianshu.io/upload_images/1741551-3bc63bdc31dea247.png)
把
Lovedays.ipa
改名为app.ipad
,并复制到/IPAPatch/Assets
目录下,替换原有的app.ipa
文件。拷贝Reveal框架文件
/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries
到IPAPatch项目中IPAPatch/Assets/Frameworks
。用XCode打开
IPAPatch.xcodeproj
工程文件。设置一个自己的
Bundle ID
,并用自己的开发团队设置签名。
![](http://upload-images.jianshu.io/upload_images/1741551-81c15282b9eb8f67.jpg)
然后就是接上手机——
RUN一下就好了!
![](http://upload-images.jianshu.io/upload_images/1741551-0215374a1d9cc772.png)
IPAPatch
就是这么强大,Hacked
弹窗是IPAPatch自带特效,在IPAPatchEntry.m
文件中实现了IPAPatch的入口类IPAPatchEntry
,当然也开方便作为我们Hook的入口。然而IPAPatch
的强大远不止于此——在Mac上运行Reveal!
![](http://upload-images.jianshu.io/upload_images/1741551-9a1332423d593a62.png)
别人的APP就像拔了毛的鸡一样躺在饭桌上了,那就开动吧。
在设置页点击
Remove Ads
弹出点餐页。在
Reveal
中刷新显示,可以获得当前视图的UI,随便选择一个view
,在右侧栏可以看到管理当前选择view
的控制器,是一个名为AdblockViewController
的类。
![](http://upload-images.jianshu.io/upload_images/1741551-a00fddbf7ee690e2.png)
接下去就是逆向最激动人心的过程了——
端上朕的机器码!
用MacOS自带
归档实用工具.app
将Lovedays.ipa
解压到桌面上,在目录中找到lovedays.app
文件,右键显示包内容
,找到lovedays
文件,不带任何后缀,这就是个二进制的Mach-O
,推荐拷贝到桌面上方便使用。运行
Hopper Disassembler
,选择File
-Read Excutable to Disassemble...
,读取方才的lovedays
二进制文件。
一百年后。
![](http://upload-images.jianshu.io/upload_images/1741551-fedaa59dc165461b.png)
Hopper
的处理进度条走完了,我们获得了lovedays
的汇编代码。
在左侧栏搜索AdblockViewController
,得到AdblockViewController
的内部和外部方法。
![](http://upload-images.jianshu.io/upload_images/1741551-5ed62d2509fcb646.png)
如果你一眼看不到关键词就再看一眼,即使在人群中千百次擦肩而过,也总会有一次回眸让你看到
-[AdblockViewController productPurchased:]
这个方法,感谢ObjC,从函数名来判断,这应该是支付成败的回调方法。这大概是你码农生涯以来第一次看代码只看注释了,珍惜机会,看什么汇编。
在注释中可以看到发送了
@selector(sharedData)
这一消息,应该对象是某个单例,从Data
关键词来猜测,估计是个管理设置数据的单例。往下看,又发送了个
@selector(setHideAd:)
消息,这就是司马昭之心了——咖啡交易,隐藏广告的方法在此!
特别说明
对于-[xxx setXXX]
方法,一般来说并不是一个通过- (void)setXXX:(id)arg;
语法声明的方法,而更可能是@property id xxx;
的set
方法。
因此在左侧栏,我们重新搜索关键词hideAd
。
![](http://upload-images.jianshu.io/upload_images/1741551-fa77b5e6e80be833.png)
搜索结果越少越好,这说明我们离靶心越来越近。仔细看,对于UserData
这个类,有hideAd
方法也有setHideAd:
方法,因此我们猜测UserData
持有_hideAd
这一属性,也许我们可以尝试修改这个BOOL属性的值,或者其get
方法。
HOOK
回到XCode中,希望你还没有把IPAPatch
工程关掉。
我们的目标是把-[UserData hideAd]
方法调包,使用一个永远return YES;
的方法替代它,就是ObjC的Method Swizzling
。虽然IPAPatch
自带了Facebook的fishhook
,但笔者没用过,还是觉得jrswizzle比较顺手,所以先git clone
一份,拷贝到工程目录下。
特别注意
把jrswizzle
拖入工程的时候,记得在Add to targets
中勾上IPAPatchFramwork
勾上IPAPatchFramework
顺便建立两个文件夹Header
和Hook
分别用来放原App中类的头文件和Hook的文件。
![](http://upload-images.jianshu.io/upload_images/1741551-8ef8c85ccb6db495.png)
在Header
文件夹下创建UserData.h
头文件,假装我们是lovedays
的开发者,虽然实际上我们只需知道UserData
有hideAd
这一get方法。因此在UserData.h
中写入如下内容。(先清空原文件中内容)
#import <Foundation/Foundation.h>
@interface UserData: NSObject
- (BOOL)hideAd;
@end
特别注意
在添加新文件的时候也别忘了添加targets。
记得勾上target
在Hook
新建UserData+Hook.h
和UserData+Hook.m
文件(别忘了targets要勾上IPAPatchFramework),用于执行Hook方法。
UserData+Hook.h
代码如下,仅仅是暴露了Hook方法——
#import <Foundation/Foundation.h>
@interface NSObject(UserDataHook)
+ (void)hookUserData;
@end
UserData+Hook.m
的代码如下,实现了Hook方法,以及用来调包的my_hideAd
方法——
#import "UserData+Hook.h"
#import "UserData.h"
@implementation NSObject(UserDataHook)
+ (void)hookUserData {
}
- (BOOL)my_hideAd {
return YES;
}
@end
Hook方法调用的时机实在类对象load
的时候,在此我们使用IPAPAtch提供的入口类IPAPatchEntry
。在IPAPatchEntry.m
中导出Hook的头文件UserData+Hook.h
,把打招呼方法[self for_example_showAlert]
注释掉,换成调用hook的方法[self hook]
——
#import "UserData+Hook.h"
@implementation IPAPatchEntry
+ (void)load
{
[self hook];
}
+ (void)hook {
[NSClassFromString(@"UserData") hookUserData];
}
@end
特别说明
对+ (void)load;
方法不了解的话可以阅读一下这篇文章,做逆向工程的话经常跟load方法py的。
回到UserData+Hook.m
中实现方法调包Method Swizzling
——
#import "JRSwizzle.h"
...
+ (void)hookUserData {
NSError *error;
[self jr_swizzleMethod:@selector(hideAd) withMethod:@selector(my_hideAd) error:&error];
if (error) {
NSLog(@"++++++++++Swizzling Error: %@", error);
}
}
特别提醒
jr_
开头的方法有四个,不要选错。
代码都写完了——
RUN一下吧!
if (没有崩溃) {
那应该能看到的就是App还是那个App,但是底下的广告栏不见了!
} else {
那就继续往下看吧。
}
其实各个类的load
方法也是有先后的,如果遇到因为unrecognized selector hookUserData
或者找不到hideAd方法
而崩溃,只需要调整一下文件的编译顺序。据说编译顺序和load
调用顺序是一致的,因此只需要让IPAPatchEntry.m
的编译顺序排在最后就可以了。
![](http://upload-images.jianshu.io/upload_images/1741551-65152f08ff72a3f8.png)
后记
本来只是在做公司产品的竞品分析,不小心误入歧途搞起了非越狱hook,但初衷还是以共同实现伟大祖国的现代化建设为主要主要目的,所以大家支持正版,咖啡不贵。
代码也上传到Git,欢迎批评指正。