iOS逆向安防从入门到秃头--钉钉居家打卡插件&开关

小谷秃头合集ios

1. 引言

  • 小谷的公司每月能够迟到3次,小谷5月的时候考勤居然迟到了4次。很难受。为了杜绝这种状况。毅然决然的研究一波偷偷打卡😃git

  • 小谷用的设备是iPhone XS MaxiOS 14.3(越狱),目前最新的钉钉:6.0.16markdown

  • Windows逆向的兄弟们,常常使用IDA和Reveal还有终端debugserver附加进程,今天小谷要用iOS开发的兄弟们都比较喜欢的Xcode和hopper。😆(其实都差很少。)函数

  • 先看效果图post

0.png

2. 钉钉打卡插件

2.1. 调试应用

蹂躏第一步,首先要装酷学习

  • 先把钉钉砸壳,取下来。我使用的是frida-ios-dump,小谷写过一篇砸壳和Frida的博客.

1.1.png

1.2.png

这样砸过壳 钉钉.ipa就出来了,先放这,咱们先无论他ui

  • 开始了! iOS兄弟们超级喜欢的,Xcode调试(曾经有兄弟问过我怎么附加的。我这里画下流程图)

2.png

这样就清晰不少了spa

  • 既然咱们能够调试的话,那就直接定位打卡的代码了啊 (找到打卡界面,viewdebug调试)

3.png

我滴天啊。是个WKWebview,定位不到!!插件

这样就结束了吗?怎么可能呢,那之后小谷要是迟到,没有办法弥补了~·debug

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:)

4.png

一共就5个,咱们就把这几个hook一下,看下log了,我猜想应该就能够定位到了(若是他调用的原生的)

  • 此次咱们就用MonkeyTweak插件了(固然也能够用THEOS,主要咱们此次可能要好几回调试才能定位,我就用Monkey方便点~)

5.png

  • 获取钉钉的APPID,并配置

6.png

7.png

  • 咱们把上面找到的那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

  • 咱们发现这个AMapLocationCLMDelegateAMapLocationManager都是成对出现的,其余的都没有走

8.png

  • 而后咱们观察下class-dump出来的header文件。发现AMapLocationCLMDelegate里面都是一些代理回调

这个时候,小谷又能够推测一波了: 在AMapLocationManager获取的位置,而后参数在把参数传给H5交互

  • 咱们把AMapLocationManager里面的方法都打一遍log,使用logify.pl

9.png

  • 而后咱们在次装上插件看下log

11.png

看到了不错的信息。咱们要继续开始Xcode调试附加

  • Hopper找到偏移位置

12.png

  • Xcode附加下,能够找到ASLR

13.png

  • 定位偏移,设置断点

14.png

0x104e70000 + 0x36b4a8 = 0x1051DB4A8

  • 当我点击考勤打卡的时候,断住了~

15.png

  • 根据咱们的思路,咱们要看函数调用栈了!!

16.png

有没有那么一丢丢爽歪歪~ (若是没有符号也没有关系,咱们就经过Hopper地址定位!)

  • 咱们hook下这个函数
%hook LAPluginInstanceCollector
- (void)handleJavaScriptRequest:(id)arg1 callback:(id)arg2{
    %log;
    %orig;
}
%end
复制代码
  • 兄弟们~ 咱们能够获得线索,这个callback是个block,说明arg1是参数,arg2是回调回去的参数

17.png

  • 想知道这个block的参数类型。就要看看他的签名了~

18.png

经过内存平移来拿他的签名~

  • 老办法,Hopper拿地址。而后Xcode附加下断点

19.png 20.png

0x100f50000 + 0x488f110 = 0x1057DF110

  • b 0x1057DF110 断住它,而后分析他

21.png 22.png

  • 这个时候们就能够继续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
复制代码
  • 而后看效果

23.png

我好像找到了,可是这里面还有好多其余跟他相关连的信息。(好比地址,城市,经纬度啥的)

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
复制代码
  • 看下效果图

24.png

3. 设置打卡开关

主要功能写的差很少了。咱们再来设置一个开关来控制一下:若是开关开启,就走打卡插件逻辑,若是开关关闭,走原来的逻辑

  • 咱们在设置里面偷偷的加个按钮~

25.png

Xcode好强大~

  • 而后找下dataSource

26.png

  • 那这就好办了啊~ (兄得们,这是个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]){

  • 看效果

0.png

4. 总结

  • 这篇博客禁止作商业用途。纯属为了学习,若是有法律责任与本人无关!!

  • 这篇博客是小谷自创的,若是转载请标明出处!

  • 小谷原本想直接HOOK-iOS回调的那个函数改值,不过感受会有误伤

  • 好了兄弟们。逆向插件开发了一波。小谷准备下一篇写个基础安防

  • 不过不能写这么长了。博客写太长,兄弟们估计不想看。我尽可能精简

  • 最后祝兄弟们,愈来愈帅!!!! 还有你们永不迟到~

相关文章
相关标签/搜索