# 关于 > Source: # 前言 这里是 Linso 我觉得我算是一个比较纯粹的**实用主义者** 与其把时间花在包装 PPT 或者争论语言优劣上 我更喜欢把软件底层的运行逻辑协议逆向还原出来 或者把自动化任务的并发量再提升一个数量级 我的技能树也不知道怎么就点歪了 主要点在了**逆向工程** **安全攻防**以及**高性能后端**上 同时对**自动化脚本**也有点探索 ## 我在做什么? ### 🛠 逆向与安全 这是我投入精力最多的领域 当你们找不到我的时候 基本上我都在和混淆代码 加密参数以及风控策略打交道 太头疼了 * **协议还原**:其实不满足于简单的抓包分析 抓包谁都会 我更倾向于从底层还原通讯逻辑 无论是 TCP/UDP 私有协议 还是 HTTP/2 流量 习惯使用 **GoPacket** 进行分析 基本上目前可以手撕 **某音** 或 **某书** 的协议算法(如 `a_bogus`, `X-s` 等) * **Native 对抗**:现在纯Java层的保护跟纸糊的差不多 我习惯把核心逻辑锁进Native库 跟调试器 Hook框架玩捉迷藏 大量通信直接走**sslpinning** 主打一个让逆向的成本指数级上升 * **脱机算法**:能脱机跑的绝不挂浏览器 不跑dll 不带任何多余的内容 必须完全纯算 通过 **Python** 或者直接用 **Go** 重写核心加密算法,实现完全脱离宿主环境的算法调用 **Go** 重写核心加密算法 实现完全脱离宿主环境的算法调用 能纯算绝不补环境 现在外面教的全都是补环境 有什么用呢?一个环境检测 都得gg ### 其余项目开发? 数据量上来之后 单纯的脚本就是玩具 * **高并发调度**:Python 用来写业务逻辑还好 但做调度还得靠 **Go** 我开发了一套基于 **Gin** + **Redis** 的分布式调度系统 (`TaskPin`),专门处理大规模的采集任务 实现高并发调度 这也算是我近期写的并发和性能最好的项目了 * **全栈开发**:除了底层逆向 偶尔也也写端侧应用 Android 端现在主要用 **Kotlin** + **Jetpack Compose** Web 端则是 **React** + **Vite** + **Supabase** 的轻量化组合 ## 闲言碎语 我不喜欢过度的封装 也不喜欢为了设计模式而设计模式 也不喜欢花里胡哨的对代码进行排版 在我看来 **代码的可控性**高于一切 * 如果现有的库性能不够 那就重写它 * 如果 HTTP/HTTPS 抓不到包 那就去挂HOOK读内存或者直接分析网卡流量 * 工具是为了解决问题存在的 **而不是为了炫技** 追求代码的简洁和效率 ## 结尾 如果你对协议逆向 风控对抗感兴趣 或者有比较有挑战性的技术难题 欢迎交流 # 首页 > Source: 欢迎来到 **Linso** 的基地。 这里主要记录我在 **逆向工程**以及 **底层原理** 方面的技术实践与研究笔记 *** ## 逆向 & 内核 深入移动端协议还原、Native 层对抗以及 Linux 内核提权技术的研究。 *** ## 🙋 关于作者 了解更多关于我的技术栈、项目经历以及开发理念。 # 某音 HTTPS 抓包与 libsscronet.so 逆向分析 > Source: 本文仅供安全研究与学习使用,请勿用于非法用途 > 针对某音 App 采用自定义 SSL 库 (`libsscronet.so`) 导致常规抓包工具失效的问题,本文提供了一种基于 Native 层 Patch 的绕过方案 ## 概述 某音 App 并未完全使用 Android 系统原生的网络栈,而是集成了基于 Chromium 网络栈定制的 **Cronet** 库,并封装在 `libsscronet.so` 中。这使得它具有独立的证书校验机制(SSL Pinning),能够绕过系统代理设置和系统证书信任存储。 本文主要探讨如何通过逆向分析 Native 库,定位并 Patch 核心证书校验函数,从而实现 HTTPS 流量的解密与抓取。 *** ## 技术架构与核心机制 ### 关键库与校验逻辑 | 组件 | 名称 | 功能说明 | | -------- | ---------------- | --------------------------------- | | Library | `libsscronet.so` | 定制的 Cronet 网络栈,包含 SSL/TLS 握手与校验逻辑 | | Function | `VerifyCert` | 负责校验服务端证书链的有效性(符号名可能被去除) | | Tool | IDA Pro | 用于静态分析汇编代码与 Patch 二进制文件 | ### 校验流程与绕过逻辑 ```mermaid flowchart TD A[App 发起 HTTPS 请求] --> B[libsscronet.so 处理] B --> C{SSL 握手} C --> D[服务端返回证书] D --> E[VerifyCert 校验] E -- 校验失败 --> F[断开连接] E -- 校验成功 --> G[建立加密通道] H[Patch 修改] -.-> E E -. 强制返回成功状态 .-> G ``` *** ### 逆向分析与定位 由于该 App 使用了自定义的 SSL 库,传统的 Xposed 模块(如 JustTrustMe)往往针对的是 Java 层的 `javax.net.ssl` 类,因此对 Native 层的 Cronet 无效。我们需要直接在汇编层面对校验逻辑进行修改。 #### 1. 符号定位 通过 IDA Pro 加载 `libsscronet.so`,利用字符串搜索功能定位关键校验函数。 * **搜索关键词**: `VerifyCert` * **目标函数**: 通常位于字符串引用附近的 `sub_XXXXXX` 函数(如 `sub_3A6DF8`) #### 2. 代码逻辑分析 在 `VerifyCert` 及其相关调用链中,校验逻辑通常会返回一个状态码来指示验证结果。 **伪代码,仅作逻辑示意** ```c int64_t VerifyCert(void* cert_chain) { // ... 复杂的证书链校验逻辑 ... if (is_valid) { return 1LL; // 原始逻辑:1 代表某种状态(如校验结束/通过) } return 0; // 0 代表另一种状态 } ``` **Patch 策略:** 通过分析发现,需要修改特定分支的返回值以绕过校验错误。根据实战经验,定位到关键判断处,修改返回值逻辑。 *** ### Patch 实战操作 #### 提取与反编译 1. **提取**: 使用 MT 管理器或 `adb pull` 从已 Root 设备中提取 `/data/app/.../lib/arm64/libsscronet.so`。 2. **分析**: 将文件拖入 IDA Pro (64-bit),等待分析完成。 #### 修改汇编指令 定位到校验函数的关键返回点,直接修改汇编指令。 ```asm ; 原始指令示例 MOV W0, #1 ; 原始逻辑可能返回 1 RET ; 修改后指令 MOV W0, #0 ; 强制修改返回值为 0 RET ``` **修改要点**: 在 `sub_3A6DF8` 等关键函数中,将原计划返回 `1` (或特定非零值) 的逻辑修改为返回 `0`。 注意:具体修改的值需根据不同版本的汇编上下文判定,核心目标是让函数返回“成功”状态。 #### 重打包与替换 1. **应用 Patch**: 在 IDA 中选择 `Edit -> Patch program -> Apply patches to input file`。 2. **替换文件**: ```bash # 备份原文件 mv /data/app/com.ss.android.ugc.aweme-.../lib/arm64/libsscronet.so /data/app/com.ss.android.ugc.aweme-.../lib/arm64/libsscronet.so.bak # 替换并赋权 cp /sdcard/libsscronet.so /data/app/com.ss.android.ugc.aweme-.../lib/arm64/ chmod 755 /data/app/com.ss.android.ugc.aweme-.../lib/arm64/libsscronet.so ``` 3. **重启应用**: 强行停止 App 并重启,必要时清除缓存。 *** ## 常见问题与排查 在替换 `so` 文件后,可能会遇到以下问题: * **App 闪退**: * 原因:Patch 位置错误导致堆栈不平衡,或 App 存在完整性校验(Signature check)。 * 解决:检查 IDA 修改的指令是否正确,尝试使用 Frida Hook 内存中的函数而非物理替换文件。 * **仍然无法抓包**: * 原因:Cronet 可能存在多级校验,仅 Patch 了一处;或 App 升级导致函数偏移变化。 * 解决:继续追踪 `VerifyCert` 的交叉引用 (Xref),检查是否有其他校验点。 * **权限拒绝**: * 原因:替换后未赋予 `chmod 755` 权限,导致系统无法加载 so 库。 *** ## 结论 * **Native 级对抗**: 对于集成 Cronet 的应用,Native 层的逆向分析是必经之路。 * **持久化方案**: 直接 Patch `so` 文件相比 Frida 脚本具有更好的持久性,但面临签名校验的风险。 * **风险控制**: 修改系统或应用库文件存在风险,操作前务必备份。 *** ## 声明 本文所提供的逆向思路仅用于**安全研究**与**技术交流**。 请勿利用相关技术攻击目标应用或窃取用户数据。 # 某多多 Titan 协议分析与商品详情 Hook > Source: 本文仅供安全研究与学习使用,请勿用于非法用途 > 针对某多多使用的 Titan 长连接协议导致无法通过常规代理抓包的问题,本文提供了一种基于 Hook 的应用层拦截方案 ## 概述 在对某多多进行协议分析时,发现其采用了名为 **Titan** 的私有长连接(LongLink)技术。这导致常规的 HTTP/HTTPS 抓包工具(如 Charles, Fiddler)无法捕获核心业务流量(如商品详情)。 本文主要探讨如何绕过网络层加密,直接在 Java 应用层截获数据: * **Titan 协议拦截**: Hook 网络库响应处理函数,获取解密后的原始响应 * **商品详情抓取**: Hook 业务层数据模型,将内存对象序列化为 JSON *** ## 技术架构与核心机制 ### 关键注入点与探针部署 | 探针类型 | 注入类/方法 | 功能说明 | | ------------ | --------------------------------- | ------------------ | | Frida | `TitanApiResponse.getBodyBytes` | 拦截 Titan 网络层原始响应数据 | | Xposed | `GoodsResponse.setRenderResponse` | 拦截商品详情页面渲染数据 | | Frida/Xposed | `Gson.toJson` | 辅助将内存对象序列化为可读 JSON | ### 数据流向与拦截逻辑 ```mermaid flowchart TD A[App 业务请求] --> B[Titan 网络层] B --> C{LongLink 长连接通道} C --> D[服务端响应] D --> B B --> E[TitanApiResponse 解析] E -- Hook 拦截 --> F[获取原始 Response String] E --> G[业务层对象转换] G --> H[GoodsResponse 渲染] H -- Hook 拦截 --> I[序列化导出 JSON] ``` *** ### Titan 协议层拦截 针对 Titan 协议,直接在网络库的回调处进行拦截是最有效的方式。此时数据已经完成了协议层的解包,但尚未分发给业务逻辑。 #### Frida 注入脚本示例 **以下脚本仅适配特定版本,生产环境需适配混淆** ```javascript Java.perform(function () { // 1. 定位目标类 (需根据混淆调整) var TitanApiResponse = Java.use("xxx.xxxx.xxxxtitan.api.TitanApiResponse"); var StringCls = Java.use("java.lang.String"); // 2. Hook getBodyBytes 方法 if (TitanApiResponse.getBodyBytes) { TitanApiResponse.getBodyBytes.implementation = function () { var hookPoint = "TitanApiResponse.getBodyBytes()"; var data = this.getBodyBytes(); // 3. 拦截并转码 if (data) { try { var resp = StringCls.$new(data, "UTF-8"); if (resp) { console.log("\n=== 📥 " + hookPoint + " 响应捕获 ==="); // 截断输出避免日志爆炸 console.log(resp.length > 2000 ? resp.substring(0, 2000) + "..." : resp); console.log("====================================\n"); } } catch (e) { console.log("[!] " + hookPoint + " - 响应解析失败:", e); } } // 4. 必须返回原始数据,否则 App 会崩溃 return data; }; } }); ``` **概述:** * **定位类**: 寻找实现 Titan 协议响应接口的关键类 * **拦截点**: `getBodyBytes` 通常是获取原始响应数据的必经之路 * **无感注入**: 仅读取数据进行打印或保存,不修改原始数据流,保证 App 正常运行 *** ### 业务层数据抓取 对于结构复杂的业务数据(如商品详情),直接分析网络层 JSON 可能较为繁琐。直接 Hook 业务逻辑层的数据对象(Model)并进行序列化,可以获得结构化的清晰数据。 #### 伪代码实现逻辑 **伪代码,需配合 Xposed/YukiHookAPI 实现** ```java // 目标:获取商品详情的结构化数据 // Hook 点:GoodsResponse.setRenderResponse(IntegrationRenderResponse response) public void beforeHookedMethod(MethodHookParam param) { // 1. 获取入参对象 (IntegrationRenderResponse) Object renderResponse = param.args[0]; if (renderResponse == null) return; // 2. 序列化为 JSON // 优先使用 App 内部混淆后的 Gson,避免类加载器冲突 // 假设 App 内部 Gson 实例可通过静态方法获取 String json = AppGson.toJson(renderResponse); // 3. 数据持久化或上报 if (json != null && !json.isEmpty()) { Logger.i("GoodsDetail", json); FileUtil.write("/sdcard/pdd_goods_" + System.currentTimeMillis() + ".json", json); } } ``` **实现要点:** * **Gson 实例**: 务必使用 App 自身的 `Gson` 实例进行序列化,否则可能会因为 ClassLoader 不同或缺少 TypeAdapter 导致序列化失败 * **线程安全**: IO 操作(如写文件、网络上报)应放入子线程执行,避免阻塞 UI 线程导致 ANR * **时机选择**: `setRenderResponse` 通常在网络请求回调后、UI 渲染前调用,是截获数据的最佳时机 *** ## 常见问题与排查 在实践过程中,可能会遇到以下问题: * **Hook 不生效**: 检查注入时机,建议在 `Application.attachBaseContext` 之后或 `Activity` 启动时进行注入 * **混淆导致类找不到**: 结合 Jadx 反编译对比版本差异,Titan 库的包名和类名经常变动 * **数据乱码**: 检查响应头是否为 Gzip 压缩,如果是,需先进行 Gzip 解压再转 String * **Longlink 无法降级**: 新版本 App 即使检测到系统代理也可能强制走 Longlink,因此直接 Hook 应用层比尝试降级协议更稳定 *** ## 声明 本文所提供的脚本和代码逻辑仅用于**安全研究**与**技术交流**。 目标应用的协议与混淆策略会频繁更新,文中涉及的类名与方法名可能已失效,请结合实际环境进行分析。 # ShiroSU 内核提权部分讲解 > Source: 此文章所有内容均已弃用 > SSU 内核是 SSU 的一部分,但其并非 SSU 的主要定位,其将会作为可选内容而存在 ## 概述 SSU 内核部分是一个针对 Android 内核的提权与权能限制处理的部分: * **root 提权**: 注入 `su` 并在其被执行时在内核层面提权为 `root` * **能力管理绕过**: 注入拦截能力检查函数,绕过 Linux 能力限制 * **SELinux 绕过**: 注入拦截 SELinux 访问控制,允许敏感操作 *** ## 技术架构与核心机制 ### 关键注入点与探针部署 | 探针类型 | 注入函数 | 功能说明 | | --------- | ----------------------- | --------------- | | kprobe | sys\_execve (按情况区分具体名称) | 监控 `su` 执行,触发提权 | | kretprobe | cap\_capable | 绕过 Linux 能力检查 | | kretprobe | avc\_denied | 绕过 SELinux 访问控制 | ### 探针注册流程 ```mermaid flowchart TD A[模块加载] --> B[注册 execve kprobe] B --> C[注册 cap_capable kretprobe] C --> D[注册 avc_denied kretprobe] D --> E[监控/劫持敏感操作] ``` *** ### 核心提权逻辑 **伪代码,并非实际逻辑** ```c struct cred *cred = (struct cred *)__task_cred(current); // 1. 提权为 root cred->uid = cred->euid = cred->suid = cred->fsuid = GLOBAL_ROOT_UID; cred->gid = cred->egid = cred->sgid = cred->fsgid = GLOBAL_ROOT_GID; cred->securebits = 0; // 2. 赋予所有能力 memset(&cred->cap_inheritable, 0xff, sizeof(kernel_cap_t)); memset(&cred->cap_permitted, 0xff, sizeof(kernel_cap_t)); memset(&cred->cap_effective, 0xff, sizeof(kernel_cap_t)); memset(&cred->cap_bset, 0xff, sizeof(kernel_cap_t)); memset(&cred->cap_ambient, 0xff, sizeof(kernel_cap_t)); // 3. 关闭 seccomp (部分需要依情况) current_thread_info()->syscall_work &= ~SYSCALL_WORK_SECCOMP; current_thread_info()->flags &= ~(TIF_SECCOMP | _TIF_SECCOMP); current->seccomp.mode = 0; current->seccomp.filter = NULL; // 4. 设置 root 组 if (cred->group_info) { // 设置组为 root ... } ``` **概述:** * **UID/GID 提权**: 将当前进程的所有`用户`和`用户组`提权为 `root`= * **能力位全开**: Linux 的 `capability` 机制用于细粒度权限控制,全部置 `1` 后进程拥有所有内核能力 * **关闭 seccomp**: `seccomp` 是 Linux 的系统调用过滤机制,关闭后进程可自由调用所有系统调用 * **组信息 root 化**: 将进程的 `group_info` 指向 `root` 组,避免组权限限制 *** *** ### SELinux 绕过机制 **伪代码,并非实际逻辑** ```c // avc_denied kretprobe handler if (current->real_cred->uid.val == /* 白名单 UID */) { regs->regs[0] = 0; // 白名单 UID 直接设置允许 } else if (!current->real_cred->uid.val) { regs->regs[0] = 0; // root 同样设置允许 } ``` **概述:** * **SELinux 绕过**: 通过 `kretprobe` 注入 `SELinux` 的访问控制决策函数以绕过进程访问资源限制 * **UID 检查**: 仅对特定 `UID`(如 `ShiroSU 管理器` 以及由用户设置的白名单 UID)或 `root` 生效 * **强制放行**: 将返回值设为 `0`,表示访问被允许,从而绕过 SELinux 的安全策略 *** ### 能力检查绕过 **伪代码,并非实际逻辑** ```c // cap_capable kretprobe handler if (current->real_cred->uid.val == /* 白名单 UID */) { regs->regs[0] = 0; // 白名单 UID 直接设置允许 } else if (!current->real_cred->uid.val) { regs->regs[0] = 0; // root 同样设置允许 } ``` **概述:** * **cap\_capable 绕过**: 通过 `kretprobe` 注入 Linux 内核能力检查的核心函数以在能力检查返回时劫持其返回值 * **UID 检查与放行**: 对指定 `UID` 或 `root` 直接返回允许,绕过所有 `capability` 检查 *** ### 进程监控与自动提权 * 通过 `kprobe` 注入 `sys_execve` (具体地址名称需要依内核版本以及架构而定),监控进程执行 `su` * 检测到执行 `su` 时,自动调用提权函数实现提权为 `root` **伪代码,并非实际逻辑** ```c char buf[128] = {0}; struct Param param; param = *(struct Param *)regs->regs[0]; if (copy_from_user(buf, param.filename, sizeof(buf) - 1)) return 0; if (!strcmp(buf, "/system/bin/su")) { // 提权进程为 root ... } ``` *** ## 声明 本文章所展示的代码为 ShiroSU 内核的部分**伪代码**,**并非实际代码**,**与实际代码会有出入**,**伪代码仅供参考**