前面我们通过Cycript去修改app里的东西,这些修改只是暂时的,即如果退出界面再进来,还是老样子,这一篇就来实操,hook代码。
安装Theos
-
brew install ldid
. 安装签名工具
image.png
遇上图问题,按照图中提示执行命令,设下权限,然后重新install
-
vim ~/.bash_profile
添加环境变量, 后面2个是后面用tweak
时需要添加的
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH
export THEOS_DEVICE_IP=127.0.0.1
export THEOS_DEVICE_PORT=10010
-
source ~/.bash_profile
更新一下 -
git clone --recursive https://github.com/theos/theos.git $THEOS
下载Theos
Tweak
- 生成
Tweak
项目,通过nic.pl
指令
image.png
-
选择15,继续往下
image.png
Project Name : 项目名
Package Name [com.yourcompany.aqy_tweak]:项目 bundle id
Author/Maintainer Name: 作者名,回车就行
MobileSubstrate Bundle filte:调试的app bundle id (可以通过MJ的工具类获取)
后面就回车就行了
即会生成项目文件夹,然后就可以在Tweak.x
这个文件里做hook的事情了

Tweak用法
- hook一个类,如Person,有一个方法
-(void)goHome
,-(void)goSchool
%hook Person
-(void)goHome{
%orig;//保留原app里的实现
// %orig(nil); //参数
NSLog(@"别想回家了");
}
%end
-
self
调用类的方法
Tweak文件里 调用[self xxxx]时 ,需要申明一下
@interface Person
- (void)goSchool;
@end
%hook Person
-(void)goHome{
[self goSchool];
}
%end
%c(Model) == NSClassFromString(@"Model")
- %log 打印方法调用情况
- %new 添加一个新方法
%new
-(void)swichOn{
%log;
}
/// 加载动态库时调用
%ctor{
}
/// 程序退出时调用
%dtor{
}
- 加载图片资源,
[UIImage imageWithContentsOfFile:@""];
- 将你需要加载进去的图片,放在
Tweak
项目如下路径
image.png
HJWeChatFiles
即是你放资源的文件夹,前面文件夹名称需要与图中一致。 - 也可以放到
Library/Caches
- 在
Tweak.x
里 增加宏#define HJFile(path) @"/Library/PreferenceLoader/Preferences/HJWeChatFiles/" #path
-
[UIImage imageWithContentsOfFile:HJFile(redbag)]
这样就可以拿到图片了。 - 编写完
Tweak
代码后,需要执行执行以下命令安装到手机上
make //直接make package 也一样
make package debug=0 // 生产包
make install //安装到手机上
简单的实践一下,不搞多复杂的分析,毕竟费脑子


如上面2张图,来自pp虾的登录界面,假设我看不惯历史记录上面的banner
广告位,看不惯更多功能和即玩小游戏模块,想给它全部去掉,并且在这个tableView
多加一组,放一个带switch
的cell
- 首先通过
Reveal
查看一下这个界面,找出我要干掉的这几个东西 - 查看可知,这个控制器是
BDSUserHomeViewController
,banner广告位
是个cell
->BDSUserHomeBannerCell
,更多功能模块也是个cell
->BDSUserHomeFooterCell
, 小游戏模块也是个cell
->BDSUserGameTableViewCell
,搞清楚了这就很简单了。 -
在BDSUserHomeBannerCell中看到一个方法
image.png
凭借多年的开发经验,很明显,这就是返回高度的,那我直接返回0不就行了。

在
更多功能的BDSUserHomeFooterCell
里也找到这么一个高度的方法,同样返回0,顺便给hidden
一下,可能高度没了,内容还在。而在
小游戏模块的BDSUserGameTableViewCell
里没有发现这样的方法,没关系,看看它的数据源。

看到
Model
有2个数组,不管那么多,直接hook
它俩的get
方法,返回一个空数组,完事。
以上分析的Tweak代码如下:
@interface BDSUserHomeFooterCell
/// 调用了self.hidden, 申明一下
@property (nonatomic,assign)BOOL hidden;
@end
%hook BDSUserGameModel
-(NSArray *)gameItems{
return @[];
}
-(NSArray *)remoteItems{
return @[];
}
%end
%hook BDSUserHomeBannerCell
+ (double)cellHeightWithBanner:(id)arg1{
return 0;
}
%end
%hook BDSUserHomeFooterCell
+ (double)cellHeight:(id)arg1{
return 0;
}
- (void)configCellWithModels:(id)arg1{
self.hidden = YES;
}
%end
我们装到手机上看看效果。
发现确实达到了我想要的结果

接下来还差一步,在末尾增加一个自己的cell,这里就要操作BDSUserHomeViewController
了。
我们在TableView
的代理里做点事情,直接贴代码了,里面有注释。
@interface BDSUserHomeViewController
/// [self numberOfSectionsInTableView:tableView] 得申明一下
- (long long)numberOfSectionsInTableView:(id)tableView;
@property(retain, nonatomic) UITableView *tableView;
@end
/// 图片路径 宏, 方便使用
#define HJFile(path) @"/Library/PreferenceLoader/Preferences/HJPPXFiles/" #path
/// 本地数据存储,记录switch 状态
#define HJDefaults [NSUserDefaults standardUserDefaults]
#define HJSwitchKey @"HJSwitchKey"
%hook BDSUserHomeViewController
- (long long)numberOfSectionsInTableView:(id)arg1{
/// 在原有的section上+1
return %orig+1;
}
- (long long)tableView:(id)tableView numberOfRowsInSection:(long long)section{
if (section != [self numberOfSectionsInTableView:tableView]-1)
{/// 原有的section 直接返回 原来的row
return %orig;
}
///新增的一组 根据switch状态展示1或2个cell
BOOL isOn = [HJDefaults boolForKey:HJSwitchKey];
return isOn?2:1;
}
- (id)tableView:(id)tableView cellForRowAtIndexPath:(id)indexPath{
if ( [indexPath section] != [self numberOfSectionsInTableView:tableView]-1)
{/// 原有的section 返回原来的cell
return %orig;
}else{
/// 新增的一组 ,做一个判断 , ps:随便写点脏代码
if ([indexPath row] == 0)
{
BOOL isOn = [HJDefaults boolForKey:HJSwitchKey];
UITableViewCell *cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MyCell"];
/// 根据switch 的状态 ,显示不同文本
cell.textLabel.text = isOn?@"隐藏版本号":@"显示版本号";
cell.backgroundColor = [UIColor whiteColor];
cell.imageView.image = [UIImage imageWithContentsOfFile:HJFile(redbag)];
UISwitch *swich = [[UISwitch alloc]init];
cell.accessoryView = swich;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
swich.on = isOn;
[swich addTarget:self action:@selector(swich:) forControlEvents:UIControlEventValueChanged];
return cell;
}else{
UITableViewCell *cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"MyCell2"];
cell.textLabel.text = @"版本号";
cell.detailTextLabel.text = [NSString stringWithFormat:@"v%@",[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]];
cell.backgroundColor = [UIColor whiteColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
}
}
- (double)tableView:(id)tableView heightForRowAtIndexPath:(id)indexPath{
if ( [indexPath section] != [self numberOfSectionsInTableView:tableView]-1)
{/// 原有的section 返回原来的高度
return %orig;
}else{ /// 新增随便给个44
return 44;
}
}
- (double)tableView:(id)tableView heightForHeaderInSection:(long long)section{
if ( section != [self numberOfSectionsInTableView:tableView]-1)
{/// 原有的section header 返回原来的高度
return %orig;
}else{/// 新增给个3,给点间隔
return 3;
}
}
/// 新增一个switch绑定的方法,点击并刷新这一组
%new
-(void)swich:(UISwitch *)swich{
[HJDefaults setBool:swich.on forKey:HJSwitchKey];
[HJDefaults synchronize];
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:[self numberOfSectionsInTableView:self.tableView]-1];
[self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
}
%end
OK,编写完毕,继续make package debug=0
, make install
安装到手机上
来看看效果:

网友评论