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

小谷秃头合集

1. 引言

  • 小谷的公司每个月可以迟到3次,小谷5月的时候考勤竟然迟到了4次。很难受。为了杜绝这种情况。毅然决然的研究一波偷偷打卡?

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

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

  • 先看效果图

0.png

2. 钉钉打卡插件

2.1. 调试应用

蹂躏第一步,首先要装酷

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

1.1.png

1.2.png

这样砸过壳 钉钉.ipa就出来了,先放这,我们先不管他

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

2.png

这样就清晰很多了

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

3.png

我滴天啊。是个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:)

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. 总结

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

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

  • 小谷本来想直接HOOKiOS回调的那个函数改值,不过感觉会有误伤

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

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

  • 最后祝兄弟们,越来越帅!!!! 还有大家永不迟到~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享