前言 疫情期间在家上班 , 打卡是个麻烦事 , 笔者就想起实现这个需求 , 本文由于讲述从思路到完整分析过程演示 , 涉及到的知识点也基本都有介绍 , 因此篇幅较长 , 对相关知识比较熟悉的同学可以自行跳过 . 本篇文章将演示从 思路 -> 探索 -> 调试 -> 注入 , 来实际逆向一个应用的完整过程 . 实现的效果是 修改 Wi-Fi 打卡以及 GPS 位置打卡规则 , 达到不在要求 Wi-Fi 环境或者 GPS 范围下也可以正常打卡 的需求 . ( 本文使用目前钉钉最新版本 5.0.6 , 后续更新有可能会更改代码逻辑导致插件不再适用 , 只是为了交流和学习 ) 声明:本文演示插件无任何商业目的,也不从事任何商业性质活动。 提示 : • 本文基于越狱环境下插件来实现 . • 非越狱环境下重签应用修改 BundleID 钉钉有相应监测 , 风险较大 . • 有一些修改手机虚拟定位的 , 是另外一种方式 , 本文不做讲述 . 前导知识 • 1️⃣、RSA加密原理&密码学&HASH • 2️⃣、应用签名原理及重签名 (重签微信应用实战) • 3️⃣、shell 脚本自动重签名与代码注入 • 4️⃣、重签应用调试与代码修改 (Hook) • 5️⃣、Mach-O文件 • 6️⃣、Hook / fishHook 原理与符号表 • 7️⃣、LLDB由浅至深 • 8️⃣、lldb高级篇 Chisel 与 Cycript • 9️⃣、越狱初探 • 关于逆向前导知识,笔者所介绍文章中还缺少应用砸壳以及汇编指令的知识,后续更新会继续讲述,以形成一套完整的篇章栏目。 • 砸壳和汇编部分内容并不影响我们今天所要做的钉钉插件。 准备工作 • 完美越狱手机 ( 本文使用 iPhone 5s , iOS 9.0.1 , 完美越狱版本 , Xcode 10.1 ) • AppStroe 安装正版钉钉,本文使用当前最新版本 ( 5.0.6 ) • Reveal ,关于 Reveal 本文会详细讲述 • frida-ios-dump 关于这个砸壳工具的安装 , 请参照 git - frida-ios-dump ( 关于砸壳 , 后续笔者会更新文章专门讲述动态砸壳 , 静态砸壳原理以及不同砸壳工具的使用和原理 ) • Hopper - Mach-O 分析工具 提取密码: x07n, dmg 解压密码 xclient.info . • Sublime Text - 文本编辑工具 , 轻量级 , 全局检索效率较高 . • MonkeyDev 与 Theos , 重签名应用以及编写越狱插件必不可少的工具 . • 越狱环境 lldb 使用 , lldb 插件 - loadCommands 一、Reveal 页面调试工具 很多逆向必须从页面入手 , 找到对应按钮的方法进行 hook , 可以帮助我们理清楚代码以及业务逻辑 . 我们都知道 , 对于已经砸壳或者自己通过代码运行在手机上的工程,是没有加密的,这种应用我们可以直接通过 Xcode -> Debug -> Attach to Process , 对正在运行的应用进行附加 ,以此可以达到 View-Debug 的效果 。 ( 对于如何判断应用 MachO 是否加壳,使用 otool -l + Macho文件名称 即可 ) 。 那么对于正版应用,也就是没有砸壳的应用,如何进行页面调试呢。这里就是 Reveal 解决的问题。 安装 1、Mac 端工具安装 Reveal-for-Mac , 提取码:iv3j , dmg 安装包密码:xclient.info 下载安装即可。 2、越狱手机插件 Cydia 中搜索 Reveal Loader 并安装。
3、手机设置 按下图设置打开权限 。
4、环境配置 打开终端 , 使用 SSH , usb 的方式做端口映射并登录手机 ( 关于越狱环境下手机和电脑的端口隐射以及文件传输参考 越狱初探 这篇文章中有非常详细的演示和讲述 ) . • 端口映射
• 登录
• 从 Mac 端拷贝文件到手机 先在手机根目录 /Library/ 下建立 RHRevealLoader 文件夹.
将电脑端 RevealServer.framework 中的 RevealServer 拷贝一份 , 改名为 libReveal.dylib , 然后拷贝到手机 /Library/RHRevealLoader/ 目录下 . scp -P 12345 -r /Users/libin/Desktop/逆向/libReveal.dylib root@localhost:/Library/RHRevealLoader/ 复制代码 你同样可以使用 iFunBox 来可视化的操作文件 . • 重启电脑端 Reveal , 重启手机 , 打开钉钉 , 即可显示如下 .
5、提示 电脑端 Reveal 并没有检测到所打开应用 , 请排查以下问题 : • 1️⃣ : 检查下 RevealServer 有没有改名为 libReveal.dylib. • 2️⃣ : 检查 libReveal.dylib 有无可执行权限 , 没有 +x 即可. • 3️⃣ : 手机与电脑端 Reveal 有无重启 . • 4️⃣ : 手机重启后要保持与电脑在同一局域网下 . 最终实现效果如下 :
二、砸壳 这里使用 frida-ios-dump 进行砸壳 , ( 关于砸壳 , 后续笔者会更新文章专门讲述动态砸壳 , 静态砸壳原理以及不同砸壳工具的使用和原理 ) 对 usb 端口映射 , 登录手机 , 然后即可使用砸壳 .
( 笔者这里配置了自定义 shell 脚本 , 其实就是为了在任意路径下使用 frida 的 dump.py , 就不用每次必须 cd 到这个目录里 , 砸完壳的应用 ipa 也就在当前目录下 . 感兴趣的同学可以参照 越狱初探 中 2.5.3 配置 shell 自动连接 usb 映射并登陆部分有详细演示 ) .
砸完壳后得到 ipa , 修改为 zip , 解压缩 , 显示包内容 , 即可找到砸壳后的 Mach-O 源文件 . 笔者这里提供一下砸壳后的应用包以及 class-dump 后的头文件 : 链接 密码: pjtt 查看应用加密情况 :
三、class-dump 使用 class-dump 导出砸壳后应用的头文件 . 不熟悉 class-dump 的同学可以参考 重签应用调试与代码修改 这篇文章 . 输入命令 : class-dump -H DingTalk -o ./headers/ • DingTalk 为当前目录下 Mach-O 文件名 • -o ./headers/ 意思是输出到同路径的 headers 文件夹中 . 结果如下 :
至此 , 准备工作就已经完成了 , 这也是我们逆向一个应用首先需要做的事情 . • 1️⃣ : 运行应用 , 页面调试 ( Reveal , 砸壳应用重签名附加到 Xcode , View-Debug , Chisel 与 Cycript 等 , 参照 lldb高级篇 Chisel 与 Cycript ) . • 2️⃣ : 应用砸壳 • 3️⃣ : class-dump 四、准备工作总结 在实际逆向开发中 , 直接上代码去调试是很不推荐的方式 , 本着时间就是成本的原则 , 我们的处理应该是如下流程 . 先理清楚思路和源开发逻辑 , 能静态分析就静态分析 ( 静态分析一般包括 class-dump 查看头文件 , macho 分析 , 汇编代码分析等等 . 当然 , 不是迫不得已的情况下不需要分析汇编 , 对于汇编非常熟悉的同学可以忽略 ) , 尽量少动态调试 , 在我们有了十足的把握之后再开始上代码 . 因此准备工具都准备完毕以后 , 我们来思考一下业务需求和逻辑 . 思路 需求 : • 当使用地点 GPS 打卡规则时 : o 我们希望可以不在指定位置也能正常打卡 . • 当使用连接上指定 Wi-Fi 打卡时 : o 我们希望可以不连接上指定 Wi-Fi 也能正常打卡 . 那么首先我们能想到的就是如下 : 思路 1 从打卡的按钮和点击方法入手 , 对该方法进行 hook 或者查看汇编进行静态分析 , 查看其处理逻辑 , 修改判断逻辑 , 当不满足条件也能正常执行打卡 . 因此 , 来到打卡页面 , 使用 Reveal 查看按钮方法 .
来到这看到一个不幸的消息 , 整个页面是个 WebView . 即使我们可以猜测其 WebView 与 OC 交互通过哪种方式 , 会调用到哪些固定方法继续查找 , 单未免时间成本过于大了一些 . 因此我们变换一下思路 . 思路 2 首先我们来考虑下 GPS 打卡的情况 , 由于 iOS 系统要求 , 任何地图的获取 GPS 位置几乎都是封装了原生的 CLLocationManager 来做的 . 因此我们想到 , 对于获取到 GPS 的代理方法中进行修改 , 使其永远获取到的是满足地址返回的经纬度 . Wi-Fi 思路也是如上 . 大体上应该可以满足需求 . 接下来我们就来尝试一下 . 调试过程 一、静态分析 使用 Hopper 打开砸完壳后的 Mach-O . 搜索方法 : locationManager:didUpdateLocations: 复制代码 搜索结果如下 :
搜索到整个工程中 , 有这几个类都实现了这个方法 . 那么我们应该怎么办 , 去看这几个方法的汇编进行分析吗 ? 笔者这里不推荐这种方式 . 由于使用习惯告诉我们 , 每次打开打卡页面时会获取 GPS 位置 . 因此我认为可以这么处理 : 使用动态调试 , 对这些类的这个方法进行 Hook , Hook 中保持继续调用原方法 , 但是加一个打印 , 以此 找到当打开页面时 , 是由哪个类去获取 GPS 位置 . 从而 缩小分析范围 . 使用 logos 进行方法 hook 是非常高效而且便捷的 . ( 不熟悉的同学可以阅读 实际逆向中 hook 方式 -- Logos ) 因此 , 直接来到动态调试阶段 . 二、动态调试
- 新建工程 在安装完 MonkeyDev 插件后 , 新建项目选择如下 :
修改文件类型为 Objective-C++ Source , 文件内容只保留一个 UIKit 的引入即可 . ( 遇到文件类型修改后不刷新情况的同学可以左侧选择其他文件再选择回来即可 )
-
获取目标工程的 Bundle ID 这里其实有很多种方式 , 可以直接去砸完壳的应用的 info.plist 中找 . 为了顺带提一点 cycript 在越狱环境下的使用 , 以及自定义 cy 指令 ( 同样参考 lldb高级篇 Chisel 与 Cycript ) 我们使用这种方式 : • ① : 端口映射及 ssh 登录手机 • ② : 手机运行钉钉 并保证前台活跃状态 • ③ : 查看钉钉进程 ID • ④ : cycript 附加当前进程 • ⑤ : 导入自定义 cy 指令 • ⑥ : 获取 Bundle ID
将 Bundle ID 填入到工程配置文件中 . -
ssh 配置 使用 MonkeyDev 编写插件需要在工程配置 ssh 登录信息 .
笔者在 .zshrc 配置了相关环境变量 , 就不用每次填写 .
关于 ssh 端口映射以及登录参考 越狱初探
至于为什么插件安装成功需要杀掉进程重新启动 , 这里我们稍微提一点 .
• 其实 MonkeyDev 的 tweak 使用的 Theos , 其原理是利用的 DYLD_INSERT_LIBRARY 原理 .
• dyld 源码中可以看到 , dyld 会根据 DYLD_INSERT_LIBRARY 这个环境变量来决定是否加载插入的动态库.
• 有些防护手段就是通过添加 __RESTRICT 段 , _restrict 节来实现越狱插件的防护的 . ( 关于这个我们会在后续越狱环境攻防中详细讲述和演示 ) .
• 越狱插件打包其实会生成 dylib 库 , 然后通过 ssh 拷贝到手机中 , 在应用加载时 , 由 dyld 去加载的 hook 代码 . ( 这也是为什么插件安装需要杀掉进程重新加载的原因 ) .
dyld 源码 :
const char* const * DYLD_INSERT_LIBRARIES;
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
复制代码
2. 编写 Logos
这里代码并没有什么值得说的 , 就是分别 hook 我们上述在 hopper 中搜出来的实现了 locationManager:didUpdateLocations: 的几个类中的这个方法 .
#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 DTCLocationManager
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{ %log; %orig; } %end
%hook LALocationManager
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{ %log; %orig; } %end
%hook LAPLocationInfo
- (void)dt_locationManager:(id)arg1 didUpdateLocations:(id)arg2{ %log; %orig; } %end
%hook DTLocationJSAPIHandler
- (void)dt_locationManager:(id)arg1 didUpdateLocations:(id)arg2{ %log; %orig; } %end
%hook VILocationManager
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{ %log; %orig; } %end
%hook MAMapView
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{ %log; %orig; } %end 复制代码 写完在确保做完本地端口 12345 已经映射到 usb 另一端手机的 22 端口 , 就可以编译一下了 . 编译成功 , 发现正在运行的钉钉已经被杀掉 .
-
查看打印 由于我们 hook 方法里加了打印 , 因此我们可以运行钉钉 , 来到打卡页面 , 查看调用的到底是哪个类的定位方法 . cmd + shift + 2 打开设备页 , 打开控制台 . 输入搜索 DingTalk 选择进程 .
• 通过搜索结果我们发现 , AMapLocationCLMDelegate 与 AMapLocationManager 的 didUpdateLocations 调用是最频繁的 , 而且一直是成对出现的 . • 那么我们可以推测 , Manager 与 delegate 存在代理关系 , 由 Manager 获取到定位信息后 , 通过代理回调到协议执行者中 . 也就是一个方法嵌套 . • 把 AMapLocationCLMDelegate 和 AMapLocationManager 的地址记录保存 . 4. 内存断点 由于生产环境包并无符号 , 因此通过类名和方法名下断点是行不通的 , 所以我们使用内存地址下断点 . • ssh 映射并登录后 . 手机端输入 : • debugserver *:12346 -a DingTalk 复制代码 • mac 端输入 lldb 进入断点状态 , 然后输入 • process connect connect://localhost:12346 复制代码 ( 以上就是越狱环境使用 usb 的方式进行 lldb 调试 ) . 使用 methods + 类内存地址 获取类的方法列表和地址 ( 此指令来自于 lldb 插件 - loadCommands ) .
使用 b + 方法内存地址 即可对 AMapLocationCLMDelegate 类的 didUpdateLocations 方法下内存断点 .
注意: 可能有些同学会经常使用如上方法进行越狱环境添加断点 . 正常情况下是没有问题的 , 但是还记得我们做了什么吗 ? 我们将方法进行了 hook , 也就是说方法实现 imp 已经被换成我们自己的方法了 , 因此 , 上述方式是错误的 !! . c 一下 , 来到下一次调用 AMapLocationCLMDelegate 的 didUpdateLocations 方法 . 可以看到来到的明显是我们自己编写的 logos hook 代码 . 那么我们改如何对未 hook 的原本这个方法下断点呢 ? • 获取方法基于 mach-O 首地址的偏移量 .
• 获取 mach-O 首地址
相加获得方法本次运行实际地址 . 该计算原理涉及 ALSR 和 pageZero 知识 , 参考 LLDB由浅至深 文章末尾 . 通过计算得到 AMapLocationCLMDelegate 的方法地址 0x100267EE8 , AMapLocationManager 的方法地址 0x1002786E4 . 那我们分别添加断点 . 添加完后过掉断点 , 来到下一次调用 :
可以发现这次进入的就不是我们 hook 的方法了 , OK , 两个方法内存断点添加完成 . 使用 bt 可以看到函数嵌套调用关系 , 验证了我们之前的猜想 :
经过如上调试 , 我们发现了 , 在打卡页面 , 调用的是 AMapLocationManager 的定位 . 而且是固定一段时间就会调用一次 . 因此 , 接下来 , 我们来看下这个类的头文件 . 5. 查看头文件 来到 sublime 中打开 class-dump 出来的头文件 , cmd + shift + f 搜索 @interface AMapLocationManager . 找到这个类的所有方法和属性 , 那么我们接下来对这个类的所有方法进行 hook , 添加打印查看并研究其逻辑与调用流程 . 由于这个类方法较多 , 手写太过麻烦 , 其实我们配置 Theos 时 , 里面有一个脚本 .
将我们 dump 出来的 AMapLocationManager.h 拷贝到工程目录下 . 输入命令 :
执行完毕将生成的 logAMapManager.xm 拖进工程 , 选择 type 为 Objective-C++ Source , 编译工程 , 此时项目文件夹中多生成一个 logAMapManager.mm , 同样拖进工程 .
此时 , 该类的所有方法 hook 代码已经自动完成了 . 此时编译会有报错 , 将报错的类添加一个声明即可 , 另外 .cxx_destruct , 一些 block 和 delegate 的方法笔者这边直接注释掉了 , 没有必要分析 . 笔者这里添加了四个类的声明 , 需要的同学直接拷贝使用即可 . @interface CLLocationManager @end @interface AMapNetworkManager @end @interface CLLocation @end @interface AMapLocationReGeocode @end 复制代码 将我们先前 hook 的那几个类的定位方法注释掉 , 防止打印过多影响判断 . 重新 build , 查看控制台打印 .
这里我们发现 , manager 调用 startUpdatingLocation , 然后后面就有了 didUpdateLocations . 那么同样 , 我们找到 startUpdatingLocation 的地址下断点 . 断点下成功后 , 重新进入打卡页面 . 来到断点 , bt 查看函数调用栈 .
- 定位函数调用流程 分析 : • 上图中我们看到函数名由于符号的原因并没有显示 , 那么我们想知道函数名称 , 同样 , 计算即可 . 图中展示为 本次进程函数实际地址 . • 那么减去 Mach-O 首地址随机偏移值 : ALSR , 即可得到基于 Mach-O 首地址偏移量 , 这样我们即可去 Hopper 搜索得到类名和函数名称 . • 注意考虑 pageZero 的问题 ( 实际物理地址和偏移量都包含了 pageZero , arm 64 中计算时减去一个 0x10000000 即可 ) . 以函数调用栈中 #2 为例 , 本次实际函数地址为 0x00000001024eaad8 , 减去 image list 中得到的 Mach-O 首地址也就是 ALSR 去掉最前面的 1 ( 即 page zero ) 为 0x94000 , 得到 0x102456AD8 . 来到 Hopper , 按 G 键 , 输入内存地址找到如下 :
( 笔者这里由于 Hopper 还未解析完毕 , 正常显示是具体的汇编指令 , Mac 端的 Hopper 实在太卡 ) .
通过以上方法 , 将 startUpdatingLocation 的函数调用栈无符号的情况下成功翻译如下 :
frame #1: 0x00000001085250e8 [AMapLocationManager startUpdatingLocation]
frame #2: 0x102456AD8 DingTalk [DTALocationManager dt_startUpdatingLocation] ?DingTalk + 248 frame #3: 0x1039148B0 DingTalk [LAPLocationInfo start:to:]?DingTalk + 1980
frame #4: 0x103959010 DingTalk` [LAPluginInstanceCollector handleJavaScriptRequest:callback:]?DingTalk + 52
复制代码
这里我们就找到了调用起始点 : webView 的 js 与 OC 交互回调 . 也就是说这个 webView 页面加载 , 与 OC 通讯 , 调起的开始定位 .
那么接下来 , hook 这个方法 , 添加打印 .
%hook LAPluginInstanceCollector
- (void)handleJavaScriptRequest:(id)arg1 callback:(id)arg2{ %orig; NSLog(@"LBHook\n\n\n\n\n" "arg1Class = %@\n" "arg1 = %@\n" "arg2Class = %@\n" "arg2 = %@\n" ,[arg1 class],arg1,[arg2 class],arg2); } %end 复制代码
通过添加打印 , 我们发现 , 参数 1 是一个 NSDictionary 类型的参数 . 参数2 是一个栈 block . 而且在第一个参数中并没有找到我们所需要的经纬度等信息 , 那么就需要来看下这个 Block . 7. block 获取签名 由于我们需要知道 这个 Block 的参数和返回值是什么 , 因此我们需要通过 Block 的签名来查看 , 关于 Block 获取签名 , 这个是老生常谈问题了 , 在 Aspect 等源码库中都有实现 . 其实就是根据 flags 的标记 , 同时根据内存偏移拿到 Block_descriptor_3 的 signature 的过程 ( 有关 block 底层原理详细探索 , 后续笔者在 iOS 底层篇章会详细讲述 ) . block 数据结构源码 : struct Block_layout { void *isa; volatile int32_t flags; // contains ref count int32_t reserved; BlockInvokeFunction invoke;//实现地址! struct Block_descriptor_1 *descriptor; // imported variables };
#define BLOCK_DESCRIPTOR_1 1 struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; };
#define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; BlockDisposeFunction dispose; };
#define BLOCK_DESCRIPTOR_3 1 struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char *signature; const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT }; 复制代码 那么按照上文讲述同样方式 , 我们给 [LAPluginInstanceCollector handleJavaScriptRequest:callback:] 添加断点 . 添加成功后运行来到该断点 .
拿到 block 地址后 , 我们来获取其签名 .
通过 po [NSMethodSignature signatureWithObjCTypes:"v16@?0@8"] 复制代码 可以很明显的得出 block 是一个无返回值 , 一个 id 类型参数的 block . 那么如何得知这个 id 类型到底是个什么类型呢 . 8. block 参数类型确定 为了明确这个 block 参数的具体类型 , 我们嵌套一个 block , 将原方法调用我们自定义的 block , 我们自定义的 block 中调用原本的 block , 同时添加一个打印即可 . 因此 , 我们将 hook 代码改写如下 : %hook LAPluginInstanceCollector
-
(void)handleJavaScriptRequest:(NSDictionary *)arg1 callback:(void(^)(id))arg2{ id myCallBack = ^(id block_arg){ NSLog(@"arg1 %@\n block_argClass: %@\nblock_arg:%@\n",arg1,[block_arg class],block_arg);
//保持原有掉用!! arg2(block_arg);}; %orig(arg1,myCallBack); } %end 复制代码 编译安装 , 重新打开钉钉 , 查看控制台打印输出 . 可以看出 , block 的参数类型为 NSDictionary , 同时 , 我们还看到了最希望看到的 GPS 位置信息 . ⚠️ 分析完毕 : 因此 , 我们完全可以推测出 , 在这个方法中调用了 AMapLocationManager 的 startUpdatingLocation , 而在获得位置后回调一个字典到这个 block 中 . 因此 , 在这个方法中 , 将经纬度直接写死为打卡要求位置的经纬度 . 完全可以满足我们的需求 . 当然 , 打印中我们看到 参数1 和 参数2 还有很多种不同的情况和状态 , 通过查看发现 , 当 参数1 中的 action 为 start , 并且 参数 2 中的 keep 为 1 时 , 代表需要重新获取经纬度 . 因此 , 修改代码 .
- 修改原代码逻辑 %hook LAPluginInstanceCollector
-
(void)handleJavaScriptRequest:(NSDictionary *)arg1 callback:(void(^)(id))arg2{ if([arg1[@"action"] isEqualToString:@"start"]){//有可能需要修改定位信息! //定义一个myBlock id myCallBack = ^(NSDictionary * block_arg){ if([block_arg[@"keep"] isEqualToString:@"1"]){//需要修改GPS
NSMutableDictionary * tempDic = [NSMutableDictionary dictionaryWithDictionary:block_arg]; //修改block中的字典的值! tempDic[@"result"][@"latitude"] = @"28.1924070001"; tempDic[@"result"][@"longitude"] = @"112.9788130003"; //使用修改后的! arg2(tempDic); }else{ //保持原有掉用!! arg2(block_arg); } }; %orig(arg1,myCallBack);}else{ %orig; } } %end 复制代码
- 效果查看
• 未安装插件 : 打卡显示异地 .
• 编译安装插件 .
三、WiFi 与 GPS 打卡插件完整版 WiFi 打卡探索思路与 GPS 位置打卡一模一样 . 探索后发现同样是我们最终 hook 的 js 与 OC 交互的那个方法中去获取当前连接 WiFi 信息与管理员设置的 WiFi 的 macIp 是否一致来判断的 . hook 代码完整版 : %hook LAPluginInstanceCollector
-
(void)handleJavaScriptRequest:(NSDictionary *)arg1 callback:(void(^)(id))arg2{ if([arg1[@"action"] isEqualToString:@"start"]){//有可能需要修改定位信息! //定义一个myBlock id myCallBack = ^(NSDictionary * block_arg){ if([block_arg[@"keep"] isEqualToString:@"1"]){//需要修改GPS
NSMutableDictionary * tempDic = [NSMutableDictionary dictionaryWithDictionary:block_arg]; //修改block中的字典的值! 这里修改为你公司设置的允许打卡 GPS 位置 tempDic[@"result"][@"latitude"] = @"31.8567980000"; tempDic[@"result"][@"longitude"] = @"118.8274580000"; //使用修改后的! arg2(tempDic); }else{ //保持原有调用!! arg2(block_arg); } }; %orig(arg1,myCallBack);}else if([arg1[@"action"] isEqualToString:@"getInterface"]){//修改WIFI!! //定义一个myBlock id myCallBack = ^(NSDictionary * block_arg){
NSMutableDictionary * tempDic = [NSMutableDictionary dictionaryWithDictionary:block_arg]; //修改block中的字典的值! , 这里修改为你公司设置的 WiFi macIP tempDic[@"result"][@"macIp"] = @"f0:b4:29:6b:fe:51"; //使用修改后的! arg2(tempDic); }; %orig(arg1,myCallBack);}else{ %orig; } } %end 复制代码 WiFi 效果就不演示了 , 与 GPS 规则表现相同 . 至此 , 你就可以拿一台越狱设备舒舒服服在家打卡了 😆. 总结 • 1️⃣ : 本文中详细展示了如何从无到有做出一个越狱插件 , 其中涉及到的各种调试工具和知识点大多都有介绍 . 希望感兴趣的同学多加练习 . • 2️⃣ : 逆向过程中 , 思路清晰非常重要 , 同时要熟练掌握 cycript , 砸壳 , 越狱环境 lldb 调试 , Theos , Chisel , 等等静态分析和动态调试工具 , 底层原理同样是必不可少的 . • 3️⃣ : 理清思路后 , 尽量使用静态分析 ( 汇编也属于静态分析 , 这个根据个人情况定 ) , 当大体思路理顺之后即可进行动态调试 , 上 hook 代码和断点调试 , cycript 等工具帮助理顺原码逻辑 . • 4️⃣ : 最后我们发现 , 其实真正 hook 注入修改代码非常简单 , 逆向之路本就是如此 , 梳理和推导往往会花费大量时间 . 最后再次声明: 本文演示插件无任何商业目的,也不从事任何商业性质活动。 纯原创文章 , 转载请标明出处 。