飘云阁(PYG官方论坛)

 找回密码
 加入论坛

QQ登录

只需一步,快速开始

扫一扫,访问微社区

[x86]PYG官方Dll优雅破解补丁制作工具[x64]PYG官方DLL优雅破解补丁制作工具[x86]PYG官方Exe优雅破解补丁制作工具飘云阁工具包(已更新第4季)PYG十周年第十一期软件安全教学视频
查看: 13130|回复: 138

[iOS] 【非越狱环境】钉钉躺床上打卡之GPS篇

    [复制链接]
  • TA的每日心情
    开心
    2017-9-27 15:18
  • 签到天数: 216 天

    [LV.7]常住居民III

    发表于 2017-1-4 21:52:21 | 显示全部楼层 |阅读模式
    本帖最后由 gengjf025 于 2017-8-20 20:51 编辑

    钉钉iOS客户端GPS修改(非越狱环境)

    一、背景
    “钉钉”是阿里巴巴的一款免费的移动办公平台。我们这次只针对其移动考勤功能做一些事情。

    二、工具准备

    三、分析
    • 1、直接从PP助手下载越狱版的ipa,免去砸壳的步骤
    • 2、解压下载的ipa包,取出可执行文件,使用命令

    • 3、使用class-dump导出可执行文件的头文件,结合Hopper Disassembler工具可以进行分析
    •    3.1我们知道获取当前GPS信息肯定会使用CLLocationManager,GPS数据更新之后会调用CLLocationManagerDelegate的locationManager:didUpdateToLocation:fromLocation:或locationManager:didUpdateLocations:这两个方法,因此我们只要Hook这两个方法即可。
    •    3.2、上面所说的两个方法如何Hook,我们采用runtime,先hookCLLocationManager的setDelegate方法,然后把delegate中实现的两个方法exchange到我们自己写的即可
    •    3.3、在Hopper中搜一下locationManager:didUpdateLocations:看看



    这里基本不需要用到多少Hopper进行分析,所以不多说,下面动手搞起。


    四、编码
        我们打算使用runtime的特性,在这边我选择了iosOpenDev的一个模板只是为了方便,减少相关配置的操作而已,也可以直接用XCode自带的试试,下面是编码工作:

    1、创建CLLocationManager类的category,并在其load方法中做一些事情;
    [Objective-C] 纯文本查看 复制代码
    + (void)load {
        
        jf_dictionaryAllClasses = [[NSMutableDictionary alloc] init];
    }

    1.1 load方法中创建了一个全局的NSMutableDictionary对象,用于存储被hookdelegate的类,避免重复hook导致错乱。
    1.2 添加jf_setDelegate:方法,hook那两个代理方法,并将类名存下来,表示以hook,后面不再重复hook
    [Objective-C] 纯文本查看 复制代码
    - (void)jf_setDelegate:(id<CLLocationManagerDelegate>)delegate {
        
        if(delegate) {
            
            Class locationDelegate = [delegate class];
            
            NSString *delegateClassName = NSStringFromClass(locationDelegate);
            
            NSString *keyName = [NSString stringWithFormat:@"%@", delegateClassName];
            
            NSNumber *number = [jf_dictionaryAllClasses objectForKey:keyName];
            
            BOOL isHooked = (number ? [number boolValue] : NO);
            
            if(!isHooked) {
                
                SEL didUpdateToLocation = @selector(locationManager:didUpdateToLocation:fromLocation:);
                if([delegate respondsToSelector:didUpdateToLocation]) {
                    
                    [DTGPSHook hook_didUpdateToLocation:locationDelegate];
                }
                
                SEL didUpdateLocations = @selector(locationManager:didUpdateLocations:);
                if([delegate respondsToSelector:didUpdateLocations]) {
                    
                    [DTGPSHook hook_didUpdateLocations:locationDelegate];
                }
                
                [jf_dictionaryAllClasses setObject:[NSNumber numberWithBool:YES] forKey:keyName];
            }
        }
        [self jf_setDelegate:delegate];
    }
    2、创建一个类DTGPSHook,并在其load方法中做一些事情;
    [Objective-C] 纯文本查看 复制代码
    + (void)load {
        
        Class locationManager = NSClassFromString(@"CLLocationManager");
        
        [JF_Helper JFHookMethod:locationManager oldSEL:@selector(setDelegate:) newClass:locationManager newSel:@selector(jf_setDelegate:)];
    }
    2.1 DTGPSHook中实现了两个类方法,分别是hook_didUpdateToLocation:hook_didUpdateLications:具体实现如下:
    [Objective-C] 纯文本查看 复制代码
    + (void)hook_didUpdateToLocation:(Class)cl {
        SEL didUpdateToLocation = @selector(locationManager:didUpdateToLocation:fromLocation:);
        
        SEL action = NSSelectorFromString(@"jf_jump_locationManager:didUpdateToLocation:fromLocation:action:selfClass:");
        
        // 新增jf_jump_locationManager:didUpdateToLocation:fromLocation:action:selfClass:方法,并与self的交换
        if(![cl respondsToSelector:action]) {
            
            _jf_hookClass_CopyAMetaMethod([self class], cl, @"jf_jump_locationManager:didUpdateToLocation:fromLocation:action:selfClass:");
        }
        
        const char *classNameChar = class_getName(cl);
        
        NSString *methodName = [NSString stringWithFormat:@"jf_%s_locationManager_didUpdateToLocation", classNameChar];
        
        SEL method = NSSelectorFromString(methodName);
        
        // 如果class有didUpdateToLocation方法,则会调用下面的block
        void (^block_jf_didUpdateToLocation)(id, CLLocationManager *, CLLocation *, CLLocation *) = ^(id self, CLLocationManager *manager, CLLocation *newLocation, CLLocation *oldLocation) {
            
            if([self class] != [NSObject class]) {
                
                if(strcmp(classNameChar, class_getName([self class])) != 0) {
                    // 当前self的class类名和注册的时候的不一样,self应该是注册时的类的supper类
                    
                    if([cl instancesRespondToSelector:method]) {
                        ((void(*)(id, SEL, id, id, id, SEL, Class))objc_msgSend)(self, @selector(jf_jump_locationManager:didUpdateToLocation:fromLocation:action:selfClass:), manager, newLocation, oldLocation, method, cl);
                    }
                }
                else {
                    
                    ((void(*)(id, SEL, id, id, id, SEL, Class))objc_msgSend)(self, @selector(jf_jump_locationManager:didUpdateToLocation:fromLocation:action:selfClass:), manager, newLocation, oldLocation, method, cl);
                }
            }
        };
        
        IMP imp = imp_implementationWithBlock(block_jf_didUpdateToLocation);
        _jf_Swizzle_orReplaceWithIMPs(cl, didUpdateToLocation, method, imp);
    }
    + (void)hook_didUpdateLocations:(Class)cl {
        
        SEL didUpdateLocations = @selector(locationManager:didUpdateLocations:);
        
        SEL action = NSSelectorFromString(@"jf_jump_locationManager:didUpdateLocations:action:selfClass:");
        
        // 新增jf_jump_locationManager:didUpdateLocations:action:selfClass:方法,并与self的交换
        if(![cl respondsToSelector:action]) {
            
            _jf_hookClass_CopyAMetaMethod([self class], cl, @"jf_jump_locationManager:didUpdateLocations:action:selfClass:");
        }
        
        const char *classNameChar = class_getName(cl);
        
        NSString *methodName = [NSString stringWithFormat:@"jf_%s_locationManager_didUpdateLocations", classNameChar];
        
        SEL method = NSSelectorFromString(methodName);
        
        // 如果class有viewDidLoad方法,则会调用下面的block
        void (^block_jf_didUpdateLocations)(id, CLLocationManager *, NSArray<CLLocation *> *) = ^(id self, CLLocationManager *manager, NSArray<CLLocation *> *locations) {
            
            if([self class] != [NSObject class]) {
                
                if(strcmp(classNameChar, class_getName([self class])) != 0) {
                    // 当前self的class类名和注册的时候的不一样,self应该是注册时的类的supper类
                    
                    if([cl instancesRespondToSelector:method]) {
                        ((void(*)(id, SEL, id, id, SEL, Class))objc_msgSend)(self, @selector(jf_jump_locationManager:didUpdateLocations:action:selfClass:), manager, locations, method, cl);
                    }
                }
                else {
                    
                    ((void(*)(id, SEL, id, id, SEL, Class))objc_msgSend)(self, @selector(jf_jump_locationManager:didUpdateLocations:action:selfClass:), manager, locations, method, cl);
                }
            }
        };
        
        IMP imp = imp_implementationWithBlock(block_jf_didUpdateLocations);
        _jf_Swizzle_orReplaceWithIMPs(cl, didUpdateLocations, method, imp);
    }

    这里两个方法的实现原理都是一样的:
    2.1.1、分别为delegate所在的类新增一个方法,具体实现为block中的内容;
    2.1.2、分别新增的方法与locationManager:didUpdateLocations:和locationManager:didUpdateToLocation:fromLocation:交换,这样GPS更新之后回调会进入block中;
    2.2、在DTGPSHook类中增加以下两个方法,用于修改GPS信息,并返回给钉钉实际需要的地方:
    [Objective-C] 纯文本查看 复制代码
    - (void)jf_jump_locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation action:(SEL)action selfClass:(Class)selfClass {
        
        if(!action) {
            
            // 执行方法为空,表示selfClass根本没有实现该方法
            return;
        }
        
        CLLocation *current_Location = [JF_Helper randomLocation:newLocation];
        
        NSString *stringAction = NSStringFromSelector(action);
        if(stringAction && ![stringAction isEqualToString:@""]) {
            
            if([self respondsToSelector:action]) {
                
                ((void(*)(id, SEL, id, id, id))objc_msgSend)(self, action, manager, current_Location, oldLocation);
            }
        }
    }
    
    - (void)jf_jump_locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations action:(SEL)action selfClass:(Class)selfClass {
        
        if(!action) {
            
            // 执行方法为空,表示selfClass根本没有实现该方法
            return;
        }
        
        NSMutableArray *array = [NSMutableArray array];
        
        if(locations && [locations count] > 0) {
            
            for (CLLocation *location in locations) {
                
                CLLocation *current_location = [JF_Helper randomLocation:location];
                
                [array addObject:current_location];
            }
        }
        else {
            
            CLLocation *current_location = [JF_Helper randomLocation:nil];
            
            [array addObject:current_location];
        }
        
        NSString *stringAction = NSStringFromSelector(action);
        if(stringAction && ![stringAction isEqualToString:@""]) {
            
            if([self respondsToSelector:action]) {
                
                ((void(*)(id, SEL, id, id))objc_msgSend)(self, action, manager, array);
            }
        }
    }

    3、创建一个类DTGPSSettingViewController,用于直接在钉钉内打开系统高德地图,选取位置之后用于篡改真实的GPS位置,核心实现为获取长按地图时所选点的高德坐标,将其转换为GPS坐标后存起来即可:
    [Objective-C] 纯文本查看 复制代码
    - (void)longPress:(UIGestureRecognizer*)gestureRecognizer {
        
            if (gestureRecognizer.state == UIGestureRecognizerStateBegan){
            
                //坐标转换
                CGPoint touchPoint = [gestureRecognizer locationInView:_mapView];
                
                coordinate_setting = [_mapView convertPoint:touchPoint toCoordinateFromView:_mapView];
            
                self.label_longitude.text = [NSString stringWithFormat:@"经度:%f", coordinate_setting.longitude];
                self.label_latitude.text = [NSString stringWithFormat:@"纬度:%f", coordinate_setting.latitude];
            
                NSString *title = [NSString stringWithFormat:@"%f, %f", coordinate_setting.longitude, coordinate_setting.latitude];
            
                MKPointAnnotation *pointAnnotation = nil;
                pointAnnotation = [[MKPointAnnotation alloc] init];
                pointAnnotation.coordinate = coordinate_setting;
                pointAnnotation.title = title;
            
                [self.mapView addAnnotation:pointAnnotation];
            }
        }
    // 存储选择的经纬度,保存并退出
    - (void)commit:(id)sender {
       
        [self.navigationController dismissViewControllerAnimated:YES completion:^{
            
            // 高德坐标转换成GPS坐标
            coordinate_setting = transformCoordinate(coordinate_setting);
            
            NSString *longitude = [NSString stringWithFormat:@"%.4f", coordinate_setting.longitude];
            NSString *latitude = [NSString stringWithFormat:@"%.4f", coordinate_setting.latitude];
            
            [[NSUserDefaults standardUserDefaults] setObject:longitude forKey:@"JF_GPS_LONGITUDE"];
            [[NSUserDefaults standardUserDefaults] setObject:latitude forKey:@"JF_GPS_LATITUDE"];
            [[NSUserDefaults standardUserDefaults] synchronize];
        }];
    }

    4JFHookMethod实际使用了runtimemethod_exchangeImplementationssetDelegatejf_setDelegate做了交换。
    [Objective-C] 纯文本查看 复制代码
    + (void)JFHookMethod:(Class)oldClass oldSEL:(SEL)oldSEL  newClass:(Class)newClass newSel:(SEL)newSel {
        
            Method oldMethod = class_getInstanceMethod(oldClass, oldSEL);
        
            Method newMethod = class_getInstanceMethod(newClass, newSel);
        
            if(oldMethod && newMethod) {
            
                method_exchangeImplementations(oldMethod, newMethod);
            }
        }

    五、注入、打包、签名
    1、注入
    • 1.1、完成编码之后,command+B生成dylib文件;
    • 1.2、insert_dylib拷贝到与app目录放一起;
    • 1.3、将自己编码生成的dylib,拷贝到app中,与可执行文件放在一起,注意insert_dylib应该在可执行文件的上一级目录中
    • 1.4、使用命令行,先cdinsert_dylib文件处,再使用
    [Objective-C] 纯文本查看 复制代码
    ./insert_dylib @executable_path/libDingTalkDylib.dylib DingTalk.app/DingTalk

          出现提示:
    [Objective-C] 纯文本查看 复制代码
    The provided dylib path doesn't exist. Continue anyway? [y/n]

          输入Y之后出现:
    [Objective-C] 纯文本查看 复制代码
    Added LC_LOAD_DYLIB to all archs in DingTalk.app/DingTalk_patched


          此时app目录中多了一个DingTalk_patched文件,这个就是已经注入成功的可执行文件,我们将原来的可执行文件删除,将DingTalk_patched重命名为DingTalk

          如果不放心,可以使用MachOView打开可执行文件查看:
          
          展开Load_Commands,滚动到最下面看到:
          

    2、打包、签名
    我们使用打包+签名一体工具iOS App Signer,友情提示:如果证书没有支持watch,请将watch目录删除;

    3、安装测试
    使用PP助手或者Apple Configuration2安装到非越狱手机打开app,进入坐标选择界面,长按出现大头针即可:
    点击右上角的“完成”试试水:


    六、代码地址

    七、结束语
    实践过程中需要灵活运用runtime的一些方法,有些描述不是很详细的地方可以直接看代码,代码中没有使用相对布局,有些界面用的绝对坐标只是随手一写,如有错误的地方请自行修改。


    钉钉3.5.1版本启用了GPS劫持检测,已针对该检测做了一些反检测的工作,具体可直接参考代码,分析过程略去1万字.....

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?加入论坛

    x

    评分

    参与人数 16威望 +19 飘云币 +31 收起 理由
    权峰 + 1
    绞尽乳汁6 + 1 很给力!
    保持沉默 + 1 PYG有你更精彩!
    风轻云淡 + 1 + 1 表格,GPS的有没有安卓思路!!
    rNKsT61v + 1 + 1 很给力!
    orz + 1 很给力!
    nociabx + 1 很给力!
    yosen2001 + 10 很给力!
    woshizs + 1 赞一个!
    世态炎凉 + 1 哥们能出个安卓的码!!!

    查看全部评分

  • TA的每日心情
    开心
    2016-6-16 14:07
  • 签到天数: 10 天

    [LV.3]偶尔看看II

    发表于 2017-1-4 22:18:38 | 显示全部楼层
    拜读好文!名师出高徒!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    前天 22:01
  • 签到天数: 99 天

    [LV.6]常住居民II

    发表于 2017-1-5 01:20:44 | 显示全部楼层
    绝佳的实践!认真学习了
    回复 支持 反对

    使用道具 举报

  • TA的每日心情

    前天 08:04
  • 签到天数: 376 天

    [LV.9]以坛为家II

    发表于 2017-1-5 08:57:26 | 显示全部楼层
            PYG有你更精彩!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情

    2017-7-7 11:19
  • 签到天数: 44 天

    [LV.5]常住居民I

    发表于 2017-1-5 10:07:06 | 显示全部楼层
    不错  啊   这个真心不错啊
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    6 天前
  • 签到天数: 373 天

    [LV.9]以坛为家II

    发表于 2017-1-5 10:09:55 | 显示全部楼层
    膜拜一下,谢谢分享。
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2017-3-28 14:37
  • 签到天数: 10 天

    [LV.3]偶尔看看II

    发表于 2017-1-5 14:47:53 | 显示全部楼层
    好东西,谢谢楼主
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2017-8-21 09:21
  • 签到天数: 60 天

    [LV.6]常住居民II

    发表于 2017-1-5 17:06:35 | 显示全部楼层
    精彩好文章,感谢分享~
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入论坛

    本版积分规则

    小黑屋|手机版|Archiver|飘云阁安全论坛 ( 粤ICP备15107817号-2|友情赞助

    GMT+8, 2017-10-23 06:45

    Powered by Discuz! X3.3

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表