├── README.md ├── simpread-DFA 还原白盒 AES 密钥.md ├── simpread-[原创] 搜狗搜索 app so 加解密分析.md ├── simpread-[原创] 某 app 加固逆向分析 - Android 安全 - 看雪 - 安全社区 _ 安全招聘 _ kanxue.com.md ├── simpread-[原创] 记一次 unicorn 半自动化逆向——还原某东 sign 算法.md ├── simpread-unidbg 算法还原术 · 某民宿 app 篇 · 上卷.md ├── simpread-unidbg 算法还原术 · 某民宿 app 篇 · 下卷.md ├── simpread-unidbg 算法还原术 · 某民宿 app 篇 · 中卷.md ├── simpread-x-zse-96 安卓端纯算, 魔改 AES.md ├── simpread-x-zse-96,android 端, 伪 dex 加固, so 加固, 白盒 AES, 字符串加密.md ├── simpread-xx 度灰 app 加密算法分析还原 - 『移动安全区』 - 吾爱破解 - LCG - LSG _ 安卓破解 _ 病毒分析 _ www.52pojie.cn.md ├── simpread-一文弄懂 arm 中立即数.md ├── simpread-人均瑞数系列,瑞数 4 代 JS 逆向分析.md ├── simpread-人均瑞数系列,瑞数 5 代 JS 逆向分析.md ├── simpread-从 trace 到二进制插桩到 Frida.md ├── simpread-使用 Xposed 进行微信小程序 API 的 hook _ AshenOne.md ├── simpread-分享一个 Android 通用 svc 跟踪以及 hook 方案——Frida-Seccomp.md ├── simpread-分析 okhttp3-retrofit2 - 截流某音通信数据.md ├── simpread-在静态分析、计算 arm64 汇编时提速.md ├── simpread-在非越狱 iOS 上实现全流量抓包.md ├── simpread-多种姿势花样使用 Frida 注入 _ AshenOne.md ├── simpread-大猿搜题 sign so 加密参数分析|unidbg.md ├── simpread-好库推荐 _ 两个解决 ja3 检测的 Python 库,强烈推荐.md ├── simpread-字节对齐.md ├── simpread-安卓 Svc 穿透获取设备信息 - 简书.md ├── simpread-安卓上基于透明代理对特定 APP 抓包 - SeeFlowerX.md ├── simpread-对 APP 逆向抓包的实践.md ├── simpread-对 Flutter 开发的某 App 逆向分析.md ├── simpread-对旅行 APP 的检测以及参数计算分析【Simplesign 篇】.md ├── simpread-当 Xiaomi 12 遇到 eBPF.md ├── simpread-快手花指令实战分析.md ├── simpread-控制流平坦化反混淆(春节红包活动 Android 高级题) - 『移动安全区』 - 吾爱破解 - LCG - LSG _ 安卓破解 _ 病毒分析 _ www.52pojie.cn.md ├── simpread-新版 a+1 站 ollvm 算法分析.md ├── simpread-某 A 系电商 App doCommandNative 浅析.md ├── simpread-某 A 系电商 App x-sign 签名分析.md ├── simpread-某乎请求头签名算法分析.md ├── simpread-某买菜 app 加密浅析及 excel 初体验.md ├── simpread-某咖啡 app 加密参数分析进阶版.md ├── simpread-某小程序平台桌面版开启 js 调试.md ├── simpread-某手抓包问题分析.md ├── simpread-某招聘网站 202312 月新版 token 生成分析.md ├── simpread-某某 App protobuf 协议逆向分析.md ├── simpread-某某网站 JS 逆向及 tls 指纹绕过分析.md ├── simpread-某查线路 app 设备检测逆向分析.md ├── simpread-某电子书阅读器加密协议分析.md ├── simpread-某车联网 App 通讯协议加密分析 (三) Trace Block.md ├── simpread-某音乐 app ollvm 算法分析.md ├── simpread-某风控 SDK 逆向分析 _ AshenOne.md ├── simpread-浅析 APP 代理检测对抗 - 先知社区.md ├── simpread-浅谈设备指纹技术和应用.md ├── simpread-深度剖析 ja3 指纹及突破.md ├── simpread-爬虫之 - 某生鲜 APP 加密参数逆向分析.md ├── simpread-猿人学 - app 逆向比赛第四题 grpc 题解.md ├── simpread-猿人学 2022 逆向比赛第七题 quic.md ├── simpread-猿人学第五题 - 双向认证分享.md ├── simpread-用 Scrapy 爬取 5 秒盾站点,结果万万没想到,速度可以这么快!.md ├── simpread-监控、定位 JavaScript 操作 cookie - 『脱壳破解区』 - 吾爱破解 - LCG - LSG _ 安卓破解 _ 病毒分析 _ www.52pojie.cn.md ├── simpread-移动安全之 IOS 逆向越狱环境准备 (上).md ├── simpread-突破 tls_ja3 新轮子.md ├── simpread-聊聊大厂设备指纹获取和对抗 & 设备指纹看着一篇就够了!.md ├── simpread-记一次 tiktok 抓包过程.md ├── simpread-记一次汽车 app 白盒 aes 还原过程.md ├── simpread-过某加固 Frida 检测.md ├── simpread-还原某里 226 控制流混淆的思路 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG _ 安卓破解 _ 病毒分析 _ www.52pojie.cn.md ├── simpread-逆向分析反调试 + ollvm 混淆的 Crackme.md ├── simpread-隐藏 Root - Zygisk 版面具 Magisk 过银行 App 等 Root 检测,Shamiko 模块的妙用 - 哔哩哔哩.md ├── 某交友 App 花指令分析.md ├── 某电商App Sign分析(1).md └── 某电商App Sign分析(2)——完整分析.md /README.md: -------------------------------------------------------------------------------- 1 | # reverse-documents 2 | 收集/转帖/整理一些系逆向文件/文章 3 | -------------------------------------------------------------------------------- /simpread-[原创] 某 app 加固逆向分析 - Android 安全 - 看雪 - 安全社区 _ 安全招聘 _ kanxue.com.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [bbs.kanxue.com](https://bbs.kanxue.com/thread-280302.htm) 2 | 3 | > [原创] 某 app 加固逆向分析 4 | 5 | [原创] 某 app 加固逆向分析 6 | 7 | 2024-1-23 14:30 9993 8 | 9 | ### [原创] 某 app 加固逆向分析 10 | 11 | ![](https://passport.kanxue.com/pc/view/img/moon.gif)![](https://passport.kanxue.com/pc/view/img/moon.gif) 12 | 13 | * * * 14 | 15 | 小伙伴公司需要代码静态分析安全性,上了壳,就逆向分析了下。 16 | 17 | Apk 打开没有太多的代码,在自定义的 application 中加载了一个名为 **Helper 的 so。 18 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_C889DC64K9H82WJ.webp) 19 | 在 doAttach 函数中能够找到反射调用原 application 的初始化。 20 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_RC4YPF5745S6T66.webp) 21 | 22 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_B9SPB3CTZSEXYWC.webp) 23 | 打开 so 查看 dynamic 表,存在. init.proc,initarray 只有一个函数。 24 | 再查看 jni_onload 函数,发现密文,猜测是 init_array 或者 init_proc 中解密 so 中代码段,修复代码段必定涉及 mprotect,要么调用 libc 要么 svc 自己实现,能够快速找到可疑位置。 25 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_85P73NUYS5JW3WG.webp) 26 | Init.proc,调用 sub_10150C,再调用三次 sub_1011A0 函数修改内存权限,通过 svc 调用 mprotect 修改该 so 在内存中的权限,修复该 so 原本的代码。 27 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_AF5C2DCWAXXJXYB.webp) 28 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_PKEMESVWJRNAKB7.webp) 29 | Dump 代码段,修复 elf,再次查看 jni_onload,ida 正常解析。 30 | 31 | 大概看了下,有些功能没有开启的。 32 | **0x3D2B0** Fork 后 Ptrace,开启各种检测线程。 33 | **0x32D08** anti_thread_body 函数检查 tracerPid 和 status 34 | **0x4EEF8** waitpid 子进程是否终止。 35 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_BH4PQ3CXWZZD5XD.webp) 36 | **0x2F488 **do_hook_log 37 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_N5QC43WQA2VA8D6.webp) 38 | 0x2E624 run_addition 函数,获取 ro.debuggable 和 signatures 等等 39 | sub_50B44 文件 hash 检测。 40 | 41 | **0x3D494** 查找 dex 的指针,找到标志位。通过 java 的 DexCache 类 dexFile 找到 dex 指针,这我也第一次知道这个结构,可以这么定位 dexFile。 42 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_UXKC7UKXSQMMD74.webp) 43 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_UB9MJGSNQ5MBAXE.webp) 44 | sub_4F8BC 计算 dex 大小,申请一大块内存,存储解密后的完整的所有的 dex。 45 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_FZW6FGG6VRFJNHN.webp) 46 | 0x3E10C 申请内存,解密 dex 的密文部分,并拷贝回上一步那块内存,加密算法,晕了。 47 | ![](https://bbs.kanxue.com/upload/attach/202401/753755_A49YTUVR9FM6UCZ.webp) 48 | dump 后,得到 11 个 dex, 收工! 49 | 50 | 附件是修复后的 so,很多字符串明文,很容易理解它的逻辑,仅供学习。 51 | 52 | [[CTF 入门培训] 顶尖高校博士及硕士团队亲授《30 小时教你玩转 CTF》,视频 + 靶场 + 题目!助力进入 CTF 世界](http://www.kanxue.com/book-brief-170.htm#h3a6WRhDT9Q_3D) 53 | 54 | 返回 -------------------------------------------------------------------------------- /simpread-一文弄懂 arm 中立即数.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/KgozptGhKU39Fk7URiIsUw) 2 | 3 | ![](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3B6WPiavosZFcyzR8y0A5ibaMM1XX5UqvEuGcOXrv8GnLZWUSOX4lNfm9DicHF1cU8lY2q0rm7icqt0A/640?wx_fmt=gif) 4 | 5 | 周末闲暇读了一下 arm_v7 的官方文档, 对 arm 存储立即数有一些感悟, 故记录下来。 6 | 7 | ![](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3B6WPiavosZFcyzR8y0A5ibaMM1XX5UqvEuGcOXrv8GnLZWUSOX4lNfm9DicHF1cU8lY2q0rm7icqt0A/640?wx_fmt=gif) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |  目录 18 | 19 | 20 | 21 | ⊙ 1. 什么是立即数? 22 | 23 | ⊙ 2. 立即数是如何存储在指令中的 24 | 25 | ⊙ 3. 立即数在逆向分析的一些自我心得 26 | 27 | **1. 什么是立即数?** 28 | 29 | 举个栗子 30 | 31 | ``` 32 | mov r0,0x1234 33 | 34 | ``` 35 | 36 | 后面的 0x1234,就是表示的立即数! 37 | 38 | 概念: 通常把在立即寻址方式指令中给出的数称为立即数, 立即数可以是 8 位、16 位或 32 位, 该数值紧跟在操作码之后。 39 | 40 | **2. 立即数是如何存储在指令中的?** 41 | 42 | 首先这个问题得知道指令存储立即数的结构 (从 arm 的官方文档截图): 43 | 44 | 下方截图的其实代表一行指令 (四个字节) 例如: mov r0,#0xff 45 | 46 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib3HGuZqfSNdnYVUGzONQAMbKY6zjlZ9jr050zxvVBsFnmAxsmWmSskibibTRLjdS10iaIVHHy4WmAHQw/640?wx_fmt=png) 47 | 48 | 49 | 50 | 在 arm 指令中若要赋值一个立即数时, 要占用执行指令的 0-11 内存单元, 其中 a-h, 直接存储数值, 而 8-11 存储的为循环右移 2 的倍数的值也就是: ROR((a-h 存储的值),2*rotation) 51 | 52 | arm 官方在设计 arm 对于立即寻址这样存储有什么意义呢? 53 | 54 | 可以尽可能使用少内存单元的情况下扩大数的表示范围。 55 | 56 | 分析下在立即数寻址中可以放置什么样的立即数? 57 | 58 | 我们知道 8 位存储的值的范围为 0-0xff, 也就是说在 59 | 60 | ``` 61 | mov r0,#0xff 62 | 63 | ``` 64 | 65 | 的时候 rotation=0; 66 | 67 | 当存储的立即数超过了这个范围那么应该如何存储呢? 68 | 69 | ``` 70 | mov r0,#0xff0 71 | 72 | ``` 73 | 74 | 首先 a-e 还是存储的为 1111 1111  75 | 76 | 而 rorarion 为循环右移的位数 / 2 77 | 78 | 79 | 如果要表示一个数: 80 | 81 | 移位前 82 | 83 | 0000 0000 0000 0000 0000 0000 1111 1111 84 | 85 | 移位后 86 | 87 | 0000 0000 0000 0000 0000 1111 1111 0000 88 | 89 | 相当于循环右移了 28 位, 此时 rotation=28 /2 =14=1110 90 | 91 | a-h:1111 1111 92 | 93 | 任何立即数赋值都合法吗? 94 | 95 | ``` 96 | mov r0,#0xff1 97 | 98 | ``` 99 | 100 | 它的二进制位表示为 1111 1111 0001 101 | 102 | 但是根据指令存储立即数的规则来说, 存储八位后通过循环右移并不可能出现 0xff1 这样的数据存在, 事实证明并不是所有的位数低的立即数存储在 arm 的寄存器中都可以的, 什么是合法的呢, 就是根据存储指令的结构的情况下可以通过移位来表示的才叫合法。 103 | 104 | 分析过程参照以下表格 (PS: 接触汇编的时候其实官方文档解释的更清晰并且没有歧义): 105 | 106 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib3HGuZqfSNdnYVUGzONQAMb9XHdTQ7amKegX1EWiaZIfSu4NUMczhKw8n34qguhZqE85Kqpz8ibI0sQ/640?wx_fmt=png) 107 | 108 | 那么这种情况, 如果我们编写代码的话怎么去写呢? 109 | 110 | ``` 111 | mov r0,0xff0 112 | add r0,r0,#1可以采用这种方式让其加1 113 | 114 | ``` 115 | 116 | 是不是超级 nice!! 117 | 118 | 3. 立即数在逆向分析的一些自我心得 119 | 120 | 上文我们讨论了 mov 指令 121 | 122 | ``` 123 | mov r0,0xffffff00 124 | 125 | ``` 126 | 127 | 很明显根据我们上文的分析,通过对 a-h 的 2 的整数倍移位是不可以达到这样的, 但是我们在逆向分析中会看到 ida 反编译成这样, 难道 ida 也错了, 并不是的我参考了 idapro 权威指南中说道, ida 反编译的汇编代码其实也是伪代码, 并不是说显示的汇编代码都是准确的, 只不过让逆向分析人员更加方便看。 128 | 129 | 那么如果我们要给 r0 寄存器赋值 0xffffff00 要怎么做呢? 130 | 131 | ``` 132 | mvn r0,0xff 133 | 134 | ``` 135 | 136 | 先解释下这个语句的意义: 把操作数 2 按位取反后送入 r0 寄存器中, 也就达到了 mov r0,0xffffff00 的目的! 137 | 138 | 上面我们说的都是一些可以利用移位或者取反能够解决的, 如果我们碰到没有规律的怎么处理呢? 139 | 140 | 然后我在 ida 中做了个实现, 看看 ida 是如何搞定的? 141 | 142 | 结论如下; 143 | 144 | ``` 145 | ldr r0, LABEL1 146 | LABEL1: 147 | .word 0xf6688156 148 | 149 | ``` 150 | 151 | 就是说用标号跳转找到立即数也可以! 152 | 153 | 总结: 多看 arm 官网的文档, 有时候 csdn 找到一些知识本来就是错的, 并且大批人搬运转载错误的知识, 随后我会持续更新 arm 官方文档的读书笔记, 感谢阅读! 154 | 155 | 我是 BestToYou, 分享工作或日常学习中关于二进制逆向和分析的一些思路和一些自己闲暇时刻调试的一些程序, 文中若有错误的地方, 恳请大家联系我批评指正。 156 | 157 | ![](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3B6WPiavosZFcyzR8y0A5ibalicqxrfTvYLw2zBMWqnyUFTG4vtoJpcjmckN4BNIusIIr8bU57ucmEA/640?wx_fmt=gif) -------------------------------------------------------------------------------- /simpread-使用 Xposed 进行微信小程序 API 的 hook _ AshenOne.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [ashenone66.cn](https://ashenone66.cn/2022/03/24/shi-yong-xposed-jin-xing-wei-xin-xiao-cheng-xu-api-de-hook/) 2 | 3 | > 大 A 的个人博客,用于记录学习过程 4 | 5 | [](#前言 "前言")前言 6 | -------------- 7 | 8 |   上一篇文章讲了安卓的虚拟定位相关的内容,最后编写了一个 frida 脚本来对 Framework 层的 API 进行 hook 实现虚拟定位。但是有几点局限性: 9 | 10 | > 1. 强制 disable WIFI 和基站定位使用 GPS 定位在某些情况下无法 work 11 | > 2. 使用 frida 进行 hook 意味着必须搭配 PC 使用,难以完成持久化的 hook 12 | 13 | frida 虽然确实调试起来相当方便,但是 Xposed 由于能够安装在用户手机上实现持久化的 hook,至今受到很多人的青睐,特别是类似虚拟定位的功能,还是使用 Xposed 作为最终实现比较方便。另外,对于微信小程序的`wx.getLocation` API,使用上篇文章中的虚拟定位方法是无法成功的,原因是这个 API 在关闭基站和 WIFI 定位后就不能正常工作。因此,本文将以该 API 作为用例,介绍如何使用 Xposed 来对微信小程序的 js API 进行 hook。 14 | 15 | [](#背景知识 "背景知识")背景知识 16 | -------------------- 17 | 18 |   众所周知,Xposed 主要用于安卓 Java 层的 Hook,而微信小程序则是由 JS 编写的,显然无法直接进行 hook。安卓的有一个 WebView 的组件能够用于网页的解析和 js 的执行,并且提供了 JSBridge 可以支持 js 代码调用安卓的 java 代码,微信小程序正是以此为基础开发了它的微信小程序框架,微信小程序特有的 API 则是由这个框架中的 WxJsApiBridge 提供的,因此以`wx.`开头的 API 都能在这个框架中找到对应的 Java 代码,所以我们虽然不能直接 hook js 代码,但是我们可以通过 hook 这些 js api 对应的 Java 代码来实现微信小程序 api 的 hook。 19 | 20 | [](#Frida调试 "Frida调试")Frida 调试 21 | ------------------------------ 22 | 23 |   在编写 Xposed 插件前,首先先使用 Frida 进行逆向分析以及 hook 调试,确保功能能够实现后在用 Xposed 编写插件,毕竟 Xposed 插件调试起来还是不如 Frida 方便。 24 | 25 |   首先我们要知道,js 代码中的`getLocation`字符串一定会在 java 层中出现,因此在 jeb 反编译完微信以后,直接搜索该字符串即可。当然,得到的结果肯定不止一个,一个一个调试排除后,确定真正主要的相关类是`com.tencent.mm.plugin.appbrand.jsapi.m.n`(8.0.19 版本,由于混淆不同版本可能名称不一样)。其实在`com.tencent.mm.plugin.appbrand.jsapi`路径下,我们可以看到很多以`JsApixxxxx`的命名格式的命名的类,这些都是微信小程序 API 对应的 Java 类,他们都有一个`NANE`字段,这个值就是`wx.xxx`中的`xxx`,比如`wx.getLocation`对应的 java 类的`NAME`值就是`getLocation`,smali 代码为`.field public static final NAME:String = "getLocation"`。所以以后我们要找哪个 API 直接在 jeb 的 bytecode 里搜索`NAME:String = "xxxx"`就行了。 26 | 27 |   定位到具体的类以后,我们可以用 Objection 来 hook 整个类来观察这个类中函数的调用情况,以此发现主要的函数。不过在 hook 之前,需要注意的是,微信小程序一般会以新进程的方式启动,其进程名为`com.tencent.mm:appbrand0`(不确定这个编号 0 是否固定)。因此,如果直接用`frida -U com.tencent.mm -l xxx`或者`objection -g com.tencent.mm explore`来 hook 的话,是无法看到函数调用的,因为你 hook 的进程不是微信小程序的进程而是微信本体的进程。所以我们要指定 pid 来进行 hook,可以使用`dumpsys activity top | grep ACTIVITY`来得到;也可以使用`frida -UF -l xxx`来 hook 当前最顶层的 Activity。对于 Xposed 则没有这个问题,只需指定微信的包名就会自动 hook 上所有的子进程。 28 | 29 | 结合动态测试的函数调用结果,随便浏览一下被调用的函数的代码,看到了一个主要函数代码如下: 30 | 31 | java 32 | 33 | ``` 34 | @Override 35 | public final void d(f arg10, JSONObject arg11, int arg12) { 36 | AppMethodBeat.i(0x23110); 37 | String v3 = Util.nullAsNil(arg11.optString("type", "wgs84")).trim(); 38 | if(Util.isNullOrNil(v3)) { 39 | v3 = "wgs84"; 40 | } 41 | 42 | boolean v4 = arg11.optBoolean("altitude", false); 43 | Log.i("MicroMsg.JsApiGetLocation", "getLocation data:%s", new Object[]{arg11}); 44 | if(!"wgs84".equals(v3) && !"gcj02".equals(v3)) { 45 | Log.e("MicroMsg.JsApiGetLocation", "doGeoLocation fail, unsupported type = %s", new Object[]{v3}); 46 | HashMap v0 = new HashMap(1); 47 | v0.put("errCode", Integer.valueOf(-1)); 48 | arg10.callback(arg12, this.m("fail:invalid data", v0)); 49 | AppMethodBeat.o(0x23110); 50 | return; 51 | } 52 | 53 | if(!n.u(arg10)) { 54 | HashMap v0_1 = new HashMap(1); 55 | v0_1.put("errCode", Integer.valueOf(-2)); 56 | arg10.callback(arg12, this.m("fail:system permission denied", v0_1)); 57 | AppMethodBeat.o(0x23110); 58 | return; 59 | } 60 | 61 | this.w(arg10); 62 | Bundle v7 = this.f(arg10, arg11); 63 | com.tencent.mm.plugin.appbrand.utils.b.a v6 = (com.tencent.mm.plugin.appbrand.utils.b.a)arg10.T(com.tencent.mm.plugin.appbrand.utils.b.a.class); 64 | if(v6 != null) { 65 | v6.a(v3, this.a(arg10, new b() { 66 | @Override 67 | public final void a(int arg8, String arg9, com.tencent.mm.plugin.appbrand.utils.b.a.a arg10) { 68 | AppMethodBeat.i(0x2310F); 69 | Log.i("MicroMsg.JsApiGetLocation", "errCode:%d, errStr:%s, location:%s", new Object[]{((int)arg8), arg9, arg10}); 70 | n.this.x(arg10); 71 | if(arg8 == 0) { 72 | HashMap v0 = new HashMap(4); 73 | v0.put("type", v3); 74 | v0.put("latitude", Double.valueOf(arg10.latitude)); 75 | v0.put("longitude", Double.valueOf(arg10.longitude)); 76 | v0.put("speed", Double.valueOf(arg10.huN)); 77 | v0.put("accuracy", Double.valueOf(arg10.usi)); 78 | if(v4) { 79 | v0.put("altitude", Double.valueOf(arg10.altitude)); 80 | } 81 | 82 | v0.put("provider", arg10.provider); 83 | v0.put("verticalAccuracy", Integer.valueOf(0)); 84 | v0.put("horizontalAccuracy", Double.valueOf(arg10.usi)); 85 | if(!Util.isNullOrNil(arg10.buildingId)) { 86 | v0.put("buildingId", arg10.buildingId); 87 | v0.put("floorName", arg10.floorName); 88 | } 89 | 90 | v0.put("indoorLocationType", Integer.valueOf(arg10.usj)); 91 | v0.put("direction", Float.valueOf(arg10.usk)); 92 | v0.put("steps", Double.valueOf(arg10.usl)); 93 | arg10.callback(arg12, n.this.m("ok", v0)); 94 | AppMethodBeat.o(0x2310F); 95 | return; 96 | } 97 | 98 | HashMap v0_1 = new HashMap(1); 99 | v0_1.put("errCode", Integer.valueOf(arg8)); 100 | arg10.callback(arg12, n.this.m("fail:".concat(String.valueOf(arg9)), v0_1)); 101 | AppMethodBeat.o(0x2310F); 102 | } 103 | }), v7); 104 | } 105 | 106 | AppMethodBeat.o(0x23110); 107 | } 108 | 109 | ``` 110 | 111 |   可以看到经度纬度的值是从`com.tencent.mm.plugin.appbrand.utils.b.a$a arg10`这个对象中返回来的,因此我们可以直接 hook 相关的函数。但是这样有一个问题就是,由于目标类的类名被混淆了,所以如果 app 版本更新,hook 的代码可能失效,我们要想办法 hook 一个更加底层的不被混淆的类,具体的分析结果就省略了,最终我定位到了一个比较好的类`com.tencent.map.geolocation.sapp.TencentLocationManager`,其中的`requestSingleFreshLocation`函数在每次调用`wx.getLocation`函数时会被调用 112 | 113 | java 114 | 115 | ``` 116 | public final int requestSingleFreshLocation(TencentLocationRequest arg8, TencentLocationListener arg9, Looper arg10, boolean arg11) { 117 | AppMethodBeat.i(0x337F6); 118 | if(arg9 != null) { 119 | if(arg10 != null) { 120 | int v0 = this.mInitStatus; 121 | if(v0 > 0) { 122 | AppMethodBeat.o(0x337F6); 123 | return v0; 124 | } 125 | ...... 126 | 127 | ``` 128 | 129 | 传参时传了个`com.tencent.map.geolocation.sapp.TencentLocationListener`对象 130 | 131 | java 132 | 133 | ``` 134 | package com.tencent.map.geolocation.sapp; 135 | 136 | public interface TencentLocationListener { 137 | public static final String CELL = "cell"; 138 | public static final String GPS = "gps"; 139 | @Deprecated 140 | public static final String RADIO = "radio"; 141 | public static final int STATUS_DENIED = 2; 142 | public static final int STATUS_DISABLED = 0; 143 | public static final int STATUS_ENABLED = 1; 144 | public static final int STATUS_GPS_AVAILABLE = 3; 145 | public static final int STATUS_GPS_UNAVAILABLE = 4; 146 | public static final int STATUS_LOCATION_SWITCH_OFF = 5; 147 | public static final int STATUS_UNKNOWN = -1; 148 | public static final String WIFI = "wifi"; 149 | 150 | void onLocationChanged(TencentLocation arg1, int arg2, String arg3); 151 | 152 | void onStatusUpdate(String arg1, int arg2, String arg3); 153 | } 154 | 155 | ``` 156 | 157 | `TencentLocationListener`的回调函数`onLocationChanged`的第一个参数为`com.tencent.map.geolocation.sapp.TencentLocation`这个接口中就有`getLatitude()`和`getLongitude()`,当然我们不能直接 hook 接口,这是没有意义的,我们要 hook 这个接口的具体的实现类中的对应函数才行。思路就是先 hook `requestSingleFreshLocation`,在调用之前通过`getClass()`获取其第二参数的对象类型,然后 hook 这个类的`onLocationChanged`函数,同样在其调用之前得到其第一参数的对象类型,最后 hook 这个类的`getLatitude()`和`getLongitude()`即可。这里就不给 Frida 版本的代码了,直接上 Xposed 的代码 158 | 159 | [](#Xposed模块编写 "Xposed模块编写")Xposed 模块编写 160 | --------------------------------------- 161 | 162 |   Xposed 模块的架子怎么搭就不介绍了,网上很多教程,这里主要讲下模块编写时踩的坑。直接使用`lpparam.classloader`来 hook 的话,发现对于安卓自带的函数能够成功 hook,但是对于微信自己特有的函数却没法 hook 成功,表现为没有报错找不到类或者方法,但是就是没有函数调用。这个问题我尝试过很多方法来解决,更换 xposed 版本、使用 lsposed 和 edxposed、换个函数 hook、排除子进程 hook 的问题等,都失败了,最后参考网上其他的微信 hook 模块的代码,先 hook `Application`的`attach`函数来获取`Context`,在从`Context`中获取`Classloader`,然后使用这个`Classloader`来完成 hook 终于成功了。上代码: 163 | 164 | Application hook: 165 | 166 | java 167 | 168 | ``` 169 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { 170 | if(!lpparam.packageName.equals("com.tencent.mm"))return; 171 | 172 | try{ 173 | XposedHelpers.findAndHookMethod(Application.class, 174 | "attach", 175 | Context.class, 176 | new XC_MethodHook() { 177 | @Override 178 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 179 | super.afterHookedMethod(param); 180 | Context context = (Context)param.args[0]; 181 | ClassLoader classLoader = context.getClassLoader(); 182 | HookLocation(classLoader); 183 | } 184 | }); 185 | }catch (Throwable e){ 186 | XposedBridge.log(e); 187 | } 188 | 189 | } 190 | 191 | ``` 192 | 193 | Location hook: 194 | 195 | java 196 | 197 | ``` 198 | private static void HookLocation(ClassLoader classLoader) throws ClassNotFoundException { 199 | double latitude = 0.0 200 | double longitude = 0.0 201 | 202 | Class clazz = classLoader.loadClass( 203 | "com.tencent.map.geolocation.sapp.TencentLocationManager"); 204 | XposedHelpers.findAndHookMethod(clazz, "requestSingleFreshLocation", 205 | "com.tencent.map.geolocation.sapp.TencentLocationRequest", 206 | "com.tencent.map.geolocation.sapp.TencentLocationListener", 207 | "android.os.Looper",boolean.class, new XC_MethodHook() { 208 | @Override 209 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 210 | XposedBridge.log("requestSingleFreshLocation"); 211 | Class tencentLocationListenerClass = param.args[1].getClass(); 212 | XposedHelpers.findAndHookMethod(tencentLocationListenerClass, 213 | "onLocationChanged", 214 | "com.tencent.map.geolocation.sapp.TencentLocation", 215 | int.class, String.class, new XC_MethodHook() { 216 | @Override 217 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 218 | Class tencentLocation = param.args[0].getClass(); 219 | XposedHelpers.findAndHookMethod(tencentLocation, 220 | "getLatitude", 221 | new XC_MethodHook() { 222 | @Override 223 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 224 | param.setResult(latitude); 225 | } 226 | }); 227 | XposedHelpers.findAndHookMethod(tencentLocation, 228 | "getLongitude", 229 | new XC_MethodHook() { 230 | @Override 231 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 232 | param.setResult(longitude); 233 | } 234 | }); 235 | 236 | 237 | } 238 | }); 239 | } 240 | } 241 | ); 242 | } 243 | 244 | ``` -------------------------------------------------------------------------------- /simpread-分享一个 Android 通用 svc 跟踪以及 hook 方案——Frida-Seccomp.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [bbs.pediy.com](https://bbs.pediy.com/thread-271815.htm) 2 | 3 | > [原创] 分享一个 Android 通用 svc 跟踪以及 hook 方案——Frida-Seccomp 4 | 5 | [](#一个android通用svc跟踪以及hook方案——frida-seccomp)一个 Android 通用 svc 跟踪以及 hook 方案——Frida-Seccomp 6 | ========================================================================================= 7 | 8 | [](#效果:)效果: 9 | =========== 10 | 11 | 对 openat 进行跟踪 12 | ------------- 13 | 14 | ![](https://bbs.pediy.com/upload/attach/202203/903162_55EERQXHHTXD4FW.jpg) 15 | 16 | 对 recvfrom 进行跟踪 17 | --------------- 18 | 19 | ![](https://bbs.pediy.com/upload/attach/202203/903162_AE84XGKCDB8RBY6.jpg) 20 | **在这里感谢珍惜大佬介绍的 seccomp 机制,推荐一波珍惜大佬的课程能学到很多有趣的骚操作。** 21 | 22 | 什么是 seccomp 23 | =========== 24 | 25 | [seccomp 沙箱机制介绍文章](http://pollux.cc/2019/09/22/seccomp%E6%B2%99%E7%AE%B1%E6%9C%BA%E5%88%B6%20&%202019ByteCTF%20VIP/#%E9%80%9A%E8%BF%87%E4%BD%BF%E7%94%A8%E8%AF%A5%E5%BA%93%E7%9A%84%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E7%A6%81%E7%94%A8execve%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8) 26 | seccomp 是 Linux 内核提供的一种应用程序沙箱机制,主要通过限制进程的系统调用来完成部分沙箱隔离功能。seccomp-bpf 是 seccomp 的一个扩展,它可以通过配置来允许应用程序调用其他的系统调用。 27 | 28 | 如何和 frida 结合 29 | ============ 30 | 31 | 基本原理 32 | ---- 33 | 34 | ``` 35 | 这是一个bpf规则: 36 | struct sock_filter filter[] = { 37 |         BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 38 |                  (offsetof(struct seccomp_data, nr))), 39 |         BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1), 40 |         BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRAP), 41 |         BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), 42 | }; 43 | 44 | ``` 45 | 46 | seccomp 的具体用法可以参考「**什么是 seccomp**」中的 seccomp 介绍文章。当返回规则设置为「SECCOMP_RET_TRAP」,目标系统调用时 seccomp 会产生一个 SIGSYS 系统信号并软中断,这时就可以通过捕获这个 SIGSYS 信号获得 svc 调用和打印具体参数。 47 | 48 | 如何脚本化安装 seccomp 规则呢 49 | ------------------- 50 | 51 | 这里使用 Frida 的 API「CModule」,CModule 提供强大的动态编译功能可以让你在 JS 中写 C, 52 | **frida 文档中的示例** 53 | 54 | ``` 55 | const cm = new CModule(` 56 | #include void hello(void) { 57 |   printf("Hello World from CModule\\n"); 58 | } 59 | `); 60 | const hello = new NativeFunction(cm.hello, 'void', []); 61 | hello(); 62 | ``` 63 | 64 | 如何捕获异常 65 | ------ 66 | 67 | 使用 Frida 的 API「**Process.setExceptionHandler**」即可捕获异常并在自己写的回调中进行数据处理。 68 | 数据处理的逻辑解释写在注释里啦。 69 | 70 | ``` 71 | // 异常处理 72 |     Process.setExceptionHandler(function (details) { 73 |         const current_off = details.context.pc - 4; 74 |         // 判断是否是seccomp导致的异常 读取opcode 010000d4 == svc 0 75 |         if (details.message == "system error" && details.type == "system" && hex(ptr(current_off).readByteArray(4)) == "010000d4") { 76 |             // 上锁避免多线程问题 77 |             lock(syscall_thread_ptr) 78 |             // 获取x8寄存器中的调用号 79 |             const nr = details.context.x8.toString(10); 80 |             let loginfo = "\n==================" 81 |             loginfo += `\nSVC[${syscalls[nr][1]}|${nr}] ==> PC:${addrToString(current_off)} P${Process.id}-T${Process.getCurrentThreadId()}` 82 |             // 构造线程syscall调用参数 83 |             const args = Memory.alloc(7 * 8) 84 |             args.writePointer(details.context.x8) 85 |             let args_reg_arr = {} 86 |             for (let index = 0; index < 6; index++) { 87 |                 eval(`args.add(8 * (index + 1)).writePointer(details.context.x${index})`) 88 |                 eval(`args_reg_arr["arg${index}"] = details.context.x${index}`) 89 |             } 90 |             // 获取手动堆栈信息 91 |             loginfo += "\n" + stacktrace(ptr(current_off), details.context.fp, details.context.sp).map(addrToString).join('\n') 92 |             // 打印传参 93 |             loginfo += "\nargs = " + JSON.stringify(args_reg_arr) 94 |             // 调用线程syscall 赋值x0寄存器 95 |             details.context.x0 = call_task(syscall_thread_ptr, args, 0) 96 |             loginfo += "\nret = " + details.context.x0.toString() 97 |             // 打印信息 98 |             call_thread_log(loginfo) 99 |             // 解锁 100 |             unlock(syscall_thread_ptr) 101 |             return true; 102 |         } 103 |         return false; 104 |     }) 105 | 106 | ``` 107 | 108 | 还有什么坑 109 | ----- 110 | 111 | ### 1.syscall 调用 resume 112 | 113 | #### 问题描述 114 | 115 | 根据 Frida 文档介绍「**setExceptionHandler**」捕获异常后只需要让回调返回 true 就会 resume 原本的线程,但是其只是跳过了 svc 指令继续执行,实际上并不会执行 svc,这时候如果不执行 syscall 轻则导致 APP 数据异常,重则导致 APP 直接崩溃。所以在异常的回调中需要手动调用了 syscall 并赋值给 x0。 116 | 但这时候会发生个新的问题,因为在主线程开启 seccomp 后,主线程和其后创建出来的线程都会被 seccomp 规则约束,在异常处理函数直接调用 syscall 同样会被 seccomp 约束再次抛出异常,就形成了” 死锁 “了。 117 | 118 | #### 如何解决 119 | 120 | 可以注意到上面 “死锁” 部分描述,那我们在主线程被约束前,提前创建一个线程,这个线程就是不被约束的,同时受到线程池启发,我们让这个 syscall 线程循环接受任务,就能完成在一个没有约束的线程里进行 syscall 调用。 121 | 122 | ### 2. 堆栈回溯 123 | 124 | #### 问题描述 125 | 126 | 直接使用 Frida 的 API「**Thread.backtrace**」很容易导致崩溃,原因可能是 seccomp 规则或者「prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)」导致的权限收紧和 Frida 实现堆栈回溯功能冲突。 127 | 128 | #### 如何解决 129 | 130 | 手动实现堆栈回溯,原理是 Arm64 中每个函数都会在函数头部位置对 x29、x30 寄存器存入栈中,所以可以对 x29 不断读取往上回溯,最后得到完整的堆栈信息。 131 | **实现** 132 | 133 | ``` 134 | function stacktrace(pc, fp, sp) { 135 |     let n = 0, stack_arr = [], fp_c = fp; 136 |     stack_arr[n++] = pc; 137 |     const mem_region = call_thread_read_maps(sp); 138 |     while (n < MAX_STACK_TRACE_DEPTH) { 139 |         if (parseInt(fp_c.toString()) < parseInt(sp.toString()) || fp_c < mem_region.start || fp_c > mem_region.end) { 140 |             break 141 |         } 142 |         let next_fp = fp_c.readPointer() 143 |         let lr = fp_c.add(8).readPointer() 144 |         fp_c = next_fp 145 |         stack_arr[n++] = lr 146 |     } 147 |     return stack_arr; 148 | } 149 | 150 | ``` 151 | 152 | ### 3.「**Process.findModuleByAddress**」「**Process.enumerateModules**」类的 API 导致崩溃或找不到 Module 信息 153 | 154 | #### 问题描述 155 | 156 | 疑似同坑 2 的原因 157 | 158 | #### 如何解决 159 | 160 | 在 CModule 中手动实现了通过地址查 soinfo 信息「base, size, soname」等 161 | 162 | ### 4.write 调用约束下调用 Frida 的 API「send」崩溃 163 | 164 | #### 问题描述 165 | 166 | 同坑 2 的原因 167 | 168 | #### 如何解决 169 | 170 | 直接改用 syscall 线程使用「**__android_print_log**」打印信息 171 | 172 | 还可以实现什么 173 | ======= 174 | 175 | 在调用线程 syscall 前后可以更改传参、返回值、地址等更改,达到 HOOK 的效果 176 | 177 | GITHUB 178 | ====== 179 | 180 | **求 star** 181 | [https://github.com/Abbbbbi/Frida-Seccomp](https://github.com/Abbbbbi/Frida-Seccomp) 182 | 183 | 如何使用 184 | ==== 185 | 186 | ``` 187 | pip3 install frida 188 | python3 multi_frida_seccomp.py 189 | 190 | ``` 191 | 192 | log 信息可以在 logcat 过滤 “seccomp” 查看 193 | 同时也自动保存到了「包名_pid_时间戳」文件夹内(支持多进程) 194 | ![](https://bbs.pediy.com/upload/attach/202203/903162_DSYWMSBGQMMVBJ8.png) 195 | 196 | [【公告】看雪团队招聘安全工程师,将兴趣和工作融合在一起!看雪 20 年安全圈的口碑,助你快速成长!](https://job.kanxue.com/position-read-1104.htm) 197 | 198 | [#基础理论](forum-161-1-117.htm) [#NDK 分析](forum-161-1-119.htm) [#HOOK 注入](forum-161-1-125.htm) [#系统相关](forum-161-1-126.htm) [#工具脚本](forum-161-1-128.htm) -------------------------------------------------------------------------------- /simpread-分析 okhttp3-retrofit2 - 截流某音通信数据.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/LS5QKLSYRNFGJm4-YPPTig) 2 | 3 | 免责声明 4 | 5 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuwT0dUxFRAYKJckhBKVG6eccyrNSOic72rU60MknlI9pEoWgY2WcDNygkNG5NtzxaWSibj18LFoScqQ/640?wx_fmt=png) 6 | 7 | 文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业和非法用途,否则由此产生的一切后果与作者无关。若有侵权,请在公众号【爬虫逆向小林哥】联系作者 8 | 9 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuwT0dUxFRAYKJckhBKVG6ecnicjTbwdy5ze4PutY5ADhJD1xKy7WpcxMqLhyw93ccIQO4QCP1T30Xg/640?wx_fmt=png) 10 | 11 | 01 12 | 13 | — 14 | 15 | 前言 16 | 17 | _**好久没发文了,之前有发过通过 hook 方式降级 douyin-quic 协议进行抓包。今天分享从 okhttp3-retrofit2 通信框架拦截 douyin 通信的请求和响应。**_ 18 | 19 | 02 20 | 21 | 效果 22 | 23 | 评论 24 | 25 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAa05BCtGFbxjJyibjW8JMojvrqpPZXTCialQNHhW37JyQbqUHeiafQqVrBQ/640?wx_fmt=png&from=appmsg)  26 | 27 |         橱窗          28 | 29 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaOGxBC1sHyTp1CD3h0ooMYria1UHNAVS4mo8zFxv34ohPu2KrDGOzSAw/640?wx_fmt=png&from=appmsg) 30 | 31 | 03 32 | 33 | — 34 | 35 | 知识点 36 | 37 | ![图片](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaLpR6icybTpDGiaTS2bU5bjnGkkHT9l6jSQADWCuGvouf63xuupI1GgKg/640?wx_fmt=png&from=appmsg) 38 | 39 | **okhttp** 40 | 41 | > OkHttp 是一个高效的 HTTP 客户端,是目前 Android 使用最广泛的网络框架 42 | 43 | 特点:支持 http1、http2、quic、websocket;无缝支持 gzip 数据压缩,减少数据流量(抖音的有些 post-data 就是 gzip 发包的);连接池复用底层 TCP; 44 | 45 | ``` 46 | private void testOkHttp() throws IOException { 47 | //Step1 48 | final OkHttpClient client = new OkHttpClient(); 49 | //Step2 50 | final Request request = new Request.Builder() 51 | .url("https://www.google.com.hk").build(); 52 | //Step3 53 | Call call = client.newCall(request); 54 | //step4 发送网络请求,获取数据,进行后续处理 55 | call.enqueue(new Callback() { 56 | @Override 57 | public void onFailure(Call call, IOException e) { 58 | } 59 | @Override 60 | public void onResponse(Call call, Response response) throws IOException { 61 | Log.i(TAG,response.toString()); 62 | Log.i(TAG,response.body().string()); 63 | } 64 | }); 65 | } 66 | 67 | ``` 68 | 69 | 如果需要加代理的话,通过 builder 添加: 70 | 71 | ``` 72 | // 设置代理服务器,这里以HTTP代理为例 73 | final String proxyHost = "your_proxy_host"; 74 | final int proxyPort = your_proxy_port; 75 | Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); 76 | // 设置代理 77 | builder.proxy(proxy); 78 | 79 | ``` 80 | 81 | _**说到代理,这里给大家推荐款 IP 代理商:**__**青果网络**_ _**!!!**_ 82 | 83 | 最近笔者也在使用这家代理,主要是给的优惠足够大  哈哈哈 84 | 85 | > 青果的国内短效代理,提供动态和短效的 IP 资源,其基于拨号 VPS 构建的高品质代理服务器,部署全国 200 + 城市与地区,IP 日流水超 600 万,更有 6 小时不限量测试时长;使用后的感觉整体 ip 稳定性、业务成功率和性价比都挺不错的。 86 | 87 | 评估下来个人感受有一下几大优势: 88 | 89 | 第一:极速响应 90 | 91 | 第二:支持短时间大量提取 IP(应对短期需要切换大量 IP 的爬虫任务)、 92 | 93 | 第三:提供代理请求统计分析(方便用于监控用量)。 94 | 95 | 第四:支持负载均衡(平均分配请求负载,防止单个服务器过载) 96 | 97 | ![图片](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuwdw9VuNibYVGvjwic2iaQFLiaricrmCX1JpzRIYAMSMEvUquN6kyR7FtOmEhzgXzyD6HEIq17fGA6tTlQ/640?wx_fmt=png&from=appmsg) 98 | 99 | _**感兴趣老板可以点击文尾**__**阅读原文**__**查看!!!**_ 100 | 101 | _**切回正片!!!**_ 102 | 103 | **retrofit** 104 | 105 | > Retrofit 将您的 HTTP API 转换为 Java 接口;Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责网络请求接口的封装; 106 | 107 | 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责网络请求接口的封装。App 应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作。 108 | 109 | 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit 根据用户的需求对结果进行解析。所以,网络请求的本质仍旧是 OkHttp 完成的,retrofit 只是帮使用者来进行工作简化的,比如配置网络,处理数据等工作,提高这一系列操作的复用性。 110 | 111 | ``` 112 | Retrofit retrofit = new Retrofit.Builder() 113 | .baseUrl("http://fanyi.youdao.com/") //设置网络请求的Url地址 114 | .addConverterFactory(GsonConverterFactory.create()) //设置数据解析器 115 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 116 | .build(); 117 | // 创建 网络请求接口 的实例 118 | GetRequest_Interface request = retrofit.create(GetRequest_Interface.class); 119 | //对 发送请求 进行封装 120 | Call call = request.getCall(""); 121 | // 异步请求 122 | call.enqueue(new Callback() { 123 | //请求成功时回调 124 | @Override 125 | public void onResponse(Call call, Response response) { 126 | //请求处理,输出结果 127 | response.body().show(); 128 | } 129 | //请求失败时候的回调 130 | @Override 131 | public void onFailure(Call call, Throwable throwable) { 132 | System.out.println("连接失败"); 133 | } 134 | }); 135 | //同步请求 136 | try { 137 | Response response = call.execute(); 138 | response.body().show(); 139 | } catch (IOException e) { 140 | e.printStackTrace(); 141 | } 142 | 143 | ``` 144 | 145 | 03 146 | 147 | — 148 | 149 | 分析过程 150 | 151 | 打开 douyin-apk,找到 retrofit2 152 | 153 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaz7sEVVibDW3ia4ZJQMDGm5qZBX7ibTcJPbEyY376hYIpiaVWvNM3iah734g/640?wx_fmt=png&from=appmsg) 154 | 155 | 目录下封装了 client、request、response,以及一个 SsCall 接口 156 | 157 | SsCall 接口有个 execute 方法,然后返回值 Response 类型 158 | 159 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaibJyc1nb3doXI1WdzyqhWNBicJ0WntiaicicEMiciaSwRcMoyuN0xYDPyiaOOg/640?wx_fmt=png&from=appmsg) 160 | 161 | 整个流程就是一个实现 Client 接口的类调用 newSsCall 会返回一个实现 SsCall 的类,类中通过 execute 调用实现 Call 的类返回 Httpresponse 162 | 163 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaD36yVjU5zg6K7cxMTbscyib4WXfhEoSjHaqWxwnbmicldFn2lXAUaBBw/640?wx_fmt=png&from=appmsg) 164 | 165 | 查下这个 SsCall 的实现,进第一个查找用例 166 | 167 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAasiaFnfFNap5ia5iczEgvQDtMHJbQYbeUWzp6wOetRbtKd2nNjSIs9l83g/640?wx_fmt=png&from=appmsg) 168 | 169 | 这里拿到 retrofit2 封装的 Response 的 body 170 | 171 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaaFwYy5ZQYpghVrEeDVKzcMtkShaLdqU1WqFfoITSZ9bSK1akDCy2Bw/640?wx_fmt=png&from=appmsg) 172 | 173 | 我们 hook 下 Response 174 | 175 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAacaXpLg3lU9mJ3ibvPZNVw2UnFekow6ic8sYpRqxFWTvN76CzHGOdeG3w/640?wx_fmt=png&from=appmsg) 176 | 177 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAanFKpjepxJAhaVYJmW9eBaJmDRP30Jmrh0s8tea5hgac0RYxzsm1xsA/640?wx_fmt=png&from=appmsg) 178 | 179 | hook 到了 180 | 181 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaVruQLr3TQNv74egfLwmCkle7Km1Ys8H1hAGQqC5MA3bbWrEPpqR7Cg/640?wx_fmt=png&from=appmsg) 182 | 183 | 参数: 184 | 185 | str 是请求的 url  i=200 应该是状态码  list 是个 object  typedInput 也是 object 186 | 187 | 从名字上可以看出 list 是 headers 的切片,最重要的是这里的 typedInput 188 | 189 | 往上回溯找到实参位置 190 | 191 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAagPicAzSnLHqFviblWDPtn7CgWXlJgvuniaQJJw00Rwh6aQLbgyZtVXwoA/640?wx_fmt=png&from=appmsg) 192 | 193 | 进去 194 | 195 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAanx8ghgly3AZ9W6icRp9uGWe1wPPeCic7eNmW87u0rqTDbYJicV0mY5cXg/640?wx_fmt=png&from=appmsg) 196 | 197 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaUUacYdW6jVpkqic1PvHVrzGPahGUdKdUhgXaA9CakpEFSRdzDHQL4Ew/640?wx_fmt=png&from=appmsg) 198 | 199 | 直接 hook 这个 getBytes 即可 200 | 201 | 因此我们上面 hook 函数改为: 202 | 203 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaPyxyyhbFWN8g9dicXtwU7tzMPtDBFodym6bkJ9HsjW2Sg4JwRt5lVlg/640?wx_fmt=jpeg) 204 | 205 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaE34k96ChjAWGskFPP8JYv4z7HzyCGaKJzKhpG0MuDU0tU35rrcVPeA/640?wx_fmt=jpeg) 206 | 207 | 我们日志文件里面以及有了字节,我们转为 str 看下 208 | 209 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaaUdqawpBib2uL6KXbcpX8YpfyqdoyJiaOFdcSMTlRvtxNvHm6YwADdUw/640?wx_fmt=png&from=appmsg) 210 | 211 | 然后就拿到了文本 212 | 213 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAaicUbwO3bUsF4Cic6Z6dTgF60WkgXfjtXSMOFZXLibeaxzEMrTJFt00spQ/640?wx_fmt=png&from=appmsg) 214 | 215 | 在 frida 利用 JavaString 将字节转为 string 216 | 217 | ``` 218 | let JavaString = Java.use("java.lang.String") 219 | let _TypedByteArray = Java.cast(typedInput, TypedByteArray) 220 | let results = JavaString.$new(_TypedByteArray.getBytes()); 221 | 222 | ``` 223 | 224 | 好了分析完毕! 225 | 226 | 其实在分析过程中找到了一个 Cronet 的开关,这个函数 hook->return false,同样可以降级抓包 227 | 228 | hook 代码星球见,星球好久没更新要被骂了 229 | 230 | 04 231 | 232 | — 233 | 234 | 算法还原 235 | 236 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/5aP6U4veSuym7c6yPA6icicqLRZSzNaWAapib8VjJUn193TDTVL12VjbpGFsRYEhVRJCNo0fxrENKBq7cN1ZnN73Q/640?wx_fmt=jpeg&from=appmsg) 237 | 238 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuwT0dUxFRAYKJckhBKVG6ecwtV3Ot2Y5VCuGU0DibxkkurkYJ2QzbN96L6ibFbBgOEM8TYpH4P8A8eQ/640?wx_fmt=png) 239 | 240 | ![](https://mmbiz.qpic.cn/mmbiz_png/5aP6U4veSuwT0dUxFRAYKJckhBKVG6ecwyVZbsGqS6q1xRoreyqHokuq1KdtUq6A4dkuPFpqVjR0loz0QElpNQ/640?wx_fmt=png) 241 | 242 | 添加好友回复:交流群 243 | 244 | 戳  “阅读原文 "  了解青果网络~ -------------------------------------------------------------------------------- /simpread-在静态分析、计算 arm64 汇编时提速.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/2YQmxIm4dR6HdDrvyLURZA) 2 | 3 | ![](https://mmbiz.qpic.cn/mmbiz_png/gY5PqCV2kYZJ4sKUniaZPr7YRoHTLnbM8ZCNohcGM2hjy5tMJyRd9RicbR9HrMkoJiaGrBrV7JjW09zqHtCHI8q8g/640?&wx_fmt=png) 4 | 5 | 在分析汇编代码的时候,我们要计算寄存器的值的来源,举个例子 6 | 7 | ``` 8 | LDRSW X3, [X27,X10,LSL#2] 这个时候想要得到x3的值,需要搞清楚x27与x10 9 | 10 | ``` 11 | 12 | ``` 13 | 的来源,然后你在多行代码中进行寻找改变x27、x10的代码,后续还 14 | 15 | ``` 16 | 17 | ``` 18 | 遇到x27和x10的来源,找起来就很费事,实在很让人恼火,那么如何办呢,首先找到 19 | 20 | ``` 21 | 22 | ``` 23 | 目标操作寄存器,然后找到第一个源和第二个源寄存器,判断我们想要寻找到对应指令的源寄存器,根据 24 | 25 | ``` 26 | 27 | ``` 28 | 源寄存器逐级递归遍历,找到所有使用寄存器的index,然后把对应指令打印出来。缺点:目前的代码对于出入栈的时候stp会存在问题,还有对于一些通过出入栈给内存 29 | 30 | ``` 31 | 32 | ``` 33 | 空间赋值的也没法处理,但是在局部分析汇编代码时,有一些用,如果其他大佬有好的 34 | 35 | ``` 36 | 37 | ``` 38 | 解决方案,欢迎提供~下方实现代码为下抛砖引玉。 39 | 40 | ``` 41 | 42 | ![](https://mmbiz.qpic.cn/mmbiz_png/ib93efMPP0actGbYAbS23TrIXMKA4Hfb3wAjuNLrpnMP1iarC8oTugBZFgEibxVv0uVSvt4F6wwMxoDdCWddEL6sw/640?&wx_fmt=png) 43 | 44 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/p5YHVYUwZib2v1Kmw5V47MCKMNA9trqSLVl7q1bibnc7yT5icStAMCekdndJTMUAcecibkw8oibQ3Uiaa8E6AxTsjkJg/640?wx_fmt=png&from=appmsg) 45 | 46 | 如果想要分析 w11, 需要找 w14 和 w11, 然后按照箭头就可以找到,然后再找 w14 和 w11 的来源,逐级递归处理;手动是不太现实的,把其实现为代码,代码用了 capstone 和 keystone 去找寻目标寄存器和操作数;代码如下。 47 | 48 | ``` 49 | from capstone import * 50 | from capstone.arm64 import * 51 | from keystone import * 52 | import copy 53 | # 示例汇编代码 54 | assembly_code = """ 55 | LSR W11, W12, #0xB 56 | AND W14, W11, #2 57 | LDRSW X3, [X27,X10,LSL#2] 58 | AND W16, W12, #0x10000000 59 | AND W2, W11, #4 60 | BFXIL W14, W12, #31, #1 61 | ORR W14, W14, W2 62 | AND W2, W11, #8 63 | AND W10, W11, #0x10 64 | LSR W11, W16, #0x1A 65 | AND W15, W12, #0x20000000 66 | ORR W2, W14, W2 67 | BFXIL W11, W12, #0x1A, #2 68 | AND W14, W12, #0x40000000 69 | ORR W10, W2, W10 70 | ORR W11, W11, W15,LSR#26 71 | ADD X2, X3, X27 72 | UBFX W9, W12, #0x15, #5 73 | UBFX W8, W12, #0x10, #5 74 | AND W13, W12, #0x80000000 75 | AND W18, W12, #0x4000000 76 | AND W17, W12, #0x8000000 77 | ORR W11, W11, W14,LSR#26 78 | """ 79 | instructions = [line.strip() for line in assembly_code.strip().split('\n') if line.strip()] 80 | # 使用 Keystone 汇编示例代码 81 | ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) 82 | encoding, count = ks.asm('\n'.join(instructions)) 83 | machine_code = bytearray(encoding) 84 | md = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN) 85 | md.detail = True # 启用详细信息 86 | allTarSour = [] 87 | for instruction in md.disasm(machine_code, 0x4): 88 | target = None 89 | src1 = None 90 | src2 = None 91 | if instruction.operands: 92 | target = instruction.reg_name(instruction.operands[0].reg) 93 | if len(instruction.operands) > 1: 94 | if instruction.operands[1].type == ARM64_OP_REG: 95 | src1 = instruction.reg_name(instruction.operands[1].reg) 96 | elif instruction.operands[1].type == ARM64_OP_MEM: 97 | src1_base = instruction.reg_name(instruction.operands[1].mem.base) 98 | src1_index = instruction.reg_name(instruction.operands[1].mem.index) 99 | src1_offset = instruction.operands[1].mem.disp 100 | if src1_index: 101 | src1 = f"{src1_base}, {src1_index}, LSL #{src1_offset >> src1_index.shift}" if src1_offset else f"{src1_base}, {src1_index}" 102 | else: 103 | src1 = f"{src1_base}, #{src1_offset}" if src1_offset else src1_base 104 | if len(instruction.operands) > 2: 105 | if instruction.operands[2].type == ARM64_OP_IMM: 106 | src2 = f"#{instruction.operands[2].imm}" 107 | elif instruction.operands[2].type == ARM64_OP_REG: 108 | src2 = instruction.reg_name(instruction.operands[2].reg) 109 | src = [] 110 | if src1: 111 | if "," in src1: 112 | src.extend( [s if ("x" in s or "w" in s) else None for s in src1.split(",")]) 113 | else: 114 | src.append(src1) 115 | if src2: 116 | if "," in src2: 117 | src.extend( [s if ("x" in s or "w" in s) else None for s in src2.split(",")]) 118 | else: 119 | src.extend([src2] if "x" in src2 or "w" in src2 else []) 120 | allTarSour.append({"target": target,"source":src}) 121 | index_list = [] 122 | def findall(data): 123 | index_list.append(len(data)-1) 124 | end_target = data[-1]['target'] 125 | sources = data[-1]['source'] 126 | for source in sources: 127 | temp_len = len(data)-2 128 | if(temp_len >-1): 129 | while(temp_len>-1): 130 | if(data[temp_len].get('target')[1:] == source[1:]): 131 | copied_list = copy.deepcopy(data[:temp_len+1]) 132 | if(len(copied_list)>1): 133 | findall(copied_list ) 134 | else: 135 | if(copied_list[0].get('target')[1:] == source[1:]): 136 | index_list.append(0) 137 | break 138 | else: 139 | temp_len -= 1 140 | findall(allTarSour) 141 | index_list.sort() 142 | assembly_lines = assembly_code.strip().split('\n') 143 | for index in index_list: 144 | print(assembly_lines[index]) 145 | 146 | ``` 147 | 148 | 结果如下: 149 | 150 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/p5YHVYUwZib2v1Kmw5V47MCKMNA9trqSLe9ia5yL0UPwicR0P9CvmwuJCf48r98cEnicNVzZSHRSFOibaaB4vEjfupg/640?wx_fmt=png&from=appmsg) 151 | 152 | 学习 js 逆向可以关注我朋友: 153 | 154 | 我是 BestToYou, 分享工作或日常学习中关于 Android、iOS 逆向及安全防护的一些思路和一些自己闲暇时刻调试的一些程序, 文中若有错误或者不足的地方, 恳请大家联系我批评指正。 -------------------------------------------------------------------------------- /simpread-在非越狱 iOS 上实现全流量抓包.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/dy534TfqE-XyL6DnYL-fcw) ![](https://mmbiz.qpic.cn/sz_mmbiz_jpg/3eicVGzibzClD0a7dtdzY0gD0yGOibA89F0ibfSflicOPUTicGibW26q43fe72hq7T1RraxeSbBFB8xQHiaQsbARJG8jgQ/640?wx_fmt=jpeg&from=appmsg)看到题图应该都懂了吧 2 | 3 | 背景 4 | == 5 | 6 | 昨晚在测试一个手机应用抓包的时候发现使用 WiFi 的 HTTP 代理无法抓到包,并且在 Burp 的日志中没有看到任何 SSL Alert,因此可以判断不是证书问题;另外虽然没有抓到包但是应用依然可以正常收发数据,这说明代理的配置被绕过了。 7 | 8 | 应用开发者要想绕过用户端的代理配置有很多办法,这种情况在笔者之前的博文中已经介绍过不少了,比如: 9 | 10 | * • [终端应用安全之网络流量分析](https://mp.weixin.qq.com/s?__biz=MzA3MzU1MDQwOA==&mid=2247484053&idx=1&sn=8b9dd1ad310f0c602bfde12be360a688&scene=21#wechat_redirect "终端应用安全之网络流量分析") 11 | 12 | * • [iptables 在 Android 抓包中的妙用](https://mp.weixin.qq.com/s?__biz=MzA3MzU1MDQwOA==&mid=2247484228&idx=1&sn=1393d6d90b27cc565f5c76197fe05812&scene=21#wechat_redirect "iptables 在 Android 抓包中的妙用") 13 | 14 | 15 | 但是这次的情况有点不一样。首先笔者常用的用于近源钓鱼的 WiFi 热点工具没有带回家,其次手头上没有 Andorid 手机只有一个 iPhone,且手机由于日常使用并没有越狱 (属于是强行给自己上强度了)。 16 | 17 | 初步尝试 18 | ==== 19 | 20 | 在现有条件下,还是先尝试了正常的抓包方案,使用 Burpsuite 启动代理并在手机端安装好证书,正常抓包发现没有问题。但是正如前面所说,在使用目标应用时发现在一些关键流量上并没有展现,由此判断是应用代码中特意忽略了系统代理配置。 21 | 22 | 这种情况下一般需要尝试透明代理或者 VPN 抓包方案,我们先考虑后者。在 iOS 中我们有 shadowsxxksR、Surge 等应用,不过都要收费,而且这些软件主要还是用于非 MITM 场景的代理。 23 | 24 | 在互联网上检索一番发现有个叫 Stream[1] 的免费软件,基于 iOS 9+ 的 Network Extension Api 进行 HTTP/HTTPS 网络流量的劫持。虽然最近一次更新已经是 4 年前了,但现在基本功能还是可用的。 25 | 26 | 使用该工具点点点之后发现已经能抓到我们想要的请求包了。按理说,我们的任务已经完成了,但是,用别人现成的工具,**你的价值体现在哪里?这件事为什么是你做不是别人做?当初招你进来的时候……** 27 | 28 | …… 开个玩笑。其实这个工具也有一些不是很完美的地方,比如抓包和改包只能在手机上点,不适合自动化;还比如对于一些非 HTTP 流量其实也没有办法抓到。 29 | 30 | 因此我们接着看有没有其他更优雅的解决方案。 31 | 32 | WireGuard 33 | ========= 34 | 35 | 既然手法限制在了 VPN 抓包,那就不得不提 WireGuard 了。 36 | 37 | WireGuard 是一种新型的 VPN(虚拟专用网络)协议和免费开源软件,通过 UDP 封装 IP 数据包,提供了一种既安全又高效的 VPN 解决方案。它适用于点对点连接、中心辐射型网络拓扑以及全网状网络拓扑。其设计哲学是简化 VPN 的配置和使用,同时不牺牲安全性和性能。 38 | 39 | WireGuard 的安装和使用可以参考官网的文章: 40 | 41 | * • Wireguard - install[2] 42 | 43 | * • Wireguard - quickstart[3] 44 | 45 | * • Source Code Repositories and Official Projects[4] 46 | 47 | 48 | WireGuard 的配置核心在于秘钥路由特性 (Cryptokey Routing)。作为一个网络接口 (eth0、wlan0、tun0),会根据路由表进行实际传入和传出网络的分发。WireGuard 同样运行在网络接口层,只不过会在分发前进行网络包的加解密,并且使用 UDP 进行传输。 49 | 50 | 基本使用 51 | ---- 52 | 53 | 在 Linux 中新建网卡和设置路由表都可以通过 `ip` 命令操作,这里新建一个名为 `wg0` 的网络接口: 54 | 55 | ``` 56 | # 网卡配置 57 | ip link add wg0 type wireguard 58 | ip addr add 10.0.0.1/24 dev wg0 59 | ip link set wg0 up 60 | 61 | ``` 62 | 63 | 然后使用 `wg` 命令对该接口进行配置,包括设置私钥、端口和 peer 等信息。 64 | 65 | ``` 66 | # 生成公私钥 67 | wg genkey > private 68 | # 添加私钥 69 | wg set wg0 private-key ./private 70 | # 设置端口 71 | wg set wg0 listen-port 51820 72 | # 添加端点 73 | wg set wg0 peer  allowed-ips 192.168.88.0/24 endpoint 209.202.254.14:8172 74 | 75 | # 可以组合成一句话 76 | wg set wg0 listen-port 51820 private-key './private' peer 'ABCDEF...' allowed-ips '192.168.88.0/24' endpoint '209.202.254.14:8172' 77 | 78 | ``` 79 | 80 | 这些配置何况使用配置文件的方式进行管理。 81 | 82 | ``` 83 | wg setconf wg0 ./config.ini 84 | 85 | ``` 86 | 87 | 也可以不用手动创建网卡而是使用 wg-quick 脚本一键部署: 88 | 89 | ``` 90 | sudo wg-quick up config.ini 91 | 92 | ``` 93 | 94 | > `wg` 和 `wg-quick` 都是 wireguard-tools 的一部分 95 | 96 | 以下面一个 Server 的配置为例: 97 | 98 | ``` 99 | [Interface] 100 | PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk= 101 | ListenPort = 51820 102 | 103 | [Peer] 104 | PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg= 105 | AllowedIPs = 10.192.122.3/32, 10.192.124.1/24 106 | 107 | [Peer] 108 | PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA= 109 | AllowedIPs = 10.10.10.230/32 110 | 111 | ``` 112 | 113 | 其中 `Interface` 指定接口的私钥和监听端口,`Peer` 则指定了客户端的公钥和 IP 网段。用简化的说法,对于发送到子网 `10.10.10.230/32` 的请求,会使用该 Peer 的公钥进行加密;对于发送到本机接口的请求,则用本地私钥进行解密。 114 | 115 | 对应 Client 的配置如下: 116 | 117 | ``` 118 | [Interface] 119 | PrivateKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= 120 | ListenPort = 21841 121 | 122 | [Peer] 123 | PublicKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= 124 | Endpoint = 192.95.5.69:51820 125 | AllowedIPs = 0.0.0.0/0 126 | 127 | ``` 128 | 129 | 其中在 Interface 字段指定了本机的私钥,Peer 字段指定服务器的公钥和地址信息。前面用到 `wg genkey` 命令来生成私钥,同时也可以用 `wg pubkey` 查看对应私钥的公钥: 130 | 131 | ``` 132 | $ wg pubkey <<< gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= 133 | gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA= 134 | $ wg pubkey <<< yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk= 135 | HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= 136 | 137 | ``` 138 | 139 | 可以看到客户端和服务端配置的一大区别是客户端配置指定的 `Peer` 包含 Endpoint 字段,指定服务器的地址和端口。 140 | 141 | 服务端上除了 WireGuard 本身的配置还要注意网卡的配置,一般情况下可能需要调整本机防火墙的策略,对于需要将虚拟网卡流量转发到实际网络接口上的场景还需要配置内核 IP 转发以及路由转发的规则,详情可参考: 142 | 143 | * • 在 Ubuntu22.04 上设置 WireGuard[5] 144 | 145 | * • Routing & Network Namespace Integration[6] 146 | 147 | * • Share wg0 over eth0[7] 148 | 149 | * • Access internal devices through the WireGuard tunnel[8] 150 | 151 | 152 | Wireshark 153 | --------- 154 | 155 | 我们可以在手机上使用 WireGuard 作为 VPN 客户端,同时在 PC 上运行 WireGuard 服务端即可以获取所有手机上的网络流量。不过这里有个问题是网卡收到的数据是加密的 UDP 数据,因此我们需要先将其解密还原成原始数据。 156 | 157 | 其实在 Wireshark 中已经支持了 WireGuard 流量解密的功能。在 Wireshark Edit -> Preferences -> Protocols -> WireGuard 中可以设置 static key 和 keylog 对加密的 UDP 流量进行解密。 158 | 159 | 这个解密过程和 SSL/TLS 的解密过程类似,其中需要 wireguard-tools 中的 extract-handshakes.sh[9] 脚本来在获取 WireGuard 握手过程中的临时秘钥,该脚本基于 kprobe 来 hook 内核中 wireguard 驱动的相关代码来提取秘钥,因此只能用于 Linux 系统。 160 | 161 | 为什么即使有双方的 Peer 私钥也无法对会话进行解密?因为握手过程使用随机生成的临时公私钥对进行 Diffie-Hellman 密钥交换,以生成共享会话密钥。这些临时密钥不会被存储且仅用于当前会话,可以认为 Peer 私钥仅参与验证而不参与加密,因此也就无法解密流量。即便某一个会话的秘钥泄露也不会影响其他会话的保密性,这个就叫前向安全 (Forward Security)。 162 | 163 | 不过话说回来,对于我们的抓包场景其实还不需要介入到解密过程,因为实际流量是进入到我们自己的机器上,然后才转发到实际的服务器,后者的流量是已经解密的。因此我们可以直接抓取网络网卡 (如 eth0) 其实也是能看到解密后的流量的。甚至我们可以直接抓取 Wireguard 网卡(如 wg0) 也能获得明文流量,从而实现了 “全流量抓包” 的愿景。 164 | 165 | * • WireGuard / Wireshark[10] 166 | 167 | * • Decoding WireGuard with WireShark[11] 168 | 169 | * • WireGuard:抓包和实时解密 [12] 170 | 171 | 172 | MITMProxy 173 | --------- 174 | 175 | 使用 WireGuard+Wireshark 我们已经实现了在 iOS 中抓取所有流量的目的,包括 UDP 和 TCP 的链接也包括 ICMP 等 IP 层数据。不过还是有一点点小瑕疵,其中获取到的 HTTPS 流量还是 TLS 加密的。 176 | 177 | 解决这个问题的方法也很多,一个最直接的方法是通过 iptables 将进入本机 (server) 443 端口的 TCP 流量转发到其他透明 HTTP 代理端口,比如 Burpsuite 或者 Yakit,借助他们的 MITM 功能来实现 HTTPS 流量加解密和重放。 178 | 179 | 不过,既然都引入了三方抓包工具,其实有一个更为便捷的方案。那就是使用 MITMProxy,其在 9.0 版本之后引入了了一种新的透明代理模式,即 wireguard mode,使用方法非常简单,如下所示: 180 | 181 | ``` 182 | mitmproxy -m wireguard 183 | 184 | ``` 185 | 186 | 其会自动生成一个 WireGuard 客户端配置文件,例如: 187 | 188 | ``` 189 | [Interface] 190 | PrivateKey = mLorvtz8GDGUH+Qm+voV4wFg2r6yJFN/tCRq4fY/Rm8= 191 | Address = 10.0.0.1/32 192 | DNS = 10.0.0.53 193 | 194 | [Peer] 195 | PublicKey = lyZoK//28trI/gSaTTaxKEce4RlU2Sxx9VafyWlqR34= 196 | AllowedIPs = 0.0.0.0/0 197 | Endpoint = 192.168.31.21:51820 198 | 199 | ``` 200 | 201 | 在手机上直接使用 WireGuard 导入该配置或者将其转为二维码扫描即可连入基于 MITMProxy 的 VPN,所有流量都会被 MITMProxy 进行劫持和转发,从而实现便捷的抓包。 202 | 203 | 值得一提的是在该模式下 MITMProxy 并不会真正地使用 WireGuard 内核模块,而是使用了其自身的应用层实现,mitmproxy-wireguard。所以观察网卡也不会看到 wg0 等虚拟接口。这样的好处是避免了系统的依赖性,同时功能也会有所裁剪,比如传统 VPN 的子网互通就无法实现。 204 | 205 | mitmproxy-wireguard 用 Rust 编写,目前已经转移到了 `mitmproxy_rs` 仓库中,感兴趣的可以去查看其源码实现。 206 | 207 | * • A more user-friendly transparent mode, based on WireGuard[13] 208 | 209 | * • mitmproxy_rs,前生为 mitmproxy-wireguard[14] 210 | 211 | 212 | 总结 213 | == 214 | 215 | 本文探讨了几种在非越狱 iOS 手机上进行抓包的方案,除了使用如 Surge、Stream 等商业工具,还可以使用 WireGuard、Wireshark、MITMProxy 等开源工具实现最初的目标。在基于 WireGuard 的方案中,Wireshark 可以确保抓取所有流量不会有遗漏,在某些游戏应用中可能会遇到类似的自定义 TCP/UDP 数据流;而使用 MITMProxy 则可以方便地对 HTTPS 流量进行解密,同时基于应用层可以实现平台无关性。 216 | 217 | 与 Android 相比,如果只是日常抓包可能 iOS 会更方便一点。因为 Android 7 及其以后都默认不信任用户添加的根证书,想加证书抓包要么 root 要么重打包,相对门槛会高一些;而 iOS 还是一如既往的可以随意添加证书,对于日常使用偶尔抓包分析的场景倒是比较方便了。 218 | 219 | #### 引用链接 220 | 221 | `[1]` Stream: _https://blog.csdn.net/weixin_44504146/article/details/121946958_ 222 | `[2]` Wireguard - install: _https://www.wireguard.com/install/_ 223 | `[3]` Wireguard - quickstart: _https://www.wireguard.com/quickstart/_ 224 | `[4]` Source Code Repositories and Official Projects: _https://www.wireguard.com/repositories/_ 225 | `[5]` 在 Ubuntu22.04 上设置 WireGuard: _https://cainiaojiaocheng.com/%E5%A6%82%E4%BD%95%E5%9C%A8Ubuntu22.04%E4%B8%8A%E8%AE%BE%E7%BD%AEWireGuard_ 226 | `[6]` Routing & Network Namespace Integration: _https://www.wireguard.com/netns/_ 227 | `[7]` Share wg0 over eth0: _https://www.reddit.com/r/WireGuard/comments/nkg77b/share_wg0_over_eth0/_ 228 | `[8]` Access internal devices through the WireGuard tunnel: _https://docs.pi-hole.net/guides/vpn/wireguard/internal/_ 229 | `[9]` extract-handshakes.sh: _https://git.zx2c4.com/WireGuard/tree/contrib/examples/extract-handshakes/README_ 230 | `[10]` WireGuard / Wireshark: _https://wiki.wireshark.org/WireGuard_ 231 | `[11]` Decoding WireGuard with WireShark: _https://blog.salrashid.dev/articles/2022/wireguard_wireshark/_ 232 | `[12]` WireGuard:抓包和实时解密: _https://blog.jinmiaoluo.com/posts/wireguard-with-wireshark_ 233 | `[13]` A more user-friendly transparent mode, based on WireGuard: _https://mitmproxy.org/posts/wireguard-mode/_ 234 | `[14]` mitmproxy_rs,前生为 mitmproxy-wireguard: _https://github.com/mitmproxy/mitmproxy_rs_ -------------------------------------------------------------------------------- /simpread-多种姿势花样使用 Frida 注入 _ AshenOne.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [ashenone66.cn](https://ashenone66.cn/2021/09/20/duo-chong-zi-shi-hua-yang-shi-yong-frida-zhu-ru/) 2 | 3 | > 大 A 的个人博客,用于记录学习过程 4 | 5 | [](#前言 "前言")前言 6 | -------------- 7 | 8 |   多种姿势花样使用 Frida 注入是怎么回事呢?Frida 相信大家都很熟悉,但是多种姿势花样使用 Frida 注入是怎么回事呢,下面就让小编带大家一起了解吧。 9 |   多种姿势花样使用 Frida 注入,其实就是用不止一种方式注入 Frida,大家可能会很惊讶 Frida 为什么要用多种方式注入?但事实就是这样,小编也感到非常惊讶。 10 |   这就是关于多种姿势花样使用 Frida 注入的事情了,大家有什么想法呢,欢迎在评论区告诉小编一起讨论哦! 11 | 12 |   在实战中使用 Frida 会遇到各种各样的问题来对你进行限制,因此在这里总结和对比一下自己在实战中使用过的一些 frida 的注入方式。关于注入方式的归纳在 [sandhook 的文档](https://github.com/asLody/SandHook/blob/master/doc/doc.md#%E8%BF%9B%E7%A8%8B%E6%B3%A8%E5%85%A5)中有一小段提到过,本文主要讲下如何将这些方式真正的应用到 Frida 上。 13 | 14 | [](#方式一:直接使用 "方式一:直接使用")方式一:直接使用 15 | -------------------------------- 16 | 17 | 思路: 18 |   开启 root 后将 frida-server push 到手机并启动,然后使用`frida -U`命令连接。这是最简单而普遍的方式,网上的 Frida 教程基本上都是这么教的没啥好说的,能够满足大部分 app 的 hook 需求,容易被检测。 19 | 20 | 具体操作: 21 | 22 | > 1. 手机 root(部分可参考附录) 23 | > 2. 下载手机对应架构的 frida-server,一般选择 android-arm64 24 | > 3. 将下载好的 frida-server 解压后改名(以防检测)为 fs 或其他名字,push 到手机 data/local/tmp 目录 25 | > 4. 修改 frida-server 权限,chmod 777 fs 26 | > 5. 直接运行或者修改端口运行(以防检测),改端口使用命令./fs -l 0.0.0.0:12345 27 | > 6. 若未改端口,使用 frida -U 命令连接;若修改端口,先进行端口转发 adb forward tcp:12345 tcp:12345,然后使用命令 frida -H 127.0.0.1:12345 连接 28 | > 7. 遇到无法 attach 目标进程时尝试使用 - f 参数 spawn 一个进程 29 | 30 | 优点: 31 | 32 | > * 简单 33 | > * 可应对大部分 app 34 | 35 | 缺点: 36 | 37 | > * 需要 root 38 | > * 容易被检测 39 | 40 | 适用场景: 41 |   适用大部分场景,在手机已 root 的情况下可以优先使用这种方式尝试 hook,如果失败则根据具体情况选择下面提到的其他方式。 42 | 43 | [](#方式二:重打包 "方式二:重打包")方式二:重打包 44 | ----------------------------- 45 | 46 | 思路: 47 |   将 apk 解包后通过修改 smali 或者 patch so 的方式植入 frida-gadget,然后重打包安装,能够达到无 root 的情况下对单个进程 hook 效果。 48 | 49 | 具体操作: 50 | 51 | > 1. 修改 smali:在 app 入口处(一般是 MainActivity 或者 Application 的静态代码区)添加 System.loadLibrary(“frida-gadget”),然后在 apk 的 lib 目录添加 libfrida-gadget.so(最好改下名字) 52 | > 2. patch so:取出 lib 目录中确定会被 app 加载的一个 so 文件,最好是没加密和混淆过的,使用 lief 进行 patch,然后替换原来的 so 文件,并加入 frida-gadget,具体 patch 方式参考附录。 53 | > 3. 签名绕过,自行分析,可能难度较大 54 | > 4. 使用工具:可以使用 objection patchapk 一键完成重打包,原理是修改 smali,但无法绕过签名校验。也可以使用 ratel 重打包工具手动完成。ratel 平头哥是一个重打包沙箱,自带 sandhook 和签名校验绕过,可以配合使用让 ratel 将 frida-gadget 注入进去,当然也可以直接使用自带的 sandhook。 55 | 56 | 优点: 57 | 58 | > * 能在未 root 的手机中使用,所以能够绕过 root 检测 59 | > * 无需 ptrace,能绕过一些 ptrace 自身的保护 60 | 61 | 缺点: 62 | 63 | > * 要绕过签名校验,这很难,特别是大厂 app 64 | 65 | 适用场景: 66 | 67 | > * 如果能绕过签名校验的话,重打包的方式还是比较推荐的,如果 app 的签名校验很弱或者根本没有签名校验,使用重打包的方式是不错的选择 68 | 69 | [](#方式三:Patch-SO "方式三:Patch SO")方式三:Patch SO 70 | -------------------------------------------- 71 | 72 | 思路: 73 |   Patch SO 可以用在重打包的方式中,也可以单独拿出来用。重打包的方式不介绍了,这里讲另一种方式。由于无法绕过签名校验,所以可以 patch /data/app/pkgname/lib/arm64(or arm) 目录下的 so 文件,apk 安装后会将 so 文件解压到该目录并在运行时加载,修改该目录下的文件不会触发签名校验。 74 |   Patch SO 的原理可以参考 [Android 平台感染 ELF 文件实现模块注入](https://gslab.qq.com/portal.php?mod=view&aid=163) 75 | 76 | 具体操作: 77 | 78 | > 1. 根据手机架构,从 apk 中提取待 patch 的 so 文件 79 | > 2. 安装并使用 lief,能很方便的 patch so 文件,为其添加 frida-gadget 的依赖 80 | > 3. 替换 lib 目录下的 so 文件并加入 frida-gadget,这里可以使用临时 root 或 twrp 的方式完成,具体请参考附录 81 | 82 | 优点: 83 | 84 | > * 可以绕过签名校验 85 | > * 可以绕过 root 检测 86 | > * 可以绕过部分 ptrace 自身的保护 87 | 88 | 缺点: 89 | 90 | > * 需要解 bl 锁,部分极端 app 可能会检测 bl 锁 91 | > * 高版本系统下,当 manifest 中的 android:extractNativeLibs 为 false 时,lib 目录文件可能不会被加载,而是直接映射 apk 中的 so 文件 92 | 93 | 适用场景: 94 | 95 | > * 当遇到一个 app 有 root 检测和签名校验而无法轻松绕过时,可以尝试这种方式,前提是 app 没有检测 bl 锁,如果检测了 bl 锁且无法绕过,还是直接放弃这个方案吧。部分 app 可能存在 so 文件被 patch,或者加载了 frida-gadget 后被检测的情况,具体原因还需单独分析。 96 | 97 | [](#方式四:编译系统 "方式四:编译系统")方式四:编译系统 98 | -------------------------------- 99 | 100 |   自己编译系统,修改系统源码,将 frida-gadget 集成到系统中,使得 app 在启动时首先动态加载 frida-gadget。我们其实能直接修改系统源码来影响 app 的动态执行过程,且不引入 frida 特征,但是集成 frida 的优势在于能够 hook app 自身的代码,且修改 hook 点较为方便。 101 | 102 | 优点: 103 | 104 | > * 无需 root 105 | > * 可回锁 bl 锁 106 | > * 无需绕过签名校验 107 | 108 | 缺点: 109 | 110 | > * 编译系统比较麻烦 111 | > * 自己编译系统极易被风控检测,有时即使不嵌入 frida 也无法正常运行 app,或触发其敏感功能 112 | 113 | 适用场景: 114 | 115 | > * 以上三种方式均失效,而 app 能在 aosp 或 lineage 系统中正常运行时,可以考虑这种方式(也是孤注一掷了)。 116 | 117 | [](#总结 "总结")总结 118 | -------------- 119 | 120 |   在实战中可以根据 app 自身的保护情况来选择合适的方式来注入,前提是 app 没有针对 frida。以上情况并不一定都能百分之百成功,不同 app 可能会有自己独特的保护方式。 121 | 122 | [](#附录 "附录")附录 123 | -------------- 124 | 125 | ### [](#lief代码示例 "lief代码示例")lief 代码示例 126 | 127 | lief 的 API 具体参考 [lief 官方文档](https://lief-project.github.io//doc/latest/tutorials/09_frida_lief.html) 128 | 129 | python 130 | 131 | ``` 132 | import lief 133 | 134 | libnative = lief.parse("path of ELF source") 135 | libnative.add_library("libfg.so") 136 | libnative.write("path of patched ELF output") 137 | 138 | ``` 139 | 140 | ### [](#frida-gadget的使用 "frida-gadget的使用")frida-gadget 的使用 141 | 142 |   frida-gadget 一般要配合一个 config 文件来使用,frida-gadget 在被加载后会去读取自身所在目录下的配置文件,配置文件必须以一定的格式命名,例如:frida-gadget 的名称为 libfg.so,那么配置文件的名称则必须时 libfg.config.so,否则该配置文件不会被读取。当没有读取到配置文件时,frida-gadget 会使用默认配置。 143 |   配置文件的编写,我个人常用以下配置 144 | 145 | json 146 | 147 | ``` 148 | { 149 | "interaction": { 150 | "type": "listen", 151 | "address": "127.0.0.1", 152 | "port": 27042, 153 | "on_load": "resume" 154 | } 155 | } 156 | 157 | ``` 158 | 159 | 默认配置是 160 | 161 | json 162 | 163 | ``` 164 | { 165 | "interaction": { 166 | "type": "listen", 167 | "address": "127.0.0.1", 168 | "port": 27042, 169 | "on_port_conflict": "fail", 170 | "on_load": "wait" 171 | } 172 | } 173 | 174 | ``` 175 | 176 |   连接时要使用 frida -U gadget 而不是 frida -U pkgname。具体参考 [frida-gadget 官方文档](https://frida.re/docs/gadget/) 177 | 178 | ### [](#临时root和twrp的方式修改系统目录 "临时root和twrp的方式修改系统目录")临时 root 和 twrp 的方式修改系统目录 179 | 180 |   为了在避免被 root 检测的前提下修改系统目录,我们可以采取临时 root 或使用 twrp 的方式。 181 | 182 | #### [](#临时root的方式 "临时root的方式")临时 root 的方式 183 | 184 | > 1. 提取系统 boot.img,可以从刷机包中提取,也有其他方式自行百度 185 | > 2. 安装 magisk manage app 186 | > 3. 使用 magisk manage app 对 boot.img 进行 patch 187 | > 4. 将 patch 后的 img 拷贝到 PC 188 | > 5. 手机连接 PC,使用 adb reboot bootloader,进入 bootloader 189 | > 6. 使用 fastboot boot boot_patch.img 启动手机 190 | > 7. adb shell 后输入 su 检查是否已获取 root 191 | > 8. 完成系统修改后重启则 root 权限清除 192 | 193 | #### [](#使用twrp的方式 "使用twrp的方式")使用 twrp 的方式 194 | 195 | > 1. 下载 twrp.img 196 | > 2. 手机连接 PC,使用 adb reboot bootloader,进入 bootloader 197 | > 3. 使用 fastboot boot twrp.img 启动手机,进入 twrp 198 | > 4. 若有必要,挂载 system 分区和其他用得上的分区 199 | > 5. 此时 adb shell 即为 root 权限 200 | > 6. 修改完成后重启 201 | 202 |   建议使用 twrp 这种方式,相对简单,而且能够修改 system 分区,顺便一提这个技巧的还有一个应用是能够在无 root 的情况下将证书安装到系统目录下。 -------------------------------------------------------------------------------- /simpread-好库推荐 _ 两个解决 ja3 检测的 Python 库,强烈推荐.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/2kZHmQpGkq89wNaz5xBwyw) 2 | 3 | **关注它,不迷路。** 4 | 5 | 本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除! 6 | 7 | 某佬丢过来一个网站,我用 requests 库请求会报错: 8 | 9 | ![](https://mmbiz.qpic.cn/mmbiz_png/Lll8tx0MDR1Zbneyt19g2s7OyBTpq3cDNQYNLLDtib0ZtSDHXm6TNLYCk1o6ehAY95e6ECW9yAbQ3iaFgjcsTYNw/640?wx_fmt=png) 10 | 11 | 先说下我的环境: Win10 + Python 3.10 + requests  2.27.1, 直接请求的话报错了,我猜测是检测了 tls,听说降低 Python 和 requests 的版本可以正常请求,或许检测不那么严格。 12 | 13 | 下面介绍两个库,过掉它的检测。 14 | 15 | 1. Pyhttpx 16 | ---------- 17 | 18 | 项目地址: 19 | 20 | ``` 21 | https://github.com/zero3301/pyhttpx 22 | 23 | ``` 24 | 25 | 你可以将整个项目下载下来,也可以直接安装这个库: 26 | 27 | ``` 28 | pip install pyhttpx 29 | 30 | ``` 31 | 32 | 我选择的是将整个项目下载下来测试,根据他的 demo,写下请求代码: 33 | 34 | ``` 35 | import pyhttpx 36 | sess = pyhttpx.HttpSession() 37 | headers = { 38 |     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", 39 | } 40 | url = "打码了" 41 | response = sess.get(url) 42 | print(response.text) 43 | 44 | ``` 45 | 46 | 可以正常返回了: 47 | 48 | ![](https://mmbiz.qpic.cn/mmbiz_png/Lll8tx0MDR1Zbneyt19g2s7OyBTpq3cDcECeRoZJbZaxmuriaYibJQTRAQqcn5odhBxTNGyqCwHO2c6GxBDdqiaBw/640?wx_fmt=png) 49 | 50 | 完美解决! 51 | 52 | 这个项目的优秀之处在于可以修改成指定的 ja3 加密套件,还能在 Windows 下运行,非常的 nice!当然我在测试某些网站的时候报错了,项目还不是那么完美,期待解决。 53 | 54 | 2. Pycurl 55 | --------- 56 | 57 | 目前这个库在 Windows 上还没办法解决 ja3 的问题,因此我选择了 Liunx. 58 | 59 | 环境: 60 | 61 | ``` 62 | Distributor ID: Ubuntu 63 | Description: Ubuntu 18.04.4 LTS 64 | Release: 18.04 65 | Codename: bionic 66 | 67 | ``` 68 | 69 | 如何安装并解决 ja3,可以参考华哥的这篇文章 [python 完美突破 tls/ja3](http://mp.weixin.qq.com/s?__biz=MzU0MjUwMTA2OQ==&mid=2247484904&idx=1&sn=cfdc0fe3cbce3c4f2662eb480c654e5c&chksm=fb18f44acc6f7d5cbc680d6ffe8be2e844d492faff5a105437dcc911c260854b4ef3535d7c44&scene=21#wechat_redirect) 70 | 71 | 也可以参考我在星球里写的: 72 | 73 | ``` 74 | https://t.zsxq.com/052z3rzN3 75 | 76 | ``` 77 | 78 | 如果你在服务器上输入下面的命令可以正常返回,说明安装成功了. 79 | 80 | ``` 81 | curl_chrome100 https://www.baidu.com 82 | 83 | ``` 84 | 85 | 把下面的请求 demo 代码上传到服务器: 86 | 87 | ``` 88 | import pycurl 89 | import copy 90 | from io import BytesIO 91 | import re 92 | import io 93 | import random 94 | class Response: 95 | def __init__(self, status_code, body, headers): 96 | self.status_code = status_code 97 | self.body = body 98 | self.headers = headers 99 | @property 100 | def text(self, encode="utf-8"): 101 | return self.body.decode(encode) 102 | class CurlClient: 103 | def __init__(self): 104 | c = pycurl.Curl() 105 | # 自动维护cookie 106 | c.setopt(pycurl.COOKIEFILE, "") 107 | c.setopt(pycurl.TIMEOUT, 30) 108 | # 开启alpn 109 | c.setopt(pycurl.SSL_ENABLE_ALPN, 1) 110 | c.setopt(pycurl.SSL_ENABLE_NPN, 0) 111 | # 跳转 112 | c.setopt(pycurl.FOLLOWLOCATION, 1) 113 | # 处理gzip 114 | c.setopt(pycurl.ENCODING, "gzip,deflate") 115 | # 是否验证ssl 116 | c.setopt(pycurl.SSL_VERIFYPEER, 1) 117 | c.setopt(pycurl.SSLVERSION, pycurl.SSLVERSION_TLSv1_2) 118 | try: 119 | c.setopt(pycurl.SSL_CERT_COMPRESSION, "brotli") 120 | c.setopt(pycurl.SSL_ENABLE_ALPS, 1) 121 | except: 122 | pass 123 | # 设置代理 124 | # c.setopt(pycurl.PROXY, "http://127.0.0.1:9091") 125 | # c.setopt(pycurl.PROXY, "http://127.0.0.1:7890") 126 | # 加密套件 127 | c.setopt( 128 | pycurl.SSL_CIPHER_LIST, 129 | "TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305,ECDHE-RSA-CHACHA20-POLY1305,ECDHE-RSA-AES128-SHA,ECDHE-RSA-AES256-SHA,AES128-GCM-SHA256,AES256-GCM-SHA384,AES128-SHA,AES256-SHA", 130 | ) 131 | self.c = c 132 | self._cookies = {} 133 | def get(self, url, headers=[]): 134 | self.c.setopt(pycurl.POST, 0) 135 | r = self.send(url, headers) 136 | return r 137 | def post(self, url, data, isjson=False, headers=[]): 138 | self.c.setopt(pycurl.POST, 1) 139 | self.c.setopt(pycurl.POSTFIELDS, data) 140 | h = copy.deepcopy(headers) 141 | if isjson: 142 | h.append("Content-Type: application/json") 143 | r = self.send(url, h) 144 | return r 145 | def send(self, url, headers): 146 | body = BytesIO() 147 | resp_header = BytesIO() 148 | h = self.default_headers 149 | h.extend(headers) 150 | self.c.setopt(pycurl.HTTPHEADER, h) 151 | self.c.setopt(pycurl.URL, url) 152 | self.c.setopt(pycurl.WRITEDATA, body) 153 | self.c.setopt(pycurl.WRITEHEADER, resp_header) 154 | self.c.perform() 155 | r = Response( 156 | self.c.getinfo(pycurl.HTTP_CODE), 157 | body.getvalue(), 158 | resp_header.getvalue().decode(), 159 | ) 160 | self.save_cookies(resp_header.getvalue().decode()) 161 | return r 162 | def save_cookies(self, resp_header): 163 | cookies = re.findall("Set-Cookie: (.*?);", resp_header, re.IGNORECASE) 164 | for cookie in cookies: 165 | k, v = cookie.split("=", 1) 166 | self._cookies[k] = v 167 | def set_proxy(self, proxy): 168 | self.c.setopt(pycurl.PROXY, proxy) 169 | @property 170 | def cookies(self): 171 | return self._cookies 172 | @property 173 | def default_headers(self): 174 | h = [ 175 | 'sec-ch-ua: ".Not/A)Brand";v="99", "Microsoft Edge";v="103", "Chromium";v="103"', 176 | "sec-ch-ua-mobile: ?0", 177 | 'sec-ch-ua-platform: "Windows"', 178 | "upgrade-insecure-requests: 1", 179 | "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44", 180 | "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 181 | "sec-fetch-site: none", 182 | "sec-fetch-mode: navigate", 183 | "sec-fetch-user: ?1", 184 | "sec-fetch-dest: document", 185 | "accept-encoding: gzip, deflate, br", 186 | "sccept-language: en-US,en;q=0.9", 187 | ] 188 | return h 189 | def test_curl(): 190 | c = CurlClient() 191 |     resp = c.get("打码了") 192 | print(resp.text) 193 | if __name__ == "__main__": 194 | test_curl() 195 | 196 | ``` 197 | 198 | 运行后,也返回了正常的数据 l 199 | 200 | ![](https://mmbiz.qpic.cn/mmbiz_png/Lll8tx0MDR1Zbneyt19g2s7OyBTpq3cDficdDsyBibN0ic8J5PUrIbic7bk8pxBhVyYyic4Xiayibaa5rYicdFhddjbejg/640?wx_fmt=png) 201 | 202 | 今天的文章就分享到这里,后续分享更多的技巧,敬请期待。 203 | 204 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/Lll8tx0MDR0xtvs5q4zuW5BXvXzbAAAdAxXH7PSebBWJT3U9dXG1XtOSKRVDQqictGWKznl3rusg5MAsGO0D6Lw/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1) 205 | 206 | 欢迎加入知识星球,学习更多 AST 和爬虫技巧。 -------------------------------------------------------------------------------- /simpread-字节对齐.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/n1wHb-RtbbvENRhtyE4naw) 2 | 3 | **“** 字节对齐对于计算机和逆向分析有很大的意义**”** 4 | 5 | ![图片](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3Cw6e08Gt75hEH5Gl0niaFicdznia2nk0HzPtDxLEQxwicibDK9D2yKP7ZNUwIYMibQfuIDBicSj0jdeWgQ/640?wx_fmt=gif) 6 | 7 | 提起字节对齐的印象, 就是大学课程中站在讲台中间的老师所讲述的 “字节对齐是字节按照一定规则在空间上排列,字节 (Byte) 是计算机信息技术用计量存储容量和传输容量的一种计量单位,一个字节等于 8 位二进制数,在 UTF-8 编码中,一个英文字符等于一个字节”, 上面比较官方的介绍了字节对齐的意义, 对于逆向分析过程中还有其他的理解方式。 8 | 9 | ![图片](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3Cw6e08Gt75hEH5Gl0niaFicdznia2nk0HzPtDxLEQxwicibDK9D2yKP7ZNUwIYMibQfuIDBicSj0jdeWgQ/640?wx_fmt=gif) 10 | 11 | 以下操作方式均在 windows xp,vc6.0 编译,32 位的机器上, pack 指令默认是 8。 12 | 13 | 01 14 | 15 | — 16 | 17 | 什么是字节对齐 18 | 19 | 声明一个结构体: 20 | 21 | ``` 22 | struct MyStruct 23 |   { 24 | int a; 25 | char b; 26 | char c; 27 | int d; 28 | }t1; 29 | 30 | ``` 31 | 32 | 按照人类的思维: sizeof(MyStruct)==(4+1+1+4)==10 33 | 34 | 按照人类的思维: a 的 offset 为 0,b 的 offset 为 4,c 的 offset 为 5,d 的 offset 为 6 35 | 36 | 但是按照编译器的思维: 37 | 38 | ``` 39 | #include "stdafx.h" 40 | #include  41 | #define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER) 42 | int main(int argc, char* argv[]) 43 | { 44 | struct MyStruct 45 |   { 46 | int a; 47 | char b; 48 | char c; 49 | int d; 50 | }t1; 51 | t1.a =1; 52 | t1.b = '2'; 53 | t1.c = '3'; 54 |   t1.d = 4; 55 |   printf("%d\n",sizeof(t1)); //12 56 | printf("a offset%d\n",offsetof(MyStruct,a));//a offset 0 57 |   printf("b offset%d\n",offsetof(MyStruct,b));//b offset 4 58 |   printf("c offset%d\n",offsetof(MyStruct,c));//c offset 5 59 | printf("d offset%d\n",offsetof(MyStruct,d));//d offset 8 60 | system("pause"); 61 | return 0; 62 | } 63 | 64 | ``` 65 | 66 | 有没有发现和编译器的不同, 那什么是字节对齐? 67 | 68 | 计算机中内存大小的基本单位是字节(byte),理论上来讲,可以从任意地址访问某种基本数据类型,但是实际上,计算机并非逐字节大小读写内存,而是以 2,4, 或 8 的 倍数的字节块来读写内存,如此一来就会对基本数据类型的合法地址作出一些限制,即它的地址必须是 2,4 或 8 的倍数。那么就要求各种数据类型按照一定的规则在空间上排列,这就是对齐。 69 | 70 | 02 71 | 72 | — 73 | 74 | 计算机是如何字节对齐的 75 | 76 | 字节对齐需要遵从这两条规则: 77 | 78 | (1)  结构体第一个成员的偏移量(offset)为 0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节,也就是说在 windows xp 默认下 79 | 80 | offset 必须满足 81 | 82 | offset % min(pack(8),sizeof(member type)) == 0 83 | 84 | (3)  结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。 85 | 86 | Align 必须满足 87 | 88 | 结构体的总大小 %min(max(sizeof(a)....sizeof(d)),pack(8)) = 0 89 | 90 | ![图片](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3Cw6e08Gt75hEH5Gl0niaFicpeQPNuRuQdWBn3nNQaGNEgkfaszRS3HOyiazAxBibibN5GWcFgU6ia12UQ/640?wx_fmt=gif) 91 | 92 | 拿上面的例子来看: 首先在 windows xp 中 pack 指令为 8, 意思是默认 cpu 默认取值为 8, 93 | 94 | ``` 95 | struct MyStruct 96 |   { 97 | int a; 98 | char b; 99 | char c; 100 | int d; 101 | }t1; 102 | 103 | ``` 104 | 105 | 1. 首先 a 的 offset 为 0 106 | 107 | 2. b 的 offset%min (8,sizeof(b))=0 , 因为 a 的类型为 int 所以 b 的 offset 从 4 记数, 4%1=0, 那么 b 的 offset 为 4 108 | 109 | 3. c 的 offset%min (8,sizeof(c))=0, 因为 b 的 offset 类型为 char 所以 c 的 offset 从 5 记数, 因为 5%1=0, 那么 b 的 offset 为 5 110 | 111 | 4. d 的 offset%min (8,sizeof(d))=0, 因为 c 的 offset 类型为 char 所以 d 的 offset 从 6 记数, 6%4!=0,7%4!=0,8%4=0, 所以 d 的 offset 为 8 112 | 113 | 114 | 根据上面的公式  x %min(,max(4,1,1,4)8)=0 ,x 必须大于等于 12(因为 d 偏移为 8,d 又为 4 字节) 115 | 116 | x 为 12 117 | 118 | 03 119 | 120 | — 121 | 122 | 为什么要字节对齐 123 | 124 | 内存是以字节为单位存放的, 但是在一些 cpu 处理器, 并不是按照一个字节来存取内存的, 处理器一般取字节的个数为 2,4,8,16,32 为单位来存取内存, 我们将这些存取单位成为内存颗粒度。 125 | 126 | 现在我们认为 cpu 是以四个字节作为颗粒度来取字节, 那么该处理器只能够从地址为 4 的倍数开始读取数据。 127 | 128 | 假如没有内存对齐的规则, 数据可以任意存放到内存之中, 假如一个结构体为 129 | 130 | ``` 131 | struct MyStruct 132 | { 133 | char a; 134 | int b; 135 | } 136 | 137 | ``` 138 | 139 | 那么 a 的 offset 为 0,b 的 offset 为 1, 如果你是 cpu 你想取 b 该如何取呢? 140 | 141 | 首先取 0-3 地址的内存, 然后抛弃 0 地址的内存, 留下 1-3 地址的内存, 同时取 4-7 然后用抛弃 5-7 留 4 地址的内存, 这样 b 才被取出来。 142 | 143 | 是不是特别复杂了! 144 | 145 | 如果我们有内存对齐的机制的话: a 的 offset 为 0, b 的 offset 为 4,cpu 仅需要取 4-7 地址的内存可以将 b 取出来。 146 | 147 | 04 148 | 149 | — 150 | 151 | 不同的 pack 对齐值有什么影响 152 | 153 | 上面我们所说的都是 pack 为 8 的情况下, 如果我们人为的可以将 pack 修改为 1 和 2 的情况, 还是用上面的例子 154 | 155 | ``` 156 | struct MyStruct 157 | { 158 | int a ; 159 | char b; 160 | char c; 161 | int d; 162 | } 163 | 164 | ``` 165 | 166 | 1.  #pragma pack(1) 时:  167 | 168 | offset: 169 | 170 | a =0: 初始地址为 0 171 | 172 | 173 |         b=4: 4%min(1,1)=0 174 | 175 |         c=5: 5%min(1,1)=0 176 | 177 |         d=6: 6%min(1,1)=0 178 | 179 | 2. #pragma pack(2) 时:  180 | 181 |         offset: 182 | 183 |         a =0: 初始地址为 0  184 | 185 |         b=4: 4%min(1,2)=0 186 | 187 |         c=5: 5%min(1,2)=0 188 | 189 |         d=6: 6%min(4,2)=0 -------------------------------------------------------------------------------- /simpread-安卓 Svc 穿透获取设备信息 - 简书.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.jianshu.com](https://www.jianshu.com/p/5952c3701e3c) 2 | 3 | 前言: 4 | --- 5 | 6 | 现在市面上改机的软件很多,大部分都是修改 Java 层的一些参数和变量,去修改或者直接反射的方式去 Set 成自己修改过的数据。 7 | 如果通过正常的 API 去获取设备信息的时候,就很容易被欺骗,导致获取的设备信息是被欺骗过的。 8 | 9 | 这时候我们可以通过读取文件的方式去获取设备信息,还需要加 CRC 对底层函数进行判断,很是麻烦 10 | 因为底层的 IO 函数一旦被 Hook,比如 openat 函数,就算读取文件的方式去可能获取的设备也可能是假的。 11 | 12 | ### 这时候有没有一种相对稳定的方式去最真实的设备信息呢? 13 | 14 | 通过 syscall 直接调用 svc 指令的方式让 Linux 切换到内核态,执行完毕以后去直接拿返回结果即可 15 | (systcall 是 Linux 内核的入口,切换到内核态以后,无法被 Hook) 16 | 17 | ### 方案: 18 | 19 | 实现也很简单,提供两种方式: 20 | 21 | * 1,直接调用 libc.so 里面的 syscall 22 | * 2,内联汇编,将 Libc.so 里面的代码抠出来 23 | 本质区别不大 24 | 25 | ### 实现: 26 | 27 | * 方式 1: 28 | 直接调用 syscall 29 | 30 | ``` 31 | std::string FileUtils::getFileText(char *path,int BuffSize) { 32 | 33 | char buffer[BuffSize]; 34 | memset(buffer, 0, BuffSize); 35 | std::string str; 36 | //int fd = open(path, O_RDONLY); 37 | long fd = syscall(__NR_open, path, O_RDONLY); 38 | 39 | //失败 -1;成功:>0 读出的字节数 =0文件读完了 40 | while (syscall(__NR_read,fd, buffer, 1) != 0) { 41 | //LOG(ERROR) << "读取文件内容 " <0 读出的字节数 =0文件读完了 64 | while (read(fd, buffer, 1) != 0) { 65 | //LOG(ERROR) << "读取文件内容 " < 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [blog.seeflower.dev](https://blog.seeflower.dev/archives/207/) 2 | 3 | > 前言本文结合多篇已有文章,基于 iptables + redsocks2 + Charles,最终实现对安卓上特定 APP 进行抓包,且 APP 无感知即 APP 不能通过检查系统代理或者 VPN 来判断是不是有... 4 | 5 | 本文结合多篇已有文章,基于`iptables + redsocks2 + Charles`,最终实现对安卓上特定 APP 进行抓包,且 APP 无感知 6 | 7 | 即 APP 不能通过检查系统代理或者 VPN 来判断是不是有抓包行为 8 | 9 | 首先先保存开机后的 iptables,如果已经修改过,请重启手机 10 | 11 | ``` 12 | iptables-save > /data/local/tmp/iptables.rules 13 | 14 | ``` 15 | 16 | 要恢复 iptables 为之前的规则,则使用如下命令,或者重启手机 17 | 18 | ``` 19 | iptables-restore /data/local/tmp/iptables.rules 20 | 21 | ``` 22 | 23 | 使用如下命令,作用是:将`uid`为`10428`所请求的在`0-65535`端口上的`tcp`流量,转发到`127.0.0.1:16666`,但是排除了来自`127.0.0.1`的请求 24 | 25 | 参考:[iptables 在 Android 抓包中的妙用](https://mp.weixin.qq.com/s/P0ESUUXBmq2aQnrqDHsDaw) 26 | 27 | ``` 28 | iptables -t nat -A OUTPUT -p tcp ! -d 127.0.0.1 -m owner --uid-owner 10428 --dport 0:65535 -j DNAT --to-destination 127.0.0.1:16666 29 | 30 | ``` 31 | 32 | 要实现抓包,其中`127.0.0.1:16666`是一个透明代理的地址 33 | 34 | 然而根据已有文章可知,如果直接设置为 Charles 的透明代理地址,对于 https 将会出现`invalid first line in request`错误,只有 http 的请求数据会被正常解析 35 | 36 | 参考:[利用 Redsocks 解决透明代理的远程抓包问题](https://blog.mythsman.com/post/62791fb4b5467000017d5c6e/) 37 | 38 | 根据文章可知,借助`redsocks`进行转发即可 39 | 40 | 我这里使用的是 [redsocks2](https://github.com/semigodking/redsocks),编译参考:[静态交叉编译 Android 的 redsocks2](https://fh0.github.io/%E7%BC%96%E8%AF%91/2019/12/07/android-redsocks2.html) 41 | 42 | 这里直接使用编译好的即可,也可以考虑自己编译下,下载后解压压缩包 43 | 44 | * [https://fh0.github.io/assets/android-redsocks2.tgz](https://fh0.github.io/assets/android-redsocks2.tgz) 45 | 46 | 创建配置文件,名为`redsocks.conf`,内容如下: 47 | 48 | ``` 49 | base { 50 | log_debug = off; 51 | log_info = on; 52 | log = stderr; 53 | daemon = off; 54 | redirector = iptables; 55 | } 56 | 57 | redsocks { 58 | bind = "127.0.0.1:16666"; 59 | relay = "192.168.1.14:8889"; 60 | type = socks5; 61 | autoproxy = 0; 62 | timeout = 13; 63 | } 64 | 65 | 66 | ``` 67 | 68 | 其中`bind`就是透明代理地址,`relay`就是 Charles 的代理地址,更多详细用法请查阅`redsocks2`的 readme 69 | 70 | 注意配置文件的每一对`{}`后面都应该有一个空行,否则会提示`unclosed section` 71 | 72 | 现在把`redsocks.conf`和`redsocks2_arm64`推送到`/data/local/tmp` 73 | 74 | 然后在 root 用户下运行`redsocks2_arm64`即可 75 | 76 | ``` 77 | adb push redsocks2_arm64 /data/local/tmp/redsocks 78 | adb shell chmod +x /data/local/tmp/redsocks 79 | adb shell 80 | su 81 | cd /data/local/tmp 82 | ./redsocks 83 | 84 | ``` 85 | 86 | 现在不出意外的话,Charles 应该就能看到`uid`为`10428`所对应 APP 的全部`tcp`数据包了(除去来自 127.0.0.1 的请求) 87 | 88 | 如果是只想对特定端口抓包,那么应该使用`-m multiport --dports 80,443`这样来限定一个或者多个端口 89 | 90 | ``` 91 | iptables -t nat -A OUTPUT -p tcp ! -d 127.0.0.1 -m owner --uid-owner 10428 -m multiport --dports 80,443 -j DNAT --to-destination 127.0.0.1:16666 92 | 93 | ``` 94 | 95 | 1. 使用`iptables`将来自特定 uid 的全部 tcp 流量转到指定的透明代理上 96 | 97 | ``` 98 | iptables -t nat -A OUTPUT -p tcp ! -d 127.0.0.1 -m owner --uid-owner 10428 --dport 0:65535 -j DNAT --to-destination 127.0.0.1:16666 99 | 100 | ``` 101 | 102 | 1. 使用`redsocks`将流量转发到正向代理,如 Charles 的 socks5 代理 103 | 104 | **redsocks2_arm64 下载地址如下** 105 | 106 | > [https://fh0.github.io/assets/android-redsocks2.tgz](https://fh0.github.io/assets/android-redsocks2.tgz) 107 | 108 | **redsocks.conf 内容如下** 109 | 110 | ``` 111 | base { 112 | log_debug = off; 113 | log_info = on; 114 | log = stderr; 115 | daemon = off; 116 | redirector = iptables; 117 | } 118 | 119 | redsocks { 120 | bind = "127.0.0.1:16666"; 121 | relay = "192.168.1.14:8889"; 122 | type = socks5; 123 | autoproxy = 0; 124 | timeout = 13; 125 | } 126 | 127 | 128 | ``` 129 | 130 | 其他补充: 131 | 132 | * 安装 Charles 证书到系统分区,Charles 才能解密 https 133 | 134 | 如果你发现了文章中的错误,请指出 135 | 136 | ![](https://blog.seeflower.dev/usr/uploads/2023/02/2884810608.png) 137 | 138 | ![](https://blog.seeflower.dev/usr/uploads/2023/02/2283399819.png) 139 | 140 | * [iptables 在 Android 抓包中的妙用](https://mp.weixin.qq.com/s/P0ESUUXBmq2aQnrqDHsDaw) 141 | * [利用 Redsocks 解决透明代理的远程抓包问题](https://blog.mythsman.com/post/62791fb4b5467000017d5c6e/) 142 | * [静态交叉编译 Android 的 redsocks2](https://fh0.github.io/%E7%BC%96%E8%AF%91/2019/12/07/android-redsocks2.html) 143 | * [redsocks2](https://github.com/semigodking/redsocks) 144 | * [默认配置文件报错](https://github.com/semigodking/redsocks/issues/131) 145 | * [redsocks 搭配 iptables 实现真全局代理](https://anuoua.github.io/2020/05/28/redsocks%E6%90%AD%E9%85%8Diptables%E5%AE%9E%E7%8E%B0%E7%9C%9F%E5%85%A8%E5%B1%80%E4%BB%A3%E7%90%86/) 146 | 147 | 148 | **广告:星球 & 加群交流** 149 | ![](https://blog.seeflower.dev/images/zsxq.png) -------------------------------------------------------------------------------- /simpread-对 APP 逆向抓包的实践.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=Mzk0MjIxNzgwNg==&mid=2247485847&idx=1&sn=3853fc474f20ee8458337d00576c33c9&chksm=c2c7c6b6f5b04fa02305526ca1bf3cffbd9060df0deed070cced16202b3cd82b9ab4c9892266&mpshare=1&scene=1&srcid=0803sb0RwLZL1lK5lAtpglyY&sharer_sharetime=1659491361355&sharer_shareid=9be5daf09995ef938577edacf59663a3&version=4.0.6.99102&platform=mac#rd) 2 | 3 | ![](http://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wrIJ0a6Vfic8AnrKAUMuscw7NI0MZJHvddaGYdjArhsDvESBdpiaPlicyXTD5ZNpNicJo9JWHGZAkxOJQ/0?wx_fmt=png&wx_head=1) ** 编码安全 ** 专注于编程安全技术的分享。 48 篇原创内容 公众号 4 | 5 | 6 | 7 | 8 | 9 | 背景基础 10 | 11 | 12 | 13 | 14 | 15 | 在对某音国际版的 APP 抓包分析数据过程中,**发现该 APP 使用了自定义的 SSL 框架**,这就大大提高直接利用现有的框架抓包分析的成本。 16 | 17 | 现成的 Xposed 框架的 JustTrustMe 模块、frida 框架的 r0capture 脚本均无法正常抓包。因为在于这些模块脚本,主要针对的是系统原生的 SSL 框架,而该 APP 采用自定义了自己的 SSL 框架。那么如果我们需要对 APP 进行抓包的话,就需要过掉该 APP 的检测机制从而实现抓包数据分析。 18 | 19 | 下面从 SSL 框架原理,以及通过两个方式实现对 APP 正常抓取数据包功能。 20 | 21 | 22 | 23 | 24 | 25 | SSL 框架原理 26 | 27 | 28 | 29 | 30 | 31 | **SSL 全称是 Secure Scoket Layer 安全套接层,它是一个安全协议,它提供使用 TCP/IP 的通信应用程序间的隐私与完整性,**它是一种为网络通信提供安全以及数据完整性的安全协议,再传输层对网络进行加密,因特网的超文本传输协议(HTTP)使用 SSL 来实现安全的通信。 32 | 33 | SSL 记录协议:为高层协议提供安全封装,压缩,加密等基本功能 34 | 35 | SSL 握手协议:用与再数据传输开始前进行通信双方的身份验证、加密算法协商、交换秘钥。 36 | 37 | 在客户端与服务器间传输的数据是通过使用对称算法(如 DES 或 RC4)进行加密的。公用密钥算法(通常为 RSA)是用来获得加密密钥交换和数字签名的,此算法使用服务器的 SSL 数字证书中的公用密钥。有了服务器的 SSL 数字证书,客户端也可以验证服务器的身份。 38 | 39 | SSL 协议的 1 和 2 版本 ,它只提供服务器认证,而第 3 版本添加了客户端认证,此认证同时需要客户端和服务器的数字证书。 40 | 41 | 42 | 43 | 44 | 45 | SO 文件修改法 46 | 47 | 48 | 49 | 50 | 51 | **该 APP 防止抓包主要通过自定义 SSL 校验来实现,那么我们只要将 APP 中检验的最终结果修改成校验通过就可以正常的抓包。** 52 | 53 | 通过对 APP 组成结构和代码的分析,该 APP 中自定义 SSL 校验是在 libsscronet.so 的文件中,那我们就可以直接将 APP 重命名为 zip 后缀,然后直接解压。解压后得到很多文件,这些 so 文件都是存在 lib 目录。 54 | 55 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BzW56Qia0OybZW0fTwRYUdHP0rGgyeJh1YfSx84eFemoQ9pS4OxVX0BQ/640?wx_fmt=png&random=0.5107817715937286&random=0.4437142885558565&random=0.8643985682626978&random=0.18358382178224653&random=0.9200225867679888&random=0.8675189315024432) 56 | 57 | 接下来就需要通过利用 IDA 静态分析工具分析和 Hex_Editor 工具结合进行对 so 文件做修改。 58 | 59 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BGPVicbRwliac3XnaYLShvydkbJl7OyUZiamXoxfqogibR2fTwQRGADgVPA/640?wx_fmt=png&random=0.35517003889110077&random=0.6553432919727913&random=0.7273709749339956&random=0.8510310523947129&random=0.5518901220988996&random=0.8760782990948659) 60 | 61 | 在 IDA 中通过字符串窗口中搜索关键词 verifycert,通过上图可以搜索到该关键词相关的,那么接下来就是通过代码交互引用然后跳转到代码调用的地方。 62 | 63 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5Ba8he27kWkqfGYZHQpruPDSxA37jMouq3MUIsLhpJE0lXWJVGecWjicg/640?wx_fmt=png&random=0.9094629515821568&random=0.4895706973170193&random=0.9244700129345349&random=0.7000395376667508&random=0.6568506727514654&random=0.520025817421617) 64 | 65 | 上图的伪代码中,可以看到该函数中的调用关键字符串的地方,那么在这个调用的地方 return 0 的时候就表示校验成功, 66 | 67 | 那么我们只需要把图中的 return 1 修改为 0 即可。 68 | 69 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BySAvahj7ib0RcGyRL7lg2lCfXwfyFtbicrvpXHS8UNsNNDwiaTcY17Ofw/640?wx_fmt=png&random=0.8129390935107774&random=0.6351110302036673&random=0.24495117746823247&random=0.6141147575659427&random=0.840192550082514&random=0.3619511396648625) 70 | 71 | 通过借助 Hex_Editor 工具,将刚才 IDA 工具分析到到 return1 改成 return0,那么重新保存下就可以了。 72 | 73 | 最后将修改后的 SO 文件替换到 APP 安装的原目录下那么就可以开始抓包分析了。 74 | 75 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BZiauSIYYtBJs4c9UlqPL2B7xpQT0of9c9WSjWJtRy9HHnyYjv5U9Vfw/640?wx_fmt=png&random=0.008995321819032398&random=0.37359076756167586&random=0.01813576684058238&random=0.8781896600599726&random=0.39897848343171827&random=0.4570350258307949) 76 | 77 | 上图是通过 Fiddler 抓包工具进行抓包分析的,Fiddler 抓包还得进行证书安装才能正常抓包。 78 | 79 | 80 | 81 | 82 | 83 | HOOK 功能函数法 84 | 85 | 86 | 87 | 88 | 89 | 直接将 APP 拖进 jadx 反编译工具,**从代码中发现 APP 使用的网络请求框架是 Retrofit,而 Retrofit 网络请求框架底层使用依旧是 Okhttp,**那么 Hook 的切入点可以从拦截器或者初始化时传入的 OkClient 入手。 90 | 91 | 通过 jadx 反编译,从代码中确认了 APP 初始化 Retrofit 的位置 92 | 93 | 是 com.bytedance.ies.ugc.aweme.network.RetrofitFactory。 94 | 95 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5Bm3YCadAKhchIcgl32MyHkiczOCtm9vz98EqPNKjRVDQNpIxbpq0pDBA/640?wx_fmt=png&random=0.790947925377137&random=0.5817932383017681&random=0.20772493595176234&random=0.5732521932351302&random=0.5372199084136549&random=0.7273032160455042) 96 | 97 | Hook 代码的方式目前常见的就是用 frida 和 xposed 这两个框架进行 hook 功能 98 | 99 | 这边主要以 frida 框架进行 hook 功能,经过对这个类多次代码逻辑梳理及 Hook,找到了 X.MBJ 的类,该类中的 LIZ 方法的返回值 c 内有请求的所有数据,猜测可能是 response。 100 | 101 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BXp0G4ZF8qyPdXRYJXzDgaGL5XRMIqpLXaPzVqvNjbiax8IBKcGvbqQQ/640?wx_fmt=png&random=0.9630573088008707&random=0.11637566545947164&random=0.23388326649390057&random=0.09243183104271413&random=0.8422957919620995&random=0.6422487997237531) 102 | 103 | 下面开始基于 frida 进行编写 python 和 js 脚本 104 | 105 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BjyRZJKBTdfaMxwpWib31icuDSdouAC4AvGsEfnUQ0MV6k7OO0G5pJO0Q/640?wx_fmt=png&random=0.1427284593095821&random=0.4692418505925531&random=0.9860466656377482&random=0.466158383641174&random=0.4523998712972561&random=0.6003925241125072) 106 | 107 | 上图是 python 脚本 108 | 109 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BqgkVJ1xCa6jibA9NfG0j9kd4AcN5TXdKd7r9asfNWJbQE08m2bZqHqg/640?wx_fmt=png&random=0.4047366034347857&random=0.9930011759853525&random=0.10415683551432875&random=0.9125371619075107&random=0.696552286917453&random=0.5230606460216505) 110 | 111 | 上图是 javascript 脚本 112 | 113 | 通过启动 frida 运行后的效果如下图 114 | 115 | ![](https://mmbiz.qpic.cn/mmbiz_png/haqlQiars3wqFDiaaHbXheIJJQ2jM4bB5BSkhE1Mf3Xt7m5KuO4bkRYlrSW5yiafUMicKLOH0AVcu5ty5S9DzRSstw/640?wx_fmt=png&random=0.17586450933662023&random=0.6164356599830527&random=0.9437772144385197&random=0.5097234741424235&random=0.328122564821667&random=0.6488563689915949) 116 | 117 | 从下面数据中以及可以看到解析到通信数据了,这样的 hook 功能效果就达到,接下来就可以开始用抓包工具进行抓包分析了。 118 | 119 | 参考借鉴 120 | 121 | https://www.jianshu.com/p/efd56ad78499 122 | 123 | https://juejin.cn/post/7122723176387346469 124 | 125 | 结束 126 | 127 | **【精彩阅读】** 128 | 129 | [**android 逆向渗透测试必备清单**](http://mp.weixin.qq.com/s?__biz=Mzk0MjIxNzgwNg==&mid=2247485813&idx=1&sn=e61530dfd289ea5effed0c8f817a27c9&chksm=c2c7c654f5b04f42afc7740c5f3f7497b0536e7ac03926b18c0d3989eb715cf59dbd4089bc9c&scene=21#wechat_redirect) 130 | 131 | [**android 安全之 frida 框架**](http://mp.weixin.qq.com/s?__biz=Mzk0MjIxNzgwNg==&mid=2247485822&idx=1&sn=1a8dd86af405e845963b68ae199b478a&chksm=c2c7c65ff5b04f490643cd60781bd63202084e67b8b6aeddab51f050eada6ffa17404d2ac4a8&scene=21#wechat_redirect) 132 | 133 | [**对某 APP 加固逆向的分析**](http://mp.weixin.qq.com/s?__biz=Mzk0MjIxNzgwNg==&mid=2247485784&idx=1&sn=9ae66a1ab7e2cae738f0c62d9c8d5795&chksm=c2c7c679f5b04f6fd1baf685bb83f90200c69bf121cf18481c7890c3f7282f70a8c3aeb1c7bf&scene=21#wechat_redirect) -------------------------------------------------------------------------------- /simpread-对 Flutter 开发的某 App 逆向分析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=Mzg2NzUzNzk1Mw==&mid=2247492329&idx=2&sn=8dbf8e6292d094217e97f2056ae31e4d&chksm=ceb8a7a7f9cf2eb1085c554d939be7abac67f36863bae660ef5c06144e1574cdebad948e690b&mpshare=1&scene=1&srcid=0610jyosBxoUxDMn9H1oDPEt&sharer_sharetime=1654839849678&sharer_shareid=9be5daf09995ef938577edacf59663a3&version=4.0.6.99102&platform=mac#rd) 2 | 3 | 哆啦安全 ,赞 30 4 | 5 | 前言 6 | 7 | ----- 8 | 9 | 最近总感觉时间不够用,很多东西都堆着,答应给朋友看的某 app,也没来得及看。所以也就一直没有空发文章,当然我也承诺过,尽量保证文章的质量,所以不能随便水文章,要嘛就不发,要嘛就发质量我觉得过得去的。 10 | 11 | 这不刚好有个 app,可以记录一下 12 | 13 | app 名: 14 | 15 | ``` 16 | dGVj{deleteme}aC5lY{deleteme}2hvaW5nLm{deleteme}t1cmls 17 | 18 | ``` 19 | 20 | 开始抓包 21 | ---- 22 | 23 | 一开始用 charles 链接 wifi 代理去抓包这个 app,发现只能抓到一点点无关紧要的包,需要的数据接口是抓不到的: 24 | 25 | 抓包工具显示的是 connect: 26 | 27 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE0PWSwQiaPWVwBUV4ccwQYNTg8vIqREEcjBiaiayA5kJOBZZEUsy4z12icg/640?wx_fmt=png) 28 | 29 | app 显示界面也是空的: 30 | 31 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEZn52G7icW7icYqZMFYtLpwj7Os94u3RMzWib2P5iaC71IayPHHQjvXmFag/640?wx_fmt=png) 32 | 33 | 所以以为 APP 可能设置了 no_proxy,使用 postern 开启 VPN,因为 VPN 抓包的方式可以解决 no_proxy 的问题,但结果 toast 提示【null】,以为 APP 开启了双向验证,逐一去尝试解决,情况跟上面一样,都没有抓到包。 34 | 35 | 那么用 r0capture 抓,也没有,很骚啊 36 | 37 | 抓不到包,感觉直接一头雾水啊 38 | 39 | 尝试 hook 打印堆栈,突然看到了 flutter 的字样,才意识到这有可能是用 flutter 开发的 APP。 40 | 41 | 什么是 flutter 42 | ----------- 43 | 44 | Flutter 是 Google 使用 Dart 语言开发的移动应用开发框架,使用一套 Dart 代码就能快速构建高性能、高保真的 iOS 和 Android 应用程序。 45 | 46 | 由于 Dart 使用 Mozilla 的 NSS 库生成并编译自己的 Keystore,导致我们就不能通过将代理 CA 添加到系统 CA 存储来绕过 SSL 验证。 47 | 48 | flutter 打包的 apk,会把核心逻辑放在 so 层,且 SSL Pining 也在 Native 层,这就导致没法抓包 49 | 50 | 怎么判定 app 是 flutter 开发的 51 | ---------------------- 52 | 53 | 把 apk 包复制一份,后缀改为 zip,然后解压,进入 lib 目录,如果看到有 libflutter.so,那就是 flutter 开发的了,否则则不是 54 | 55 | 而今天这个 app,查看,确实是 flutter 了 56 | 57 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEAeYjSmdgwKLlvHJ3jp2LXNibajk2o7kiaAQnQABUBZhabqicOBg2M76yg/640?wx_fmt=png) 58 | 59 | 反抓包分析 60 | ----- 61 | 62 | 为了解决这个问题,就必须要研究 libflutter.so 了 63 | 64 | **当然我搞得这个 app,不是很难那种** 65 | 66 | 一开始是在看雪里找到这个帖子: 67 | 68 | https://bbs.pediy.com/thread-261941.htm 69 | 70 | 然后跟着操作了一下,在打开 app 的包文件,用 IDA 去打开 libflutter.so(注意 32 位与 64 位) 71 | 72 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEvicEgyVvyOpeLGZcm2x1MQjyCKKakODXibTH94WiakRiaEKP5iar40lenkA/640?wx_fmt=png) 73 | 74 | 然后点 search->text 75 | 76 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEr6BgGIpHle1INrCnasia6lbBBv78oA5bHUGdpITs1Fq8OsSHtKKxsiag/640?wx_fmt=png) 77 | 78 | 输入 ssl_client, 79 | 80 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEdfdugCZlibMKYtyRUkNFgOvyNyOxIIsNcbJibicBpfeoXWTmJj00xAHbw/640?wx_fmt=png) 81 | 82 | 点 ok,等一会儿,找到有【Rx,PC :"ssl_client"】之类的字眼 83 | 84 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE9FgH9ibTxTbhziaz1BsmAen4rhmCBOiaNb3JdeOUXfl5QQ8QspUTGg33w/640?wx_fmt=png) 85 | 86 | 点进去,进入这里: 87 | 88 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEDWvUUicHU4mEA0BLkmapNWJQ0dddW0r6ACvIuTRVADVGSdVJjbYibX2w/640?wx_fmt=png) 89 | 90 | 按下空格键 91 | 92 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEElYF8ezJ9ZGTmBMep6Ww1K0icbWNPGMru50JDqGLiatMuIM3ib4MRp5YA/640?wx_fmt=png) 93 | 94 | 这时候,点 ida 里的菜单栏,options->General,把这个由 0 改成 4(这个步骤我找了很久,请教了奋飞大佬找到的,ida 操作不熟没办法) 95 | 96 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEgjuibLBXDiaNBDhWkCZwOn5vSBbEicoYKVF0MTroPDWjdBhMibac4s7rJA/640?wx_fmt=png) 97 | 98 | Number of opcode bytes 设置为 4 99 | 100 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEgWmVov8WJYficFTL800R6lGkDqT8lvKicEGFXb6lGSIGDU4LeibMiaxUTA/640?wx_fmt=png) 101 | 102 | 点 ok,现在再看,已经多了点东西了 103 | 104 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEU8YtsicqXeIraOIvjOovOoLN6GlGToK3861Idxk5Mg2B2Do5T7d1Nnw/640?wx_fmt=png) 105 | 106 | 然后当前位置往上找,找到有_unwind 开头的,停下来,从_unwind 开始,拿到字符串前面 10 个字符(也可以大于 10),照这个前 10 个字符,我尝试好久才知道是要从这个 unwind 开始拿字符串,而不是在找到 ssl_client 位置开始拿前 10 个字符串,上面看雪那个帖子也没说清楚(也可能是我菜,没读懂)。 107 | 108 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE0Nqg3VO2ah7T08ohwsfiaPlxQQ0QDIOxMg29nZJX0391aZDRRibYPPGw/640?wx_fmt=png) 109 | 110 | 用那几个字符串,加上 flutter 的 hook 脚本,配合 drony 或者 postern 就可以抓包了(亲测过,postern 一样可以抓,配置起来没有 drony 繁琐): 111 | 112 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEncNFBh7zLtAcXBAhsvMImUPKY8Yzxmqv6fIsMrJIRwQL2TtPepXUEw/640?wx_fmt=png) 113 | 114 | 用 frida hook attach 模式启动,同时重新刷新页面 115 | 116 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE08ia7JOOrWTFjSibedNYDWzlxBurwzaia9JQbAxXQheURKpicVia5ibHMWaQ/640?wx_fmt=png) 117 | 118 | 果然抓到了包,而且 app 页面也正常显示数据,不好意思打的码有点厚,不过不重要,这种结果就是有数据的意思 119 | 120 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEzUG5ibEkwlFhCjhrvicRanjp21Goicib0PMLicFDsNNXmzbLgrvfaSibYM1g/640?wx_fmt=png) 121 | 122 | ok,抓包搞定了,现在就看看有没有啥加密参数了 123 | 124 | 加密参数逆向 125 | ------ 126 | 127 | 再看刚才的接口,就一个加密字段 sign: 128 | 129 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYErceFkbDxXwwcXiboL6dA6We63ESCbXH3L4YQ1nrdumThBmkkBicPia35g/640?wx_fmt=png) 130 | 131 | 所以,找找这个怎么生成的即可,打开 jadx,搜索: 132 | 133 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEtc4TepO46VDuqUKwHHIh6VzkyZqpZa1JeNLE8F23F1VoQrA6w6AEWQ/640?wx_fmt=png) 134 | 135 | 就两个地方可疑,点进去: 136 | 137 | 第一个 138 | 139 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEI2lRtXBAlwgjKotFh4gqGWyDptG2vsffvIWVaNDQmoicNuWM3b93PPQ/640?wx_fmt=png) 140 | 141 | 第二个: 142 | 143 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEZz94hmuj5cFygiadc2AMy2vIydibmiblVvQ8pk2SaKZUSicnpnfTu7Km9g/640?wx_fmt=png) 144 | 145 | 而实际上,第一个里返回的最后也调用了第二个里的,所以实际就是第二个了,写个脚本对它进行 hook 操作: 146 | 147 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEkRovTCPOKqBMICEugOAATk6w42icEnjPREk6Vib9zMJico3O4qamR5NLg/640?wx_fmt=png) 148 | 149 | 看结果,对照下抓包工具里的结果: 150 | 151 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE2PEqqiciaXg0E5RVfOIbSVU7cLUfMlicxia00WLCic2mL7vek3aYbwzVZYw/640?wx_fmt=png) 152 | 153 | 有点长,我复制出来对比下,一模一样,那就是这里了 154 | 155 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYESlsIHxgL3VmTWG1XeNwgNJ8kVjJH9zKviao4HnYiaEjPtmMhDG6gJSXA/640?wx_fmt=png) 156 | 157 | 相信你发现了,这后续流程跟常规的 app 逆向没有任何区别,那是因为这个 app 并不难,如果是那种所有逻辑都在 so 层,就难搞了,这里的加密逻辑还是在 java 层,只是一开始的抓包就把部分朋友难住了 158 | 159 | 不废话,继续看,这个看来就是个 rsa 加密了,当我正要分析的时候,程序意外崩溃了,这个不要紧,估计没有返回正常的值导致的,那行,既然找到就这个位置了 160 | 161 | 打印堆栈跟下加密逻辑,顺便看到了加密的 privateKey: 162 | 163 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEI8nv7W4KtniabibXRpJDIDvPvQ69H9TTvPes3o4oGmQ21L5UuzibIlDHg/640?wx_fmt=png) 164 | 165 | 而其实刚才那段源码里,这个 key 其实就在了 166 | 167 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEQicoqicTQXvjNy6E881SylMuibBktsibHFQ7dukgQB26iaYiaiaocqaiboibVhQ/640?wx_fmt=png) 168 | 169 | 接下来就找传进去的第一个参数了,回到刚才的逻辑,第一个参数是这个, 170 | 171 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEVXFbaWPyDwS4ZoIxmMyhQgibfWmaP98iaiaaALUWeUw0ta9nWfa0BZc2Q/640?wx_fmt=png) 172 | 173 | 直接对这个方法进行 hook: 174 | 175 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEHTBwaicl7r3wCG5rC82JecJmn5oia06rWVbCkwEFV12NvLY2ac8Buhvg/640?wx_fmt=png) 176 | 177 | 这 str 不是很眼熟吗,就是一个 url 啊 178 | 179 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE42w9hibuLOf1tf9DEv6tZ8VYmj8sDau1Fg8yT1vGib9n7fzP8qqvFIhw/640?wx_fmt=png) 180 | 181 | 那这个基本也稳了,不过注意的是,有的 url 里面的参数里就多了个开始时间,结束时间,和当前时间 182 | 183 | python 代码还原 + 验证 184 | ---------------- 185 | 186 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYEU7LWDibJ6sOraXTQFRjN9N7Miawq8WbsAEORIyBFJkax659iaMIcmCdFw/640?wx_fmt=png) 187 | 188 | hook 的结果: 189 | 190 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE4KIgjAfxHiaibApDFWno2DtxBVMoIvmneoBLY3XtfooXM20g4WmQHqSA/640?wx_fmt=png) 191 | 192 | 对比下,完美,一模一样 193 | 194 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXUjwzq5lCqicPtwIo1GngaYE2TpjLZ7dB7OPic0icDR98qJj9RFodo3FGEfzRHiamk6GA7xux8KKBl6Fw/640?wx_fmt=png) 195 | 196 | 然后这个接口有的是带了时间的,有时间的加密结果也一样,我就不贴图了 197 | 198 | python 代码,(感谢成都逆向天团里颜值最高的徐少给的代码),我就不自己重写了 199 | 200 | ``` 201 | from Crypto.PublicKey import RSA 202 | from Crypto.Signature import PKCS1_v1_5 203 | from Crypto.Hash import SHA256 204 | import base64 205 | import warnings 206 | warnings.filterwarnings("ignore") 207 | def RSA_sign(data, privateKey): 208 | private_keyBytes = base64.b64decode(privateKey) 209 | priKey = RSA.importKey(private_keyBytes) 210 | signer = PKCS1_v1_5.new(priKey,) 211 | hash_obj = SHA256.new(data.encode('utf-8')) 212 | signature = base64.b64encode(signer.sign(hash_obj)) 213 | return signature 214 | def main(startTime=None, endTime=None): 215 | data = '/cactus-api/posts/by-tagtagIds%5B%5D=4&orderBy=updatedAt&offset=10&limit=101648403473200' 216 | privateKey = '''自己用jadx打开放到这里吧,太长了,占篇幅''' 217 | res_sign1 = RSA_sign(data, privateKey) 218 | signature = res_sign1.decode('utf8') 219 | print(signature) 220 | if __name__ == '__main__': 221 |     startTime = 1648051200 # 有时间的接口这么用 222 | endTime = 1648137599 # 有时间的接口这么用 223 | main(startTime, endTime) 224 | 225 | ``` 226 | 227 | 题外话 228 | --- 229 | 230 | 对了,网洛者爬虫练习平台的站长 LeeGene,他读了 python 的 requests 源码后自己用 golang 实现了个 requests 库:https://github.com/wangluozhe/requests,支持 http2.0+ja3 指纹修改,经作者自己测试,可以过猿人学内部题 22 题 + 29 题,赶紧学起来。 231 | 232 | 结语 233 | -- 234 | 235 | 终于算是对 flutter 有个大概的逆向操作流程了,其实本次目标并不难,如果是那种很难的就难搞了。这个 app 其实还有某盾加固,但由于不是一线大厂加固,我直接忽略加固一样操作 236 | 237 | 那遇到那种难的,把大部分的逻辑都直接编译在 so 层的,怎么办?dart 反编译吧,但是反编译的效果不是很好,目前没法通杀,只能多方尝试了 238 | 239 | 参考链接 240 | ---- 241 | 242 | > http://91fans.com.cn/post/fruittwo/ 243 | > 244 | > https://github.com/google/boringssl 245 | > 246 | > https://rloura.wordpress.com/2020/12/04/reversing-flutter-for-android-wip/ 247 | > 248 | > https://github.com/rscloura/Doldrums 249 | > 250 | > https://bbs.pediy.com/thread-261941.htm 251 | > 252 | > https://mp.weixin.qq.com/s/Ad0v44Bxs1LFy93RT_brYQ 253 | > 254 | > https://tinyhack.com/2021/03/07/reversing-a-flutter-app-by-recompiling-flutter-engine/ 255 | > 256 | > https://blog.tst.sh/reverse-engineering-flutter-apps-part-1/ 257 | > 258 | > https://blog.tst.sh/reverse-engineering-flutter-apps-part-2/ 259 | > 260 | > https://github.com/hellodword/xflutter/blob/main/snapshot-hash.csv 261 | > 262 | > https://raphaeldenipotti.medium.com/bypassing-ssl-pinning-on-android-flutter-apps-with-ghidra-77b6e86b9476 263 | > 264 | > https://github.com/G123N1NJ4/c2hack/blob/0f85a9b0208e9ee05dcbfb4fbcebd1c7babc4047/Mobile/flutter-ssl-bypass.md 265 | > 266 | > https://github.com/ptswarm/reFlutter?msclkid=41b563c4ab7a11ecad34e0a8139bac19 267 | > 268 | > https://github.com/mildsunrise/darter 269 | > 270 | > flutter app 的流量监控和逆向库 271 | > 272 | > https://github.com/ptswarm/reFlutter 273 | > 274 | > dart 反编译,只能编译 2018 年前的 275 | > 276 | > https://github.com/hdw09/darter 277 | 278 | 哆啦安全 ,赞 16 279 | 280 | **推荐阅读** 281 | 282 | [Android JNI 动态库逆向](http://mp.weixin.qq.com/s?__biz=Mzg2NzUzNzk1Mw==&mid=2247492242&idx=1&sn=55e534a3701ec1929ebb146807f379d3&chksm=ceb8a7dcf9cf2ecacd30bb469adacadb56308e8a6367c1f8d913bf342f8ed31ad97398832384&scene=21#wechat_redirect) 283 | 284 | [JNI 与 NDK 编程 (基础到精通) 最全总结](http://mp.weixin.qq.com/s?__biz=Mzg2NzUzNzk1Mw==&mid=2247489894&idx=1&sn=59e1f782914a3c46833d563e96d03501&chksm=cebb5c28f9ccd53e81d919fdd5ee5904d2df7edc15e90cf9acc499a44036f665b89ef837fbdc&scene=21#wechat_redirect) 285 | 286 | [Android 和 iOS 逆向分析 / 安全检测 / 渗透测试框架 (建议收藏)](http://mp.weixin.qq.com/s?__biz=Mzg2NzUzNzk1Mw==&mid=2247492319&idx=1&sn=3b5396e0d19dd5583466c34116bc0ce7&chksm=ceb8a791f9cf2e871bfe88c9635a13b1ed16be6764eea51bd2c6c53ec85859712aad68cf2053&scene=21#wechat_redirect) -------------------------------------------------------------------------------- /simpread-当 Xiaomi 12 遇到 eBPF.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=MzU5ODgyOTUzNw==&mid=2247484156&idx=1&sn=8493384dc6489fc0237a6f9366d0e811&chksm=febf7272c9c8fb64b81caa7a2c524277a909cfa3e171a133dd2c0bd68cb8af1b4f4d2406957b&mpshare=1&scene=1&srcid=0622k2pUFLoJBXLoRwVTyMSa&sharer_sharetime=1655876814783&sharer_shareid=9be5daf09995ef938577edacf59663a3&version=4.0.6.99102&platform=mac#rd) 2 | 3 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/PicMqQs6T6MPPjqgFChxkZVTyOa8cGqcQnxqvu7ozmhNElGibaibXZggx4CZ4XKZvXkjjLESqjSKFuawgff4hlWicQ/640?wx_fmt=jpeg) 4 | 5 | 最近有大佬在 android 上实践 ebpf 成功 6 | 前有 evilpan 大佬:https://bbs.pediy.com/thread-271043.htm 7 | 后有 weishu 大佬:https://mp.weixin.qq.com/s/mul4n5D3nXThjxuHV7GpMA 8 | 当然还有其他隐藏的大佬啦,就不一一列举啦 9 | 遂 android-ebpf 大火 10 | 两位大佬的方案也很有代表性,一个是 androdeb + 自编内核 + 内核移植 + 内核 4.19(文章中看的),一个是 androdeb + 内核 5.10(pixel 6) 11 | 目前来看,androdeb + 高版本内核 方案可以更快上手,花钱投资个新设备就好了,而且 weishu 大佬也已经手把手把工具都准备好了 12 | 故本次就是对 weishu 大佬视频号直播的 "搭建 Android eBPF 环境" 的文字实践 + 反调试样本测试 13 | 14 | eBPF 是啥 15 | ------- 16 | 17 | > 来自大佬的总结:https://mp.weixin.qq.com/s/eI61wqcWh-_x5FYkeN3BOw 18 | 19 | 失败尝试 20 | ---- 21 | 22 | > 魅族 18 内核版本 5.4 23 | > 虽说环境编译成功了,但体验脑壳疼 24 | > opensnoop 没有 path 25 | > execsnoop pwd 命令监控不到,长命令被截断 26 | 27 | 环境准备 28 | ---- 29 | 30 | > PC 环境:macOS 31 | > 小米 12 内核版本 5.10.43 32 | > magisk 提供 root 33 | > androdeb 连接方式选取的也是 ssh 方式,故安装 SSH for Magisk 模块提供 ssh 功能 34 | > 手机最好也科学上网一下吧,要 git 拉一些东西 35 | 36 | 环境准备 over,开干 37 | ------------ 38 | 39 | > #### 确保手机 ssh 已开启,先去 adb shell 中 ps 一下 40 | > 41 | > ps -ef|grep sshd 42 | > 43 | > #### 没问题的话,就查看下 PC 上的 ssh key 44 | > 45 | > cat ~/.ssh/id_rsa.pub 46 | > 47 | > #### 然后把 key 粘贴到手机 authorized_keys 文件中,再改下权限 48 | > 49 | > su 50 | > cd /data/ssh/root/.ssh/ 51 | > /data/adb/magisk/busybox vi authorized_keys 52 | > chmod 600 authorized_keys 53 | > 54 | > #### 再看下手机 ip(因为是 ssh 连接,故手机和 PC 在同一局域网下) 55 | > 56 | > ifconfig |grep addr 57 | > 58 | > #### 在 PC 上测试下 ssh 是否可以成功连接 59 | > 60 | > ssh root@手机 ip 61 | > 62 | > #### 没问题的话,直接开搞准备好的 androdeb 环境了(weishu 大佬用 rust 重写了叫 eadb) 63 | > 64 | > sudo chmod 777 ./eadb-darwin 65 | > ./eadb-darwin --ssh root@手机 ip prepare -a androdeb-fs.tgz 66 | > 67 | > #### 等待完成后,进 androdeb shell, 开始编译 bcc 68 | > 69 | > ./eadb-darwin --ssh root@手机 ip shell 70 | > git clone https://github.com/tiann/bcc.git --depth=1 71 | > cd bcc && mkdir build && cd build 72 | > cmake .. make -j8 && make install 73 | > 74 | > #### 等待成功后,就有各种工具可以用了 75 | 76 | > #### 👆👆👆得益于 weishu 大佬的手把手环境工具包,androdeb + 内核 5.10 的 eBPF 环境搭建起来就是这么简单 77 | 78 | 反调试样本实操 79 | ------- 80 | 81 | > DetectFrida.apk 核心逻辑: https://github.com/kumar-rahul/detectfridalib/blob/HEAD/app/src/main/c/native-lib.c 82 | > 83 | > #### 哎😆,这里我直接就拿山佬的实践来说,至于为啥后面再说 84 | > 85 | > ![](https://mmbiz.qpic.cn/mmbiz_jpg/PicMqQs6T6MPPjqgFChxkZVTyOa8cGqcQtnYibEB7jhbjzNic3UINAtbicvic3tY97VAShcrfGku83OXEicvvZrS28Zg/640?wx_fmt=jpeg) 86 | > 87 | > ![](https://mmbiz.qpic.cn/mmbiz_jpg/PicMqQs6T6MPPjqgFChxkZVTyOa8cGqcQv7bXDDgKks9DUjKMTwQJPic7dOV3DlHGsSk4OfiaialiczOdMmGrkvDjJQ/640?wx_fmt=jpeg) 88 | > 89 | > #### 还少了一个关键的 90 | > 91 | > ![](https://mmbiz.qpic.cn/mmbiz_jpg/PicMqQs6T6MPPjqgFChxkZVTyOa8cGqcQNuw5FuAfzqysVDib81sN1k43s8rBqnUl2bcjK2MUMcFHLbV0HEq0ib1w/640?wx_fmt=jpeg) 92 | > 93 | > #### 手写 trace 干它 94 | > 95 | > trace 'do_readlinkat"%s", arg2@user' --uid 10229 96 | > 97 | > #### 再来一次 98 | > 99 | > ![](https://mmbiz.qpic.cn/mmbiz_jpg/PicMqQs6T6MPPjqgFChxkZVTyOa8cGqcQqxNZmt6lVdbBctmNMiarJD1sWiaJEEwpf4oRL3nm7Zsbcq4YJR7UlUWQ/640?wx_fmt=jpeg) 100 | > 101 | > #### 👆👆👆可以了,差不多了,这样分析已经为后续对抗 bypass 提供了很大的帮助 102 | > 103 | > #### 当然了,上述只是最基础的操作,后续还得继续深入探索学习,解锁更多顶级玩法 104 | > 105 | > #### 还有就是,其实我的 Xiaomi 12 还没搞好,在等解 BL 锁,至于秒解,我不想花钱,所以就拿山佬的实践来借花献佛了,真是个好主意啊,哈哈😄 106 | 107 | 总结 108 | -- 109 | 110 | 基于内核级别的监控,让应用中所有的加固 / 隐藏 / 内联汇编等防御措施形同虚设,而且可以在应用启动的初期进行观察,让应用的一切行为在我们眼中无所遁形 111 | 这是真真正正的降维打击,内核级的探测能力提供了无限可能,堪称:屠龙技 112 | 113 | 最后 114 | -- 115 | 116 | 文中用的工具和软件,我已经打包整理好了 117 | 公众号聊天界面回复 "ebpf" 即可 118 | 再次感谢先行者大佬们的无私奉献,和为技术发展所做的贡献🎉🎉🎉 -------------------------------------------------------------------------------- /simpread-快手花指令实战分析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/tmDf8xJ8s6_TjN1xAv37hw) 2 | 3 | ![](https://mmbiz.qpic.cn/mmbiz_png/gY5PqCV2kYZJ4sKUniaZPr7YRoHTLnbM8ZCNohcGM2hjy5tMJyRd9RicbR9HrMkoJiaGrBrV7JjW09zqHtCHI8q8g/640?&wx_fmt=png) 4 | 5 | 新的最新版 armv8 的,快手花指令脚本,本文为了学习安全思路,切勿做一些非法事情。 6 | 7 | ![](https://mmbiz.qpic.cn/mmbiz_png/ib93efMPP0actGbYAbS23TrIXMKA4Hfb3wAjuNLrpnMP1iarC8oTugBZFgEibxVv0uVSvt4F6wwMxoDdCWddEL6sw/640?&wx_fmt=png) 8 | 9 | ![](https://mmbiz.qpic.cn/mmbiz_png/zJkrRpjYPhNfP5zichlATibarrJQVsf9t5nNRBIicAoVVXVx4sZ6RgF24KTG4ibkcFX4vyLITzJosTaCZYPD0iaDicrQ/640?&wx_fmt=png) 10 | 11 | 12 | 13 | ⊙1.jnionload 不能 f5 14 | 15 | ⊙2. 手算花指令跳转 16 | 17 | ⊙3. 写代码用 idapatch 18 | 19 | ⊙4. 总结 20 | 21 | 1.jnionload 不能 f5 22 | 23 | libkwsgmain.so 24 | 25 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/p5YHVYUwZib1DZX6Ria0WZVhn7UmbpthzKUMY7ibCVlofSjVPCvpYkuxZJsbhKETRzliaCPesQlOicNQvrYhakJ8bAw/640?wx_fmt=png) 26 | 27 | 2. 手算花指令跳转 28 | 29 | ``` 30 | 31 | .text:00000000000462C4                 STP             X0, X1, [SP,#-32]! 32 | 33 | .text:00000000000462C8                 STP             X2, X30, [SP,#16] 34 | 35 | .text:00000000000462CC                 ADR             X1, dword_462EC 36 | 37 | .text:00000000000462D0                 SUBS            X1, X1, #0xC 38 | 39 | .text:00000000000462D4                 MOV             X0, X1 40 | 41 | .text:00000000000462D8                 ADDS            X0, X0, #0x3C ; '<' 42 | 43 | .text:00000000000462DC                 STR             X0, [SP,#24] 44 | 45 | .text:00000000000462E0                 LDP             X2, X9, [SP,#16] 46 | 47 | .text:00000000000462E4                 LDP             X0, X1, [SP],#0x20 48 | 49 | .text:00000000000462E8                 BR              X9 50 | 51 | ``` 52 | 53 | 导致不能 f5 的原因是在地址 0x462E8,x9 寄存器的值没有被 ida 计算出来 54 | 55 | 我们看 x9 是从 0x462E0,相当于从 [SP,#24], 又因为在 0x462DC 是 x0 给予的。 56 | 57 | 所以我们搞清 X0 的来源,就可以算出来,x9 的值了。 58 | 59 | ``` 60 | .text:00000000000462CC ADR X1, dword_462EC 61 | .text:00000000000462D0 SUBS X1, X1, #0xC 62 | .text:00000000000462D4 MOV X0, X1 63 | .text:00000000000462D8 ADDS X0, X0, #0x3C 64 | 65 | ``` 66 | 67 | 可以得出 X0 = 0x462EC -0xc + 0x3c = 0x4631c 68 | 69 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/p5YHVYUwZib1DZX6Ria0WZVhn7UmbpthzKN6RwYjSw6XuxXQ4OsbedeZjEDX3o7viaibjjXlCkhqJxdIe4mV6kLqJQ/640?wx_fmt=png) 70 | 71 | 这不是逗我吗,怎么全出来了 72 | 73 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/p5YHVYUwZib1DZX6Ria0WZVhn7UmbpthzK2KQySbwVTvlTibURXqllVnQaAL8enLY2B1cKBusImohDNcTVrdoy5JQ/640?wx_fmt=png) 74 | 75 | 但是发现后面也有很多的花 76 | 77 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/p5YHVYUwZib1DZX6Ria0WZVhn7UmbpthzKsIKV2KaTdel0Lsa0NSWeWYowOL1fqIfbXsiaW6wbMMj3vYqNsPibdJPg/640?wx_fmt=png) 78 | 79 | 那我们可以先写个 python 脚本 80 | 81 | 思路遍历所有的指令,如果当前指令是 82 | 83 | ``` 84 | 85 | .text:0000000000041688                 ADR             X1, qword_416A8 86 | 87 | .text:000000000004168C                 SUBS            X1, X1, #0xC 88 | 89 | .text:0000000000041690                 MOV             X0, X1 90 | 91 | .text:0000000000041694                 ADDS            X0, X0, #0x3C ; '<' 92 | 93 | ``` 94 | 95 | 第一条指令为 x1,且下三条指令为 subs mov adds,这个时候就可以计算,x0 的值,然后获取当前 0x41688  +0x7 *0x4 的位置是不是 br 96 | 97 | 然后直接去替换 98 | 99 | 我记得之前 ks 花指令比这个复杂来着,不知道为啥变成这样了 100 | 101 | 附代码 102 | 103 | 3. 写代码用 idapatch 104 | 105 | ``` 106 | from capstone.arm64_const import * 107 | import idaapi 108 | import idc 109 | import idautils 110 | from capstone import * 111 | from keystone import * 112 | import ida_bytes 113 | import keypatch 114 | ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) 115 | md = Cs(CS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) 116 | kp = keypatch.Keypatch_Asm() 117 | md.detail = True 118 | def get_all_adr(code="ADR"): 119 | allAddr = [] 120 | adr_count = 0 121 | for seg in idautils.Segments(): 122 | start = idc.get_segm_start(seg) 123 | end = idc.get_segm_end(seg) 124 | for ea in idautils.Heads(start, end): 125 | insn = idaapi.insn_t() 126 | if idaapi.decode_insn(insn, ea): 127 | if insn.get_canon_mnem() == code: 128 | adr_count += 1 129 | is_fake_adr(ea) 130 | return allAddr 131 | def is_fake_adr(ea): 132 | codelist = ida_bytes.get_bytes(ea, 0x100) 133 | for insn in md.disasm(codelist[0:4], ea): 134 | if insn.mnemonic == "adr" and len(insn.operands) > 0 and insn.operands[0].type == ARM64_OP_REG: 135 | if(insn.operands[0].reg - ARM64_REG_X0 == 1): 136 | subsFlag =None 137 | addsFlag = None 138 | for subs_insn in md.disasm(codelist[4:8], 0): 139 | if subs_insn.mnemonic =="subs": 140 | subsFlag = subs_insn 141 | for adds_insn in md.disasm(codelist[3*4:3 *8], 0): 142 | if adds_insn.mnemonic == "adds": 143 | addsFlag = adds_insn 144 | if(subsFlag and addsFlag): 145 | print(subsFlag.operands[2].type) 146 | if(subsFlag.operands[2].type ==ARM64_OP_IMM and addsFlag.operands[2].type ==ARM64_OP_IMM): 147 | x9Addr = insn.operands[1].imm -subsFlag.operands[2].imm + addsFlag.operands[2].imm 148 | print(hex(x9Addr),hex(ea)) 149 | for br_insn in md.disasm(codelist[7 * 4:7 * 8], ea + 4*7): 150 | if br_insn.mnemonic == "br": 151 | print("addr",hex(ea + 4*7),hex(x9Addr)) 152 | kp.patch_code(ea + 4*7, "b " + hex(x9Addr), None, None, None, 153 | None) 154 | get_all_adr() 155 | 156 | ``` 157 | 158 | 为了验证我们的 patch 是否有问题,从手机上替换了他,app 正常运行,完美 159 | 160 | ![](https://mmbiz.qpic.cn/sz_mmbiz_jpg/p5YHVYUwZib1DZX6Ria0WZVhn7UmbpthzKaFA1Ss4bc2ibZNia9z2GHicW15fnqDUTqZZQwnbvEHwO6mO4d5F5pg/640?wx_fmt=jpeg) 161 | 162 | 4. 总结 163 | 164 | 后面继续分析 ks 的一些算法。 165 | 166 | 如果想获取更多知识,欢迎关注我朋友 167 | 168 | 我是 BestToYou, 分享工作或日常学习中关于 Android、iOS 逆向及安全防护的一些思路和一些自己闲暇时刻调试的一些程序, 文中若有错误或者不足的地方, 恳请大家联系我批评指正。 169 | 170 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/p5YHVYUwZib1LXXkvk6YVoJQJ90OVr4VIkII5veh8SKAFHGiazq7ic7DWSfeTLl6Usp0GMANicVMvC5U0hKkDDg/640?wx_fmt=jpeg) 171 | 172 | 扫码加我为好友 -------------------------------------------------------------------------------- /simpread-某 A 系电商 App doCommandNative 浅析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/bKq8x6m2u0Gx6xoPSO3JuA) 2 | 3 | 一、目标 4 | 5 | 李老板: 奋飞呀,x-sign 你都水了好几篇了,一直在 Apk 里面打转,咱们啥时候分析分析它的 so? 6 | 7 | 奋飞: 循序渐进嘛,我们上次刚定位了它的 so,今天我们来分析分析。 8 | 9 | App 版本: v4.15.1 10 | 11 | 二、步骤 12 | ---- 13 | 14 | ### Native 层的入口 15 | 16 | 先回忆下这个堆栈 17 | 18 | ``` 19 | [NewStringUTF] bytes:x-sign 20 | Rc Full call stack:dalvik.system.VMStack.getThreadStackTrace(Native Method) 21 | tt: java.lang.Thread.getStackTrace(Thread.java:1538) 22 | tt: com.txxxao.wireless.security.adapter.JNICLibrary.doCommandNative(Native Method) 23 | tt: com.axxbxxx.wireless.security.mainplugin.а.doCommand(Unknown Source:0) 24 | tt: com.axxbxxx.wireless.security.middletierplugin.c.d.a.a(Unknown Source:280) 25 | tt: com.axxbxxx.wireless.security.middletierplugin.c.d.a$a.invoke(Unknown Source:56) 26 | tt: java.lang.reflect.Proxy.invoke(Proxy.java:913) 27 | tt: $Proxy12.getSecurityFactors(Unknown Source) 28 | tt: mtopsdk.security.d.a(lt:620) 29 | tt: mtopsdk.mtop.a.a.a.a.a(lt:218) 30 | tt: mtopsdk.framework.a.b.d.b(lt:45) 31 | tt: mtopsdk.framework.b.a.a.a(lt:60) 32 | 33 | 0xcb434e10 libsgmiddletierso-6.5.50.so!0x33e10 34 | 0xcb404e28 libsgmiddletierso-6.5.50.so!0x3e28 35 | 0xc9dd5536 libsgmainso-6.5.49.so!0x10536 36 | 0xc9dd71c8 libsgmainso-6.5.49.so!0x121c8 37 | 0xf365607a libart.so!art_quick_generic_jni_trampoline+0x29 38 | 0xf364068a libart.so!MterpAddHotnessBatch+0x29 39 | 0xf3651b76 libart.so!art_quick_invoke_stub_internal+0x45 40 | 41 | ``` 42 | 43 | 堆栈会说话的,他告诉我们 44 | 45 | 1、jni 函数叫 com.txxxao.wireless.security.adapter.JNICLibrary.doCommandNative。 46 | 47 | 2、doCommandNative 的实现在 libsgmainso-6.5.49.so 中,可能在偏移 0x121c8 附近。 48 | 49 | ### 先 Hook jni 函数一把 50 | 51 | jni 函数会告诉我们入参和返回值的类型,所以不能放过。 52 | 53 | 这个 jni 函数的声明在 libsgmain.so 这个假 so 里面 54 | 55 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBNnnt5ZVThzJhlAib4yaDv9ceG4FLickKFvIJZw2zzFOffict9YSmFExRA/640?wx_fmt=png)1:docmd 56 | 57 | 这个 jni 函数有两个参数,第一个参数是 int 型,第二个参数是 Object 数组 58 | 59 | 我们先上 frida 看看它是不是我们的目标。 60 | 61 | ``` 62 | Java.enumerateClassLoaders({ 63 | "onMatch": function(loader) { 64 | if (loader.toString().indexOf("libsgmain.so") >= 0 ) { 65 | Java.classFactory.loader = loader; // 将当前class factory中的loader指定为我们需要的 66 | console.log("loader = ",loader.toString()); 67 | 68 | } 69 | }, 70 | "onComplete": function() { 71 | console.log("success"); 72 | } 73 | }); 74 | 75 | // 此处需要使用 Java.classFactory.use 76 | var signCls = Java.classFactory.use('com.txxxao.wireless.security.adapter.JNICLibrary'); 77 | signCls.doCommandNative.implementation = function(a,b){ 78 | var retval = this.doCommandNative(a,b); 79 | console.log(" #### >>> a = " + a); 80 | 81 | if( a == 70102){ 82 | console.log(" #### >>> Obj = " + b); 83 | } 84 | 85 | console.log(" #### >>> rc= " + retval) // .entrySet().toArray()); 86 | 87 | // var stack = threadinstance.currentThread().getStackTrace(); 88 | // console.log("#### >>> Rc Full call stack:" + Where(stack)); 89 | 90 | return retval; 91 | } 92 | // */ 93 | 94 | ``` 95 | 96 | 这里先解释下这个 70102 的来历,doCommandNative 明显承担了很多功能,我们全部打印出来太乱了。 97 | 98 | 从之前的堆栈 com.axxbxxx.wireless.security.middletierplugin.c.d.a.a 这个类里面知道做 x-sign 签名的时候使用的命令参数是 70102 (对应的代码在 libsgmiddletier.so 这个假 so 里面) 99 | 100 | 跑一下 101 | 102 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBfjL32wWPbjAm2qAItFGgp1hhicZ9QltvxYn6ss08a64Mgqnh7g9rBIw/640?wx_fmt=png)1:frc1 103 | 104 | 确认过眼神,就是它了。 105 | 106 | ###### Tip: 107 | 108 | Frida spawn 模式跑这个脚本的时候, loader 没有输出, 这时候把脚本任意修改个空格,然后再保存。Frida 会重新自动载入,这时候才能 有输出。 109 | 110 | ### ida 一下 libsgmainso-6.5.49.so 111 | 112 | 这个 so 还是很有料的。 113 | 114 | 首先他的导出函数里面找不到 doCommandNative 说明它是动态注册的。 115 | 116 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBXv4alE4xjaKtD1d4QSEKtRYyuzTSawINkt7jESLMM44EHXn1fBN60Q/640?wx_fmt=png)1:ida1 117 | 118 | 其次是 so 里面稍微有点身份的函数都是这种动态跳转。有效的抵抗了 ida 的 F5。 119 | 120 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBia6WgOcKOfe0NllesePk3wsLXZrjVPdXzXsye0pEzsiavKia66MyoEY6Q/640?wx_fmt=png)1:ida2 121 | 122 | 我们一个一个来解决。 123 | 124 | 动态注册我们不怕,Hook RegisterNatives 就可以搞定它 125 | 126 | ``` 127 | [RegisterNatives] java_class: com.txxxao.wireless.security.adapter.JNICLibrary name: doCommandNative sig: (I[Ljava/lang/Object;)Ljava/lang/Object; fnPtr: 0x7637c25ba4 module_name: libsgmainso-6.5.49.so module_base: 0x7637c07000 offset: 0x1eba4 128 | 129 | ``` 130 | 131 | 结果出来,我们的目标是 0x1eba4 132 | 133 | 比较尴尬的是,ida 中的 0x1eba4 的位置怎么看都不像函数的样子。 134 | 135 | 怎么办? 136 | 137 | 从这个 so 的种种表现来看,会不会它在运行时有些自修改之类的玩法? 138 | 139 | 先不管那么多了,我们把这个 so 从运行时 dump 出来再说。 140 | 141 | ###### Tip: 142 | 143 | dump so 的方法参考 http://91fans.com.cn/post/carcommunitytwo/ 144 | 145 | 我的测试手机是 64 位的,所以 dump 出来了一个 64 位的 so 146 | 147 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBcGYHyKqvRCxXuyy1HYCicIpcic59nK69GMsQGyZJRa9ynqKV3AQ117Iw/640?wx_fmt=png)1:idasub 148 | 149 | 这次有那么点意思了,不过由于烦人的 BR X11 动态跳转,导致我们还是不能愉快的 f5 150 | 151 | ### 修复一下 152 | 153 | 如果我们知道这个 BR X11 指令的 x11 的值,然后把它改成一个静态跳转,是不是可以修复可怜的 F5? 154 | 155 | 说干就干 156 | 157 | ``` 158 | var mbase = Module.getBaseAddress('libsgmainso-6.5.49.so'); 159 | Interceptor.attach(mbase.add(0x1EC18),{ 160 | onEnter:function(args){ 161 | console.log('Context : ' + JSON.stringify(this.context)); 162 | } 163 | }); 164 | 165 | ``` 166 | 167 | 打印出来 168 | 169 | ``` 170 | Context : {"pc":"0x7637921c18","sp":"0x7639089340","x0":"0x20","x1":"0x76390893e4","x2":"0x2776","x3":"0x28","x4":"0x1","x5":"0x0","x6":"0x4","x7":"0x0","x8":"0x16","x9":"0x7639089350","x10":"0x7637a6cd60","x11":"0x7637921c2c","x12":"0x76390893e8","x13":"0x76390893d8","x14":"0x1","x15":"0x0","x16":"0x76dadbf000","x17":"0x76da67d440","x18":"0x0","x19":"0x76506125e0","x20":"0x0","x21":"0x2776","x22":"0x76390896bc","x23":"0x7650261ddf","x24":"0x8","x25":"0x196","x26":"0x763908d588","x27":"0x2","x28":"0x76390893e8","fp":"0x76390893b0","lr":"0x76dadbf60c"} 171 | 172 | ``` 173 | 174 | 当前地址是 0x7637921c18 - 0x1EC18 = 0x763793000, 说明 so 基地址是 0x763793000 , x11 的值是 0x7637921c2c - 0x763793000 = 0x1EC2C, 说明这里要跳转到 0x1EC2C 175 | 176 | 那就把这行指令修改成 b 0x1EC2C 177 | 178 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBrTIAtDaRsuZxXRE4x59wxHucM4CZfYheiau2RMvTgR1jicGLaGwQuSuw/640?wx_fmt=png)1:idabx 179 | 180 | 再 F5 一下,就比原来漂亮一些了 181 | 182 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBCHqlnOHcRHBYO08YNSrqJd2IMshsXISKibBKaVOUIU2bbZV3ic0yTwUA/640?wx_fmt=png)1:idasubok 183 | 184 | ### Hook 这个 Native 层的 doCommandNative 185 | 186 | 这里主要就是介绍下 Hook Native 函数的时候,如何打印 Object[] 类型的参数 187 | 188 | ``` 189 | var mbase = Module.getBaseAddress('libsgmainso-6.5.49.so'); 190 | // 1ed4c 191 | Interceptor.attach(mbase.add(0x1EBA4),{ 192 | onEnter:function(args){ 193 | console.log('doCommandNative = ' + args[2].toString(10)); 194 | 195 | 196 | var Object_javaArray = Java.use('[Ljava.lang.Object;'); 197 | var ArrayArgs_3 = Java.cast(args[3], Object_javaArray); 198 | var ArrayClz = Java.use("java.lang.reflect.Array"); 199 | var len = ArrayClz.getLength(ArrayArgs_3); 200 | 201 | if( args[2].toString(10) == 70102) { 202 | for(let i=0;i!=len;i++){ 203 | var objUse = ArrayClz.get(ArrayArgs_3,i); 204 | if(objUse != null){ 205 | console.log("args[3] String value:", objUse.toString()); 206 | } 207 | } 208 | } 209 | 210 | } 211 | }); 212 | 213 | ``` 214 | 215 | 先用 Java.cast 转一下类型,然后再 java.lang.reflect.Array 来遍历。 216 | 217 | 结果还是比较漂亮的 218 | 219 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBhV6XE62iaT3nl7libBV0MmjG4y0fNuu1gTO1LoNOOoqmlfibsl59m3FibQ/640?wx_fmt=png)1:rc 220 | 221 | 三、总结 222 | ---- 223 | 224 | Native 层的保护措施更多,大家都太卷了。 225 | 226 | 熟练掌握 java 的反射用法,是玩好 frida 的必要条件。 227 | 228 | ida 的 F5 也是大家严防死守的,所以修复的方案也要了解下。 229 | 230 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBfKRGQByJyZtYMxNLHib2icib4wgzPCJkffqLoKGw5IoLRNrD7WTk45CvQ/640?wx_fmt=png)1:ffshow 231 | 232 | 本想游戏人间,为何最后反被人间游戏。 233 | 234 | ###### Tip: 235 | 236 | 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我 wx: fenfei331 讨论下。 237 | 238 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBkanMcfKVkjXYE68JYDfa51tUm5vJiaqesYVPL5MBjdJFQ18RLFxwuiaA/640?wx_fmt=png) 239 | 240 | 关注微信公众号,最新技术干货实时推送 241 | 242 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib1GTDIkAVibTS1Va9Frwz8tBAwv0OILXQaAHKkr2DMuRHwbxkRtwR3vz3ffM4hOp6Q6IFAjgiczIx9g/640?wx_fmt=png) 243 | 244 | 手机查看不方便,可以网页看 245 | 246 |     http://91fans.com.cn 247 | 248 | 往期推荐 249 | 250 | [ 251 | 252 | 某 A 系电商 App x-sign 签名分析 253 | 254 | 255 | 256 | ](http://mp.weixin.qq.com/s?__biz=MzI4NzMwNDI3OQ==&mid=2247484555&idx=1&sn=acd26b99921dd6f1ea68ae6d8b243003&chksm=ebcefbb3dcb972a52bbf29446dc9432ae36dd94fe2d0c2e4ea6bd75bfaad3606b365816ed279&scene=21#wechat_redirect) 257 | 258 | [ 259 | 260 | 某汽车社区 App 签名和加解密分析 (二) : Frida Dump so 261 | 262 | 263 | 264 | ](http://mp.weixin.qq.com/s?__biz=MzI4NzMwNDI3OQ==&mid=2247484542&idx=1&sn=2322a3e5f86aa09d9998eea85e449a28&chksm=ebcefb46dcb972501805f25453b37d9ac6e31e16a9bf2495d0741cfb4c8d6de4fa0d0801c05f&scene=21#wechat_redirect) -------------------------------------------------------------------------------- /simpread-某 A 系电商 App x-sign 签名分析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/c-9K_QByPH3xrQc9cPkI_Q) 2 | 3 | 一、目标 4 | 5 | 前不久 (我去,都大半年了) 分析过 某二手电商 App x-sign 签名分析 类成员变量的分析 我们找到了几个伪装成 so 的 jar 包。 6 | 7 | 虽然 rpc 调用 ok 了,但是它的实际运算过程还是在 so 里面的。 8 | 9 | 今天我们从它们同族的 App 来入手,利用 Native 层字符串定位的方式来定位下在哪个 so 中去做的运算。 10 | 11 | App 版本: v4.15.1 12 | 13 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2kMAz2TozwhnJrNT1X4VCicFQFj0jgCRInkWBFhhJboG5P7LgoQSxqFnZ6OGtVULbvPXMVk6zjnKQ/640?wx_fmt=png)1:main 14 | 15 | 二、步骤 16 | ---- 17 | 18 | ### 特征字符串定位 19 | 20 | 一开始选择的特征字符串是 x- , 后来发现没有 x-sign 好使 21 | 22 | ``` 23 | Interceptor.attach(addrGetStringUTFChars, { 24 | onEnter: function (args) {}, 25 | onLeave: function (retval) { 26 | if (retval != null) { 27 | var bytes = Memory.readCString(retval); 28 | if(bytes != null) { 29 | if(bytes.toString().indexOf("x-sign") >= 0 ) 30 | { 31 | console.log("[GetStringUTFChars] result:" + bytes); 32 | var threadef = Java.use('java.lang.Thread'); 33 | var threadinstance = threadef.$new(); 34 | 35 | var stack = threadinstance.currentThread().getStackTrace(); 36 | console.log("Rc Full call stack:" + Where(stack)); 37 | 38 | // Native 层 堆栈 39 | console.log(Thread.backtrace(this.context, Backtracer.FUZZY) 40 | .map(DebugSymbol.fromAddress).join("\n")) 41 | 42 | } 43 | } 44 | 45 | } 46 | } 47 | }); 48 | 49 | ``` 50 | 51 | 跑一下 52 | 53 | ``` 54 | [NewStringUTF] bytes:x-sign 55 | Rc Full call stack:dalvik.system.VMStack.getThreadStackTrace(Native Method) 56 | tt: java.lang.Thread.getStackTrace(Thread.java:1538) 57 | tt: com.txxxao.wireless.security.adapter.JNICLibrary.doCommandNative(Native Method) 58 | tt: com.axxbxxx.wireless.security.mainplugin.а.doCommand(Unknown Source:0) 59 | tt: com.axxbxxx.wireless.security.middletierplugin.c.d.a.a(Unknown Source:280) 60 | tt: com.axxbxxx.wireless.security.middletierplugin.c.d.a$a.invoke(Unknown Source:56) 61 | tt: java.lang.reflect.Proxy.invoke(Proxy.java:913) 62 | tt: $Proxy12.getSecurityFactors(Unknown Source) 63 | tt: mtopsdk.security.d.a(lt:620) 64 | tt: mtopsdk.mtop.a.a.a.a.a(lt:218) 65 | tt: mtopsdk.framework.a.b.d.b(lt:45) 66 | tt: mtopsdk.framework.b.a.a.a(lt:60) 67 | 68 | 0xcb434e10 libsgmiddletierso-6.5.50.so!0x33e10 69 | 0xcb404e28 libsgmiddletierso-6.5.50.so!0x3e28 70 | 0xc9dd5536 libsgmainso-6.5.49.so!0x10536 71 | 0xc9dd71c8 libsgmainso-6.5.49.so!0x121c8 72 | 0xf365607a libart.so!art_quick_generic_jni_trampoline+0x29 73 | 0xf364068a libart.so!MterpAddHotnessBatch+0x29 74 | 0xf3651b76 libart.so!art_quick_invoke_stub_internal+0x45 75 | 76 | ``` 77 | 78 | 有之前分析的基础,我们在 java 层的堆栈,重点关注 com.axxbxxx.wireless.security.middletierplugin.c.d.a.a 这个类, Native 层的堆栈就必须是 libsgmiddletierso-6.5.50.so 和 libsgmainso-6.5.49.so 79 | 80 | ### 缩小范围 81 | 82 | jadx 打开 apk,搜索一下 com.axxbxxx.wireless.security.middletierplugin.c.d.a.a, 奇怪,这个类搜不到。 83 | 84 | 在 某二手电商 App x-sign 签名分析 类成员变量的分析 的文章里面,我们是通过 类成员变量的分析来定位的。现在我们知道了 这个类大概率是在那两个假的 so 里面 85 | 86 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2kMAz2TozwhnJrNT1X4VCicb9jHKNuwQXib3AKrfnr6yibuzXIjKfyHH5LDSEoueuzMJ2Xy4iazNDvFw/640?wx_fmt=png)1:so 87 | 88 | 是的,他俩是假的 so,本质上是 jar 包, jadx 伺候 89 | 90 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2kMAz2TozwhnJrNT1X4VCicy5bdj72AvwYKic1zZhu6EAtq4KtI1WQ5q7NR6ANvWYKJJyH4AohI16Q/640?wx_fmt=png)1:find 91 | 92 | 在 libsgmiddletier.so 这个 jar 包里面找到了。 93 | 94 | ### 上 Frida 95 | 96 | ``` 97 | var signCls = Java.use('com.axxbxxx.wireless.security.middletierplugin.c.d.a'); 98 | signCls.a.implementation = function(a){ 99 | console.log(">>> sign = " + a); 100 | var retval = this.a(a); 101 | console.log(">>> sign Rc = " + retval); 102 | return retval; 103 | } 104 | 105 | ``` 106 | 107 | 跑一下,提示这个类找不到? 108 | 109 | 为啥找不到?因为这个 jar 包是动态加载的,所以他的 Classloader 是不同的,不能使用默认的。 110 | 111 | ### Hook 动态加载的类 112 | 113 | #### 先要遍历一下所有的 ClassLoader 114 | 115 | ``` 116 | Java.enumerateClassLoaders({ 117 | "onMatch": function(loader) { 118 | console.log(loader); 119 | }, 120 | "onComplete": function() { 121 | console.log("success"); 122 | } 123 | }); 124 | 125 | ``` 126 | 127 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2kMAz2TozwhnJrNT1X4VCick7MyoTnkVJvAMMPI6uHFwsFAdv564qAThvaOibQ5nLKzyicvZhpnhyug/640?wx_fmt=png)1:jar 128 | 129 | 没毛病就是它了。 130 | 131 | #### 指定 ClassLoader 132 | 133 | ``` 134 | Java.enumerateClassLoaders({ 135 | "onMatch": function(loader) { 136 | if (loader.toString().indexOf("libsgmiddletier.so") > 0 ) { 137 | Java.classFactory.loader = loader; // 将当前class factory中的loader指定为我们需要的 138 | } 139 | }, 140 | "onComplete": function() { 141 | console.log("success"); 142 | } 143 | }); 144 | 145 | // 此处需要使用 Java.classFactory.use 146 | var signCls = Java.classFactory.use('com.axxbxxx.wireless.security.middletierplugin.c.d.a'); 147 | signCls.a.overload('java.util.HashMap').implementation = function(a){ 148 | var retval = this.a(a); 149 | console.log(" #### >>> a = " + a.entrySet().toArray()); 150 | console.log(" #### >>> rc= " + retval.entrySet().toArray()); 151 | 152 | var stack = threadinstance.currentThread().getStackTrace(); 153 | console.log("#### >>> Rc Full call stack:" + Where(stack)); 154 | 155 | return retval; 156 | } 157 | 158 | ``` 159 | 160 | 再跑一下,这下 Ok 了 161 | 162 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2kMAz2TozwhnJrNT1X4VCic1EJUibfhLx5G6yNGZWwHLIoiaiambC9ddibI5NAPa2ricKAVLPCnRiaDNSaA/640?wx_fmt=png)1:rc 163 | 164 | 三、总结 165 | ---- 166 | 167 | 我们找到了最接近的 jave 层的接口,也找到了 so 中对应的函数,但是要继续分析这个 so 还是需要费不少功夫的。 168 | 169 | frida 提示找不到类的时候不要慌,遍历大法好。 170 | 171 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/llIox45YGib2kMAz2TozwhnJrNT1X4VCic1OPTF6HnMeGSJp7uczeNg3eGQZOMt3kBlkeJ0JKeWuakmYxx6Ml7ibg/640?wx_fmt=jpeg)1:ffshow 172 | 173 | 每当年关将至,总会想起刘瑜这段话———— 忙,却似乎也没忙成什么,时间被碾得如此之碎,一阵风吹过,稀里哗啦全都不知去向,以至于我试图回想这一年到底干了些什么,发现自己简直是从一场昏迷中醒来。 174 | 175 | ###### Tip: 176 | 177 | 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我 wx: fenfei331 讨论下。 178 | 179 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2kMAz2TozwhnJrNT1X4VCicFjicMkd3cvRV7UOlwoqrHJ3QjulZAMIKJmnf9frgibQLWAe5SWnpna3g/640?wx_fmt=png) 180 | 181 | 关注微信公众号,最新技术干货实时推送 182 | 183 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2kMAz2TozwhnJrNT1X4VCicDibZuLt1qxjzjaWrkONDsAfib3aoV2zUnp9CHksJiax9CaKiaMv0rJ0S0A/640?wx_fmt=png) 184 | 185 |  手机查看不方便,可以网页看 186 | 187 |     http://91fans.com.cn 188 | 189 | 往期推荐 190 | 191 | [ 192 | 193 | 某汽车社区 App 签名和加解密分析 (二) : Frida Dump so 194 | 195 | 196 | 197 | ](http://mp.weixin.qq.com/s?__biz=MzI4NzMwNDI3OQ==&mid=2247484542&idx=1&sn=2322a3e5f86aa09d9998eea85e449a28&chksm=ebcefb46dcb972501805f25453b37d9ac6e31e16a9bf2495d0741cfb4c8d6de4fa0d0801c05f&scene=21#wechat_redirect) 198 | 199 | [ 200 | 201 | 某二手电商 App x-sign 签名分析 类成员变量的分析 202 | 203 | 204 | 205 | ](http://mp.weixin.qq.com/s?__biz=MzI4NzMwNDI3OQ==&mid=2247483987&idx=1&sn=4e8b2556e4ae9cda9ff64b5123c63b5e&chksm=ebcefd6bdcb9747dde38bbcda1fef251dfd5df901dc1f7b2cdd156136720af59b48dc556356b&scene=21#wechat_redirect) -------------------------------------------------------------------------------- /simpread-某乎请求头签名算法分析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=Mzg5NTY3MTc2Mg==&mid=2247483704&idx=1&sn=8e08162f0768fa6a793de5d761eff369&chksm=c00d8d85f77a04939dd5ce0481c8a8ca4426c25c7a1cd037f4568803f479bef2b355b80e6ee5&mpshare=1&scene=1&srcid=0201FpqYNSfgJft3lH6ugVUI&sharer_sharetime=1646873400933&sharer_shareid=56da189f782ce62249ab4f6494feca50&version=3.1.20.90367&platform=mac#rd) 2 | 3 | 前言 4 | -- 5 | 6 | * 小小白在这里给各位大佬拜个年了,祝各位大佬新年快乐,大吉大利,身体健康,技术更上一层楼 7 | 8 | * 本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除! 9 | 10 | 11 | 网站 12 | -- 13 | 14 | aHR0cHM6Ly93d3cuemhpaHUuY29tL3NlYXJjaD90eXBlPWNvbnRlbnQmcT1weXRob24= 15 | 16 | 加密定位 17 | 18 | 需要分析的接口以及加密参数 x-zse-96 如下图 19 | 20 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqP261tbMCynIBPQt7MibYoTg7w6oaJE391znh2kuGgvV1geA26cmTuicQ/640?wx_fmt=png) 21 | 22 | 直接搜关键字 x-zse-96,发现一个文件,点进去 23 | 24 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqianSPE7iccYfRAmChrT8Pb5fr7uh6ASNwrvuudHicaPP5kuOcTMMSkCGw/640?wx_fmt=jpeg) 25 | 26 | 格式化后再搜索 x-zse-96,发现两个可疑入口,分别是这两个 27 | 28 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqibEE3P1z6zLKZgPX2wv25BwZxibQHQHV49ux1B4oKicGUEOnDou35IvUA/640?wx_fmt=png) 29 | 30 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqsyAnqgTljo9eq82vpN3EsulzjxAFK65IoLJQlibFoRrcg2cF5dwaWVQ/640?wx_fmt=png) 31 | 32 | 重新刷新网站,可以看到断点断在下图这个地方,选中执行函数可以发现加密入口就是这里 33 | 34 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqibF7O8e1MYGeHWDJ30Anzhbuc3RVVjSRJK0xLEyfJwiaMZqkXwc7l4Ow/640?wx_fmt=png) 35 | 36 | 加密分析 37 | 38 | s 明文数据: 39 | 40 | ``` 41 | s = '101_3_2.0+'(版本号) + 42 |     '/api/v4/search_v3?t=general&q=python&correction=1&offset=0&limit=20&filter_fields=&lc_idx=0&show_all_topics=0&search_source=Normal+'(接口后缀) + 43 |     '"AIAf1GdgbRSPTtoYPsJrpvRp_MB-_8SxwGQ=|1643717522"'(dc0 cookie) 44 | 45 | ``` 46 | 47 | 第一层加密 48 | 49 | ``` 50 |  l()(s) = '7dd6414484df2c210ddbb996c55cf64c' -> md5_str 51 | 52 | ``` 53 | 54 | 根据 js 逆向的一些小经验,32 位密文很容易想到 md5 签名加密,拿到在线 md5 加密测试网站去试试 55 | 56 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqmDktpMQiakWPUtWAbZ5sxcHJV5hLXSXQuIs8WkHUz5WFRPJgViaImNpA/640?wx_fmt=png) 57 | 58 | perfect,完美对应上,由此可知,第一层加密就是原生的 md5 加密 59 | 60 | 第二层加密 61 | 62 | ``` 63 | a()(md5_str) = 'a0xqSQe8cLYfSHYyThxBHD90k0xxN9xqf_tqr6UqH9Op' 64 | 65 | ``` 66 | 67 | 选中 a(),点进去,会进入到这个函数,加密参数正是第一层加密得到的 md5 字符串,如图所示 68 | 69 | 70 | 71 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqicQ18dvO6q7hh3Nkh67FnK8N4pezrRpR6Q3MJLlnaIzCyk8gzwz8exQ/640?wx_fmt=png) 72 | 73 | 然后就单步跟,跟到这里就先别跟了,这里正是 jsvmp 循环一条条执行指令的地方,下面的代码会根据时间来检测调试 74 | 75 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqElkf0BictGG3jAZ4xicVZmX4icAyibp5yUMH2puf8pBHBZfyx6Od8Jiboqg/640?wx_fmt=png) 76 | 77 | 78 | 79 | 然后就在 35865 行打上 log 输出断点,把 this.C 索引值跟 this 对象里面的值都给打印出来,注意要把 window 除去,不然会缺数据,索引值方便后序调试可用 80 | 81 | ``` 82 | "索引:", this.C, " 值:", JSON.stringify(this, function(key, value) {if (value == window) {return undefined} return value}) 83 | 84 | ``` 85 | 86 | 慢慢等待输出结束,掉头发的逆向之路就可以开始了 87 | 88 | 直接在输出的 log 日志里面搜索加密生成的密文 89 | 90 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqqBedKV7Or179TRnwf5tVblVYTiaTyNb7PV77MgliaT6HTExzPCc40BxQ/640?wx_fmt=png) 91 | 92 | 这个 vmp 生成的加密参数是分成 11 组来计算的,每 4 个一组 93 | 94 | 这里面有一个固定不变的字符串,就是这个玩意 95 | 96 | ``` 97 | fix_str = "RuPtXwxpThIZ0qyz_9fYLCOV8B1mMGKs7UnFHgN3iDaWAJE-Qrk2ecSo6bjd4vl5" 98 | 99 | ``` 100 | 101 | 拿第一部分 a0xq 举个例子,看看他是如何生成的,其他的都差不多 102 | 103 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqnkKicqyvUDjf4jEiabbYtHe2hiaUEQzBzryR0w5E5nm82QibjU7iciaeuhEQ/640?wx_fmt=png) 104 | 105 | fix_str[13] = 'q' 106 | 107 | q 就是这样来的,每一个字符都是通过索引那个固定字符串得到的,所以,接下来,就该来寻找索引下标是怎么生成的,向上寻找,看到这个 108 | 109 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPq2WS6CopzpBAXicVGIhzONuUCJmzMy6HtU6VNs6eicyLmwibk4ORLs4vSQ/640?wx_fmt=png) 110 | 111 | 112 | 3433258 >> 18 = 13 113 | 114 | 这里的 18 是固定的,其实判断是不是固定的数字,只需要对比两个控制台里面 log 输出的对应分析就可以很清楚的知道哪些是固定不变的了 115 | 116 | 再来搜索 3433258 这个数字怎么来的 117 | 118 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqGCD3GB5xTEDU7sYdxKozljpbPYJvrDONwBqia4eD90drKfic4lfiaAhoQ/640?wx_fmt=png) 119 | 120 | 可以很清晰的看得到,下面这几种运算都可以得到这个数字,那怎么判断是哪一种运算得到的,我的办法是:调试 121 | 122 | ``` 123 | 1. 25386 + 3407872 = 3433258 124 | 2. 25386 ^ 3407872 = 3433258 125 | 3. 25386 | 3407872 = 3433258 126 | 127 | ``` 128 | 129 | 重新打开一个浏览器打开链接,在 log 输出相同地方换上一个条件断点,断点的条件是 this.C = 342,具体跟进去看看他到底是进行的什么运算 130 | 131 | 132 | 133 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqPFfM2tE8tHVwt5WCCUs24IVqL0fplic07c9KSzCkia2Sv1Xib29vOeBBg/640?wx_fmt=png) 134 | 135 | 136 | 137 | 这里这一步我就直接说了,是第三种,| 运算得到的,知道运算符之后,又要去分析两个参与运算的值是怎么得到的,这里就需要慢慢去一步步去逆向搜索分析了,所有参与运算的值都可以用这种同样的方法搜索分析得到 138 | 139 | 这部分所有的运算流程都在下面了,请各位大佬们自行慢慢对比 log 输出去参悟,理解 140 | 141 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqlSua5j0qpucYCb4k33FK0eVLwQfYPCdy6KYj55phlME30owTVAnVaw/640?wx_fmt=png) 142 | 143 | 最后验证: 144 | 145 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqJzhLgSsIW2WyP9xgeOPovrUIrwJibwkGuVO81toNlGI3dJu2EQVNTrg/640?wx_fmt=png) 146 | 147 | ![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqic5ibgIQVlBOjYKCP6VTalmSUMUrxLsXv5P5c2T76feqnZmDyHhaW45A/640?wx_fmt=png) 148 | 149 | 150 | 151 | 可以发现,算法还原下来只有短短的 50 几行,而且最后本地生成的加密参数跟浏览器生成的完全一致,完结撒花![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqqozA2Q2bROFIyQf9pNECcly4F8z9gvicniciagmbt63mYSdsBKFfyDxFw/640?wx_fmt=png)![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqqozA2Q2bROFIyQf9pNECcly4F8z9gvicniciagmbt63mYSdsBKFfyDxFw/640?wx_fmt=png)![](https://mmbiz.qpic.cn/mmbiz_png/EiaqCy3pb5k3LlZI6mUqIP5k6v7cPBHPqqozA2Q2bROFIyQf9pNECcly4F8z9gvicniciagmbt63mYSdsBKFfyDxFw/640?wx_fmt=png) -------------------------------------------------------------------------------- /simpread-某买菜 app 加密浅析及 excel 初体验.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=MzIxOTQ1OTA0Ng==&mid=2247484368&idx=1&sn=c743dd5f76188d44402af4db21069707&chksm=97dbbf59a0ac364f46f284b7d7d862c4adc3129a574663da7a15bb87cd68d100244ef47c43f2&mpshare=1&scene=1&srcid=0622sFj8mD27Sjl5PB26hGbU&sharer_sharetime=1655877089339&sharer_shareid=9be5daf09995ef938577edacf59663a3&version=4.0.6.99102&platform=mac#rd) 2 | 3 | 堂主以前只用过 sql、spark、hive 做过数据处理,最近学习了一下 excel 数据处理,发觉功能很强悍,特此通过某买菜 app 的数据进行相应演示,希望对小伙伴们有所帮助。 4 | 5 | 1 app 抓包体验 6 | ---------- 7 | 8 | 经测试,一共进行了三个参数的校验,分别是 sign,sesi,nars 9 | 10 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUNRNTiaZt4cbccFjic5ZI4Pk7rOPCyibcFUT68a0dWZibErIeMoPibjjLXKw/640?wx_fmt=png) 11 | 12 | 2 逆向体验 13 | ------ 14 | 15 | 使用 https://github.com/lasting-yang/frida_dump 进行脱壳,jadx 打开。 16 | 17 | sign 参数在 java 层,位置是 e51。 18 | 19 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAU0qKKsFDLhJJTv8QF2u6IqCcYj0HeyFqdKDXprGeT9wlFMBe9DS45Ng/640?wx_fmt=png) 20 | 21 | sesi、nars 参数在 so 层,位置是 libli11li1.so 22 | 23 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAU2CqSuN1Fqy9s51K0iazgQp7Op6Tx91ln1dJeITib90aEsLX5NZBFNUjg/640?wx_fmt=png) 24 | 25 | 使用 frida 以及 unidbg,成功地还原出算法。浅浅地总结下,sign 是加个 privatekey 后的 md5,sesi 涉及到两次魔改 md5 及 base64,nars 涉及到一次魔改 md5 和一次 aes-cbc 和一次 base64。 26 | 27 | 这次主要是讲 excel 的初体验,对参数感兴趣的小伙伴可以自己尝试下。 28 | 29 | 3 excel 体验 30 | ---------- 31 | 32 | ### 3.1 透视表 33 | 34 | 我们用 wps 打开获取的数据,随便在数据中点击一下。 35 | 36 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUTWc14FkorQ3cvZgnbib5YtYibOPGWC74nib23zia9eht5Tqv1Ufnw9HCBg/640?wx_fmt=png) 37 | 38 | 接着点击模块【插入】中的【数据透视表】。 39 | 40 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUkdqRgM4kdqzlOdoLtZz51rUxTYa4RUZ6ibOpP4Mw52LhtQUYHg1qI1g/640?wx_fmt=png) 41 | 42 | 点击弹出框中的确定按钮。 43 | 44 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUtS24bo1vKUodZbvnTdyDwFN53b0o62dsRazlaHrguViaCYYZl9rntjA/640?wx_fmt=png) 45 | 46 | 我们可以看到下图中的内容。 47 | 48 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUyl4InSg2LDm1jga1micdwmvONjJ4k7cicPicZFNp15oucxdgNDuCicicbPQ/640?wx_fmt=png) 49 | 50 | 接着我们将 city 移入行,cate_name_1 移入列,price 移入值,我们欣喜地发现,透视表(如下所示)映入眼前。其中的值是每个城市的每个列表的商品计数。一行代码不用写,就出来这么完美的结果。 51 | 52 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUMyxjIXbtBcE9GbibcKndbRicvahGMUl1lWFwsVOeYA4kBticxYiaV0xC4g/640?wx_fmt=png) 53 | 54 | 随后,我们进行值的探索,我们点击计数项:price 下拉按钮。 55 | 56 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUnsuD4opEmnzZiaOMlDqzDzdeQpIFqQRde4rJpHjPFeWcIoPEsRx17iaA/640?wx_fmt=png) 57 | 58 | 选择 “值字段设置”。 59 | 60 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUibcwBbeURnHgribTsNnN5dfjjs7EyMB8myOHv42sYVC04wiaUXP0cbKibQ/640?wx_fmt=png) 61 | 62 | 点击平均值。 63 | 64 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUeamQvtwWGueYjVLDXfsfiaPl7IfrZiag2LzRoZYr8WDiazIPjL42kSzRQ/640?wx_fmt=png) 65 | 66 | 我们发现,值已经变成了每个城市的每个列表的商品价格平均数,效果蛮不错的哦,啥代码都不用写。 67 | 68 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUPiaCJ7u7icARciaCoUK3q7I6UcT2HT2mkMzkOmALbzRUrX4Fbw92ObxUQ/640?wx_fmt=png) 69 | 70 | 透视表更多处理小伙伴们可以自行尝试。 71 | 72 | ### 3.2 VLOOKUP 73 | 74 | 我们发觉数据中的城市是拼音,比如 shanghai,hangzhou。我们想替换成中文,怎么操作呢?这时就介绍神奇函数 vlookup。 75 | 76 | 我们首先添加拼音以及对应中文的映射关系,如下所示。 77 | 78 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUfGRSTfJWxdlV3aoJTgTXrM8u6m5tMnIrKOHEuZIpEKwDHI98ooe2pg/640?wx_fmt=png) 79 | 80 | 接着我们在新的一列中输入 =VLOOKUP(A2,K:L,2,FALSE) 回车键,直接跳出中文了。 81 | 82 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAU7UdzQmexPbcnmJGhice7lsgMA1Vf6DRibSLUx5zF8clvOMNzH6q9piaxQ/640?wx_fmt=png) 83 | 84 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUxGBxSkw0MD1lQUic0Ccx4BnIqDPDUzEjAmQxqDH3FJ5VoCXvX1DsvxA/640?wx_fmt=png) 85 | 86 | 下拉一下,预期的中文映入眼帘。 87 | 88 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1Z7h19LicjD3LwJuaSMibvQAUVxZxfRJMrjuRoBOBIBxDye00f0icCH3ZFBjZpwu6PXMbuzIYJ4Dk4PA/640?wx_fmt=png) 89 | 90 | 4 总结 91 | ---- 92 | 93 | 数据处理 excel 相当强大,大家都可以去学习下,没必要都搞成 dataframe 处理。 94 | 95 | 更多精彩尽在 https://github.com/darbra -------------------------------------------------------------------------------- /simpread-某咖啡 app 加密参数分析进阶版.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=MzIxOTQ1OTA0Ng==&mid=2247484164&idx=1&sn=dab3441a211bc568bcc8a94a34857e1f&chksm=97dbbf8da0ac369b34ff82796b00273fb2f9e397b31c98d4a1ecfc2aac6f7bdc2d4a305cda35&mpshare=1&scene=1&srcid=0302sQN7FCkfK824hr7fDsXH&sharer_sharetime=1646203681892&sharer_shareid=56da189f782ce62249ab4f6494feca50&version=3.1.20.90367&platform=mac#rd) 2 | 3 | > 仅供学习研究 。请勿用于非法用途,本人将不承担任何法律责任。 4 | 5 | 前言 6 | -- 7 | 8 | * app 某某咖啡 9 | 10 | * v4.4.0 11 | 12 | 13 | mitmproxy 抓包 14 | ------------ 15 | 16 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicjccdL8L6WkeY7vEMaLCr1qqmXxYNVG2hZ8tWGuwfPHibNiahSpv0USvg/640?wx_fmt=png) 17 | 18 | java 分析 19 | ------- 20 | 21 | 定位到 CryptoHelper 类的名为 md5_crypt 的 native 静态方法。 22 | 23 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicqUbian9FmyyvYBHN3T4SJ9NiaHicSicklDq0hkqqicNZBuTp5F1uVx6WDxQ/640?wx_fmt=png) 24 | 25 | 26 | 27 | frida hook 28 | ---------- 29 | 30 | 脚本如下所示 31 | 32 | ``` 33 | function hook() { 34 |     Java.perform(function() { 35 |     var CryptoHelper = Java.use('com.l*****e.safeboxlib.CryptoHelper'); 36 |     CryptoHelper.md5_crypt.implementation = function (x, y) { 37 |         console.log('md5_crypt_x', bytes2str(x)); 38 |         console.log('md5_crypt_y', y); 39 |         var result = this.md5_crypt(x, y); 40 |         console.warn('md5_crypt_ret', bytes2str(result)); 41 |         return result; 42 |         }; 43 |   } 44 |   } 45 | 46 | 47 | ``` 48 | 49 | 我们可以看到,hook 的结果和抓包结果一致 50 | 51 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpiczCo3k1WFSYeef3I3fgoyicnAtoWejRBP2tk2OjCtSV0mo5UvYDQlmFA/640?wx_fmt=png) 52 | 53 | 54 | 55 | so 分析 56 | ----- 57 | 58 | 使用 lasting-yang 的脚本 hook_RegisterNatives 59 | 60 | 脚本地址:https://github.com/lasting-yang/frida_hook_libart 61 | 62 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicZRkq410XCxicOhDf5L8v8O5MwBlxPWYCIHfOEibh2caJdZCk17hApdgw/640?wx_fmt=png) 63 | 64 | 65 | 66 | 使用开源的 cutter 到 so 去一探究竟,我们搜索上图中的偏移 0x1a981,来到 android_native_md5 函数。 67 | 68 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpiclwgufef4e9yAbO66DcRMPa0PrND9Meh91VOswzWJJefaic4EEqM56AA/640?wx_fmt=png) 69 | 70 | 71 | 72 | 经过一番分析,应该是 md5 加密完之后,还有一个 bytesToInt 的逻辑。 73 | 74 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpic850QMsGxmicFMuEbrKnmlQKcttaf9OJ2LIRqGMMOEDKaI5fX639yOuA/640?wx_fmt=png) 75 | 76 | 77 | 78 | unidbg 79 | ------ 80 | 81 | 去年的文章里用 frida 就能搞定了,这次我们用 lilac、qinless、qxp 等大佬极力推荐的神器 unidbg 进行辅助分析。 82 | 83 | 首先是搭建架子 84 | 85 | ``` 86 | package com.*; 87 | 88 | import com.github.unidbg.AndroidEmulator; 89 | import com.github.unidbg.Module; 90 | import com.github.unidbg.debugger.Debugger; 91 | import com.github.unidbg.file.IOResolver; 92 | import com.github.unidbg.linux.android.AndroidEmulatorBuilder; 93 | import com.github.unidbg.linux.android.AndroidResolver; 94 | import com.github.unidbg.linux.android.dvm.*; 95 | import com.github.unidbg.linux.android.dvm.array.ByteArray; 96 | import com.github.unidbg.linux.android.dvm.array.IntArray; 97 | import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger; 98 | import com.github.unidbg.memory.Memory; 99 | 100 | import java.io.File; 101 | import java.io.IOException; 102 | import java.nio.charset.StandardCharsets; 103 | 104 | public class Md5Crypt440 extends AbstractJni { 105 |     private final AndroidEmulator emulator; 106 |     private final Module module; 107 |     private final VM vm; 108 | 109 |     public String apkPath = "/Users/darbra/Downloads/apk/com.*/temp.apk"; 110 |     public String soPath2 = "/Users/darbra/Downloads/apk/com.*/lib/armeabi-v7a/libcryptoDD.so"; 111 | 112 |     Md5Crypt440() { 113 |         emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.*").build(); 114 |         final Memory memory = emulator.getMemory(); 115 |         memory.setLibraryResolver(new AndroidResolver(23)); 116 |         vm = emulator.createDalvikVM(new File(apkPath)); 117 |         vm.setVerbose(true); 118 |         vm.setJni(this); 119 |         DalvikModule dm2 = vm.loadLibrary(new File(soPath2), true); 120 |         dm2.callJNI_OnLoad(emulator); 121 |         module = dm2.getModule(); 122 |     } 123 | 124 |     public void call() { 125 |         String methodId = "md5_crypt([BI)[B"; 126 |         DvmClass SigEntity = vm.resolveClass("com/luckincoffee/safeboxlib/CryptoHelper"); 127 |         String fakeInput1 = "cid=210101;q=j***=;uid=***"; 128 |         ByteArray inputByteArray1 = new ByteArray(vm, fakeInput1.getBytes(StandardCharsets.UTF_8)); 129 |         DvmObject ret = SigEntity.callStaticJniMethodObject( 130 |                 emulator, methodId, 131 |                 inputByteArray1, 132 |                 1 133 |         ); 134 |         byte [] strByte = (byte[]) ret.getValue(); 135 |         String strString = new String(strByte); 136 |         System.out.println("callObject执行结果:"+ strString); 137 |     } 138 | 139 |     public static void main(String[] args) { 140 |         Md5Crypt440 getSig = new Md5Crypt440(); 141 |         getSig.call(); 142 |         getSig.destroy(); 143 |     } 144 | 145 |     private void destroy() { 146 |         try { 147 |             emulator.close(); 148 |         } catch (IOException e) { 149 |             e.printStackTrace(); 150 |         } 151 |     } 152 | } 153 | 154 | 155 | 156 | ``` 157 | 158 | 值得注意的是,这个 app 不用补任何环境,非常轻松地就运行出了结果。 159 | 160 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpic9bNKxxFpZ0EQ6VLKPT2GcHz9ficdbkPCQgrBkNjljLhGIxpmicj3paVg/640?wx_fmt=png) 161 | 162 | 163 | 164 | 结果和抓包、frida 完全一致。 165 | 166 | 我们在 md5 函数打个断点。 167 | 168 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicq13rMv8DMjy2rUIbKhFY0AvypXt4bOxaeiavy519N8DM1Z5yDeHjPIw/640?wx_fmt=png) 169 | 170 | ``` 171 | public void HookByConsoleDebugger(){ 172 |     Debugger debugger = emulator.attach(); 173 |     debugger.addBreakPoint(module.base+0x13E3C); 174 | } 175 | 176 | 177 | ``` 178 | 179 | 180 | 181 | 182 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicW9iaUZoVjarou5mHG56Hy7pj8qiapAQJDZ09eft7oKuaicIVlzJFIU2kg/640?wx_fmt=png) 183 | 184 | 输入 mr0,我们可以看到第一个参数就是明文,但不够完整。 185 | 186 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicBCgITQmoibrLF7ianKHamPThz18PoaRfP9g2WcjiaE3fGGFuLJEAl7icdA/640?wx_fmt=png) 187 | 188 | 189 | 190 | 接着输入 mr0 0x100,后面可以跟的是大小。我们欣喜地发现,之前的那坨明文后面加了个 salt 值,d******9 191 | 192 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpiciaeiaQ5Vpvo7VMicG4CicZR1hSNn7Ds2BYPgXVxMibqjT6ZSzMMoJpoErng/640?wx_fmt=png) 193 | 194 | 195 | 196 | 参数 2,r1=0xef=239,我们去 cyberchef 看看这个明文长度是否为 239 197 | 198 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicIKpTN1BuXD6VBx36418grWJXPZDndO8XKpxsZia0lEnHVByhWsmAMFg/640?wx_fmt=png) 199 | 200 | 201 | 202 | 果不其然是的。 203 | 204 | 接着输入 mr2,查看第三个参数。 205 | 206 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicrwU6C0lviaAAaEQh478y7Uz1QVjsSNgnGe3p0OBmMcZDBvVYLQAHdSw/640?wx_fmt=png) 207 | 208 | 209 | 210 | 看情况参数 3 不出重大意外是 buffer,所以我们需要 hook 它的返回值。在这里我们先记下 r2 的地址 --->>> 0xbffff5d8。 211 | 212 | 我们使用 blr 命令用于在函数返回时设置一个一次性断点,然后 c 运行函数,它在返回处断下来。 213 | 214 | 接着输入刚刚记录下来的 m0xbffff5d8 215 | 216 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1acjmOUUvfhcLB6de0OCba2n84qxWpT7NO89jsib4bVGLibwNW3TWYSOxYCl1iar6GL0ahdNfxXJGjCg/640?wx_fmt=png) 217 | 218 | 219 | 220 | 我们去 cyberchef 进行验证,完全正确! 221 | 222 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1awrIic90ricvhRkstywCbTpicibEoicicuu5cFk4BL0znibUqFbD54whFmqbCokXtwhBiaib04icePuDSIbn9A/640?wx_fmt=png) 223 | 224 | 225 | 226 | md5 步骤解决了,接着查看 bytesToInt。 227 | 228 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1acjmOUUvfhcLB6de0OCba2TZCPPyxnzpJSAN3qdXpwWxU6hWl85FGxlCd83bmIL2y2gtIZoBpugA/640?wx_fmt=png) 229 | 230 | 231 | 232 | 我们在 0x13924 那里进行 hook 操作 233 | 234 | ``` 235 | public void hookBytesToInt() { 236 |         IHookZz hookZz = HookZz.getInstance(emulator); 237 | 238 |         hookZz.wrap(module.base + 0x13924 + 1, new WrapCallback() { 239 |             @Override 240 |             public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) { 241 |                 Inspector.inspect(ctx.getR0Pointer().getByteArray(0, 0x10), "参数1"); 242 |                 System.out.println("参数2->>" + ctx.getR1Int()); 243 |             }; 244 |             @Override 245 |             public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) { 246 |                 System.out.println("返回值->>" + ctx.getR0Int()); 247 |             } 248 | 249 |         }); 250 |     } 251 | 252 | 253 | ``` 254 | 255 | 我们看一下输出结果了,四个输出值都能对应上最后的 sign 结果。 256 | 257 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1acjmOUUvfhcLB6de0OCba29YghiaA0PWIXmASUOicKMIgOLY6kLJl207b8zFnDicHCY9qfvcRPxCTfA/640?wx_fmt=png) 258 | 259 | 260 | 261 | 其中的正负号处理应该是对应的如下逻辑 262 | 263 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1acjmOUUvfhcLB6de0OCba2rK63Wl0ibewXSvHz4eV5ZELhO5fkJSkiaJxXN7WBPMWibibeEW9VmtOv3A/640?wx_fmt=png) 264 | 265 | 266 | 267 | 我们写个小脚本还原下,与之前的抓包、frida、unidbg 都一致,大功告成。 268 | 269 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1acjmOUUvfhcLB6de0OCba2T5AuxrMzyOuXw7EZBnHfG3nuTXKO9HoiayBibqDKZwp8MorOaKwW4T5Q/640?wx_fmt=png) 270 | 271 | 272 | 273 | 在本人 github.com/darbra/sign 有更多的一些思路交流,如果对朋友们有所帮助,不甚欣喜。 -------------------------------------------------------------------------------- /simpread-某小程序平台桌面版开启 js 调试.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/hjzYVflkK2Azi0wyjeKcTA) 2 | 3 | 网上有很多大同小异的文章介绍如何逆向此类小程序的 js,工作流大体如下: 4 | 5 | 1. 安装小而美 app 到 root 过的 Android(或模拟器) 6 | 7 | 2. 使用小程序 8 | 9 | 3. 在 /data 目录下查找 wxpkg 10 | 11 | 4. 使用有人实现好的 nodejs 库解包 12 | 13 | 5. 将解包导入 IDE,实现 js 单步调试 14 | 15 | 16 | 这个流程相当于做了重打包,在导入 IDE 之后无法通过开发者的 appid 检查,涉及到网络请求的部分会出错。 17 | 18 | 能不能直接在原汁原味的生产环境上开启 js 单步调试呢? 19 | 20 | ![](https://mmbiz.qpic.cn/mmbiz_png/6N4b2yN3FOKApCcwLcb4QtmhibKqmFK9CRxquk6X0cUzAZsicFNAU8alica1yZ3QQmlsKwhGEO2nA8KRDPsMPKENQ/640?wx_fmt=png) 21 | 22 | 在 mac 上的小程序使用 JavaScriptCore 实现。JSC 自带了单步调试,只是默认没有开启。另外由于小程序渲染界面的引擎并不是简单的 WKWebView,本文仅限单步调试 js,不能审查界面元素。 23 | 24 | 之前公众号发过一个插件,也介绍了原理,但是并没有点破可以直接拿来调小程序:[全局开启 iOS / mac 的 WebView 调试](http://mp.weixin.qq.com/s?__biz=Mzk0NDE3MTkzNQ==&mid=2247483775&idx=1&sn=dfa5cf10a82521cf6502810c04cca6c3&chksm=c329ff8ff45e7699f3a916389ebea225fee84e3618e7723f0aab4e7fa87a9ed41ab029b5e187&scene=21#wechat_redirect) 25 | 26 | 最近 Safari 更新到 16.4,把我的插件搞不兼容了。想起这一茬,顺手水了篇推送。之前的插件实现方式是注入代码到 webinspectord,修改系统检查 entitlement 的逻辑。 27 | 28 | 引用的文章链接中提供的 frida 脚本直接可以用在 mac 上,然后再打开某小而美 mac 版,就可以看到各种可以审查的页面: 29 | 30 | ![](https://mmbiz.qpic.cn/mmbiz_png/6N4b2yN3FOKApCcwLcb4QtmhibKqmFK9CP9ZdsquKGTiaWYTFyQcGszYcb9reCxYmxhQCs7clg9tGWmIicAibdRq6w/640?wx_fmt=png) 31 | 32 | 居然还用了 JSPatch(注意里面的 _OC_callC)。 33 | 34 | 在新版的 macOS 上这段脚本不能用了,来看 WebKit 博客怎么说的。 35 | 36 | ![](https://mmbiz.qpic.cn/mmbiz_png/6N4b2yN3FOKApCcwLcb4QtmhibKqmFK9CXFeYQyFabzGcGKibrgrdEF95yMTCicNGAV7qeTp75VYHFyFibPcuic6vUg/640?wx_fmt=png) 37 | 38 | 之前 app 的 WKWebView 和 JSContext 能否被调试,取决于应用是否是 Xcode 调试版(具有 get-task-allow),或者使用其他特殊的 entitlement。 39 | 40 | 现在 Safari 将决定权全权交给开发者,直接在 app 当中设置 WKWebView / JSContext 的 isInspectable 属性为 @YES,就可以细粒度地控制某个页面允许用 F12。 41 | 42 | 例如 WebKit 官方给的开启 JSContext 调试的示例: 43 | 44 | ``` 45 | JSContext *jsContext = [JSContext new]; 46 | jsContext.inspectable = YES; 47 | 48 | ``` 49 | 50 | 那么我们只要注入 app(而不是之前的 webinspectord)就可以修改了。 51 | 52 | 除了 hook 类的初始化方法,frida 里正好有一个 ObjC.chooseSync 函数,可以根据 class 在内存中搜索已经初始化好的对象。 53 | 54 | 因此我们只需要启动好对应的小程序,然后 frida 附加到 Mini Program 上执行一行 js 即可: 55 | 56 | ``` 57 | ['WKWebView', 'JSContext'].forEach( 58 | clazz => ObjC.chooseSync(ObjC.classes[clazz]).forEach( 59 | v => v.setInspectable_(ptr(1)) 60 | ) 61 | ) 62 | 63 | ``` 64 | 65 | 小而美的小程序和主程序不在一个进程当中运行,通常会看到两个 Mini Program 进程。 66 | 67 | 用 python 自动筛选一下: 68 | 69 | ``` 70 | import frida 71 | mac = frida.get_local_device() 72 | if mac.query_system_parameters()['os']['id'] != 'macos': 73 | raise RuntimeError('This script is only for Mac OS') 74 | for proc in mac.enumerate_processes(): 75 | if proc.name not in ['WeChat', 'Mini Program']: 76 | continue 77 | print('Patching %s (%d)' % (proc.name, proc.pid)) 78 | session = mac.attach(proc.pid) 79 | script = session.create_script(''' 80 | ['WKWebView', 'JSContext'].forEach( 81 | clazz => ObjC.chooseSync(ObjC.classes[clazz]).forEach( 82 | v => v.setInspectable_(ptr(1)) 83 | ) 84 | ) 85 | ''') 86 | script.load() 87 | script.unload() 88 | session.detach() 89 | 90 | ``` 91 | 92 | 这个脚本需要关闭 macOS 的 SIP(rootless),将显著降低系统安全性。在 Apple Silicon 上除了关掉 sip,还需要开启 am64e abi,然后重启。 93 | 94 | ``` 95 | sudo nvram boot-args="-arm64e_preview_abi" 96 | 97 | ``` 98 | 99 | 来看效果。 100 | 101 | 直接调试某社交网站的小程序: 102 | 103 | ![](https://mmbiz.qpic.cn/mmbiz_png/6N4b2yN3FOIcRZ4iccQwCEFuoGt0kQSyft1VrDU8ibOoGNxSfZwj5usgPZSS1X7jPVeqMrEGgutjX8S4zmpXw3nw/640?wx_fmt=png) 104 | 105 | 单步进入请求参数,都抓下来了 106 | 107 | ![](https://mmbiz.qpic.cn/mmbiz_png/6N4b2yN3FOIcRZ4iccQwCEFuoGt0kQSyfhnm7kLVNSI1zNpFMFOkL1hjgShchPEZqWUiaeGbBZeL1G7GicGmvy9yw/640?wx_fmt=png) 108 | 109 | 本文仅限技术讨论。如遇到账号被风控甚至产生其他后果,请自行承担… -------------------------------------------------------------------------------- /simpread-某手抓包问题分析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [blog.mythsman.com](https://blog.mythsman.com/post/6213072906087a00011b7e4d/) 2 | 3 | > 背景 不知从什么版本后,对快手进行简单抓包似乎 “不可行” 了。 4 | 5 | 背景 6 | -- 7 | 8 | 不知从什么版本后,对快手进行简单抓包似乎 “不可行” 了。表现就是使用常规的 HTTP 正向代理抓包工具([charles](https://www.charlesproxy.com/)、[mitmproxy](https://mitmproxy.org/)、[fiddler](https://www.telerik.com/fiddler) 之类)并且把自签名证书种到系统证书里后,数据依然能刷出来,也能抓到一些零星的报文,但是关键出数据的那些接口报文都没有。 9 | 10 | 一般来说,常规方法无法抓安卓应用的 https 包,通常有以下几种可能: 11 | 12 | 1. 证书信任问题。在 Android 7 以上,应用会默认不信任用户证书,只信任系统证书,如果配置不得当则是抓不到包的。 13 | 2. 应用配置了 SSL pinning,强制只信任自己的证书。这样一来由于客户端不信任我们种的证书,因此也无法抓包。 14 | 3. 应用使用了纯 TCP 传输私有协议(通常也会套上一层 TLS)。由于甚至都不是 HTTP 协议,当然就抓不到包了。 15 | 4. 应用使用 WebSocket 长链接,将不同的接口封装在这个长链接里。在 WebSocket 里承载的协议一般是用某种自定义方式来模拟 http 请求,因此也难以抓包。 16 | 5. 应用使用了基于 UDP 的 http3.0 协议等。大部分代理工具目前还不支持 quic 等协议,所以这样一般是抓不到包的。 17 | 6. 应用配置了 Proxy.NO_PROXY ,强制不走系统代理。这样 http 流量就绕过我们配置的代理,自然抓不到包。 18 | 19 | 当前的现象是数据能刷出来,那就说明并不是证书信任相关的问题。接下来就需要验证它究竟是使用了什么样的传输方式,对症下药。 20 | 21 | 最稳妥的验证方式当然是白盒测试看源码,不过快手反编译的代码看起来也费劲,于是考虑直接当成黑盒来测试看看。以下验证方式均以 **快手 8.2.31.17191** 版本为例。 22 | 23 | 分析 24 | -- 25 | 26 | ### 环境部署 27 | 28 | #### 准备自签名 CA 证书 29 | 30 | 需要在 Linux 主机上使用 openssl 工具生成一波证书。当然,这一步可以忽略,直接使用 mitmproxy 生成的证书。只是手动配置一下能够加深一下对 openssl 的理解。 31 | 32 | ``` 33 | openssl genrsa -out cert.key 2048 34 | 35 | 36 | openssl req -new -x509 -key cert.key -out -days 5480 cert.crt 37 | 38 | 39 | cat cert.key cert.crt > cert.pem 40 | 41 | 42 | cp cert.pem ~/.mitmproxy/mitmproxy-ca.pem 43 | 44 | 45 | mitmproxy -p 8000 46 | 47 | 48 | curl -x localhost:8000 --cacert ~/.mitmproxy/mitmproxy-ca.pem https://www.baidu.com 49 | 50 | 51 | openssl x509 -subject_hash_old -in cert.crt -noout 52 | 53 | 54 | cp cert.crt a5176621.0 55 | 56 | 57 | ``` 58 | 59 | 值得注意的是,不要尝试使用 `mitmproxy --certs` 来配置证书,这种方式只能配置 leaf 证书,而不能配置根 CA 证书。因此还是老老实实的把根证书放在默认路径下。 60 | 61 | #### 准备设备 62 | 63 | 为了方便测试,我在 arm 服务器上使用 [redroid](https://github.com/remote-android/redroid-doc) 准备了一台安卓虚拟机。 64 | 65 | ``` 66 | docker run -itd --rm --memory-swappiness=0 \ 67 | --privileged --name redroid \ 68 | --mount type=bind,source=/home/myths/exp/a5176621.0,target=/system/etc/security/cacerts/a5176621.0 \ 69 | -p 5555:5555 \ 70 | redroid/redroid:11.0.0-arm64 \ 71 | redroid.width=720 \ 72 | redroid.height=1280 \ 73 | redroid.fps=15 \ 74 | redroid.gpu.mode=guest 75 | 76 | ``` 77 | 78 | 其中 /home/myths/exp/a5176621.0 替换成实际文件所在路径。 79 | 80 | 然后在 arm 主机上用 adb 连接安卓的 tcpip 端口,下载并安装快手 8.2.31.17191 版本。 81 | 82 | ``` 83 | adb connect localhost:5555 84 | 85 | 86 | adb install kuaishou.apk 87 | 88 | ``` 89 | 90 | 为了方便远程操作,需要在本地机器上连接 arm 服务器上的安卓虚拟机,并用 scrcpy 操作。 91 | 92 | ``` 93 | adb connect :5555 94 | 95 | 96 | scrcpy 97 | 98 | ``` 99 | 100 | 到这一步骤时,可以检测安卓中的网络应该都已经是通的了。 101 | 102 | ### 复现抓包问题 103 | 104 | 先尝试使用传统正向代理的方式进行抓包。 105 | 106 | ``` 107 | mitmproxy -p 8000 108 | 109 | 110 | adb shell settings put global http_proxy 172.17.0.1:8000 111 | 112 | ``` 113 | 114 | 设置完成后,观察手机的网络状况,现象如下: 115 | 116 | 1. 使用浏览器访问普通网站,返回均正常,mitmproxy 也能抓到包。 117 | 2. 刷快手推荐页,返回也正常,但是 mitmproxy 只能抓到一些静态资源,无法抓到接口。 118 | 119 | ![](https://blog.mythsman.com/content/images/2022/02/Xnip2022-02-20_15-30-21.png) 120 | 121 | ### Ban 掉不走代理的所有流量 122 | 123 | 有数据但是抓不到包,怀疑应当是有些流量漏掉了,于是尝试把这些流量 Ban 掉看看效果。 124 | 125 | ``` 126 | echo 1 > /proc/sys/net/ipv4/ip_forward 127 | 128 | 129 | mitmproxy -p 8000 130 | 131 | 132 | adb shell settings put global http_proxy 172.17.0.1:8000 133 | 134 | 135 | 136 | sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 ! -d 172.17.0.1 -j REDIRECT --to-ports 1234 137 | 138 | sudo modprobe ipt_owner 139 | 140 | 141 | iptables -t nat -D PREROUTING 1 142 | 143 | ``` 144 | 145 | 设置完成后,观察手机的网络状况,现象如下: 146 | 147 | 1. 使用浏览器访问普通网站,返回均正常,mitmproxy 也能抓到包。 148 | 2. 刷快手推荐页,显示 “无网络连接 “。 149 | 150 | 说明这里的确是有流量漏了,没有走正向代理,但是依然出了外网。 151 | 152 | ### Ban 掉不走代理的 443/80 流量 153 | 154 | 那么这些流量到底是私有的四层 TCP 流量、还是没走正向代理的 80/443 流量呢?因此尝试把非 80/443 的流量放开试试。 155 | 156 | ``` 157 | mitmproxy -p 8000 158 | 159 | 160 | adb shell settings put global http_proxy 172.17.0.1:8000 161 | 162 | 163 | 164 | sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 80 -j REDIRECT --to-ports 1234 165 | sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 443 -j REDIRECT --to-ports 1234 166 | 167 | 168 | iptables -t nat -D PREROUTING 1 169 | iptables -t nat -D PREROUTING 1 170 | 171 | ``` 172 | 173 | 设置完成后,观察手机的网络状况,现象如下: 174 | 175 | 1. 使用浏览器访问普通网站,返回均正常,mitmproxy 也能抓到包。 176 | 2. 刷快手推荐页,依然显示 “无网络连接 “。 177 | 178 | 这就说明,控制快手推荐页的流量并不是所谓私有流量,而就是走的 80/443 端口,只是没有走正向代理。 179 | 180 | ### 改用透明代理模式 181 | 182 | 既然七层的代理配置会被忽略,那就尝试使用四层的透明代理,将流量强制转到透明代理服务器上即可。 183 | 184 | ``` 185 | mitmproxy -p 8000 -m transparent 186 | 187 | 188 | adb shell settings put global http_proxy :0 189 | 190 | 191 | 192 | sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 80 -j REDIRECT --to-ports 8000 193 | sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 443 -j REDIRECT --to-ports 8000 194 | 195 | 196 | iptables -t nat -D PREROUTING 1 197 | iptables -t nat -D PREROUTING 1 198 | 199 | ``` 200 | 201 | 设置完成后,观察手机的网络状况,现象如下: 202 | 203 | 1. 使用浏览器访问普通网站,返回均正常,mitmproxy 也能抓到包。 204 | 2. 刷快手推荐页,能成功刷出,并且 mitmproxy 也抓到了包。 205 | 206 | ![](https://blog.mythsman.com/content/images/2022/02/K20220220-174206.png) 207 | 208 | 总结 209 | -- 210 | 211 | 目前看来,快手并非像很多网上传的那样,大多数接口都走了 kquic 协议导致无法抓包。其实很多接口只是做了一个禁止走系统代理的设置,简单使用透明代理模式进行抓包即可轻松绕过。当然,不排除有些关键接口也做了 SSL pinning、走私有协议之类的。。。 212 | 213 | 参考 214 | -- 215 | 216 | [https://docs.mitmproxy.org/stable/concepts-certificates/](https://docs.mitmproxy.org/stable/concepts-certificates/) 217 | 218 | [https://stackoverflow.com/questions/56830858](https://stackoverflow.com/questions/56830858) 219 | 220 | [https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/proxy/](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/proxy/) -------------------------------------------------------------------------------- /simpread-某某 App protobuf 协议逆向分析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/VhyG4diX1wNUR4xdtCK8uQ) 2 | 3 | 4 | 5 |      大家好,我是 TheWeiJun。前期发布的两篇某大厂的文章因为收到律师函,作者已经对文章进行删除。今天我们分享一个 protobuf 协议逆向分析,教你如何在无结构的数据中抽取并定义 proto 文件。各位读者在阅读的同时不要忘记点赞 + 关注哦⛽️ 6 | 7 | 特别声明:本公众号文章只作为学术研究,不用于其它用途;如有侵权联系删除。 8 | 9 | ![](https://mmbiz.qpic.cn/mmbiz_gif/m5qEELWt8A6N3l9obtATicYg0vSreUhmEsiciaibJfHjG8MEHndnoJycRKpgeeK3LkxKu6qlz7oyC2Gelexa4W4Bfw/640?wx_fmt=gif) 10 | 11 | 12 | 13 | 立即加星标 14 | 15 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A6N3l9obtATicYg0vSreUhmEG7mwRfEUOichNPSVJkJl4IDcjeqvJyO5GGxL9CaYcRQqg13wjyXVzicQ/640?wx_fmt=png) 16 | 17 | 每天看好文 18 | 19 |  目录 20 | 21 | 22 | 23 | 一、什么是 protobuf? 24 | 25 | 二、protobuf 堆栈结构 26 | 27 | 三、proto 定义及编译 28 | 29 | 四、完整代码实现 30 | 31 | 五、心得分享及总结 32 | 33 | 趣味模块 34 | 35 |       小明是一名爬虫开发工程师,自从上次小明解决了字体反爬、websocket 协议、B 站 protobuf 协议后,小明一直所向披靡,过五关斩六将,在一个多月的时间里一直没有遇到过有难度的问题。但是今天,小红遇到了某某 App 端的 protobuf 协议加密,不再是 js 端一样去找定义的类型 id 了。那么今天我们去分析下小红同学遇到的新问题吧,如何通过无结构定义 proto 文件并还原某某 App 的 protobuf 协议! 36 | 37 | 一、什么是 protobuf 协议? 38 | 39 | **前言:**Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台,无关语言,可扩展,轻量级高效的序列化结构的数据格式,用于将自定义数据结构序列化成字节流,和将字节流反序列化为数据结构。所以很适合做数据存储和为不同语言,不同应用之间互相通信的数据交换格式,只要实现相同的协议格式,即后缀为 proto 文件被编译成不同的语言版本,加入各自的项目中,这样不同的语言可以解析其它语言通过 Protobuf 序列化的数据。目前官方提供 c++,java,go 等语言支持。 40 | 41 | 二、protobuf 堆栈输出 42 | 43 | 1、首先从 charles 应用中保存我们抓到的某某 App 数据包,截图如下所示: 44 | 45 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7tj3p18Qv2gHB2jkQHnTT6OSdhmXKrTicAthCdf9HeITbibFJUicYUibrSeLDso0EJ0gAu6w0uqYEaSw/640?wx_fmt=png) 46 | 47 | 2、使用 Postman 访问协议接口,将 response 响应内容保存为. bin 格式文件,截图如下所示: 48 | 49 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7tj3p18Qv2gHB2jkQHnTT6yGBUI9jrwtibVwpXgRMXaPv8qB4PtQQVIqXlfPNx61OsrDraFXg63sA/640?wx_fmt=png) 50 | 51 | 3、使用 **protobuf_inspector** python 第三方包打印 protobuf 文件堆栈信息,截图如下所示: 52 | 53 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7tj3p18Qv2gHB2jkQHnTT6Fe8YibMxlZz0S8HWKDFD4GF13BfyuW1wYyxbrFyjicSukRdLA9gyLtdQ/640?wx_fmt=png) 54 | 55 | **总结:**打印 protobuf 数据堆栈信息后,接下来我们只需要根据打印的堆栈信息定义 proto id 类型文件并编译为 python 可执行文件即可完成对某某 App protobuf 协议还原。 56 | 57 | 三、proto 文件定义及编译 58 | 59 | 1、通过分析打印的堆栈信息,确定我们本次要提取的文字内容,截图如下所示: 60 | 61 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7tj3p18Qv2gHB2jkQHnTT6jNojqJsBIKNDwuwJy6ln4XYvbuwP2M9jERILHhgR9iaysLAMiaYYlF7g/640?wx_fmt=png) 62 | 63 | 2、确定我们要提取的文字内容后,定义 proto 文件格式,定义后的结构体如下所示: 64 | 65 | ``` 66 | syntax = 'proto3'; 67 | message DanMo{ 68 | message Message{ 69 | int32 id = 1; 70 | int32 time = 8; 71 | string content = 7; 72 | } 73 | repeated Message message = 1; 74 | } 75 | 76 | ``` 77 | 78 | 3、执行如下命令,编译为 python protobuf 可执行文件: 79 | 80 | ``` 81 | protoc  --python_out=. *.prot 82 | 83 | ``` 84 | 85 | 4、运行命令后,生成 python protobuf py 文件,截图如下所示: 86 | 87 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7tj3p18Qv2gHB2jkQHnTT6wx5qIYSIbEmE3Q4quMtMcs2lZBJgchSlAZlONO4sT0xl2ibwykqiaKMw/640?wx_fmt=png) 88 | 89 | **总结:**走到这里 protobuf 协议就完全还原了,接下来让我们进入完整代码实现环节吧。 90 | 91 | 四、完整代码实现 92 | 93 | 1、整个项目 Python 完整代码实现如下: 94 | 95 | ``` 96 | from google.protobuf.json_format import MessageToDict 97 | from danmo_pb2 import DanMo 98 | with open('test.bin', 'rb') as f: 99 | _info = DanMo() 100 | _info.ParseFromString(f.read()) 101 | _data = MessageToDict(_info, preserving_proto_field_name=True) 102 | items = _data.get("message", []) 103 | for item in items: 104 | _id = item.get("id") 105 | _time = item.get("time") 106 | content = item.get("content") 107 | print(_time, content, _id) 108 | 109 | ``` 110 | 111 | 2、代码编写完成后,我们运行代码,代码输出截图如下所示: 112 | 113 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7tj3p18Qv2gHB2jkQHnTT6NzuJzaaUuQvwmIdXvAaUQHiceBg7Sextib8nGZibPRgKa0d1ibwib3DG19Q/640?wx_fmt=png) 114 | 115 | 五、心得总结分享 116 | 117 |       通过本次案例分享,我发现有些技术不是难,而是没有找到好的方案去解决;有的问题需要思考 3 个小时甚至更久,但是解决只需要 10 分钟。今天分享到这里就结束了,欢迎大家关注下期文章,我们不见不散⛽️ 118 | 119 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A43BLL5j8ShhkUSRLT9ayEsmde5yxQmlKq8qZgsltoYaFpliaXKlmJUtNr6uZHibrn2xgIsQJUXu2nA/640?wx_fmt=png) 120 | 121 | 关注我们获得更多精彩内容 122 | 123 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7revypRO1iacSSjh6m3iaeZ7k7QiaRDzFktiaSbkClw0pXa6NV1Q9ge9a6D5nxGOojicqVQUQqQK0NOHg/640?wx_fmt=png) 124 | 125 | _**END**_ 126 | 127 | ![](https://mmbiz.qpic.cn/mmbiz_png/m5qEELWt8A7revypRO1iacSSjh6m3iaeZ7zpB5TQPCHMeJVyX5BicWRibtHzfCIJvrJRAiaLC9akyJxXrfKVMnUS6rw/640?wx_fmt=png) 128 | 129 | 130 | 131 | ![](https://mmbiz.qpic.cn/mmbiz_gif/m5qEELWt8A4g05V4rHL4vZMyGTE8ic691Wt6FFglTFeeibsPZT5F1vAiafn06J37WwvPkkGVX2B14Qh3gpPmic5Dpw/640?wx_fmt=gif) 132 | 133 |     我是 **TheWeiJun**,有着执着的追求,信奉终身成长,不定义自己,热爱技术但不拘泥于技术,爱好分享,喜欢读书和乐于结交朋友,欢迎加我微信与我交朋友。 134 | 135 |     分享日常学习中关于爬虫、逆向和分析的一些思路,文中若有错误的地方,欢迎大家多多交流指正☀️ 136 | 137 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/m5qEELWt8A4g05V4rHL4vZMyGTE8ic691PicricStHwRzqmIO1cPGTPsCk5SmfU2AZQLL2B6KSpxaHGguqZjXnjiaw/640?wx_fmt=jpeg) 138 | 139 | ![](https://mmbiz.qpic.cn/mmbiz_gif/m5qEELWt8A7lKEcIibT9GVduic8eSDCiaFLwXGFXXOX978IvJSokfVzoN9L2EUB8bUAC0QqT1WJmckZfBfZJl3FFA/640?wx_fmt=gif) 140 | 141 | **点分享** 142 | 143 | ![](https://mmbiz.qpic.cn/mmbiz_gif/m5qEELWt8A7lKEcIibT9GVduic8eSDCiaFLAWgUzHv0vx1STQhKykWTpicN12F4UdUeNXS1WsXYicqeBzmEUQbB3dAg/640?wx_fmt=gif) 144 | 145 | **点收藏** 146 | 147 | ![](https://mmbiz.qpic.cn/mmbiz_gif/m5qEELWt8A7lKEcIibT9GVduic8eSDCiaFLJzbzyLgP8z7NIwqluryicesaw2PBoEPNvQ8K0jpyqn7AlA3vHGq1n3Q/640?wx_fmt=gif) 148 | 149 | **点点赞** 150 | 151 | ![](https://mmbiz.qpic.cn/mmbiz_gif/m5qEELWt8A7lKEcIibT9GVduic8eSDCiaFLXdRibvzbwpGJb8wcTtFaUYTx1WXpUiaaD9TYy6Rk7jYhSwAL7c7BsSbA/640?wx_fmt=gif) 152 | 153 | **点在看** -------------------------------------------------------------------------------- /simpread-某查线路 app 设备检测逆向分析.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/HMrhWmB4k_c90WWPOyBDkA) 2 | 3 | 言 4 | - 5 | 6 | 又好久没更新了。最近一直都在知识的海洋里苦苦挣扎,也算是学了一些东西,自以为技术还可以了,结果遇到几个难搞的 app,又把我干废了。 7 | 8 | 还是得多学多练啊 9 | 10 | 这个 app 是好友 @Artio 给我的,我本着好奇想玩一下,结果还挺有意思。 11 | 12 | 分析 13 | -- 14 | 15 | 打开 app,wifi 访问,习惯性的开 postern+charles,准备抓个包看看: 16 | 17 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6MOC0wntZG3BIibliatibl7HcHS7VtI2KKkK3J4LueE2KjfHjHuS19icDJg/640?wx_fmt=png) 18 | 19 | 然后就有这个提示,我开流量访问,就很正常的访问数据,我就觉得很奇怪了,难道这个 app 禁止 wifi 访问?如果禁止 wifi 访问的话,是不是也算一种另类的反抓包办法啊哈哈 20 | 21 | 搞逆向的兄弟们应该都知道,像这种提示,是 toast 实现的(有关 toast 文档在这里:https://developer.android.google.cn/guide/topics/ui/notifiers/toasts?hl=en#java),那就跟下逻辑,找堆栈,看他是怎么识别我用的 wifi 的 22 | 23 | objection 启动,内存漫游分析 24 | 25 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6E3VicnC3ga1j56CrpJrsey8pmfZwEO0X0j68ftoXh8mKEKtG6L7jdAg/640?wx_fmt=png) 26 | 27 | 卧槽,一启动就挂了,看来有 frida 检测,用脚本过掉 frida 检测,这里我用的脚本是针对两个的,一个字符串,一个 pthread 的 28 | 29 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6hXFFN8QITkmyZ4mlpGJaaX4g7cF8eKWvdf2daz4YUIsz0zbW2dMCew/640?wx_fmt=png) 30 | 31 | 先只开针对 str 的: 32 | 33 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6oCtQVo1kGqmLhtIaZ2YNdWmu1pdKuyZcOleZKCaebzlV8URgw16Xrg/640?wx_fmt=png) 34 | 35 | ok,app 正常打开,frida 也没掉,那就算过了(后面我才知道并没有这么简单) 36 | 37 | 那么要 objection 分析的话,就开两个终端 38 | 39 | 先执行 objection -g xxx explore 40 | 41 | 另一个终端马上执行  frida -U -l hook.js   xxx 42 | 43 | 好了,现在就可以在 objection 里分析了,用 search  wifi,找到了 3000 多个 44 | 45 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6jY9owF0rI9Fibs2y6I5gk86GCAuWDMvNnbjJicxcTdCDxsFc8gPXhADg/640?wx_fmt=png) 46 | 47 | 我人麻了,哪个才是啊,后面一步步跟逻辑,发现了 toast 的类, 48 | 49 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6vqU7Fjmib9eq85mL8dIPxMW9NnHXCk2pKtuZicHiaynXNXNe8baoI2pZQ/640?wx_fmt=png) 50 | 51 | 然后在 toast 基础之上他自己封装了一个类: 52 | 53 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia62nOzMUFn75tBukCWMZWED4EfuG3eRqxSFMDV3CmPcKaJibjoW9paJuA/640?wx_fmt=png) 54 | 55 | 然后用 show 调的 56 | 57 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6ibsNBrV3rX4d0BJCUemEnoNH8Q0n09X3MZNNSLKzJWnianZVjMhO2bOg/640?wx_fmt=png) 58 | 59 | 那就一步步回溯堆栈看他是怎么判断的了。思路是没问题的对吧。我调试了老半天,愣是没找到怎么判断的,找到下面几个比较可疑的地方,结果 hook 发现根本没走到这里 60 | 61 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6ibibCgAoMuxUzk4AQOD6j026R2ky9CHODUS4SoFbibzEUfD08icCenTx3A/640?wx_fmt=png) 62 | 63 | 迷茫了 64 | 65 | 好了,今天的技术分享到此结束,遇到问题睡大觉,要学会放弃,菜狗就得认命。。。。 66 | 67 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6CMMXNtAM5uJYYEmVOcwiczNCgXjxyVr7v6l5iasqxgcJN7wxJljubBKQ/640?wx_fmt=jpeg) 68 | 69 | 好了,不扯了。后面冷静之后再分析,发现其实是 frida 检测到了,只是对 strhook 是不够的,还得有 pthread 的,所以我把上面 frida 对抗的脚本两个都开上了,用 spwan 模式再次启动 70 | 71 | 然后就有了,wifi 可以使用了: 72 | 73 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6dVsZfMDNmXiawyiaxCL9VUVChYbJ50vic73LH3gMugG584PfF5aelFFRA/640?wx_fmt=png) 74 | 75 | 为了验证这个,我把 frida-server 停了再重启 app,wifi 可以正常打开,还真的是被检测了 76 | 77 | 好的,现在再打开 postern 看看,发现还是提示网络错误那个,那就是有 vpn 检测了 78 | 79 | 对于 vpn 检测的怎么搞呢,肉师傅星球里有相关的,同时里面也有个练习 app,Ipconfig,可以安装打开看看: 80 | 81 | 这是啥都不开的状态 : 82 | 83 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6ToRtAgeO6EK0icX00zZ79M2OCKiby4npInhE27QK22muqUs0pelk2Mng/640?wx_fmt=png) 84 | 85 | 这是开了流量的状态: 86 | 87 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6p4JzPvIXiaR6wNGgDBReaQHmS37B7mGZ5ZFECYnaur9sBLY76TlVK8Q/640?wx_fmt=png) 88 | 89 | 这是开了 wifi 的状态(开不开流量都是如下): 90 | 91 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6LA8L5j5vqXBiaz4AHbO6NDdxNHfWbCtG2wGQeY1Shcoyicu68beHmIVQ/640?wx_fmt=png) 92 | 93 | 这是开了 postern vpn 的状态: 94 | 95 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6MFDRD4ibNt7maF7vQJlWP7ZgVSuAVOH91WbMla9Aia4kOo1pwcEF0ARQ/640?wx_fmt=png) 96 | 97 | 这区别是不是很大啊,所以,其实,大部分的 vpn 检测就是对 tun0 的检测,那我们再搞个 hook vpn 检测的 frida 脚本是不是就搞定了 98 | 99 | 好像说得通,那么,还愣着干嘛,搞起来啊,这下面的就是主要的逻辑 100 | 101 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6icv9F76G69fJ7JslJmfnmlibsic0ouTG4KK0iaF1OPJ2ibbbIuRl00Gia4Jg/640?wx_fmt=png) 102 | 103 | 现在跑一下这个过 vpn 的脚本,然后看看 Ipconfig 是不是被修改了就行了: 104 | 105 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6EDqmQ36dpiafwhjUeHVYqEGicvFevSasbk8v1BH7WRmEMbjWulZVicAow/640?wx_fmt=png) 106 | 107 | Ipconfig 显示: 108 | 109 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6m7BmNdEaXxe26zJ40DpJQoWomIH3u3n9DhxP4iaNSRY1I9JQPcdSicbg/640?wx_fmt=png) 110 | 111 | 两个图片对比下: 112 | 113 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6pOs8dsQJaEHExsNiaZ5x4Jzuk4yWGrQmkVzaSibCALwN3Dq6OEy81W3Q/640?wx_fmt=png) 114 | 115 | 好的,至少是过了,注意 ccmni2 的值 wlan0,ccmni1 的值还是 tun0,如果你想改的更彻底一点的话,可以再把这个值也改了,或者把值置空,这里我就不去操作了,自己研究了。 116 | 117 | 好的,把这段过 vpn 的代码封装成函数,跟上面过 frida 检测的放在一个 js 文件里,spwan 模式启动: 118 | 119 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6iaW2ibgfvlmQ1sRyxnfsGKxdv73B3gEV5Wialr2ykMlez2GTrA4K7zrmw/640?wx_fmt=png) 120 | 121 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6a9drwEFqdvSTCzexWyaj4yz23ZeX1suZESoqWnFo430k5f0tkZ4enQ/640?wx_fmt=png) 122 | 123 | 选择两个地方 124 | 125 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6Rewc3m0nPTmeXmJiapUicIvRDVibvZ6VSa3TVbp07FicxbEiba2gbia8R8LQ/640?wx_fmt=png) 126 | 127 | 然后 charles 看结果,ok,完美抓到包,返回的结果刚好就是刚才就是手机页面上显示的推荐路线 128 | 129 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6pHgD8rbG3nVbhFic8sMBpmDCEsVT2zgSL9HBHdE86lQzh6I0TMNicNYA/640?wx_fmt=png) 130 | 131 | 接下来就是处理加密参数了,卧槽,等会儿,还有 wtoken(嘻嘻,有点意思) 132 | 133 | 除了 wtoken 以外,其他就这几个参数需要传 134 | 135 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6mVcpe4wrhAZy8LaSlzCUOLQGETibica9ibFIyPnwCPt1sddnooN9IribAg/640?wx_fmt=png) 136 | 137 | 发现,time-stamp 是啥就不说了,request-id 是 time-stamp 加了几位数 138 | 139 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6eX4S5p7lvhJ0J9IVxV0yhK1Xw1m1KiaZavpOEQ8ZOgpbRgx4QGpYH0Q/640?wx_fmt=png) 140 | 141 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6uN3tJmWNTC0QTKo5kARCaLlicvB8iciaE480uXBcVYV4ETFiaN8jE3ia66Q/640?wx_fmt=png) 142 | 143 | hmac 逻辑在这里: 144 | 145 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6fJcraibDfMxbxYscuEM5RaGpWCoqZnbQY5IFLktgKsBMnusrllZibicxA/640?wx_fmt=png) 146 | 147 | ![](https://mmbiz.qpic.cn/mmbiz_png/l4m5icTfxSXW1yAnbkjfD1sOsqDmSzuia6YcXCDBph3iayoyzGbZs1czZrrhucmgSnFWKf04Ou7ribAHtZv4zqbpHg/640?wx_fmt=png) 148 | 149 | ok,都是在 java 层里直接可以看到的参数 ,这里就不再继续了,over 150 | 151 | 结语 152 | -- 153 | 154 | 搞逆向还是得沉住气啊,慢慢来,别慌,不然思维容易卡住,一点点来,抽丝剥茧 155 | 156 | 当我 hook 掉之后,再次打开这个 app,就不再限制 wifi 访问了,好神奇,具体细节我就没去研究了。 157 | 158 | 至于他怎么判断 wifi 的,我也没去抠细节了,可以看看相关的文章: 159 | 160 | https://blog.csdn.net/qiqigeermumu/article/details/110429819 161 | 162 | https://blog.csdn.net/cn_yaojin/article/details/76854159 163 | 164 | Ipconfig 想玩一下的,可以在这里下载: 165 | 166 | > 链接:https://pan.baidu.com/s/1K22pbglrt4Fawt_T_F8O1g 167 | > 168 | > 提取码:ipjc -------------------------------------------------------------------------------- /simpread-某车联网 App 通讯协议加密分析 (三) Trace Block.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/f7KbPmTDnOJLh0Sm0GlN1Q) 2 | 3 | 一、目标 4 | 5 | 之前我们已经用 unidbg 跑通了 libencrypt.so,那么如何判断跑出来的结果是对是错?再如何纠正 unidbg 跑错误的流程,是我们今天的目标。 6 | 7 | v6.1.0 8 | 9 | 二、步骤 10 | ---- 11 | 12 | ### 找到明显的接口来判断 13 | 14 | checkcode 是加密,加密的结果确实不好判断是否正确。不过我们可以试试解密,能解密就是对的,简单粗暴。这里解密函数是 decheckcode 。 15 | 16 | 跑一下,这个结果明显不对,死心了 17 | 18 | ``` 19 | call decheckcode: fac34ffa581987c7a1ffa5b876ca96ce 20 | 21 | ``` 22 | 23 | ### 分析问题 24 | 25 | 结果不对,肯定是过程不对。 26 | 27 | 那么解决方案就是 分析对比 unidbg 运行的流程和 app 运行的流程 有哪里有不同? 28 | 29 | 对比运行流程有三个粒度, 函数、代码块和代码。 (在 ida 里按空格,出现的流程图中的每个块就是代码块) 30 | 31 | 我们今天主要要对比 decheckcode 函数,所以先从代码块的粒度来做 Trace。 32 | 33 | ### Trace Block 34 | 35 | unidbg 提供一个 BlockHook,每运行到一个代码块就触发这 Hook,我们就利用他来做 Trace Block 36 | 37 | Trace Block 结束之后,把命中的代码块地址都打印出来,用于在 Frida 中去 hook 38 | 39 | 有了这两个函数就可以干活了 40 | 41 | 在执行 decheckcode 之前去做 Trace, 执行之后去打印所有命中的 Block 地址。 42 | 43 | ###### Tip:  44 | 45 | 这个样本没那么复杂,所以就直接 Trace 所有代码范围,讲究人是需要缩小范围,只 Trace 自己感兴趣的部分。 46 | 47 | ``` 48 | Find native function Java_com_bangcle_comapiprotect_CheckCodeUtil_decheckcode => RX@0x4002b1bc[libencrypt.so]0x2b1bc 49 | sub_2b1bc 50 | sub_2b20c 51 | sub_2b238 52 | sub_2b254 53 | sub_2b270 54 | sub_2b28c 55 | sub_2b2a8 56 | sub_2b2c4 57 | sub_2b2e0 58 | sub_2b2fc 59 | sub_2b318 60 | sub_2b334 61 | ... 62 | 63 | subTrace len = 127 64 | ,['sub_21400','0x21400'] ,['sub_21c00','0x21c00'] ,['sub_22200','0x22200'] ,['sub_2b604','0x2b604'] ... 65 | 66 | ``` 67 | 68 | Trace 的结果出来了,命中的地址列表也打印出来了,一共命中了 127 个地址块。 69 | 70 | ### frida Hook 对比 71 | 72 | 把命中的地址列表导入到 frida 里面去 hook,然后就可以对比出来 unidbg 跑的流程和 App 跑的流程的差别了。 73 | 74 | ### 对比结果 75 | 76 | 对比的方法比较 low,先把 unidbg Trace Block 的结果复制到文本文件 1,然后把 frida hook 打印的结果复制到文本文件 2。 最后开启 Beyond Compare 来对比 77 | 78 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2LDicFEPdEMBgOzlic0CTnjOTZjd0nrFeZUiaQZK9PickIib8ILzAARXma3pFrMJo1YGRCoicUVKyPJt7w/640?wx_fmt=png)1:cmp 79 | 80 | 过程虽然很 low,但是结果可一点都不 low,从对比的结果看,大家之前都是好朋友,不过 sub_18650 之后就开始分道扬镳了。 81 | 82 | 这时候就需要问问 ida 了。 83 | 84 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2LDicFEPdEMBgOzlic0CTnjOicGwBIYaBLLyicVETibpTcCx0f7S1Y4nKhY3RLKP0XmncqoIDu8DU7cKQ/640?wx_fmt=png)1:fopen 85 | 86 | 这里 fopen 了一个文件,文件名是做了 base64。 87 | 88 | base64 谁不会呢,随便写两行代码就可以解出来了 /proc/%d/cmdline 89 | 90 | 这又在考我们的 android 编程知识了,问了下谷哥,哥说了,这是在读进程名,对于 apk 来说,进程名就是他的包名。 91 | 92 | 回想在 unidbg 中 有个不起眼的报错 93 | 94 | ``` 95 | [11:26:48 927] INFO [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:1309) - openat dirfd=-100, pathname=/proc/2256/cmdline, oflags=0x0, mode=0 96 | 97 | ``` 98 | 99 | 这也是在提醒我们,读取进程名失败了。 100 | 101 | ### 重定向 io 102 | 103 | unidbg 是支持这种情况的,先让 CaranywhereDemo 多继承一个 IOResolver 来做 io 重定向 104 | 105 | ok 了,这几步又和 App 跑的一样了,大家又是好朋友了。 106 | 107 | 不过问题还是没有解决,跑出来的结果还是不对,木有解密成功。而且貌似这个 app 还有坑,hook 点一多就摆烂,直接崩溃。 108 | 109 | 得找新武器对付它了,期待下一章的大结局吧。 110 | 111 | 三、总结 112 | ---- 113 | 114 | 何以解忧,唯有 Trace。 115 | 116 | 能下断点 Debug 的 App,一定就逃不出手心了。所以现在 App 的关注点都是抵抗 Debug,抵抗下断点。 117 | 118 | 结果不对,就和正确的结果去对比流程,跑的和你一模一样,总没毛病吧? 119 | 120 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/llIox45YGib2LDicFEPdEMBgOzlic0CTnjOhy2kB68bIZ3kPTkt1tVIQ1y2QmW3sHevBicNm8c0ANntN6REm5gBZsg/640?wx_fmt=jpeg)1:ffshow 121 | 122 | 凡说之难 在知所说之心 可以吾说当之 123 | 124 | ###### Tip:  125 | 126 | : 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我 wx: fenfei331 讨论下。 127 | 128 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2LDicFEPdEMBgOzlic0CTnjOmoJrsUBF9exib74V3ack5OZianQKbAYfNmo8MlMbiaP88IcqDZQHf5Orw/640?wx_fmt=png) 129 | 130 | 关注微信公众号,最新技术干货实时推送 131 | 132 | ![](https://mmbiz.qpic.cn/mmbiz_png/llIox45YGib2LDicFEPdEMBgOzlic0CTnjOFklWic5CrgDmsUB30FKL0J4Y4KWhusPFNxoaU9SyQQnXu2fpgX5GibicQ/640?wx_fmt=png) 133 | 134 |  手机查看不方便,可以网页看 135 | 136 |     http://91fans.com.cn -------------------------------------------------------------------------------- /simpread-浅谈设备指纹技术和应用.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=MzUxODkyODE0Mg==&mid=2247489364&idx=1&sn=105b191c86eef427356768fbe192a9df&chksm=f9803535cef7bc23050f1e8bced3a0621f8ec158be662b0886bee2940a98203b98f9561dd7c4&mpshare=1&scene=1&srcid=0613UR9PRXZD3TqbB85OcLZr&sharer_sharetime=1655091039124&sharer_shareid=9be5daf09995ef938577edacf59663a3&version=4.0.6.99102&platform=mac#rd) 2 | 3 | ![](http://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7asExq0Evsib5pHoD24M70jEAcyibfbqhMc6X4ZXy9ezVsmbY7Fictyz3GM6C3V7mu0KrenKlv0wwmw/0?wx_fmt=png) ** 小道安全 ** 以安全开发、逆向破解、黑客技术、病毒技术、灰黑产攻防为基础,兼论程序研发相关的技术点滴分享。 41 篇原创内容 公众号 4 | 5 | **背景** 6 | 7 | 当你手机 APP 上刷着某些视频并多停留几秒,后续再刷视频的时候,是否有感觉到更多是推送同类型的视频; 8 | 9 | 当你在某 APP 搜索某产品的时候,后面在启动 APP 时候,是否能感觉到更多给你推送相关的产品信息; 10 | 11 | 当你更换手机的时候,在登录同一个 APP 的时候,是否会有提示你是新环境登录需要特别验证才能登录; 12 | 13 | 当你手机上安装一些作弊软件,在进行支付的时候,是否会有提示你当前环境不安全不允许支付或支付不成功; 14 | 15 | 这些的背后都是依靠哪些技术进行支撑实现呢?这些场景下都离不开一个重要的**设备指纹技术**,下面就梳理设备指纹技术的细节。 16 | 17 | **理论基础** 18 | 19 | **设备指纹是指可以用于唯一标识出该设备的设备特征或者独特的设备标识。** 20 | 21 | 设备指纹围绕着设备的唯一 ID、设备环境风险特征、设备历史风险标签等维度对设备进行全方位刻画,识别设备风险、并且透传设备风险特征。 22 | 23 | 设备指纹在同一设备中的不同应用,必须具备设备 ID 不变,同一设备卸载重装 APP 应用,设备 ID 同样要保持不变,在 IOS 设备中重置 IDFA 后,设备 ID 不变改机软件修改属性后,设备保持 ID 不变。 24 | 25 | 设备指纹需要考虑设备指纹唯一标识的稳定性、唯一标识的唯一性、设备风险标签的精准度、设备风险标签的准召率、设备指纹所需的隐私权限、微行为无感识别能力、设备终端覆盖识别。 26 | 27 | **设备指纹的关键用途:设备信息唯一性、渠道流量检测、风险设备识别、通用风控策略。** 28 | 29 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpZKuuDlw3ib82HqK2crdRojm3uZUEENtSpwAQib8SAkdTEkqYkpbwmRnw/640?wx_fmt=png) 30 | 31 | (上图来源网络) 32 | 33 | **技术分析** 34 | 35 | 采集设备信息需要关注的问题:用户设备是真实设备?哪个设备信息是稳定?Android 系统大版本升级是否导致权限变动?采集的数据是否符合隐私合规政策?采用什么算法来计算出唯一 ID?新 APP 上线所有设备 ID 是全新? 36 | 37 | **技术实现流程:通过采集客户端的特征属性信息并将其加密上传到云端,然后通过特定的算法分析并为每台设备生成唯一的 ID 来标识这台设备。** 38 | 39 | **设备指纹必须具备**:稳定性、唯一性、安全性、易用性、高性能。 40 | 41 | **设备环境风险特征**:识别模拟器环境、多开、ROOT、篡改设备参数、脚本,等异常环境特征。具有稳定性高,性能高。 42 | 43 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpWCdnnKErVSu3cJf0CEQm7fy91sGB2nprDWP4JurPz2icpeurggZfSDQ/640?wx_fmt=png) 44 | 45 | 通常情况下,设备指纹采集到用户的设备数据后,数据会通过**异步方式**先上传到业务的服务器上,然后再通过代理服务端进行转发到对应设备指纹的服务端。这样也是为了保证数据的安全性,客户端采集数据功能**防止被剥离**,从而采集不到设备数据。**采集的数据一般通过 json 格式加密数据进行上传。** 46 | 47 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpibZlONlqYXicUGYhbtwmStFtApQSa93F4Tf29JYlpzibekcUF7VdHHMzA/640?wx_fmt=png) 48 | 49 | 设备指纹上传一般采用 URL 的 POST 请求,并集成 json 格式,并且所采集的字段信息中会有一些字段是无用的,有一些字段适用于对 json 信息采用强校验的混淆信息。 50 | 51 | **设备指纹中设备风险识别的微行为常用的属性**:电池状态、重力传感器、加速度传感器状态、联网状态、USB 状态、触摸轨迹、压感、按压时长、剪切板。 52 | 53 | **设备指纹的 SDK 主要以 java 代码和 C、C++ 代码为主**,java 代码部分是以 aar 包或 jar 包方式存在,C\C++ 代码主要以 SO 方式存储的。例如某易的设备指纹就是以 aar 包 (NEDevice-SdkRelease_v1.7.0_2022xxxxxx.aar) 单独方式存在,某盾的设备指纹以 aar 文件(fraudmetrix-xxx.aar)和 so 文件(libtongdun.so)两者相结合存在,某美的设备指纹以 aar 包 (smsdk-x.x.x-release.aar) 和 so 文件(libsmsdk.so)相结合存在。 54 | 55 | 设备指纹主要是通过集成到 APP 中的 SDK,还有小程序的 SDK,通常情况下是采用 aar 包方式进行提供的 SDK,还有就是强度较高的是通过 aar 包和 SO 文件进行结合集成的设备指纹 SDK。将关键的采集信息集成到 SO 中代码中实现,并且 SO 文件采用虚拟机保护。 56 | 57 | 下面是某设备指纹以 aar 形式的,它关键代码都是 java 实现的。**并且 java 代码利用 proguard 混淆规则**进行对 aar 的 class 类进行混淆类名而已,并没有混淆到函数名称和变量名称,字符串信息。 58 | 59 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpKurUo7Dx5qs6nYs8vYxNL4D0ho1nVoPX9w3G8NJpbzsBmOPcDensmw/640?wx_fmt=png) 60 | 61 | 下面是某设备指纹的 java 代码和 C++ 代码部分,java 代码和 C++ 代码都采用了**虚拟化保护技术进行保护。** 62 | 63 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEplEvDV6DE6JPwDWKFBp6WRFEBeW3ZAuibqtg2fGdFOnFGaL2lTLTDjvw/640?wx_fmt=png) 64 | 65 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpkN653wGjqwMh5nM13nibI9eOJX1tXdhqks3BtE3eoZpMy0ZibhiaW1HLg/640?wx_fmt=png) 66 | 67 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpv5s3QDIMibe6s8NOaukXibUXLataE47iaUMBJWcSZrl35RfomSCMiciaFpA/640?wx_fmt=png) 68 | 69 | 设备指纹读取用户信息,通常需要涉及到向用户申请权限的情况,所以**在 android 的 AndroidManifest.xml 配置文件**中通常有一系列的权限申请。 70 | 71 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEplwxGPUnUF6otQF1WFss5jedJ7Aic1OdoSiaTZkXePL4ianRsDJJZzR1Zg/640?wx_fmt=png) 72 | 73 | (上图只是申请权限的一小部分) 74 | 75 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpYEzZ030qubOrib5F0SK3dOvEQ0PMRGZXxFt2TNlmp4zZEb2K79pdXNg/640?wx_fmt=png) 76 | 77 | **设备指纹合规** 78 | 79 | 设备指纹应用中,在采集用户设备指纹信息的过程,首先必须确保用户 APP 中有**《用户隐私政策》**,并且在首次启动 APP 时就弹出《用户隐私政策》获得用户的同意,不得默认用户已勾选。并且确保只有用户同意的时候才可以进行对用户信息的采集。 80 | 81 | 根据**《网络安全法》**等相关法律法规要求,APP 应当在隐私政策中向最终用户告知收集、使用、于第三方共享最终用户个人信息的目的、方式和范围,并征得最终用户明示同意才可以采集用户信息。 82 | 83 | 设备信息采集需要遵循必要最小化低频采集非敏感信息原则,在采集用户属性时候,不应采集用户行为和应用列表、传感器状态、通讯录、相册等敏感信息,采集这些属性容易出现不符合隐私合规政策。支持按需采集和合规上架指导,采集信息 合规和安全加固,不触碰用户隐私,不会被黑产破解,兼容性好。 84 | 85 | 设备指纹 SDK 的初始化时机,在安装后首次启动时,并且只有在用户同意隐私协议后,才进行设备指纹 SDK 初始化。**如果用户没有同意隐私协议不可进行采集数据。** 86 | 87 | **风控场景分析** 88 | 89 | 设备指纹的在游戏应用场景中主要风控维度:手机设备、游戏账号、账号行为、账号动机。 90 | 91 | 设备指纹识别设备的风控特征:模拟器、协议刷数据、脚本外挂、设备改机、多开工具、云手机等。 92 | 93 | 设备指纹识别游戏账号的风控特征:批量注册游戏账号、猫池、接码手机、通讯小号等等。 94 | 95 | 设备指纹识别游戏行为的风控特征:批量养号、黄牛养号、鱼塘账号。 96 | 97 | 设备指纹识别账号动机的风控特征:账号的买卖、游戏的代练等 98 | 99 | 设备指纹识别风险识别重点在于:注册、登录、营销、交易、充值、渠道推广等业务场景中,识别出虚假注册、盗号、养号、薅羊毛、虚假推广、作弊行为,然后对应采取一定的对抗策略方案。 100 | 101 | 游戏中黑灰产破解移动端的技术及工具不断在更新变化发展,设备指纹中核心的技术攻防点主要围绕,root(非法读取文件,反安全检测)、自动化工具(批量注册、活动作弊)、模拟器(自动注册小号、秒杀)、多开(虚假作弊、养号、)、改机、群控(薅羊毛、虚假流量),app 重打包(植入广告、破解功能限制)等问题。 102 | 103 | **通过基于设备指纹技术,可以实现 IP 风险画像、风险情报、邮箱风险画像去识别游戏的黑灰产行为,然后对游戏黑灰产进行重点打击**。 104 | 105 | ![](https://mmbiz.qpic.cn/mmbiz_png/jVCRndy8Lr7jracFzJVIKFlR5eXB7TEpcWRbzCkB3PcDKX2V1gcf17ltdxuRrnfZFu37oPYpHZG3IRAVT8NbjA/640?wx_fmt=png) 106 | 107 | (上图来源网络) 108 | 109 | **设备指纹思考** 110 | 111 | 一个人常用设备的总是有限,一般正常情况下一段时间内**不会超过 5 个**,因此可以通过这些信息进行作为风控的策略,而设备指纹中关键的一个采集点是**网络相关信息的采集**,通过采集网络相关信息,可以判断出同一网络下的用户的设备数量。 112 | 113 | 一个好的设备指纹必须具备:**1. 可以灵活定制化**,可以根据不同的业务需求提供灵活的接口;**2. 高准确性**,对设备唯一码必须准确率足够高;**3. 性能卓越**,设备指纹的 sdk 不能影响到 APP 的性能;**4. 系统兼容性好**,需要兼容到 android4.0 到 android 13 的所有系统。 114 | 115 | 如果作为开发者,开发一个设备指纹 sdk 中需要综合考虑的:LaunchTime、闪退率、体积大小、网络消耗情况。 116 | 117 | 设备指纹技术存在一定的被动性。黑灰产研发者处在暗处,公司的业务在明处,在什么时间段,采用什么方式的攻击方式,都是黑灰产攻击者决定的,这一点业务安全人员也是非常头疼。更进一步,**黑灰产可以不断试错通过分析设备指纹技术采用的算法**,尝试采用新的攻击方式绕过已有加解密算法,就可以达到新的攻击目的。**设备指纹技术对抗过程中是存在一定的滞后性。** 118 | 119 | 120 | 121 | 122 | 123 | 结束 124 | 125 | 126 | 127 | 128 | 129 | **【推荐阅读】** 130 | 131 | [**APP 应用安全检测**](http://mp.weixin.qq.com/s?__biz=MzUxODkyODE0Mg==&mid=2247489000&idx=1&sn=a9ff1413307a7d20691ea0ca1696f20b&chksm=f9803789cef7be9f8b7617c063eefaa0ecd1bffb43de85a66fce7c453eb17b8c7b37edf1fa51&scene=21#wechat_redirect) 132 | 133 | [**App 安全测试**](http://mp.weixin.qq.com/s?__biz=MzUxODkyODE0Mg==&mid=2247486713&idx=1&sn=967a0a8435add4949a829eeaf2c4c647&chksm=f9802e98cef7a78e8a5439c9d64c0f8d59d800050a517eb0388877b13187d7577ef0106f1298&scene=21#wechat_redirect) 134 | 135 | [**APP 安全合规**](http://mp.weixin.qq.com/s?__biz=MzUxODkyODE0Mg==&mid=2247485420&idx=1&sn=20f1069713342043488db05568afec9e&chksm=f980258dcef7ac9b2e142ef68076b89878da5026834854a5baf517cdecd3be961947137c5c47&scene=21#wechat_redirect) -------------------------------------------------------------------------------- /simpread-猿人学 - app 逆向比赛第四题 grpc 题解.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=MjM5NDMzMzAwNQ==&mid=2247483889&idx=1&sn=67c589b25ad9267991fa46a466b0b60e&chksm=a68829d391ffa0c51f0c9759244c73dbde660128bdbef26c486dfc8a6f09e65b1244b35c6ab5&mpshare=1&scene=1&srcid=0517MzN1HFdcNztEIdMRoIQm&sharer_sharetime=1652717632886&sharer_shareid=9be5daf09995ef938577edacf59663a3&version=4.0.2.90460&platform=mac#rd) 2 | 3 | ![](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3B6WPiavosZFcyzR8y0A5ibaMM1XX5UqvEuGcOXrv8GnLZWUSOX4lNfm9DicHF1cU8lY2q0rm7icqt0A/640?wx_fmt=gif) 4 | 5 | 周五晚 8 点参加了猿人学的 app 逆向比赛, 和我的队友 "duoduo" 兄一起把这道题做出来了, 于是简单分享一下思路和过程。 6 | 7 | ![](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3B6WPiavosZFcyzR8y0A5ibaMM1XX5UqvEuGcOXrv8GnLZWUSOX4lNfm9DicHF1cU8lY2q0rm7icqt0A/640?wx_fmt=gif) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |  目录 18 | 19 | 20 | 21 | ⊙一 . 什么是 grpc 22 | 23 | ⊙二. 什么是 protobuf 24 | 25 | ⊙三. 数据包协议分析 26 | 27 | ⊙四. sign 加密与 python 代码实现 28 | 29 | ⊙五. 总结 30 | 31 | **一 . 什么是 grpc** 32 | 33 | gRPC 是一个现代开源的高性能远程过程调用 (RPC) 框架,可以在任何环境中运行。它可以通过对负载平衡、跟踪、健康检查和身份验证的可插拔支持有效地连接数据中心内和跨数据中心的服务。它也适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。 34 | 35 | `gRPC`基于 `HTTP/2`协议传输。 36 | 37 | 客户端传递个函数进去, 然后服务端执行成功后给客户端结果。 38 | 39 | **二. 什么是 protobuf** 40 | 41 | Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。 42 | 43 | 可以简单理解为,是一种跨语言、跨平台的数据传输格式。与 json 的功能类似,但是无论是性能,还是数据大小都比 json 要好很多。 44 | 45 | protobuf 之所以可以跨语言,就是因为数据定义的格式为`.proto`格式,需要基于 protoc 编译为对应的语言。 46 | 47 | **三. 数据包协议分析** 48 | 49 | **初看 java 层** 50 | 51 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDfsmMmVHiaIHsKibo5ZsSzbiaKSqbJJlf4eBQYbn9soYjH2KHzyEzHyGkQ/640?wx_fmt=png) 52 | 53 | 主要的逻辑是: 把时间戳和每次下拉之后页数自动加 1 后的数据放入到函数中去执行, java 层被混淆的面目全非, 中间掉用了一个 sign 为 native 的方法, 于是换种思路从数据包上面开始分析。 54 | 55 |  3.1 抓包 56 | 57 | 拿到这道题的时候受前面几道题目的影响以为直接可以抓到包, 使用 Drony 转发到 charle 上后发现没有这道题的数据包, 初步怀疑使用了其他的协议, 然后在 pc 打开 wireshark 进行数据包的拦截, 同时开启 frida hook 第四题中的 sign。 58 | 59 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDYRSsz7XqYpURJyf9Ra0ttbuBW3Do6LQw04RplSicc84fLWhTPAa8zGg/640?wx_fmt=png) 60 | 61 | 62 | 发现 frida hook 的值和 wireshark 抓到的数据包相同, 这也就坐实了这个数据包为第四题的发包函数。 63 | 64 | 然后我就想着能不能再找到收到的响应呢? 65 | 66 | 在 wireshark 输入过滤条件 67 | 68 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDeIjYD1D6QJB1Z5ViasvcncibdzKnoYJuXIZUjxxuRAMeNHmibBcSDVicQA/640?wx_fmt=png) 69 | 70 | 然后一个一个包来看 71 | 72 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDfDjF7UUkvuCaZQFBOBcf5wATYvtabUVIcLYYutiaIV6QLouI0cRCEmQ/640?wx_fmt=png) 73 | 74 | 确实找到了返回数据。 75 | 76 | 3.2 数据包分析 77 | 78 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDy3Sx2NwLKIZFDDJXeTQUqJRGqOB88K6icxYm14mwbE02qqYhO3me5pg/640?wx_fmt=png) 79 | 80 | 于是找来两个不同时间发送的数据包来对比下差异 81 | 82 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoD5xLVIlClvYmvGjvRsWFt0VaPO4yObYml70TvFONEtOianceoLh5mljQ/640?wx_fmt=png) 83 | 84 | 也就是说结尾位置的为一些重要的参数, 我们能够伪造到然后加上前面的数据段不久可以请求服务器产生数据了? 85 | 86 | 那么如何伪造这些参数呢? 87 | 88 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDjvZLLqJ8A3tN0iaQPGTPYXZibll1GgN8W7s78HibmXMCLpYp09qyutUZg/640?wx_fmt=png) 89 | 90 | 在数据的 hexdump 中只能看到 sign 的身影, 那么 page 和时间戳的身影跑哪去了, 经过了查阅资料后发现 grpc 数据的传送经过了 protobuf 的编码, 字符串在这里可以显现, 但是时间戳在 hexdump 里面并没有被显现出来。 91 | 92 | 那么开始编写 protobuf 文件吧 93 | 94 | ``` 95 | syntax = "proto3";//指定版本为proto3,默认为proto2 96 | message SearchRequest { 97 | uint32 page =1; 98 | uint64 time = 2; 99 | string sign =3; 100 | } 101 | 102 | ``` 103 | 104 |  然后执行: protoc --proto_path=./ --python_out=./ test.proto      105 | 106 | ``` 107 | def bytes2hex(byte_arr: bytes) -> str: 108 | return byte_arr.hex() 109 | import test_pb2 110 | # 实例化协议对象 111 | ser = test_pb2.SearchRequest() 112 | ser.page = 1 113 | ser.time = 1652580021332 114 | ser.sign ="b94a543f66e23656" 115 | # 对数据进行序列化 116 | data = ser.SerializeToString() 117 | print(type(data)) 118 | print(bytes2hex(data)) 119 | 120 | ``` 121 | 122 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDQ9vEVia3DvCrKprp6dyQgcl1Nm2smNiazgxItTiaicQgwBuddzQvnicCwnQ/640?wx_fmt=png) 123 | 124 | 这个时候当事人 1 和当事人 2 都非常开心, 是不是就可以解决了?解决了就可以睡大觉了 125 | 126 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDnstavm4HmZlv4p2aRlzjBeq8NRrYiaTfW9zicZ9oic8W4icXickBPxQaXxQ/640?wx_fmt=png) 127 | 128 | 然后十分钟: 连接服务器 把 tcp 头部和数据拼接起来发给服务器。。。。。经过实验 失败,服务器会莫名断开连接。 129 | 130 | 四. sign 加密与 python 代码实现 131 | 132 | 于是想了下 python 如何 grpc 的请求和服务器交互, 说干就干. 经过了一番查阅资:需要以下条件 133 | 134 | 服务器的地址和端口 (废话), 前文中我们提到 grpc 那么就有要被执行的函数, 有被执行的函数就得有接受函数执行完的结构。 135 | 136 | 于是乎 下面的代码就出来了 137 | 138 | ``` 139 | syntax = "proto3"; 140 | package challenge; 141 | message app4 { 142 | uint32 page =1; 143 | uint64 time = 2; 144 | string sign =3; 145 | } 146 | message HelloReply { 147 | repeated string message = 1; 148 | } 149 | service Challenge{ 150 | rpc SayHello (app4) returns (HelloReply) {} 151 | } 152 | 153 | ``` 154 | 155 | ``` 156 | # -*- coding: utf-8 -*- 157 | import grpc 158 | import data_pb2,data_pb2_grpc 159 | _HOST = '180.76.60.***' 160 | _PORT = '9901' 161 | def run(): 162 | conn = grpc.insecure_channel(_HOST + ':' + _PORT) 163 | client = data_pb2_grpc.ChallengeStub(channel=conn) 164 | page_time = "1:1652586112285" 165 | sign = "92979f42250a942b" 166 | page_time = "4:1652593296974" 167 | sign = "f9f5c6fe5b3da911" 168 | page = page_time.split(":")[0] 169 | time = page_time.split(":")[1] 170 | response = client.SayHello(data_pb2.app4(page = int(page),sign = sign,time =int(time))) 171 | print( str(response)) 172 | if __name__ == '__main__': 173 | run() 174 | 175 | ``` 176 | 177 | 在终端下执行: 178 | 179 | ``` 180 | #python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. data.proto 181 | 182 | ``` 183 | 184 | 即可生成 grpc 和 probuf 协议的文件。 185 | 186 | run 一下 187 | 188 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDeE0u57OvfVeyrtaXtYFRvkCpmbY0Kpdlu73KELE1HMVibUQWUzFDv2w/640?wx_fmt=jpeg) 189 | 190 | 发现返回的数据结构怎么这么多位, 服务器抽风了还是我的程序抽风了, 后来 "duoduo" 兄说前面的 004 说明后面有四个值是对的。 191 | 192 | 于是协议模拟的问题告一段落。 193 | 194 | 要让整个算法跑出来算 100 页的数据, 还需要 sign 参数 195 | 196 | frida 代码如下 197 | 198 | ``` 199 | var signclass1 = Java.use("com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment"); 200 | signclass1.sign.implementation = function (arg1,arg2) { 201 | console.log(""); 202 | console.log("page_time= \""+arg1+"\""); 203 | console.log("sign= \""+this.sign(arg1,arg2)+"\""); 204 | return this.sign(arg1,arg2); 205 | } 206 | 207 | ``` 208 | 209 | 发现 sign : 210 | 211 | 第一个参数为 page: 时间戳。第二个参数就是时间就是时间戳 212 | 213 | 由于比赛原因 谁先搞完就能排名高, 掏出 unidbg(也想还原来着, 那么短时间不太现实) 214 | 215 | 写 unidbg 调用 216 | 217 | ![](https://mmbiz.qpic.cn/mmbiz_png/p5YHVYUwZib2ZORWVdSL5GibQd9DAf7SoDz4E9JLlwvza3xGp6BraV9zHl1I7CF0tFYfWZf4YSMnibVfWhsViaiciaNw/640?wx_fmt=png) 218 | 219 | 主要代码如下 220 | 221 | ``` 222 | public String callsign(String input,long j){ 223 | List list=new ArrayList<>(10); 224 | list.add(vm.getJNIEnv()); 225 | list.add(0); 226 | list.add(vm.addLocalObject(new StringObject(vm,input))); 227 | list.add(j); 228 | //Number number=module.callFunction(emulator,0xdf0,list.toArray())[0]; 229 | Number number = module.callFunction(emulator, "Java_com_yuanrenxue_match2022_fragment_challenge_ChallengeFourFragment_sign", list.toArray()); 230 | return vm.getObject(number.intValue()).getValue().toString(); 231 | } 232 | 233 | ``` 234 | 235 | 然后排名上升两名  end。 236 | 237 | 五. 总结 238 | 239 | 要去看更多的东西才能扩宽自己的眼界, 有些技术不是难, 而是没接触过 (hhh 接触过也不一定会), 还有 "duoduo" 兄 yyds。 240 | 241 | **我是 BestToYou, 分享工作或日常学习中关于二进制逆向和分析的一些思路和一些自己闲暇时刻调试的一些程序, 文中若有错误的地方, 恳请大家联系我批评指正。** 242 | 243 | ![](https://mmbiz.qpic.cn/mmbiz_gif/p5YHVYUwZib3B6WPiavosZFcyzR8y0A5ibalicqxrfTvYLw2zBMWqnyUFTG4vtoJpcjmckN4BNIusIIr8bU57ucmEA/640?wx_fmt=gif) -------------------------------------------------------------------------------- /simpread-猿人学第五题 - 双向认证分享.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/7wU8almDyjwXeEvVbEWgvw) 2 | 3 | 前言 4 | -- 5 | 6 | 这次和紫星大佬组队,获得了猿人学 - Android 端爬虫比赛的第一名。 7 | 8 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTvnxgiaq62wBfpTHoV5mvj4pE4uI5rKwkkaatZIUEB9E4x5IuRxNibM5g/640?wx_fmt=png) 9 | 10 | 在此先说一句:紫星大佬牛逼! 11 | 12 | 比赛链接:https://appmatch.yuanrenxue.com/ 13 | 14 | 获取 key 15 | ------ 16 | 17 | 第五题是双向认证,拿出珍藏的脚本 tracer-keystore.js 试试。 18 | 19 | ``` 20 | function hookKeystoreGetInstance() { 21 | var keyStoreGetInstance = Java.use('java.security.KeyStore')['getInstance'].overload("java.lang.String"); 22 | keyStoreGetInstance.implementation = function (type) { 23 | //console.log("[Call] Keystore.getInstance(java.lang.String )") 24 | console.log("[Keystore.getInstance()]: type: " + type); 25 | var tmp = this.getInstance(type); 26 | keystoreList.push(tmp); // Collect keystore objects to allow dump them later using ListAliasesRuntime() 27 | return tmp; 28 | } 29 | } 30 | 31 | 32 | 33 | ``` 34 | 35 | 下载地址:https://github.com/FSecureLABS/android-keystore-audit/blob/master/frida-scripts/tracer-keystore.js 36 | 37 | 打开 app,点击第五题,就出来 bks 证书的密码了。 38 | 39 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTX5yOEiavESLYtD31a3AkNMcWGJI3BCzuhciaq2YwfBicthYk29OXGU75g/640?wx_fmt=png) 40 | 41 | bks 到 p12 的转换 42 | ------------- 43 | 44 | 接着打开神器 keystore-explorer,进行 bks 到 p12 的转换。 45 | 46 | 下载链接:https://keystore-explorer.org/downloads.html 47 | 48 | 打开 clientCA.bks 49 | 50 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTVMhd4KtibGcbW3c2h7RPUmdwAdsL1VmxfFtNj2pND3xTPTZMg5LsiaicA/640?wx_fmt=png) 51 | 52 | 输入前面 hook 到的密码 53 | 54 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTL3y6Nxc2r0Yprg3vJAAl1g2Uzs7YpYHlmXOxqTu8fDnVawabDibrDIw/640?wx_fmt=png) 55 | 56 | 转成 p12 57 | 58 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTe5HaIku3lXjy1IdLX7zfZFkhxnD35bl9hRaJMICwZzNRv3US1VicGEw/640?wx_fmt=png) 59 | 60 | 导出证书 61 | 62 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTvM704Zr1a2AWtFc0K4FI2qPpCD1bia2K2Ejna4icLMYFoQBMFyCaJomw/640?wx_fmt=png) 63 | 64 | 抓包 65 | -- 66 | 67 | 效仿以前抓 soul 包的方式,将较早之前生成的 p12 证书导入 charles。 68 | 69 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTuibqrUEyHhRyrnfu7LRXmibCL09GVGSvafzSh8n5yic4LULVjlQQnARDg/640?wx_fmt=png) 70 | 71 | 发现提示密码错误。 72 | 73 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTt0kNT739tG5OibhW7rI9tnG0hGKLicPzYwSsOw5l2r9JxYV6DGUADAmw/640?wx_fmt=png) 74 | 75 | 【PS: 就在我还在对密码错误怀疑人生的时候,紫星巨佬说到:为啥一定要抓包?然后他就搞出结果了。。】 76 | 77 | 那换种思路,用神器 r0capture 试试。 78 | 79 | 下载链接:https://github.com/r0ysue/r0capture 80 | 81 | 运行神器后,请求流程自吐了出来。 82 | 83 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTfb7ZT231eC7Jq6CwKMBy7jTicH6Fr9XEPapVjd2ic3mhAFV6qnOvTrew/640?wx_fmt=png) 84 | 85 | POST 请求 86 | 87 | url 是 *.*.*.*:*/api/app5 88 | 89 | Content-Type 是 application/x-www-form-urlencoded 90 | 91 | User-Agent 是 okhttp/3.14.9 92 | 93 | data 是 page=1 94 | 95 | 脚本书写 96 | ---- 97 | 98 | 带上之前转化成功的 p12 证书,构造刚刚得到的请求,结果就呼之欲出了。 99 | 100 | ``` 101 | import requests_pkcs12 102 | 103 | def get_page(page): 104 | url = 'https://*.*.*.*:*/api/app5' 105 | hd = { 106 | 'Content-Type':'application/x-www-form-urlencoded', 107 | 'user-agent': 'okhttp/3.14.9' 108 | } 109 | data = { 110 | 'page': page 111 | } 112 | resp = requests_pkcs12.post(url, 113 | headers=hd, data=data, pkcs12_filename='1.p12', 114 | pkcs12_password='**********', verify=False) 115 | print(resp.json()) 116 | 117 | get_page(1) 118 | 119 | 120 | ``` 121 | 122 | ![](https://mmbiz.qpic.cn/mmbiz_png/8ib9picwJag1YxPOT8Etich3jQREXTgYXXTNCVwCGiaQPibFqhq0GhSSKL1MHgibico033PnLnAxMLzYJ6ib9jOvhB6emw/640?wx_fmt=png) 123 | 124 | 答案呼之欲出了。 125 | 126 | 队友紫星大佬的 52pojie:https://www.52pojie.cn/home.php?mod=space&uid=358970 127 | 128 | 紫星大佬的 github:https://github.com/zixing131 129 | 130 | 本人收藏了很多优秀文章:https://github.com/darbra/sperm 131 | 132 | 本人还有些可复现的案例:https://github.com/darbra/sign -------------------------------------------------------------------------------- /simpread-突破 tls_ja3 新轮子.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/dti6j1OFH6VW3m_vw-pCFA) 2 | 3 | 我之前的文章介绍了 SSL 指纹识别 4 | 5 | [https://mp.weixin.qq.com/s/BvotXrFXwYvGWpqHKoj3uQ](https://mp.weixin.qq.com/s?__biz=MzI5Mjg3MzM3OA==&mid=2247483806&idx=1&sn=92d0e3c000613ac56edc2aef163de026&scene=21#wechat_redirect) 6 | 7 | 很多人来问我 ByPass 的方法 8 | 9 | #### 主流的 BYPASS 方法有两大类: 10 | 11 | 1. 使用定制 ja3 的网络库 go 在这块的库比较流行(比如 go 的库 requests 还有 cycletls) 缺点在于,就是得用 go 语言开发(cycletls 有 nodejs 的但是也是开了一个 go 语言的一个 websocket) 12 | 13 | 2. 魔改 openssl, curl,最有名的就是 curl-impersonate 对应 win 版本的 https://github.com/depler/curl-impersonate-win 缺点就是编译复杂,使用方式上,得用包装 curl 的第三方库,用的多就是 python 的 pycurl 其他语言的比较少 14 | 15 | 16 | 正好五一有时间,站在巨人们的肩膀上我用 go 语言开发了一个代理服务. 17 | 18 | 只需要设置这个代理服务,就可以自定义 ja3 参数 19 | 20 | **这样任何语言都可以直接用了,而且只需要加一个 webproxy 即可** 21 | 22 | **具体效果可以往下看** 23 | 24 | ![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5RZsvvbInbxFNib9ZCibl9MkTVpsrgbdHLTEOmGmtNv1oklrCKiaQbIzlIEU5eYLJFyVnUT2WsesEedw/640?wx_fmt=png)image 25 | 26 | 解压后如上图,包含 2 个文件 (文件包加我要) 27 | 28 | * ja3proxy.exe  **go 开发的一个控制台程序** 29 | 30 | * localhost_root.pfx 本地证书 31 | 32 | 33 | 为了方便本机测试,先安装 localhost_root.pfx 证书, (如果不安装证书,也可以运行,只不过你需要将请求忽略 ssl verify)![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5RZsvvbInbxFNib9ZCibl9MkTG3DjhAz7fFm912HT9a5UamXNprmQ5SzPUIeC6W2icSHGq8cVyrMiaRMg/640?wx_fmt=png) 34 | 35 | 证书密码为 123456 36 | 37 | ![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5RZsvvbInbxFNib9ZCibl9MkTsh5A8ITagib1usObDEriczHtYgUjvpIygpS7GF3h5PEluabaicfR7Ep3Q/640?wx_fmt=png)image 38 | 39 | 选择位置为:受信任的根证书颁发机构 40 | 41 | ![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5RZsvvbInbxFNib9ZCibl9MkTNQyjniaMb5xMe6swXv0U4Co11mFTbEMlSibqs29RmseY8DVP8QJOqBLg/640?wx_fmt=png)image 42 | 43 | 安装成功后,使用如下命令 运行 ja3proxy.exe (为了测试方便,用 win 版本) 44 | 45 | ``` 46 | ja3proxy.exe -pfxFile=localhost_root.pfx -pfxPwd=123456 47 | 48 | 49 | 50 | ``` 51 | 52 | ![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5RZsvvbInbxFNib9ZCibl9MkTbiaYdS5jVTRlrtR0LsTR4n2iaKqnKAdAFo0UswSakX8iaOQwxTqzxqicxQ/640?wx_fmt=png)image 53 | 54 | 支持的参数共有如下: 55 | 56 | * pfxFile (pfx 类型证书) 57 | 58 | * pfxPwd (pfx 证书的密码) 59 | 60 | * authName 如果你要开启 ja3proxy 代理服务的 basicauth 认证, 可以设置 61 | 62 | * authPwd (同上) 63 | 64 | * httpPort (ja3proxy 代理的 http 端口,默认为 8080) 65 | 66 | * httpsPort (ja3proxy 代理的 https 端口,默认为 8443) 67 | 68 | * certFile 非 pfx 类型证书可以设置 69 | 70 | * keyFile 同上 71 | 72 | 73 | ja3proxy 运行成功后,测试 csharp 代码如下: 74 | 75 | ``` 76 | var proxy = new WebProxy 77 | { 78 |     // 这就是我们的ja3proxy 79 |  Address = new Uri($"http://localhost:8080") 80 | }; 81 | 82 | var httpClientHandler = new HttpClientHandler 83 | { 84 |  Proxy = proxy, 85 | }; 86 | 87 | // 因为我们再上面把证书添加到本机受信任了 所以这行代码不需要,如果你不操作受信任证书的话,就需要 88 | //httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; 89 | 90 | 91 | var client2 = new HttpClient(handler: httpClientHandler, disposeHandler: true); 92 | 93 | // 设置ja3指纹 94 | client2.DefaultRequestHeaders.Add("tls-ja3","771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,17513-10-18-11-51-13-27-0-35-65281-43-16-45-5-23-21,29-23-24,0"); 95 | // 设置ja3proxy执行请求的超时 96 | client2.DefaultRequestHeaders.Add("tls-timeout","10"); 97 | // 设置ja3proxy执行请求用代理,设置后请求目标服务器拿到的就是代理ip 98 | // client2.DefaultRequestHeaders.Add("tls-proxy","http://252.45.26.333:5543"); 99 | 100 | // 设置当前请求的useragent 101 | client2.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"); 102 | 103 | 104 | var result = await client2.GetStringAsync("https://kawayiyi.com/tls"); 105 | Console.WriteLine(result); 106 | 107 | 108 | 109 | ``` 110 | 111 | 执行后,校验 ja3 一致 112 | 113 | ``` 114 | { 115 |   "sni": "kawayiyi.com", 116 |   "tlsVersion": "Tls13", 117 |   "tcpConnectionId": "0HMQ8N2PQCRQE", 118 |   "random": "AwN2jHvxe/TKafrfmZ1KG2JWrD7u6M1N4dpeIGdYQwA=", 119 |   "sessionId": "FvNiwCLizsA2JZt0/8865tX2A5VsfbgjlCu4Qg4jPjg=", 120 |   "tlsHashOrigin": "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,17513-10-18-11-51-13-27-0-35-65281-43-16-45-5-23-21,29-23-24,0", 121 |   "tlsHashMd5": "05556c7568c3d3a65c4e35d42f102d78", 122 |   "cipherList": [ 123 |     "TLS_AES_128_GCM_SHA256", 124 |     "TLS_AES_256_GCM_SHA384", 125 |     "TLS_CHACHA20_POLY1305_SHA256", 126 |     "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 127 |     "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 128 |     "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 129 |     "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 130 |     "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", 131 |     "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", 132 |     "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 133 |     "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 134 |     "TLS_RSA_WITH_AES_128_GCM_SHA256", 135 |     "TLS_RSA_WITH_AES_256_GCM_SHA384", 136 |     "TLS_RSA_WITH_AES_128_CBC_SHA", 137 |     "TLS_RSA_WITH_AES_256_CBC_SHA" 138 |   ], 139 |   "extentions": [ 140 |     "extensionApplicationSettings", 141 |     "supported_groups", 142 |     "signed_certificate_timestamp", 143 |     "ec_point_formats", 144 |     "key_share", 145 |     "signature_algorithms", 146 |     "compress_certificate", 147 |     "server_name", 148 |     "session_ticket", 149 |     "renegotiation_info", 150 |     "supported_versions", 151 |     "application_layer_protocol_negotiation", 152 |     "psk_key_exchange_modes", 153 |     "status_request", 154 |     "extended_master_secret", 155 |     "padding" 156 |   ], 157 |   "supportedgroups": [ 158 |     "X25519", 159 |     "CurveP256", 160 |     "CurveP384" 161 |   ], 162 |   "ecPointFormats": [ 163 |     "uncompressed" 164 |   ], 165 |   "proto": "HTTP/2", 166 |   "h2": { 167 |     "SETTINGS": { 168 |       "1": "65536", 169 |       "3": "1000", 170 |       "4": "6291456", 171 |       "5": "16384", 172 |       "6": "262144" 173 |     }, 174 |     "WINDOW_UPDATE": "15663105", 175 |     "HEADERS": [ 176 |       ":method", 177 |       ":authority", 178 |       ":scheme", 179 |       ":path" 180 |     ] 181 |   }, 182 |   "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", 183 |   "clientIp": "103.219.192.197" 184 | } 185 | 186 | 187 | 188 | ``` 189 | 190 | h2 的 header 顺序:m,a,s,p 和 chrome 保持一致 191 | 192 | nodejs 测试也没问题 193 | 194 | ![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5RZsvvbInbxFNib9ZCibl9MkTuYC1QyYKgI2MuQabSSbcCGIHIePex3uAb2rHicmVpOR5CzSO0HAU6nQ/640?wx_fmt=png) 195 | 196 | ### 原理 197 | 198 | ![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5RZsvvbInbxFNib9ZCibl9MkTNWx89ay9Kpp0rm477UC7OlJOPSE7QePQjomGsLPYATzYT8Ir1fNaDQ/640?wx_fmt=png) 199 | 200 | ja3proxy(是一个中间人)接管你的请求,然后自己去目标建立 tls,clienthello 就用你指定的 ja3 参数 201 | 202 | ### 关于我 203 | 204 | ![](https://mmbiz.qpic.cn/mmbiz_png/ticEicibZjRw5SO4BTsscauC9SWo8eyZrg6U1eDTKAqIwJuPGscic7zM8KDFy5dsVYYMG6sibUic6ibYtELDkXRvypDNQ/640?wx_fmt=png)image 205 | 206 | 微软最有价值专家是微软公司授予第三方技术专业人士的一个全球奖项。27 年来,世界各地的技术社区领导者,因其在线上和线下的技术社区中分享专业知识和经验而获得此奖项。 207 | 208 | MVP 是经过严格挑选的专家团队,他们代表着技术最精湛且最具智慧的人,是对社区投入极大的热情并乐于助人的专家。MVP 致力于通过演讲、论坛问答、创建网站、撰写博客、分享视频、开源项目、组织会议等方式来帮助他人,并最大程度地帮助微软技术社区用户使用 Microsoft 技术。 209 | 210 | 更多详情请登录官方网站 [https://mvp.microsoft.com/zh-cn](https://mvp.microsoft.com/zh-cn) -------------------------------------------------------------------------------- /simpread-记一次 tiktok 抓包过程.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/s1doxCFy9riCF9t7zOeIuQ) 2 | 3 | 叠甲:**文章中所有内容仅供学习交流使用,不用于其他任何目的,仅供学习参考** 4 | 5 | **![](https://mmbiz.qpic.cn/sz_mmbiz_jpg/ZYeIDv4JOubGQtdBrmoiaib5AYb7mWxwJ9Iib1XiafBSAAWM7ICVKVkLCcb8Do6cSu906mFYPOXlGkvGwcDuRPA3qA/640?wx_fmt=jpeg)** 6 | 7 | **00 主要内容 8 | ** 9 | 10 | 先放效果图 (搜索) 11 | 12 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/ZYeIDv4JOubGQtdBrmoiaib5AYb7mWxwJ9qaA6s1CPzdOFYWPX1iclkN2YHibrS77qtSKdAGwibb6b1nPMz17Eh18Bw/640?wx_fmt=png&from=appmsg) 13 | 14 | **01 安装** 15 | 16 | Apk 来源: 17 | 18 | ``` 19 | 谷歌商店直接下载 20 | 搜索下载:https://www.apkmirror.com/ 21 | 22 | ``` 23 | 24 | 主要问题:tiktok 对于国内用户有锁区的情况,且需要代理,并且除了网络外还会识别设备信息比如说 sim 归属之类。最直接的方式是通过某些手段将其修改,下面有两种方法。(下面介绍第二种方式的插件使用) 25 | 26 | ``` 27 | - 重打包(非root方案):更改对于设备识别这部分,将开发的插件重新打包进apk,安装后即可使用。(网上有打包好的) 28 | ** 注意 ** 29 | ! 对应的插件推荐:https://github.com/Xposed-Modules-Repo/com.nnnen.plusne 30 | ! 网上有很多打包好的,如果想要自己打包则需要一个root设备借助lspatch来进行重打包(https://github.com/LSPosed/LSPatch) 31 | - hook:前提需要xposed环境,通过xposed插件来进行对指定软件进行修改 32 | ** 注意 ** 33 | ! 对应插件推荐:https://github.com/appenv/appenv.github.io 34 | 35 | ``` 36 | 37 | 打开应用变量选择目标应用 38 | 39 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/ZYeIDv4JOubGQtdBrmoiaib5AYb7mWxwJ9jQR9o9j6P7fSw8ibmvtnjzdhv39SBAoLMerSKzrrkeBsy86FHiaaUUyw/640?wx_fmt=png&from=appmsg)                  ![](https://mmbiz.qpic.cn/sz_mmbiz_png/ZYeIDv4JOubGQtdBrmoiaib5AYb7mWxwJ9YK6AJ38TicCZbHRJ2yRbdLM3GkJ465icTLdFDkuqTI7Zic8XWDaib8s30g/640?wx_fmt=png&from=appmsg) 40 | 41 | 配置对应 apk 的国家为其他允许使用的海外国家 比如说 us,保存后观察是否生效(如果没生效重启一下设备) 42 | 43 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/ZYeIDv4JOubGQtdBrmoiaib5AYb7mWxwJ9LvYf8nSbZOvzickCsO3ER5kshXYLY6icyun0ibNE6SJyQPMz00gZ56ykA/640?wx_fmt=png&from=appmsg) 44 | 45 | 上述配置完成,打开代理,就不会再出现网络的问题 46 | 47 | **02 配置抓包环境** 48 | 49 | charles 配置抓包:由于目标为海外 app 需要代理,因此需要配置 `External Proxy Settings`为 pc 端海外代理的接口 50 | 51 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/ZYeIDv4JOubGQtdBrmoiaib5AYb7mWxwJ9cuh0pguWTUNtWicEHKGYF9kYHpCuBc67yStZR7BeliaTTXe2jicXSZmMA/640?wx_fmt=png&from=appmsg) 52 | 53 | 配置的地址和 ip 要和你自己的代理一致 54 | 55 | ![](https://mmbiz.qpic.cn/sz_mmbiz_png/ZYeIDv4JOubGQtdBrmoiaib5AYb7mWxwJ9fdspqTEHndpAT1gCxKTE1VhnyavoqZTDqicsafjXJ9IibyaHFeWySQUQ/640?wx_fmt=png&from=appmsg) 56 | 57 | 抓包插件配置 58 | 59 | * 流量全局转发:使用 **postern** 进行对于流量的转发 60 | 61 | * 【插件一】升降级脚本:  quic 切换为 http 62 | 63 | 64 | ``` 65 | // 参考 https://blog.csdn.net/jmm18363027827/article/details/132217390 66 | // 通杀脚本 67 | // xposed 脚本 68 | String packageName = "com.zhiliaoapp.musically"; 69 | ...... 70 | if (lpparam.packageName.equals(packageName)){ 71 | Log.d(TAG, "handleLoadPackage: =======dyCapture====================="); 72 | Class CronetClient = XposedHelpers.findClass("org.chromium.CronetClient", lpparam.classLoader); 73 | XposedBridge.hookAllMethods(CronetClient, "tryCreateCronetEngine", 74 | new XC_MethodReplacement() { 75 | @Override 76 | protected Object replaceHookedMethod(XC_MethodHook.MethodHookParam methodHookParam) throws Throwable { 77 | Log.d(TAG, "replaceHookedMethod: hook 成功"); 78 | return null; 79 | } 80 | }); 81 | } 82 | 83 | ``` 84 | 85 | * 【插件二】sslpinning:发现连上 charles 就出现断网,以往经验来看是 sslpinning 问题, 使用经典`JustTrueMe`插件即可解决 -------------------------------------------------------------------------------- /simpread-过某加固 Frida 检测.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/RzdyDWRZ4un2_mC4Om8XZg) 2 | 3 | 过某加固 Frida 检测 4 | ============= 5 | 6 | 现象 7 | -- 8 | 9 | 通过 jadx 分析可知使用了某厂商加固 10 | 11 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3wvq5RiczWR3I0JO4hI7459e5omKhz3LkJ09L5978kRnNJaeRXHhZOlA/640?wx_fmt=png) 12 | 13 | 使用 frida 启动的时候,会退出应用 14 | 15 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3nM5uYOrmumPoD7vjMBw3gZkemMQqt5bxns2HowWdiaMGaJ6aGb5zTvw/640?wx_fmt=png) 16 | 17 | 后多次尝试发现,只启动 server 时,也会使程序退出 18 | 19 | 推测该应用检测了 Frida 20 | 21 | 定位 22 | -- 23 | 24 | 直接在 lib 目录里搜索 frida 25 | 26 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3LQ4rs7apINDGn3tNcGO8iajAqNLB0mYEVTA7fKB6TUsYuPARH6cSbWw/640?wx_fmt=png) 27 | 28 | 通过 010 逐一排查,只有红框中的 so 是 “LIBFRIDA”,其他都是 Friday。红框中正是壳 so,我们只需分析它即可 29 | 30 | Dump So 31 | ------- 32 | 33 | 首先把 libxxx.so 拖入到 IDA 中,发现报错 34 | 35 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk37iabNlwY1EOzBOQQRzeia2A3s82YH0mB7XPOosl8KcxEKicW9qHRYa2uQ/640?wx_fmt=png) 36 | 37 | 继续后发现 38 | 39 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3boIiarv8H7tUufpApvTBMHoCg98NSTcbgLqqUMmicMlOVF9XFMDPDdXA/640?wx_fmt=png) 40 | 41 | 为了防止我们的静态分析,它已经把 section 段的结构打乱了 42 | 43 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3lic7lTyicjhiaDkNlwlibz8Bked60QQNfQ2iaS1dqmpOA8FQzUL6YvVdpzQ/640?wx_fmt=png) 44 | 45 | ELF 两种视图:链接视图、程序视图,执行的时候只用知道 Program 中 Segement 即可。 46 | 47 | 正常情况下 so 加密分两种,一种是 so 自解密可以把解密方法放在. init_array 段,另一种是先加载一个解密 so A,再加载加密后的 so B,让 A 去解密 B。不管哪种为了正常运行,内存中的 so 都应该是解密后的。 48 | 49 | 使用 GG 模拟器 Dump 内存中的 so,首先先让程序运行起来。 50 | 51 | 打开 GG 模拟器,选中我们要 Dump 的应用,选择最右侧栏,打开选项选择导出内存 52 | 53 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk35xJFet944iayAKDbP62p4MfHXAML5lkfnow7mwMGXBBhHZfjYXsJw3Q/640?wx_fmt=png) 54 | 55 | 选择起始地址,点击保存,导出 dump 下来的 so 56 | 57 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3IQZkrIZQyBgzOxJHLFm6kjpWGbUXpmvibx6zcGFrHXQ90eF4ysTD5cQ/640?wx_fmt=png) 58 | 59 | 因为程序运行的时候是不需要知道 section 的,只用知道 segement 就行,但是静态分析又需要 section,所以我们要通过从内存中导出的 so,去修复 section 段,让 ida 能够正常反编译。 60 | 61 | SoFixer[1] 62 | 63 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3FXXGOgHZFoqXgIc58R1AUpwRntn3dvskw3JtkIp7349Tibu43Uxfl9w/640?wx_fmt=png) 64 | 65 | -s 添加需要修复的 so 66 | 67 | -o 要保存的路径 68 | 69 | -m 我们 dump 的起始地址,gg 模拟器已经自动将开始和结束地址保存下来了,只用加开始地址就行(16 进制形式) 70 | 71 | -d 输出信息 72 | 73 | 分析 74 | -- 75 | 76 | 这时候再拖到 IDA 中分析,看到代码已经还原出来了 77 | 78 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3McfKib3vNeHFWy8P4p85IeOyWBHE0nnK2ABSdBvjrQh2mibYdSIoaJyg/640?wx_fmt=png) 79 | 80 | 首先我们得先了解一下有哪些检测 Frida 的手段 81 | 82 | 1. 1. 遍历运行的进程列表从而检查 fridaserver 是否在运行 83 | 84 | 2. 2. 遍历 data/local/tmp 目录查看有无 frida 相关文件 85 | 86 | 3. 3. 遍历映射库,检测 frida 相关 so 87 | 88 | 4. 4. 检查 27042 这个默认端口 89 | 90 | 5. 5. 内存中扫描 frida 特征字符串 “LIBFRIDA” 91 | 92 | 6. 6. frida 使用 D-Bus 协议通信,我们为每个开放的端口发送 D-Bus 的认证消息 93 | 94 | 7. 7. 检测有无 frida 特征的线程名,如:gum-js-loop 95 | 96 | 97 | 其实通过刚才的现象结合着这些检测点我们已经大致能推测出是哪种方式检测的了,因为还没有使用 frida 附加,仅启动 server 就会使应用退出,那么检测点 1、2、3、5、7 都不符合。 98 | 99 | 我们就推测它通过检测点 6 去检测 Frida,向每个开放端口发送认证消息,认证消息里会包含 “AUTH“字符串,我们直接在 Strings Window 里搜索 “AUTH“ 100 | 101 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk3ggt4mGmagPmA1u2elwbKS9YG7pAa2z1GiblMBZab7373hr69CvnzuvA/640?wx_fmt=png) 102 | 103 | 查找引用果然发现检测代码 104 | 105 | ``` 106 | void __noreturn sub_2CA40() 107 | { 108 |   FILE *v0; // r11 109 |   int v1; // r4 110 |   __pid_t v2; // r0 111 |   unsigned int v3; // r0 112 |   char v4; // r0 113 |   char *v5; // r1 114 |   unsigned int v6; // r2 115 |   int v7; // r5 116 |   _BYTE *v8; // r6 117 |   int v9; // r3 118 |   int v10; // r4 119 |   int v11; // r6 120 |   bool v12; // zf 121 |   unsigned int v13; // r0 122 |   unsigned int v14; // r2 123 |   unsigned int v15; // r6 124 |   int v16; // r4 125 |   __pid_t v17; // r0 126 |   int v18; // [sp+0h] [bp-458h] 127 |   int v19; // [sp+4h] [bp-454h] 128 |   void *v20; // [sp+8h] [bp-450h] 129 |   int v21; // [sp+10h] [bp-448h] 130 |   unsigned int v22; // [sp+14h] [bp-444h] 131 |   void *v23; // [sp+18h] [bp-440h] 132 |   int v24; // [sp+20h] [bp-438h] 133 |   int v25; // [sp+24h] [bp-434h] 134 |   void *v26; // [sp+28h] [bp-430h] 135 |   char v27; // [sp+30h] [bp-428h] 136 |   int buf; // [sp+430h] [bp-28h] 137 |   __int16 v29; // [sp+434h] [bp-24h] 138 |   char v30; // [sp+436h] [bp-22h] 139 |   struct sockaddr addr; // [sp+438h] [bp-20h] 140 | 141 |   *(_DWORD *)&addr.sa_data[6] = 0; 142 |   *(_DWORD *)&addr.sa_data[10] = 0; 143 |   *(_DWORD *)&addr.sa_family = 0; 144 |   *(_DWORD *)&addr.sa_data[2] = 0; 145 |   addr.sa_family = 2; 146 |   inet_aton("127.0.0.1", (struct in_addr *)&addr.sa_data[2]); 147 |   while ( 1 ) 148 |   { 149 |     v26 = 0; 150 |     v24 = 0; 151 |     v25 = 0; 152 |     v0 = fopen("/proc/net/tcp", "r"); 153 |     if ( v0 ) 154 |     { 155 |       while ( fgets(&v27, 1024, v0) ) 156 |       { 157 |         v23 = 0; 158 |         v21 = 0; 159 |         v22 = 0; 160 |         v3 = strlen(&v27); 161 |         sub_138AC((unsigned int *)&v21, (int)&v27, v3); 162 |         v4 = v21; 163 |         v5 = (char *)v23; 164 |         v6 = v22; 165 |         if ( !(v21 & 1) ) 166 |         { 167 |           v6 = (unsigned int)(unsigned __int8)v21 >> 1; 168 |           v5 = (char *)&v21 + 1; 169 |         } 170 |         if ( v6 >= 0xA ) 171 |         { 172 |           v7 = (int)&v5[v6]; 173 |           v8 = v5 + 9; 174 |           v9 = (int)&v5[v6]; 175 |           if ( (signed int)(v6 - 9) >= 1 ) 176 |           { 177 |             v10 = v6 - 9; 178 |             do 179 |             { 180 |               v9 = (int)v8; 181 |               if ( *v8 == 58 ) 182 |                 break; 183 |               --v10; 184 |               ++v8; 185 |               v9 = (int)&v5[v6]; 186 |             } 187 |             while ( v10 ); 188 |           } 189 |           v11 = v9 - (_DWORD)v5; 190 |           v12 = v9 == v7; 191 |           if ( v9 != v7 ) 192 |             v12 = v11 == -1; 193 |           if ( !v12 ) 194 |           { 195 |             v20 = 0; 196 |             v13 = v6 - (v11 + 1); 197 |             v14 = v11 + 5; 198 |             v18 = 0; 199 |             v19 = 0; 200 |             if ( v13 < v11 + 5 ) 201 |               v14 = v13; 202 |             sub_138AC((unsigned int *)&v18, v9 + 1, v14); 203 |             if ( v24 & 1 ) 204 |             { 205 |               *(_BYTE *)v26 = 0; 206 |               v25 = 0; 207 |             } 208 |             else 209 |             { 210 |               LOWORD(v24) = 0; 211 |             } 212 |             sub_1E4F8(&v24, 0); 213 |             v24 = v18; 214 |             v25 = v19; 215 |             v26 = v20; 216 |             v15 = std::__ndk1::stoi(&v24, 0, 16); 217 |             v16 = socket(2, 1, 0); 218 |             *(_WORD *)addr.sa_data = bswap32(v15) >> 16; 219 |             if ( connect(v16, &addr, 0x10u) != -1 ) 220 |             { 221 |               v30 = 0; 222 |               v29 = 0; 223 |               buf = 0; 224 |               send(v16, byte_72C60, 1u, 0); 225 |               send(v16, "AUTH\r\n", 6u, 0); 226 |               usleep(0xBB8u); 227 |               if ( recv(v16, &buf, 6u, 64) != -1 && !strcmp((const char *)&buf, "REJECT") ) 228 |               { 229 |                 close(v16); 230 |                 v17 = getpid(); 231 |                 kill(v17, 9); 232 |               } 233 |             } 234 |             close(v16); 235 |             v4 = v21; 236 |           } 237 |         } 238 |         if ( v4 & 1 ) 239 |           operator delete(v23); 240 |       } 241 |       fclose(v0); 242 |     } 243 |     else 244 |     { 245 |       v1 = socket(2, 1, 0); 246 |       *(_WORD *)addr.sa_data = -23959; 247 |       if ( connect(v1, &addr, 0x10u) != -1 ) 248 |       { 249 |         v30 = 0; 250 |         v29 = 0; 251 |         buf = 0; 252 |         send(v1, byte_72C60, 1u, 0); 253 |         send(v1, "AUTH\r\n", 6u, 0); 254 |         usleep(0xBB8u); 255 |         if ( recv(v1, &buf, 6u, 64) != -1 && !strcmp((const char *)&buf, "REJECT") ) 256 |         { 257 |           close(v1); 258 |           v2 = getpid(); 259 |           kill(v2, 9); 260 |         } 261 |       } 262 |       close(v1); 263 |     } 264 |     sleep(4u); 265 |     if ( v24 & 1 ) 266 |       operator delete(v26); 267 |   } 268 | } 269 | 270 | ``` 271 | 272 | 建立 socket 链接,然后通过端口去发送认证消息,recv 函数去发送这个消息,如果不等与 - 1 表示能发送成功,比较返回结果中是否包含 “REJECT”,如果包含则说明存在 fridaserver 273 | 274 | 过检测 275 | --- 276 | 277 | 我们可以直接 Hook recv 函数,这个是 libc 的函数,让它返回 - 1 278 | 279 | ``` 280 | Interceptor.attach(Module.getExportByName("libc.so", "recv"), { 281 |     onEnter: function(args) { 282 |         // console.log(Memory.readCString(args[1])) 283 | 284 |     }, 285 |     onLeave: function(retval) { 286 |         retval.replace(-1) 287 |         console.log("bypass") 288 |     } 289 | }); 290 | 291 | ``` 292 | 293 | 这时我们再次启动应用,发现已经可以正常使用 frida 了 294 | 295 | ![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBuljAdKNFDh8siaPSMgibTXNVk33eYQ2h1dvNDfsClias2NpLhG8odvbcx3oxQ6h8kiaxxiahgVvsG29Fibeg/640?wx_fmt=png) 296 | 297 | #### 引用链接 298 | 299 | `[1]` SoFixer: _https://github.com/F8LEFT/SoFixer_ 300 | 301 | [AOSP 编译保姆级教程(二)](http://mp.weixin.qq.com/s?__biz=MzI3Mzk2OTkxNg==&mid=2247485231&idx=1&sn=06bb88069289cf1b686a26dbba50b6ae&chksm=eb1a60bcdc6de9aa80a913c659f8c20df87de4e698c4f0e836b63e9f54e6538c3e7826e77c5d&scene=21#wechat_redirect) 302 | 303 | [Android 病毒分析基础(二)](http://mp.weixin.qq.com/s?__biz=MzI3Mzk2OTkxNg==&mid=2247485190&idx=1&sn=2ae591514733e03d72b79bd428bea06b&chksm=eb1a6095dc6de98368d5be4bdb8009965f9ac20f6058e6138d4e1c4b1af30292cbe75e57d8fd&scene=21#wechat_redirect) 304 | 305 | [ROOT 检测通杀 - 改造优化篇](http://mp.weixin.qq.com/s?__biz=MzI3Mzk2OTkxNg==&mid=2247485162&idx=1&sn=ea692f2c5cbed633a649ef7d27ff1722&chksm=eb1a6179dc6de86f057971834b196a28ea17bb56ec975610e7d960923e1cc0b31da9da2514f3&scene=21#wechat_redirect) 306 | 307 | [编译 Frida16.0.1 并魔改相关特征](http://mp.weixin.qq.com/s?__biz=MzI3Mzk2OTkxNg==&mid=2247485149&idx=1&sn=922d7be04644dcfad45f4ea775b5e4de&chksm=eb1a614edc6de8589c11a220a9a96b566f71b61bd46bdff823b3222e4de790ecf293a9e32b27&scene=21#wechat_redirect) 308 | 309 | ![](https://mmbiz.qpic.cn/mmbiz_jpg/uVibiccmzBulhVCW50eBA98rxXekpIU3VXE5E2cxSjzYe4coRpZhsfxF0KbAm7dx17kSABJzJ2cDKbIHPiaRTdU3Q/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1)![](https://mmbiz.qpic.cn/mmbiz_png/uVibiccmzBulhjUxulpjWt1s3EwkAX6nBD0pMhG7AjB4OLAFZ4qDHrfUjpUPrDTTSSzSSomvlibwGlNFG7lqQE83w/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1) 310 | 311 | 随手分享、点赞、在看是对我们最大的支持![](https://mmbiz.qpic.cn/mmbiz_gif/uVibiccmzBulhVCW50eBA98rxXekpIU3VXnygTsE8U2uib95Ub2AicqZnyYbNycVt7YuJOSiao8IqIPCnyibY6JUsEnQ/640?wx_fmt=gif&wxfrom=5&wx_lazy=1) -------------------------------------------------------------------------------- /simpread-逆向分析反调试 + ollvm 混淆的 Crackme.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?__biz=MzUxMjE3Nzc3Mw==&mid=2247484051&idx=1&sn=967c66798db322e049a1548eb4af3131&chksm=f9692191ce1ea887861845a74f73b46ad24c32b91d5e337e46c14de0b8ac0b7a5bce5155d9fe&mpshare=1&scene=1&srcid=0302JrksiN3jOHoRNvUMAIWI&sharer_sharetime=1646204024692&sharer_shareid=56da189f782ce62249ab4f6494feca50&version=3.1.20.90367&platform=mac#rd) 2 | 3 |     最近学了点 ollvm 相关的分析方法, 正好之前朋友发我一个小 demo 拿来练练手. 4 | 5 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5ibCiaXqPAKcsrib2lgxrAEqRdia2uVMFF8WY2obaW9cG0YdibnPam7gY1gA/640?wx_fmt=png) 6 | 7 |     看上去很简单 就是找 flag 用 jadx 打开发现加壳了 8 | 9 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5ickQcQc8PWLakzy75BW2hYjqTibBHZPqLRibWkXb2LbYHfH4bwBYIibIXQ/640?wx_fmt=png) 10 | 11 |     然后想试试直接用 fridadexdump 脱壳的时候发现 frida 上就崩了 12 | 13 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5DehcibeZ27XEcibnc7dd6D6MS6O2wrUapMl2MDPRnwGgSshWiaOl1kFoQ/640?wx_fmt=png) 14 | 15 |     上葫芦娃的 strongfrida 直接重启了!.... 16 | 17 |     这只能去过反调试了, 打开 so 找了下. init 和. initarray(反调试常见位置, so 比较早的加载时机) 18 | 19 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V58lVHF7KIBcAFJVWBP3k9vpsvf0GtKjR3YaDujibnmLrD8JhOf2xeHWg/640?wx_fmt=png) 20 | 21 |     ctrl+s 打开 initarray 里面有两个奇怪的函数 decode, 这就是 ollvm 默认的字符串加密 22 | 23 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5KPTdaibwIw0tKtvrMibowyBBXVyD3YQHib7AhJsW5Tlic11icqF8wOplMIA/640?wx_fmt=png) 24 | 25 |     导出函数中搜索 init 找到里面也有函数定义, 创建了一个线程执行反调试函数, 进入这个函数看到字符串都被加密了 26 | 27 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5gCWsexicez4OjpgCBMXJ2CVWlvbzy25OYD3ictaZ0ic6JKxS5huqCy6HQ/640?wx_fmt=png) 28 | 29 |     看了下代码 有个 kill 函数, 一开始就 hook kill 函数不让他自杀, 发现没用... 30 | 31 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5GkT8IVsjdv49BQGaZCjEM0n0jOtdHJ1ibToiao3LG5DGcz64mibNcjAOQ/640?wx_fmt=png) 32 | 33 |     然后我继续找了下发现有个 strstr 函数 这有点可疑, 拿来比较字符串的, 直接 hook 一波 34 | 35 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5vpjD5GNPuiacZIdcicwmicQYCiakCgjicV4cricNzJkLPvnwTAcOJrn5KsjQ/640?wx_fmt=png) 36 | 37 |     从代码中得知 str2 是我们要关心的对象, 是个 char * 类型直接 readCstring 打印 38 | 39 |     然后发现出现很多 frida 然后进程就崩了 40 | 41 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5iadHgKkzm4fNrLYXiavhZ7T8OUaTRHhGu5pFDORMPicrkrCG4jvsHic3Vw/640?wx_fmt=png) 42 | 43 |     所以接下来就有两种方法 直接 hook pthread 不让这个线程起来 和 hook strstr, 我这就直接 hook str 了. 44 | 45 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V508Sj9MPCkZNrRIsTgTyw0hwjVbheSU9qQy0ngia2B3VUhYiaaMficnhoQ/640?wx_fmt=png) 46 | 47 |     然后就不崩了, 可以愉快的 frida 了~ 48 | 49 |     先拖个壳, Fridadexdump 一波 (可以直接 fart 脱的全, 但是懒的刷机, 直接用这个了..),hook events 定位 50 | 51 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5V6spHicpuDWpAk92tpJACHNuVpm0dsRhaTUFjicQ2riaicNAgYw8R1s1sw/640?wx_fmt=png) 52 | 53 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5YCWmCGiclOcJic13XsJqEN5rNkylqOqBtIrEJAHxrttZWpQCen0edCXA/640?wx_fmt=png) 54 | 55 |     oncreate 直接是 native 化了 应该是 360 的 vmp, 这里肯定不是让你逆 360 的 vmp, 盲猜一波就是 check 方法, 先 hook 一下. 56 | 57 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V53IZQr0nYInQrJyFXq0HajQtia64HM9sw5YRmX0b6iaLBPxmjcY04NuFA/640?wx_fmt=png) 58 | 59 |     直接返回 false, 应该是 true 就会拿到 flag, 为了不每次都手动输入 直接写个主动调用. 60 | 61 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5icvqImdg12BtYSbSeHmtXjLx7AWzMCbqBujUibggyasV1DQC2ktFZn0Q/640?wx_fmt=png) 62 | 63 |     接下来去看 so 了, 导出函数就这几个.. 肯定是动态注册, 先直接 hook 64 | 65 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5L2pwojyHxz4OicTW7n68ANsdtqBm2E1SpZuZC9H9UfChicnmqEW4mbiaA/640?wx_fmt=png) 66 | 67 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V53xk5eYAHJdlrtraMb1qNz1kpuHDQQADpIqNDTskMjkqRdjxJ0B06pg/640?wx_fmt=png) 68 | 69 |     hook 到了偏移, 去 so 看一下 70 | 71 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5GPASEJotibrHGFmm6oHc66eNKpXc8FUliaecuiadwPCwwEQTuM9rbbJxw/640?wx_fmt=png) 72 | 73 |     改了些名字后 操作流程就是将输入的 jstring 转为 char* 然后判断长度是否为 20 看到很多都是 unk_开头的, 这些就是被 ollvm 加密后的字符串, 要怎么看他解密后的内容呢? 因为他加载到内存的时候肯定是解密状态, 直接读这个地址打印 cstring 就可以得到解密后的字符串了. 74 | 75 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5QmF37PaDev6IvFwF01npBKuibI9a4TL6hAr8Im8gft6yEVEdT8vJbYw/640?wx_fmt=png) 76 | 77 |     然后继续改名, 改完名字就是这样 78 | 79 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5ve5BvsHOtyvdcYKLxPcFNXUOGH4k88XD0VkZ1ANJMiciawQibns1wJsOQ/640?wx_fmt=png) 80 | 81 |     发现他重新对 check 的方法进行了动态注册, 然后 sub_1234 里面也进行了动态注册里面嵌套了好几次动态注册, 然后用 strcmp 比较 输入的值和 &unk_5100(解密后为 kanxue) 如果相等那就返回 true. 但是输入要 20 个字符 kanxue 只有 6 个字符, 我们 hook 一下这个比较的地方看看, 82 | 83 |     直接 hook strcmp 蹦的比较厉害, 我选择 inlinehook, 按 tab 切换到汇编, 打开 opcode,s1 的值给到了 R0 我选择在 0x1564 进行 hook, 因为是 2 个 opcode 所以为 thumb 指令, 需要地址 + 1 84 | 85 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5kAsQ41iaghSeT1weTBaJ9PyeExM2icgIn45Qyv9yNmbsoWckPwp8xVlg/640?wx_fmt=png) 86 | 87 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5x18joVCW7ZhUNbyEzje1oIygzAPxSYF162wNyoUxRFk53cNbwGYBrw/640?wx_fmt=png) 88 | 89 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5Vjz5RaRQticxKETWhMc6DZmDYZPJI24ZqCINgwN9UtAxHOByjuedOlw/640?wx_fmt=png) 90 | 91 |     我们输入了 kanxue00000000000000,hexdump 打印 s1, 发现 s1 就是 kanxue, 但是程序没提示通过, 然后 hook registernative hook 到他又进行了 2 次动态注册, 根据这个地址 我们往前找 92 | 93 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5UfWl8n0Eian5s9LEGSibVWtibEBJaQT2pBkqQ9fG1hau7Ie8C6zUlU71Q/640?wx_fmt=png) 94 | 95 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5fPsvHdDkvw0DnFIbibZ0BKgp7FYZtuwz1SMaicxOsvrkAFaU3hTBoQsQ/640?wx_fmt=png) 96 | 97 |     发现第二次动态注册的和最开始的代码很像, 第三次就短了很多, 然后仔细一看第二次动态注册中又注册了 sub_1148, 原来就是嵌套了 3 次动态注册, 注册了 3 个函数, 根据一开始的分析 对 strcmp 这里的字符串解密 98 | 99 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5icqp3Uucia5qQ9qI5icdIwVmJjluiaznjbWqxickDj9zO9viaf73AFx4L6tg/640?wx_fmt=png) 100 | 101 | 然后分别 inlinehook 剩下几个 strcmp 验证下! 102 | 103 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5EXXagDTuNNX2R25qw4lQEevAHQB9augyp6XlscIbQI0zSvKz4nQH5g/640?wx_fmt=png) 104 | 105 |     发现第二次比较是输入的 8 个 0 这样区分不了是第几个输入, 我们用 abcd 来实验, 输入 kanxueabcdefghieklfn 20 个字符 106 | 107 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5rT32Kzc0KrusvQCXLhu80IWhhBoEUSenmyDK4DDoX1g0EQ2uiakm8xg/640?wx_fmt=png) 108 | 109 |     是 abcdefgh 这 8 个, 所以是加到 kanxue 后面的, 然后真正的字符串之前我们已经解密了, 是 unk_50F7(即为 training), 然后后面猜也猜得到剩下 6 个字符是之前解密的 unk_5096(即为 course) 为了保证严谨性, 我就在 inlinehook 一次 110 | 111 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5dricfDbxuF8qt2mYfL2LcsILHsRjE76QQZJesPVAZsNibDJvJpRvhAzQ/640?wx_fmt=png) 112 | 113 |     果然就是我们最后六位进行比较 所以答案前面的解密的字符合起来, 就是 kanxuetrainingcourse 114 | 115 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5sx24KmxUY18Z4icG6OHQricRUYZiaqoBow3EroiblAkXLKdq1amPqnFe6g/640?wx_fmt=png) 116 | 117 | ![](https://mmbiz.qpic.cn/mmbiz_png/J7WeDiaX8bpQs6E5OK6eLtbgvvYxp95V5KQEm4z50x8KLbg8bywfMOicgphTLlicqX8E9fK2nF8eRAybUUiaaiaqxpA/640?wx_fmt=png) 118 | 119 |     这里有个 bug 每次输错都要重启一次, 不然到最后答案就变成 course 或者 trainingcourse 了~ -------------------------------------------------------------------------------- /simpread-隐藏 Root - Zygisk 版面具 Magisk 过银行 App 等 Root 检测,Shamiko 模块的妙用 - 哔哩哔哩.md: -------------------------------------------------------------------------------- 1 | > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.bilibili.com](https://www.bilibili.com/read/cv15350941) 2 | 3 | > Zygisk 版本面具 Magisk 虽然移除了 MagiskHide 但也可以实现隐藏 Root 具体该如何实现呢且听我细细道来各位看官老爷们大家好呀我是沉迷搞机无法自拔的阿蒲之前我们聊过最新版本的面具 Magisk...... 4 | 5 | Zygisk 版本面具 Magisk 虽然移除了 MagiskHide 但也可以实现隐藏 Root 6 | 7 | 具体该如何实现呢 8 | 9 | 且听我细细道来 10 | 11 | 各位看官老爷们 12 | 13 | 大家好呀 14 | 15 | 我是沉迷搞机无法自拔的阿蒲 16 | 17 | 之前我们聊过最新版本的面具 Magisk 移除在线模块和 MagiskHide 功能 18 | 19 | 最新 Zygisk 版面具 Magisk 将隐藏的相关工作交了出来 20 | 21 | ![](http://i0.hdslb.com/bfs/article/7a98793546ab3ab90584f73ff0f0cc37000c2ee0.png@894w_600h_progressive.webp) 22 | 23 | 反而促进了隐藏领域的快速发展 24 | 25 | 其中 LSPosed 开发者出品的 Shamiko 广受好评 26 | 27 | 我们先刷入面具 Magisk 28 | 29 | ![](http://i0.hdslb.com/bfs/article/1444be6c3cbdd669222e393afda3985a74eaf37b.png@942w_707h_progressive.webp) 30 | 31 | 32 | 在其设置中找到并点击隐藏 Magisk 应用 33 | 34 | ![](http://i0.hdslb.com/bfs/article/737b379fe7f00b3fda2c757e5433994a6c4af0c2.jpg@942w_531h_progressive.webp) 35 | 36 | 设置一个新的 Magisk 应用名称并点击确认 37 | 38 | ![](http://i0.hdslb.com/bfs/article/341b9c9145293401c7582662bebc2eecc2452ce8.jpg@942w_531h_progressive.webp) 39 | 40 | 稍等片刻后会生产一个随机包名的面具包 41 | 42 | 安装后我们添加一个快捷方式到桌面 43 | 44 | 我们通过该快捷方式打开面具 45 | 46 | 在其设置中我们找到 Zygisk 并点击开启 47 | 48 | ![](http://i0.hdslb.com/bfs/article/76c671b53481da844a68d4e71bddecc36ba993b6.jpg@942w_1058h_progressive.webp) 49 | 50 | 然后重启手机 51 | 52 | 再次打开面具 53 | 54 | 在设置中找到遵守排除列表并开启 55 | 56 | ![](http://i0.hdslb.com/bfs/article/f275eecf12fe53b17e5120388bbd22908dcf7886.jpg@942w_782h_progressive.webp) 57 | 58 | 在设置中点击配置排除列表 59 | 60 | 在排除列表中找到需要隐藏 ROOT 的对应应用并一一开启 61 | 62 | ![](http://i0.hdslb.com/bfs/article/7e1e692ebfe9134a347c3bbe6b5c96378cb7ab4b.jpg@942w_1029h_progressive.webp) 63 | 64 | 开启时注意将应用下的选项全部开启 65 | 66 | 否则可能会出现隐藏 ROOT 失败的问题 67 | 68 | ![](http://i0.hdslb.com/bfs/article/71c007cd61f4f22f3360d3592408e8feb866c8c9.jpg@573w_1080h_progressive.webp) 69 | 70 | 我们打开排除列表中的银行类应用 71 | 72 | 基本上不会出现因 ROOT 而产生的异常提醒 73 | 74 | 但是此时排除列表中的应用是无法使用虚拟框架和模块的 75 | 76 | 如果我想对某个排除列表中的应用使用虚拟框架和模块 77 | 78 | 就需要使用到 Shamiko 模块 79 | 80 | ![](http://i0.hdslb.com/bfs/article/f0dfd488b74a46dd2d404bc92df627fb3b26e369.png@942w_483h_progressive.webp) 81 | 82 | 我们去浏览器中下载 Shamiko 模块并保存到方便的位置 83 | 84 | 在面具 Magisk 的模块功能中使用本地安装刷入该模块 85 | 86 | ![](http://i0.hdslb.com/bfs/article/dd960796932f0c9ee294c5d0ca36321e886e8db1.jpg@942w_531h_progressive.webp) ![](http://i0.hdslb.com/bfs/article/0962964954ede1947f46839f7845c841b4b2813c.jpg@942w_531h_progressive.webp) ![](http://i0.hdslb.com/bfs/article/a45eff57b84c57a36f219124a44e5f37a5837b9f.jpg@585w_1080h_progressive.webp) 87 | 88 | 重启手机 89 | 90 | 再次打开面具 Magsik 91 | 92 | 在其模块功能中可以看到 Shamiko 模块已经成功刷入并加载 93 | 94 | 此时 Shamiko 中有显示 Shamiko doesn't work since enforce denylist is enabled 95 | 96 | ![](http://i0.hdslb.com/bfs/article/d24196ef7189e01035eb10d676de8824801321c8.jpg@942w_531h_progressive.webp) 97 | 98 | 我们在排除列表中开启支付宝 99 | 100 | 在浏览器或 LSPosed 仓库中下载秋风模块并安装 101 | 102 | ![](http://i0.hdslb.com/bfs/article/da0b8a6d9ef69a47a05ee73623ae7ebf38632b0d.jpg@942w_531h_progressive.webp) 103 | 104 | 去 LSPosed 的模块功能开启该模块并设置其作用域 105 | 106 | ![](http://i0.hdslb.com/bfs/article/26d42facbc7a094b0dc1ae36309d1a8491b11d7f.jpg@942w_531h_progressive.webp) 107 | 108 | 打开秋风设置功能进行一定的个性化设置 109 | 110 | ![](http://i0.hdslb.com/bfs/article/0499f3f09bee35848deabf54c93f75911585e933.jpg@942w_911h_progressive.webp) 111 | 112 | 我们在面具 Magisk 的设置中关闭遵守排除列表 113 | 114 | ![](http://i0.hdslb.com/bfs/article/786d215d631f76d3335c799bcbde706e82d50932.jpg@942w_531h_progressive.webp) 115 | 116 | 再次回到模块功能 117 | 118 | 此时 Shamiko 中显示 Shamiko is working as blacklist mode 119 | 120 | 我们打开排除列表中的银行类应用 121 | 122 | 仍然未出现因 ROOT 而产生的异常提醒 123 | 124 | 我们重启手机 125 | 126 | 打开支付宝即可看到秋风模块已生效 127 | 128 | 大家有什么好玩有趣有料的资源 129 | 130 | 欢迎留言推荐 131 | 132 | 各位看官老爷们 133 | 134 | 有钱的捧个钱场 135 | 136 | 没钱的捧个人场 137 | 138 | 跪求一键三连 139 | 140 | 咱们回见 141 | 142 | 本期关键词:【Shamiko】 143 | 144 | ![](http://i0.hdslb.com/bfs/article/33b44d59f06d2d2de108f1c6222b3e695803c228.jpg@942w_530h_progressive.webp) -------------------------------------------------------------------------------- /某交友 App 花指令分析.md: -------------------------------------------------------------------------------- 1 | 2 | **观前提示:** 3 | 4 | **本文章仅供学习交流, 切勿用于非法通途, 如有侵犯贵司请及时联系删除** 5 | 6 | ``` 7 | 样本:soul -> libsoulpower.so 8 | 9 | ``` 10 | 11 | 12 | 0x1 花指令分析 13 | ========= 14 | 15 | 大姐姐打开`libsoulpower.so` 16 | 17 | 直接跳转到`0xaa73c` 18 | 19 | 如果发现这里没被正常识别 20 | 21 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4icltD4ibldGiaMeYmA407UoYpjYfaHyChfianj8DD0nCxlxRytrgLCUb2aow/640?wx_fmt=png) 22 | 23 | 直接在`0xaa73c`处快捷键`P`即可 24 | 25 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclLaiaG2F8zro4LkjTBVRmKaIwicJMJ5tzOQMVaWlj6NqWek40NQYSkLQw/640?wx_fmt=png) 26 | 27 | 直接 F5 反编译看伪代码 28 | 29 | 然后就会发现不明代码 30 | 31 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclKGPPcdEOdsrE2sCM9z5iatVPhZdY39VicuvfDicqWaHu2p51Wvyv4JchQ/640?wx_fmt=png) 32 | 33 | 转到汇编 34 | 35 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclW630MZBnTTalbGRErExgj0iblvEHlWRj5xlO0q33Knak3cvY1Ur5v3w/640?wx_fmt=png) 36 | 37 | 这里是将`R0`的值作为地址 跳转到该地址处 38 | 39 | ``` 40 | BX              R0 41 | 42 | 43 | ``` 44 | 45 | 那么`R0`从哪里来呢 继续往上看 46 | 47 | 这里调用了`sub_AECF0`并且将返回值放在了`R0` 48 | 49 | ``` 50 | BL              sub_AECF0 51 | 52 | 53 | ``` 54 | 55 | 所以真实的跳转地址可能就是在`sub_AECF0`中计算得出的 56 | 57 | 继续往上看还能看到入参的操作 58 | 59 | ``` 60 | LDR             R0, =(off_2C1B64 - 0xAA7A4) 61 | MOV             R1, #0 62 | STR             R1, [SP,#160] 63 | MOV             R1, #1 64 | ADD             R0, PC, R0 ; off_2C1B64 65 | 66 | 67 | ``` 68 | 69 | 这里 IDA 已经帮我们识别好了伪代码 直接看就行 70 | 71 | ``` 72 | sub_AECF0(&off_2C1B64, 1) 73 | 74 | 75 | ``` 76 | 77 | 进`sub_AECF0`看是啥操作 78 | 79 | ``` 80 | int __fastcall sub_AECF0(int a1, int a2) 81 | { 82 |   return *(_DWORD *)(a1 + 4 * a2); 83 | } 84 | 85 | 86 | ``` 87 | 88 | 很简单啊 直接复现一下 89 | 90 | 0x2C1B64 + 4 * 1 = 0x2C1B68 91 | 92 | 得到`0x2C1B68`后 跳转过去 93 | 94 | ``` 95 | 002C1B68                 DCD sub_AA7B8 96 | 97 | 98 | ``` 99 | 100 | 可以看到`0x2C1B68`对应的就是`sub_AA7B8` 101 | 102 | 所以 R0 = 0xAA7B8 103 | 104 | 而 IDA 并不会去执行这些操作 所以达到花指令的目标 105 | 106 | 0x2 手动去花 107 | ======== 108 | 109 | 前面计算了`R0`的真实跳转地址 110 | 111 | 拿到真实地址后就可以在`0xAA7B4`处手动 Patch 了 112 | 113 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4icltzC2ZdwHEal5WJRse3cic8Ms865PGGOTqvWms0Sp6rc0AnHDz72SNug/640?wx_fmt=png) 114 | 115 | Patch 完事之后再次`F5`反编译 116 | 117 | 反编译完发现代码仅仅多了一行 118 | 119 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclQOr6e4fF4FROf3RmbZcxiaGwoRIosic3zwzKZ3vNcUwzn5HZELhfle0w/640?wx_fmt=png) 120 | 121 | 转到汇编 这里同样出现`BX R0` 122 | 123 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclchXBAlYwWCficvQ4rzTsOKITsS73iaEoibkphUhr4CgqibkA2GNWFAWNTQ/640?wx_fmt=png) 124 | 125 | 所以这个样本应该是多处插花 那就继续还原 126 | 127 | 按上述操作 找到俩个传入参数 然后手动计算出真实地址 128 | 129 | 0x2C1B64 + 4 * 2 = 0x2C1B6C 130 | 131 | ``` 132 | 002C1B6C                 DCD sub_AA9F8 133 | 134 | 135 | ``` 136 | 137 | R0 = 0xAA9F8 138 | 139 | 继续重复操作 Patch 140 | 141 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclyvDOqmwnLkRR1yIJlob9f3RzNLfYMQ4fJgR2SPsbEvLoZ2y1DPLNCA/640?wx_fmt=png) 142 | 143 | 代码又多了一点 然后不出意料 又是一处插花 144 | 145 | ``` 146 | LDR             R0, =(off_2C1B64 - 0xAAA08) 147 | MOV             R1, #3 148 | ADD             R0, PC, R0 ; off_2C1B64 149 | BL              sub_AECF0 150 | BX              R0 151 | 152 | 153 | ``` 154 | 155 | 继续重复几次操作后 代码多了这么多 但是还没完全去花 156 | 157 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4icldn9HYM4NTcr4hUGZXLsp5XqPfI22uUqtiaMKmuick32gVIGJUsBL4pVw/640?wx_fmt=png) 158 | 159 | 同时在视图中可以发现 ollvm 后面还不知道会出现啥 继续 Patch 160 | 161 | #### 停!!! 162 | 163 | 怎么可能 量很大 年轻人 你把握不住的 该上脚本了! 164 | 165 | 0x3 脚本去花 166 | ======== 167 | 168 | 首先明确要干啥先 169 | 170 | 1. 找出所有的插花方法 171 | 172 | 2. 找出所有插花方法的调用处 173 | 174 | 3. 拿到俩个参数 175 | 176 | 4. 计算正确地址 177 | 178 | 5. Patch 179 | 180 | 181 | 前面我们知道 每次计算都会进入`sub_AECF0` 182 | 183 | 但是我猜测可能不止一个`sub_AECF0` 184 | 185 | 也许是同操作 但是方法名不一样 这里做个验证 186 | 187 | 拿到方法 HEX`01 01 90 E7 1E FF 2F E1` 188 | 189 | ``` 190 | 01 01 90 E7                 LDR             R0, [R0,R1,LSL#2] 191 | 1E FF 2F E1                 BX              LR 192 | 193 | 194 | ``` 195 | 196 | 然后`Binary search` 197 | 198 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4icloytruGw7dAGiciaLQJ5LibOlQ0uCowtBMSicnAY7OxcRM1ibWZlhevpriciaQ/640?wx_fmt=png) 199 | 200 | 搜索得出五个结果 201 | 202 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclN3S48epP1QUsic7DMSqOoh6iaE9QHuABc6Or60NDNMbhn7Abr3vSyCZQ/640?wx_fmt=png) 203 | 204 | 查看每个方法的交叉引用都有很多处 205 | 206 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4icl3mS4thjZ1QL8rjAcRkER9Uv78wPNCsjR1jIzyNpiajMxmY3ibn94tA7A/640?wx_fmt=png) 207 | 208 | 那可以使用`idautils.CodeRefsTo`遍历交叉引用 209 | 210 | 但是我担心有可能存在漏识别 所以我采用了从头到尾的遍历方法 211 | 212 | ``` 213 | import capstone 214 | from keystone import * 215 | 216 | import flare_emu 217 | import ida_ida 218 | import idautils 219 | import idaapi 220 | import ida_segment 221 | import idc 222 | import ida_bytes 223 | 224 | # 初始化架构和模式 225 | CS = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM) 226 | # 设置为详细反汇编模式 227 | CS.detail = True 228 | # 设置反汇编跳过数据 229 | CS.skipdata = True 230 | 231 | 232 | # 获取.text段的范围 233 | def getAddrRange(): 234 |     start = ida_ida.inf_get_min_ea() 235 |     size = ida_ida.inf_get_max_ea() - start 236 |     # 将地址范围限定于text节 237 |     for seg in idautils.Segments(): 238 |         seg = idaapi.getseg(seg) 239 |         segName = ida_segment.get_segm_name(seg) 240 |         if segName == ".text": 241 |             start = seg.start_ea 242 |             size = seg.size() 243 |     return start, size 244 | 245 | 246 | def binSearch(start, end, pattern): 247 |     matches = [] 248 |     addr = start 249 |     if end == 0: 250 |         end = idc.BADADDR 251 |     if end != idc.BADADDR: 252 |         end = end + 1 253 |     while True: 254 |         addr = ida_bytes.bin_search(addr, end, bytes.fromhex(pattern), None, idaapi.BIN_SEARCH_FORWARD, 255 |                                     idaapi.BIN_SEARCH_NOCASE) 256 |         if addr == idc.BADADDR: 257 |             break 258 |         else: 259 |             matches.append(addr) 260 |             addr = addr + 1 261 |     return matches 262 | 263 | # 获取代码段的起始地址和长度 264 | start, size = getAddrRange() 265 | codebytes = idc.get_bytes(start, size) 266 | sub_matches = binSearch(0, 0, "01 01 90 E7 1E FF 2F E1") 267 | disasmCodes = list(CS.disasm_lite(codebytes, start, size)) 268 | for i in range(len(disasmCodes)): 269 |     (address, size, mnemonic, op_str) = disasmCodes[i] 270 |     if mnemonic == 'bl': 271 |         sub_addr = int(op_str.replace('#', ''), 16) 272 |         if sub_addr in sub_matches: 273 |             print(hex(address)) 274 | 275 | 276 | ``` 277 | 278 | 效果很不错 找出了很多的调用处 279 | 280 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclr3WwFrUyqDchOEz0Mn3PJoIg7OQNT6FV6sumt6wfdFogwR4lsaicuQQ/640?wx_fmt=png) 281 | 282 | 找出调用处随便跳转几个看能不能找到规律匹配参数 283 | 284 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclRn8RxAezjic7sj4pkI6iaUUxMP6T1IFrufcus7ygopbJajlxgbNsHtqg/640?wx_fmt=png)![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclu51yZqjUS26soIZx6Kk9SJcQ0tU98AhTRQcC03RVYPcphxe2Ac4Wlw/640?wx_fmt=png) 285 | 286 | 这里发现 参数的位置并不固定 不好做匹配 287 | 288 | 但是又可以发现 参数位置 调用位置 跳转位置 都是处于同一个 block 中 289 | 290 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclADTUgyD0xcxyLJ2oicfepFIrOwtDiatEZJa0ByXky5wVsxmDdFaYrN4w/640?wx_fmt=png)![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclDSomkS4Bv22vOgX2qxlVHTtJ80cpokE6Sc9CvDiaibyjdQoXhsofJW0w/640?wx_fmt=png)![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclbIvBr1db7UTVdh9ccuHCQRO8V4TeMpjEmXEWsmXNkVupPvCaCIBhow/640?wx_fmt=png) 291 | 292 | 知道了这一点 就只需要匹配出相对应的 block 就可以了 293 | 294 | ``` 295 | def find_block(blocks, addr): 296 |     for block in blocks: 297 |         start_ea = block.start_ea 298 |         end_ea = block.end_ea 299 |         if start_ea < addr < end_ea: 300 |             return block 301 |     return None 302 | func = idaapi.get_func(address) 303 |   if func: 304 |     block = find_block(idaapi.FlowChart(func), address) 305 | 306 | 307 | ``` 308 | 309 | 即使匹配出了地址块 也不好匹配出参数 那咋办呢 310 | 311 | 其实 这里我匹配地址块的原因就是不想再写一堆匹配代码找出`R0`和`R1` 312 | 313 | 所以我打算交给`unicorn`去做 314 | 315 | 而我又懒得写很多代码 所以 我又使用了封装好的`flare_emu` 316 | 317 | 例如 我匹配出了一个 block 我仅需要把 block 的起始地址匹配到方法执行前即可 318 | 319 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclowcUDMqe9N3J547Awgk6JkewARaZEeeiaLib0fMbzuia1KGkK7zKqCnTg/640?wx_fmt=png) 320 | 321 | 测试看结果 322 | 323 | ``` 324 | import flare_emu 325 | myEH = flare_emu.EmuHelper() 326 | myEH.emulateRange( 327 |   startAddr=0x804DC, 328 |   endAddr=0x804FC 329 | ) 330 | Out[31]:  331 | 332 | myEH.getRegVal("R0") 333 | Out[32]: 0x2c14d0 (OCSP_SIGNATURE_it + 0x2dc) 334 | 335 | myEH.getRegVal("R1") 336 | Out[33]: 1 337 | 338 | 339 | ``` 340 | 341 | 效果完美 342 | 343 | 后面仅需自己写个计算方法 将参数传入计算即可 344 | 345 | ``` 346 | def calcu_addr(a, b): 347 |     return a + 4 * b 348 | 349 | 350 | ``` 351 | 352 | 得到真实跳转地址后 还得匹配出跳转地址存储的地址 353 | 354 | 而`idc.print_operand` 刚好能实现这个操作 355 | 356 | ``` 357 | idc.print_operand(0x2c14d4,0) 358 | Out[34]: 'sub_80508' 359 | 360 | 361 | ``` 362 | 363 | 拿到这个真实地址 最后 Patch 就行 364 | 365 | 不过这里需要注意的是 我们不知道后面第几行是需要 Patch 的`BX`处 366 | 367 | 但是我们前面就得到了块的起始地址 同样的也可以得到块的结束地址 368 | 369 | 只需要 (结束地址 - 方法调用地址)/4 然后遍历就能找到`BX`所在位置 370 | 371 | 但是但是 我觉得不优雅 372 | 373 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclbIvBr1db7UTVdh9ccuHCQRO8V4TeMpjEmXEWsmXNkVupPvCaCIBhow/640?wx_fmt=png) 374 | 375 | 从这张图 可以知道`BX`一直在块的最后一行 那我何必去遍历呢 直接那最后一行的地址不就行了 376 | 377 | 一下子就优雅起来了 378 | 379 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4icltRpU3JKAicGGXs5S0z2GV2KVWOtxdZEGCIqAO29Ric3DwJkCLf8RnADw/640?wx_fmt=png) 380 | 381 | 最后写下 patch 382 | 383 | ``` 384 | code = f"B {hex(b_addr)}" 385 | b_code = generate(code, block.end_ea - 4) 386 | # Patch 1 处理花指令 387 | ida_bytes.patch_bytes(block.end_ea - 4, bytes(b_code)) 388 | 389 | 390 | ``` 391 | 392 | 将所有代码串起来 执行测试一下效果 393 | 394 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclRNgVFFic8APWgaib6Mk2E4pTKPFkibzUKxZHUVDyE8xyllHEGdl2j4Ung/640?wx_fmt=png) 395 | 396 | 执行完后应用更改 397 | 398 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclkz4mhdR20giabbqFiamEqeNv1iclzHNpOdrAUmF3lYubDTAwN6KCBI2ZQ/640?wx_fmt=png)![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclF2OQTgtQAb6YLUJWuiaJhT0E466GKcHAMS6MF2kNAfwPnLx9g1icic33w/640?wx_fmt=png) 399 | 400 | 最后重新打开 so 401 | 402 | 跳转到最开始分析的`0xaa73c` 403 | 404 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclHggqibia1fgNZjrQuKK17Qrb25bddTpR7Rw4yrB5GZzpNvIUE4ou1JAw/640?wx_fmt=png) 405 | 406 | 成功识别到 N 多行代码了 407 | 408 | 去花工作完成 处理脚本和对应的 so 文件放在样本中 409 | 410 | 如果你想学习关于 so 反混淆相关的知识 411 | 412 | 例如 IDA 脚本开发 字符串加密处理 花指令处理 龙龙的星球里都写得很详细 413 | 414 | 龙龙老卷王了 内容丰富  很多东西我都是跟着龙龙学的 让我受益匪浅 415 | 416 | 所以 我吹爆!  417 | 418 | ![](https://mmbiz.qpic.cn/mmbiz_png/Em7AaVMQYsNgowvdHibr4SBZS4yxFr4iclURAm76zqaMUibHcmOmSu2hoKygshaJObJFNu3eUbEDfwtFbnrMs38UA/640?wx_fmt=png) 419 | 420 | **感谢各位大佬观看** 421 | 422 | **感谢大佬们的文章分享** 423 | 424 | **如有错误 还请海涵** 425 | 426 | **共同进步 带带弟弟** 427 | 428 | 点赞 在看 分享是你对我最大的支持 429 | 430 | 逆向 lin 狗 431 | --------------------------------------------------------------------------------