目录
一、Logos 简介
Logos语法其实是CydiaSubsuct框架提供的一组宏定义。便于开发者使用宏进行HOOK操作。语法简单,功能强大且稳定。
http://iphonedevwiki.net/index.php/Logos
Logos 语法
Logos语法分为三大类:
Block level
这一类型的指令会开辟一个代码块,以%end结束。
%group、%hook、% subclass 、 %end
Top level
这个TopLevel指令不放在BlockLevel中。
%config、%hookf、%ctor、%dtor
Function level
这一块的指令就放在方法中。
%init、%class、 %c、 %orig、%log
常用语法
HOOK 某个类里面的某个方法
%hook ClassName
// 对象方法
- (void)instanceMethod {
}
// 类方法
+ (void)classMethod {
}
%end
为某个类添加新方法
%hook ClassName
// 添加对象方法
%new
- (void)newInstanceMethod {
}
// 添加类方法
%new
+ (void)newClassMethod {
}
%end
-
%group
用来将代码分组。开发中hook代码会很多,这样方便管理Logos代码。
%group group1
%hook ClassName
%end
%end
%ctor {
NSString *version = [UIDevice currentDevice].systemVersion;
if (version.doubleValue >= 14.0) {
%init(group1);
}
// 如果有group未在某种条件下初始化就会报错
}
%ctor(constructor)
构造函数,用于确定加载那个组。和%init结合用%init
用来初始化某个组。%log;
能够输出日志!! 输出方法调用的详细信息
%orig(original)
这个就是保持原有的方法实现,如果原来的方法有返回值,那么%orig 就有返回值的。%new
给某个类添加方法,在%hook 和 %end 中使用。
%new
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
-
%c(ClassName)
类似getClass函数,获得一个类对象。一般用于调用类方法。
二、Logos 使用
- 创建目标项目
Demo
创建Hook项目
HookDemo
(MonkeyApp),并安装到手机上将目标项目
Demo
的IPA放到Hook项目HookDemo
中的TargetApp
文件夹中
重新运行
HookDemo
即可重签注入修改HookDemoDylib.xm type
关于xm文件:xm表示支持OC、C/C++语法
2.1 HOOK loginBtnClick:方法
修改HookDemoDylib.xm文件中的代码
#import <UIKit/UIKit.h>
// 下面两行代码self 调用showViewController: sender:方法需要
@interface ViewController : UIViewController
@end
%hook ViewController
- (void)loginBtnClick:(id)sender {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"HOOK成功!" message:nil preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleCancel) handler:nil];
[alertVC addAction:cancel];
[self showViewController:alertVC sender:nil];
}
%new
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
%end
2.2 将要HOOK的类的头信息导入HookDemoDylib.xm文件
- dump出目标项目
Demo
的头文件
class-dump -H Demo -o DemoHeaders/
导入头后就能直接使用原APP中的属性和方法
三、使用 Logos 为 WeChat 设置界面添加Cell
- 完成后的界面如下:
3.1 找到需要修改的控制器
- 使用Debug View Hierarchy看到设置界面的控制器为:
NewSettingViewController
- 使用Cycript也能查看当前的控制器
3.2 分析界面中的数据源由当前控制器管理还是在其他类中管理
- dump出目标项目WeChat的头文件
class-dump -H WeChat -o WeChatHeaders/
- 将
WeChatHeaders
拖到Sublime Text
中打开,Command + Shift + F搜索NewSettingViewController
可以看到NewSettingViewController
中没有tableView相关的数据源和代理方法:
因此我们要修改设置界面的cell数量就只能HOOK NewSettingViewController
中的WCTableViewManager
。因为设置界面的TableView的DataSource就是WCTableViewManager
。
cy# #0x1156ad000.dataSource
#"<WCTableViewManager: 0x280c44270>"
cy#
由于
WCTableViewManager
在多个控制器中使用,因此HOOKWCTableViewManager
时还需要判断当前的控制器(通过响应链条
找到当前控制器:
[tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)])
-
Sublime Text
中Command + Shift + F搜索WCTableViewManager :
。可以看到TableView的数据源和代理方法均在这里实现:
3.3 实现修改界面的代码
- CycriptDemoDylib.xm中的代码如下:
// See http://iphonedevwiki.net/index.php/Logos
#import <UIKit/UIKit.h>
#define CJDefaults [NSUserDefaults standardUserDefaults]
#define CJSWITCHKEY @"CJSWITCHKEY"
#define CJTIMEKEY @"CJTIMEKEY"
// 关于界面
@interface WCTableViewManager
- (long long)numberOfSectionsInTableView:(id)arg1;
@end
@interface NewSettingViewController:UIViewController
@end
%hook WCTableViewManager
%new
- (void)cjtextFieldDidChangeValue:(NSNotification *)notification {
UITextField *sender = (UITextField *)[notification object];
[CJDefaults setValue:sender.text forKey:CJTIMEKEY];
[CJDefaults synchronize];
}
%new
- (void)cjswitchChang:(UISwitch *)switchView {
[CJDefaults setBool:switchView.isOn forKey:CJSWITCHKEY];
[CJDefaults synchronize];
[MSHookIvar <UITableView *>(self,"_tableView") reloadData];
}
- (void)scrollViewWillBeginDragging:(id)arg1 {
%orig;
[MSHookIvar <UITableView *>(self,"_tableView") endEditing:YES];
}
//返回高度
- (double)tableView:(UITableView *)tableView heightForRowAtIndexPath:(id)indexPath {
//定位设置界面,并且是最后一组
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]
&& [indexPath section] == [self numberOfSectionsInTableView:tableView]-1){
return 44;
}
return %orig;
}
//每一个Cell
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(id)indexPath {
//定位设置界面,并且是最后一组
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]
&& [indexPath section] == [self numberOfSectionsInTableView:tableView]-1) {
UITableViewCell * cell = nil;
if ([indexPath row] == 0) {
static NSString *swCell = @"SWCELL";
cell = [tableView dequeueReusableCellWithIdentifier:swCell];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:nil];
}
cell.textLabel.text = @"自动抢红包";
//抢红包开关!!
UISwitch *switchView = [[UISwitch alloc] init];
switchView.on = [CJDefaults boolForKey:CJSWITCHKEY];
[switchView addTarget:self action:@selector(cjswitchChang:) forControlEvents:(UIControlEventValueChanged)];
cell.accessoryView = switchView;
cell.imageView.image = [UIImage imageNamed:([CJDefaults boolForKey:CJSWITCHKEY] == 1) ? @"unlocked" : @"locked"];
} else if([indexPath row] == 1) {
static NSString * waitCell = @"waitCell";
cell = [tableView dequeueReusableCellWithIdentifier:waitCell];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:nil];
}
cell.textLabel.text = @"等待时间(秒)";
UITextField * textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 150, 40)];
//监听键盘输入
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cjtextFieldDidChangeValue:) name:UITextFieldTextDidChangeNotification object:textField];
textField.text = [CJDefaults valueForKey:CJTIMEKEY];
textField.borderStyle = UITextBorderStyleRoundedRect;
cell.accessoryView = textField;
cell.imageView.image = [UIImage imageNamed:@"clock"];
}
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
return %orig;
}
//每组多少行
- (long long)tableView:(UITableView *)tableView numberOfRowsInSection:(long long)section {
//定位设置界面,并且是最后一个
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]
&& section == [self numberOfSectionsInTableView:tableView]-1) {
return 2;
}
return %orig;
}
//多少组
- (long long)numberOfSectionsInTableView:(UITableView *)tableView {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]) {// 定位设置界面
// 在原来基础上多搞一组
return %orig+1;
}
return %orig;
}
%end
%hook NewSettingViewController
%new
-(void)cjkeyboardWillShow:(NSNotification *)note {
UIView * view = self.view;
CGRect keyBoardRect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
view.frame = CGRectMake(0, -keyBoardRect.size.height, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height );
}
%new
-(void)cjkeyboardWillHide:(NSNotification *)note {
UIView *view = self.view;
view.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
}
- (void)viewDidLoad {
%orig;
//监听textField弹出和消失
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(cjkeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(cjkeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
%end
注意一:logos中获取对象的成员变量一般有如下三种方法:
- 可以导入头文件中属性和成员变量的定义
- KVC
- MSHookIvar :UITableView *tableView = MSHookIvar<UITableView *>(self,"_tableView");
注意二:所有
%new
新添加的方法一定要加上自己的前缀,避免意外覆盖了原APP已有的方法
注意三:可以在
CycriptDemoDylib.xm
的方法中添加断点进行调试
注意四:引用的图片的方法:将ipa包解压后添加需要的图片并打包即可
zip –ry 输出文件 输入文件 将输入文件压缩为输出文件
参考:iOS 逆向开发12:iOS 应用重签名