
要是不需要越狱,就可以用Reveal实时查看他人APP的UI、阅读并修改他们APP的代码、甚至使用Method Swizzling掉包他人APP的方法,那还要啥自行车?
事实上这真的是可以做到的,而且非常简单,因为我们拥有大杀器IPAPatch。
效果展示
(仅供学习参考,请支持正版,亲测做程序员真的很辛苦)
Lovedays
是一款不错的交公粮记日器,界面简约美观,尤其底部的广告栏更是美如画。

当然,App内部提供了请咖啡去广告的功能,不过咖啡喝多了不健康,为了开发者的身体考虑,笔者决定帮他省下这杯咖啡钱。

准备工作
首先你需要一台越狱手机
基础工具: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助手下载的版本。

把
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
,并用自己的开发团队设置签名。

然后就是接上手机——
RUN一下就好了!

IPAPatch
就是这么强大,Hacked
弹窗是IPAPatch自带特效,在IPAPatchEntry.m
文件中实现了IPAPatch的入口类IPAPatchEntry
,当然也开方便作为我们Hook的入口。然而IPAPatch
的强大远不止于此——在Mac上运行Reveal!

别人的APP就像拔了毛的鸡一样躺在饭桌上了,那就开动吧。
在设置页点击
Remove Ads
弹出点餐页。在
Reveal
中刷新显示,可以获得当前视图的UI,随便选择一个view
,在右侧栏可以看到管理当前选择view
的控制器,是一个名为AdblockViewController
的类。

接下去就是逆向最激动人心的过程了——
端上朕的机器码!
用MacOS自带
归档实用工具.app
将Lovedays.ipa
解压到桌面上,在目录中找到lovedays.app
文件,右键显示包内容
,找到lovedays
文件,不带任何后缀,这就是个二进制的Mach-O
,推荐拷贝到桌面上方便使用。运行
Hopper Disassembler
,选择File
-Read Excutable to Disassemble...
,读取方才的lovedays
二进制文件。
一百年后。

Hopper
的处理进度条走完了,我们获得了lovedays
的汇编代码。
在左侧栏搜索AdblockViewController
,得到AdblockViewController
的内部和外部方法。

如果你一眼看不到关键词就再看一眼,即使在人群中千百次擦肩而过,也总会有一次回眸让你看到
-[AdblockViewController productPurchased:]
这个方法,感谢ObjC,从函数名来判断,这应该是支付成败的回调方法。这大概是你码农生涯以来第一次看代码只看注释了,珍惜机会,看什么汇编。
在注释中可以看到发送了
@selector(sharedData)
这一消息,应该对象是某个单例,从Data
关键词来猜测,估计是个管理设置数据的单例。往下看,又发送了个
@selector(setHideAd:)
消息,这就是司马昭之心了——咖啡交易,隐藏广告的方法在此!
特别说明
对于-[xxx setXXX]
方法,一般来说并不是一个通过- (void)setXXX:(id)arg;
语法声明的方法,而更可能是@property id xxx;
的set
方法。
因此在左侧栏,我们重新搜索关键词hideAd
。

搜索结果越少越好,这说明我们离靶心越来越近。仔细看,对于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的文件。

在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
的编译顺序排在最后就可以了。

后记
本来只是在做公司产品的竞品分析,不小心误入歧途搞起了非越狱hook,但初衷还是以共同实现伟大祖国的现代化建设为主要主要目的,所以大家支持正版,咖啡不贵。
代码也上传到Git,欢迎批评指正。
网友评论