1.背景:
最近想要完善自己的技术栈,觉得微信自动抢红包挺好玩的,想去了解其中的原理。特意上转转淘了个越狱机,研究iOS逆向,然后花了大半个月的工资买了MJ大神的逆向视频。出于好奇,研究如何破解大型APP的某些功能,实现开挂的效果。
在这里我以破解企业微信打卡为例,讲述我自己是如何实现该功能。其实,逆向可以实现很多实用的功能,比如:微信多开、微信自动抢红包、微信修改步数、支付宝蚂蚁森林自动收取能量和去除各大影视APP的广告等等。
2.前提:
一台越狱的iPhone机,本人用的是iPhone6 系统:iOS 8.0.2
安装好必备的越狱工具:
1. cycript: 混合了OC与JS语法的一个工具,让开发者在命令行下和应用交互,在运行时查看和修改应用
ps -e 查看iPhone6上面应用的进程名 便于cycript注入
cycript -p 进程ID
cycript -p 进程名称
@import MJTool (MJ大神写的一个库,已经封装好了众多方法)
查看APP的APPID:MJAppId && [NSBundle mainBundle].bundleIdentifier
查看APP当前的控制器:MJFrontVc()
查看UIButton上面绑定的事件:MJBtnTouchUpEvent(#0x00000000123);
递归打印viewController的层级结构:MJChildVcs(#0x00000000123);
递归打印view的层级结构:MJSubviews(#0x00000000123);
2.1.Clutch: 用于砸壳
Clutch -i 查看是否加壳
Installed apps: (已加壳的应用会显示出来)
1: 爱思助手 <com.diary.mood>
2: 大神互动-手游视频电竞大神直播 <cn.vlang.GameShow>
Clutch -d 1 砸壳完毕后会出现路径
Zipping aisiweb.app
DONE: /private/var/mobile/Documents/Dumped/com.diary.mood-iOS6.0-(Clutch-2.0.4)-2.ipa
otool -l Mach-o路径 | grep crypt (otool -l /Users/stone/Desktop/iOS/YouShiXiu/YouShiXiu | grep crypt) cryptid:0 已脱壳
cryptoff 16384
cryptsize 6766592
cryptid 0
2.2.dump-dumpdecrypted: 用于砸壳
将编译好的dumpdecrypted.dylib 放在var/root 目录下, cd var/root
DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/14B844A8-DA1F-4D5A-8D35-857533E66ECE/aisiweb.app/aisiweb
在当前文件夹生成:aisiweb.decrypted (已脱壳)
3.class-dump: 输出可执行文件的头文件
class-dump -H aisiweb -o aisiwebHeaders aisiwebHeaders可拖到Sublime中
4.Reveal: 查看应用的UI布局
5.Hopper Disassembler:用于静态分析,查看Mach-o 文件的堆栈信息
6.codesign:对lib.dylib 和 .ipa文件进行重签名
1. 查看证书ID: security find-identity -v -p codesigning 看用哪个证书ID用作签名
2. 将wwtweak.dylib(/Library/MobileSubstrate/DynamicLibraries/wwtweak.dylib) CydiaSubstrate(/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate) 拷贝至ipa的内部
3. 签名企业微信:
oTool -L wework
insert_dylib @executable_path/wwtweak.dylib wework --all-yes --weak wework
oTool -L wwtweak.dylib
install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @loader_path/CydiaSubstrate wwtweak.dylib
codesign -fs 证书ID wwtweak.dylib
codesign -fs 证书ID CydiaSubstrate
7.iOS App Signer:可视化工具,应用重签名,可以安装到非越狱手机上
将一个合法的embedded.mobileprovision 文件拷贝至ipa内部
3.破解步骤:
1.通过命令行连接越狱机,用USB的方式,端口转发,传输速度更快
1.开个命令行:
1.1. cd /Users/stone/Desktop/iOSReverse/usbmuxd-1.0.8/python-client
1.2. python tcprelay.py 22:10010
2.另开个命令行:
2.1. ssh root@127.0.0.1 -p 10010 默认登录密码:alpine
2.1.找到需要逆向APP的进程名或进程id,并用cycript调试
ps -e
683 ?? 0:29.47 /var/mobile/Containers/Bundle/Application/E6477F72-45A2-4138-8654-02561C35DB54/wework.app/wework
cycript -p wework
cy# @import MJTool
cy# MJAppId
@"com.tencent.ww" 企业微信的APPID
cy# MJFrontVc()
#"<WWKAttendanceBinaryCheckViewController: 0x14f72b370>" 企业微信中打卡的界面
2.2.也可以通过Reveal查看
![](https://img.haomeiwen.com/i1927676/89a82f5eb6d5ca9b.png)
通过Reveal查看打卡界面的视图层级
3.找到ipa的可执行文件(Mach-o)看是否需要砸壳,由于我的企业微信是从91助手上安装的,属于未加壳应用,于是我直接用class-dump命令直接拿企业微信的头文件,若是从appStore上面直接下载的app,属于加壳应用(注意:加壳应用无法直接导出头文件,需砸壳),砸壳命令在上面有陈述
class-dump -H wework -o weworkHeaders weworkHeaders可拖到Sublime中
![](https://img.haomeiwen.com/i1927676/c9ab32c3fda26d2c.png)
企业微信头文件并拿到打卡界面的头文件
5.nic.pl Hook WWKAttendanceBinaryCheckViewController.h 的所有公开方法并打印,看看进打卡界面到底是用到了哪几个方法
5.1 直接创建一个Tweak项目
stone@stonedeMacBook-Pro % nic.pl
NIC 2.0 - New Instance Creator
------------------------------
[15.] iPhone/tweak
Choose a Template (required): 15
Project Name (required): weworktweak (注意不要大写)
Package Name [com.yourcompany.weworktweak]: com.stone.weworktweak
Author/Maintainer Name [stone]:
[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.tencent.ww (需要Hook的应用ID)
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]:
Instantiating iphone/tweak in weworktweak/...
Done.
修改Makefile:
export THEOS_DEVICE_IP = 127.0.0.1
export THEOS_DEVICE_PORT = 10010
logify.pl WWKAttendanceBinaryCheckViewController.h > WWKAttendanceBinaryCheckViewController.xm
最后执行:make package install
5.2 由于Hook后方法太多,很难定位到具体代码,参考了这篇文章:
需要Hook
WWKLocationRetriever中:+ (void)fetchAddrWithCoordinate:(struct CLLocationCoordinate2D)arg1 withCallback:(id )arg2;
WWKLocationRetrieverBaseTask中:- (void)mapView:(id)arg1 didUpdateUserLocation:(id)arg2 updatingLocation:(_Bool)arg3;
5.3.1 下面我来解释下代码:
YLZLocationManager.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
NS_ASSUME_NONNULL_BEGIN
@interface YLZLocationManager : NSObject
//地理位置:
@property (nonatomic, strong) CLLocation *location;
//地理经纬度:
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
//记录插件是否打开的开关:
@property (nonatomic, assign) BOOL isOpen;
+ (instancetype)shareInstance;
@end
NS_ASSUME_NONNULL_END
YLZLocationManager.m
#import "YLZLocationManager.h"
@interface YLZLocationManager()
@end
@implementation YLZLocationManager
static YLZLocationManager * _shareInstance = nil;
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_shareInstance = [[super allocWithZone:NULL] init];
});
return _shareInstance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [YLZLocationManager shareInstance];
}
+ (instancetype)copyWithZone:(struct _NSZone *)zone {
return [YLZLocationManager shareInstance];
}
@end
Tweak.xm
#import <UIKit/UIKit.h>
#import "src/YLZLocationManager.h"
//其它位置: 将这个位置设置成公司位置即可:
static NSString *latitudeString = @"24.497288808694895";
static NSString *longitudeString = @"118.17793642306556";
%hook WWKLocationRetriever
+ (void)fetchAddrWithCoordinate:(struct CLLocationCoordinate2D)arg1 withCallback:(id )arg2 {
//判断下开关是否打开:
if ([YLZLocationManager shareInstance].isOpen) {
arg1.latitude = [YLZLocationManager shareInstance].coordinate.latitude;
arg1.longitude = [YLZLocationManager shareInstance].coordinate.longitude;
}
%orig;
}
%end
%hook WWKLocationRetrieverBaseTask
- (void)mapView:(id)arg1 didUpdateUserLocation:(id)arg2 updatingLocation:(_Bool)arg3
{
//判断下开关是否打开:
if ([YLZLocationManager shareInstance].isOpen) {
if ([NSStringFromClass([arg2 class]) isEqualToString:@"QUserLocation"]) {
[YLZLocationManager shareInstance].location = [[CLLocation alloc] initWithLatitude:[YLZLocationManager shareInstance].coordinate.latitude longitude:[YLZLocationManager shareInstance].coordinate.longitude];
[arg2 setValue:[YLZLocationManager shareInstance].location forKey:@"_location"];
}
}
%orig;
}
%end
5.3.2 实现的效果:
![](https://img.haomeiwen.com/i1927676/774aaa291820ff49.jpeg)
企业微信打卡界面
5.4.1 我Hook了下WWKApplicationPageController控制器,在该界面增加了是否打开插件开关的入口
Tweak.xm
#import <UIKit/UIKit.h>
#import "src/YLZLocationManager.h"
#define YLZDefaults [NSUserDefaults standardUserDefaults]
#define YLZKey @"switch_isOpen"
@interface WWKApplicationPageController : UIViewController
@end
%hook WWKApplicationPageController
- (void)viewDidLoad {
%orig;
[YLZLocationManager shareInstance].coordinate = CLLocationCoordinate2DMake([latitudeString doubleValue], [longitudeString doubleValue]);
[YLZLocationManager shareInstance].isOpen = [YLZDefaults boolForKey:YLZKey];
}
// 一共有多少组
- (long long)numberOfSectionsInTableView:(id)tableView {
return 2;
}
// 每一组有多少行
- (long long)tableView:(id)tableView numberOfRowsInSection:(long long)section {
if (section == 0) {
return 0;
} else {
return 2;
}
}
// 监听插件开关(新方法需要添加%new)
%new
- (void)onSwitch:(UISwitch *)switchView {
[YLZLocationManager shareInstance].isOpen = switchView.isOn;
[YLZDefaults setBool:switchView.isOn forKey:YLZKey];
[YLZDefaults synchronize];
}
// 返回每一行的cell
- (id)tableView:(id)tableView cellForRowAtIndexPath:(id)indexPath
{
if ([indexPath section] == 1 && [indexPath row] == 1) {
NSString *cellId = @"switchCellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
cell.backgroundColor = [UIColor whiteColor];
}
cell.textLabel.text = @"打卡插件开关";
// 开关
UISwitch *switchView = [[UISwitch alloc] init];
switchView.on = [YLZDefaults boolForKey:YLZKey];
[switchView addTarget:self action:@selector(onSwitch:) forControlEvents:UIControlEventValueChanged];
cell.accessoryView = switchView;
return cell;
}
return %orig;
}
// 每一行的高度
- (double)tableView:(id)tableView heightForRowAtIndexPath:(id)indexPath {
return %orig;
}
// 点击的监听
- (void)tableView:(id)tableView didSelectRowAtIndexPath:(id)indexPath
{
if ([indexPath section] == 1 && [indexPath row] == 1) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
return;
} else {
%orig;
}
}
%end
5.4.2 实现的效果:
![](https://img.haomeiwen.com/i1927676/d9eee5baed3f16bb.jpeg)
企业微信工作台
6.签名并打包
将一个合法的embedded.mobileprovision wwtweak.dylib(/Library/MobileSubstrate/DynamicLibraries/wwtweak.dylib) CydiaSubstrate(/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate) 拷贝至ipa的内部
先签名wwtweak.dylib 和 CydiaSubstrate,具体命令请移步至2.6
然后用iOS App Signer签名ipa
最后上传至分发平台分发该APP (注意:需要安装设备的UUID必须在embedded.mobileprovision里面)
![](https://img.haomeiwen.com/i1927676/86bb974d3822f41f.png)
签名并打包
4.总结:
1.逆向挺好玩的,本人还处于初级阶段,了解了其内部的实现原理
2.进阶需要用到LLDB 汇编相关的知识,根据汇编代码还原源代码
3.个人理解逆向有两种方案:
3.1.直接通过Hopper Disassembler修改Mach-O文件的汇编指令代码
3. 2.利用Tweak Hook 源代码,并注入相应的插件代码
5.另附最终效果展示:
![](https://img.haomeiwen.com/i1927676/7c16799502085382.gif)
逆向企业微信实现的最终效果:上班在家打卡
![](https://img.haomeiwen.com/i1927676/0baf33949c460fc7.gif)
逆向企业微信实现的最终效果:下班在家打卡
网友评论