1. 引言
-
小谷的公司每个月可以迟到3次,小谷5月的时候考勤竟然迟到了4次。很难受。为了杜绝这种情况。毅然决然的研究一波
偷偷打卡?
-
小谷用的设备是
iPhone XS Max
,iOS 14.3(越狱
),目前最新的钉钉:6.0.16
。 -
用
Windows
做逆向
的兄弟们,经常使用IDA和Reveal
还有终端debugserver附加进程
,今天小谷要用iOS开发的兄弟们都比较喜欢的Xcode和hopper
。?(其实都差不多。) -
先看效果图
2. 钉钉打卡插件
2.1. 调试应用
蹂躏第一步,首先要装酷
- 先把钉钉砸壳,取下来。我使用的是
frida-ios-dump
,小谷写过一篇砸壳和Frida的博客.
这样
砸过壳
的钉钉.ipa
就出来了,先放这,我们先不管他
- 开始了!
iOS
兄弟们超级喜欢的,Xcode
调试(曾经有兄弟问过我怎么附加的。我这里画下流程图)
这样就清晰很多了
- 既然我们可以调试的话,那就直接
定位打卡
的代码了啊 (找到打卡界面,viewdebug
调试)
我滴天啊。是个
WKWebview
,定位不到!!
这样就结束了吗?怎么可能呢,那以后小谷要是迟到,没有办法弥补了~·
2.1.1. 思路
整理下思路和线索
- 线索:
- 打卡界面是个
WKWebview
界面 - 我们打卡成功的时候
H5
会有变化,说明是有地方告诉H5
定位成功 - 如果是原生定位
告诉H5
的,那么他们就一定有交互代码
- 打卡界面是个
- 思路:
- 如果他们有
交互
,我们知道hook住
这个交互
就可以了(看里面的回传
,通过改变值可能做到) - 如果是iOS的定位,一定会走
locationManager: didUpdateLocations:
- 很有可能就是,
H5交互的时候开始定位
,然后回调定位信息
- 如果他们有
那么我们接下来的任务就是
定位交互的代码
和打印回传的信息
了!! 搞起,搞起~
2.2. 定位代码
-
我们刚开始导出了
钉钉.ipa
,解压
,拿MachO
文件 -
然后通过
class-dump
取出header
class-dump -H DingTalk -o dingHeader
- 把二进制文件拖进
Hopper
(搜索locationManager: didUpdateLocations:
)
一共就
5
个,我们就把这几个hook
一下,看下log
了,我猜测应该就可以定位到了(如果他调用的原生的
)
- 这次我们就用
Monkey
写Tweak插件
了(当然也可以用THEOS
,主要我们这次可能要好几次调试才能定位,我就用Monkey
方便点~)
- 获取
钉钉的APPID
,并配置
- 我们把上面找到的那
5
个,hook
一下
#import <UIKit/UIKit.h>
%hook AMapLocationCLMDelegate
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{
%log;
%orig;
}
%end
%hook AMapLocationManager
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{
%log;
%orig;
}
%end
%hook LALocationManager
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{
%log;
%orig;
}
%end
%hook DTCLocationManager
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{
%log;
%orig;
}
%end
%hook MAMapView
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{
%log;
%orig;
}
%end
复制代码
然后我们安装插件,看下
log
- 我们发现这个
AMapLocationCLMDelegate
和AMapLocationManager
都是成对出现的,其他的都没有走
- 然后我们观察下
class-dump
出来的header
文件。发现AMapLocationCLMDelegate
里面都是一些代理回调
这个时候,小谷又可以推测一波了: 在
AMapLocationManager
获取的位置,然后参数在把参数传给H5交互
- 我们把
AMapLocationManager
里面的方法都打一遍log
,使用logify.pl
- 然后我们在次装上插件看下
log
看到了不错的信息。我们要继续开始
Xcode调试附加
了
- 从
Hopper
找到偏移位置
Xcode
附加下,可以找到ASLR
- 定位偏移,设置断点
0x104e70000 + 0x36b4a8 = 0x1051DB4A8
- 当我点击考勤打卡的时候,断住了~
- 根据我们的思路,我们要看
函数调用栈
了!!
有没有那么一丢丢爽歪歪~ (如果没有符号也没有关系,我们就通过
Hopper
地址定位!)
- 我们
hook
下这个函数
%hook LAPluginInstanceCollector
- (void)handleJavaScriptRequest:(id)arg1 callback:(id)arg2{
%log;
%orig;
}
%end
复制代码
- 兄弟们~ 我们可以得到线索,这个
callback
是个block
,说明arg1
是参数,arg2
是回调回去的参数
- 想知道这个
block
的参数类型。就要看看他的签名
了~
通过
内存平移
来拿他的签名
~
- 老办法,
Hopper
拿地址。然后Xcode附加下断点
0x100f50000 + 0x488f110 = 0x1057DF110
b 0x1057DF110
断住它,然后分析他
- 这个时候们就可以继续
hook
了,看看他里面的参数
是啥
%hook LAPluginInstanceCollector
- (void)handleJavaScriptRequest:(id)arg1 callback:(void (^) (id))arg2{
//我们可以自定义个block看下。
id xg_callback = ^(id argCb){
//可以先把传入的参数打印下,看看有没有啥触发器
NSLog(@"xg_callback arg1:%@",arg1);
//然后把传入的参数打印下,也看下他是什么类型的
NSLog(@"xg_callback argCbClass:%@, argCb:%@",[argCb class],argCb);
arg2(argCb);
};
//xg_callback替换arg2。(只做了一个转接的过程)
%orig(arg1,xg_callback);
}
%end
复制代码
- 然后看效果
我好像找到了,但是这里面还有好多其他跟他相关连的信息。(比如地址,城市,经纬度啥的)
2.3. 打卡插件代码
我们知道他类型是个字典。
action=start
的时候触发
- 直接上代码了(最新版)
%hook LAPluginInstanceCollector
- (void)handleJavaScriptRequest:(id)arg1 callback:(void (^) (id))arg2{
id xg_callback = ^(id argCb){
//小谷做的时候其实打了好多log。。
NSDictionary *arg1Dic = (NSDictionary *)arg1;
if([arg1Dic[@"action"] isEqualToString:@"start"]){
NSLog(@"xg_callback text:start");
NSMutableDictionary * CbDic = [NSMutableDictionary dictionaryWithDictionary:argCb];
if (CbDic[@"result"][@"latitude"] && CbDic[@"result"][@"longitude"]){
NSLog(@"xg_callback text:result");
NSMutableDictionary * resultDic = [NSMutableDictionary dictionaryWithDictionary:CbDic[@"result"]];
resultDic[@"latitude"] = @"40.0361208767361";
resultDic[@"longitude"] = @"116.4161067708333";
[CbDic setValue:resultDic forKey:@"result"];
}
arg2(CbDic);
}else{
arg2(argCb);
}
};
//xg_callback替换arg2。(只做了一个转接的过程)
%orig(arg1,xg_callback);
}
%end
复制代码
- 看下效果图
3. 设置打卡开关
主要功能写的差不多了。我们再来设置一个开关来控制一下:如果
开关开启
,就走打卡插件逻辑
,如果开关关闭
,走原来的逻辑
。
- 我们在设置里面偷偷的加个按钮~
Xcode
好强大~
- 然后找下
dataSource
吧
- 那这就好办了啊~ (兄得们,这是个
tableview
啊,把他的代码一hook
加行cell
,不是很easy
吗)
@interface DTTableViewHandler : NSObject
- (long long)numberOfSectionsInTableView:(UITableView *)tableView;
@end
%hook DTTableViewHandler
%new
-(void)xg_switchChang:(UISwitch *)switchView{
[XGDefaults setBool:switchView.isOn forKey:XGSWITCHKEY];
[XGDefaults synchronize];
}
- (long long)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// 如果是最后一组,增加一行
if([tableView.nextResponder .nextResponder isKindOfClass:%c(DTSettingListViewController)] && (section == [self numberOfSectionsInTableView:tableView]-1)){
return 1;
}
return %orig;
}
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//最后一组,最后一行,设置一个cell
if([tableView.nextResponder .nextResponder isKindOfClass:%c(DTSettingListViewController)] && ([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 = [XGDefaults boolForKey:XGSWITCHKEY];
[switchView addTarget:self action:@selector(xg_switchChang:) forControlEvents:(UIControlEventValueChanged)];
cell.accessoryView = switchView;
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
return nil;
}else{
return %orig;
}
}
- (double)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//设置最后一组最后一行的行高
if([tableView.nextResponder .nextResponder isKindOfClass:%c(DTSettingListViewController)] && ([indexPath section] == [self numberOfSectionsInTableView:tableView]-1)){
return 45;
}
return %orig;
}
- (long long)numberOfSectionsInTableView:(UITableView *)tableView {
//增加一组
if([tableView.nextResponder .nextResponder isKindOfClass:%c(DTSettingListViewController)]){
return %orig + 1;
}
return %orig;
}
%end
复制代码
- 然后在原来打卡的地方加个判断,就好了
if([arg1Dic[@"action"] isEqualToString:@"start"] && [XGDefaults boolForKey:XGSWITCHKEY]){
- 看效果
4. 总结
-
这篇博客禁止做商业用途。纯属为了学习,如果有法律责任与本人无关!!
-
这篇博客是小谷自创的,如果转载请标明出处!
-
小谷本来想直接
HOOK
–iOS回调
的那个函数
改值,不过感觉会有误伤
-
好了兄弟们。
逆向插件
开发了一波。小谷准备下一篇写个基础安防
的 -
不过不能写这么长了。博客写太长,兄弟们估计不想看。我
尽量精简
-
最后祝兄弟们,越来越帅!!!! 还有大家永不迟到~