iOS常见问题汇总iOS开发文章库iOS逆向开发

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

2017-10-17  本文已影响494人  喂草
图片来源网络

要是不需要越狱,就可以用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中写入如下内容。(先清空原文件中内容)

#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方法——

#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,欢迎批评指正。

上一篇下一篇

猜你喜欢

热点阅读