不一

非越狱环境下的iOS逆向工程:IPAPatch + Reveal

字数统计: 2.1k阅读时长: 7 min
2017/10/17 Share

要是不需要越狱,就可以用Reveal实时查看他人APP的UI、阅读并修改他们APP的代码、甚至使用Method Swizzling掉包他人APP的方法,那还要啥自行车?
事实上这真的是可以做到的,而且非常简单,因为我们拥有大杀器 IPAPatch

效果展示

(仅供学习参考,请支持正版,亲测做程序员真的很辛苦)

Lovedays 是一款不错的交公粮记日器,界面简约美观,尤其底部的广告栏更是美如画。
Lovedays截图

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

准备工作

首先你需要一台越狱手机

  • 基础工具: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助手下载的版本。
PP助手下载砸壳后的ipa

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+Lovedays
IPAPatch 就是这么强大, Hacked 弹窗是IPAPatch自带特效,在 IPAPatchEntry.m 文件中实现了IPAPatch的入口类 IPAPatchEntry ,当然也开方便作为我们Hook的入口。然而 IPAPatch 的强大远不止于此——
在Mac上运行Reveal!
Reveal查看Lovedays的UI
别人的APP就像拔了毛的鸡一样躺在饭桌上了,那就开动吧。
在设置页点击 Remove Ads 弹出点餐页。
Reveal 中刷新显示,可以获得当前视图的UI,随便选择一个 view ,在右侧栏可以看到管理当前选择 view 的控制器,是一个名为 AdblockViewController 的类。
调试点餐页UI
接下去就是逆向最激动人心的过程了——
端上朕的机器码!
用MacOS自带 归档实用工具.appLovedays.ipa 解压到桌面上,在目录中找到 lovedays.app 文件,右键 显示包内容 ,找到 lovedays 文件,不带任何后缀,这就是个二进制的 Mach-O ,推荐拷贝到桌面上方便使用。
运行 Hopper Disassembler ,选择 File - Read Excutable to Disassemble... ,读取方才的 lovedays 二进制文件。

一百年后。

Hopper界面
Hopper 的处理进度条走完了,我们获得了 lovedays 的汇编代码。
在左侧栏搜索 AdblockViewController ,得到 AdblockViewController 的内部和外部方法。
在Hopper中搜索指定类的方法
如果你一眼看不到关键词就再看一眼,即使在人群中千百次擦肩而过,也总会有一次回眸让你看到 -[AdblockViewController productPurchased:] 这个方法,感谢ObjC,从函数名来判断,这应该是支付成败的回调方法。
接下去让我开始愉快地阅读汇编吧,从第一行说起,stp指令是… 这大概是你码农生涯以来第一次看代码只看注释了,珍惜机会,看什么汇编。
在注释中可以看到发送了 @selector(sharedData) 这一消息,应该对象是某个单例,从 Data 关键词来猜测,估计是个管理设置数据的单例。
往下看,又发送了个 @selector(setHideAd:) 消息,这就是司马昭之心了——咖啡交易,隐藏广告的方法在此!

特别说明
对于 -[xxx setXXX] 方法,一般来说并不是一个通过 - (void)setXXX:(id)arg; 语法声明的方法,而更可能是 @property id xxx;set 方法。

因此在左侧栏,我们重新搜索关键词 hideAd
搜索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

顺便建立两个文件夹 HeaderHook 分别用来放原App中类的头文件和Hook的文件。
文件结构
Header 文件夹下创建 UserData.h 头文件,假装我们是 lovedays 的开发者,虽然实际上我们只需知道 UserDatahideAd 这一get方法。因此在 UserData.h 中写入如下内容。(先清空原文件中内容)

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>

@interface UserData: NSObject

- (BOOL)hideAd;

@end

特别注意 在添加新文件的时候也别忘了添加targets。

记得勾上target

Hook 新建 UserData+Hook.hUserData+Hook.m 文件(别忘了targets要勾上IPAPatchFramework),用于执行Hook方法。
UserData+Hook.h 代码如下,仅仅是暴露了Hook方法——

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>

@interface NSObject(UserDataHook)

+ (void)hookUserData;

@end

UserData+Hook.m 的代码如下,实现了Hook方法,以及用来调包的 my_hideAd 方法——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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] ——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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 ——

1
2
3
4
5
6
7
8
9
10
11
#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一下吧!

1
2
3
4
5
if (没有崩溃) {
那应该能看到的就是App还是那个App,但是底下的广告栏不见了!
} else {
那就继续往下看吧。
}

其实各个类的 load 方法也是有先后的,如果遇到因为 unrecognized selector hookUserData 或者 找不到hideAd方法 而崩溃,只需要调整一下文件的编译顺序。据说编译顺序和 load 调用顺序是一致的,因此只需要让 IPAPatchEntry.m 的编译顺序排在最后就可以了。
修改编译顺序

后记

本来只是在做公司产品的竞品分析,不小心误入歧途搞起了非越狱hook,但初衷还是以共同实现伟大祖国的现代化建设为主要主要目的,所以大家支持正版,咖啡不贵。

CATALOG
  1. 1. 效果展示
  2. 2. 准备工作
  3. 3. 逆向分析
  4. 4. HOOK
  5. 5. 后记