|
黑客软件破解深度论文系列之五:网络验证破解——本地服务器模拟、中间人攻击与API Hook 摘要:网络验证是目前商业软件最主流的授权方式,但黑客仍然可以通过多种技术手段突破。本文以超过一万四千字的篇幅,系统讲解网络验证的架构模式、通信协议逆向方法、本地验证服务器模拟、中间人攻击篡改、客户端API Hook以及服务端校验绕过等核心技术。文章包含四个完整的实战案例,涵盖HTTP/HTTPS、TCP自定义协议、JWT篡改和授权文件替换等场景。全文高频使用“黑客”、“破解软件”、“网络验证”、“中间人攻击”、“API Hook”、“本地服务器模拟”等关键词。 第一章 网络验证的架构与安全模型1.1 为什么网络验证成为主流传统的本地验证(序列号、注册文件、硬件锁)存在一个根本性缺陷:验证逻辑最终必须在客户端执行。这意味着黑客总能通过逆向工程找到验证点并绕过。网络验证将授权决策从客户端转移到远程服务器,客户端仅负责收集证据(用户名、密码、机器码)和展示结果。如果设计得当,即使客户端被完全逆向,黑客也无法直接获得授权,因为关键的“是否允许使用”的判断在服务器上完成。 网络验证的核心安全假设:服务器是不可攻破的(或攻破成本极高),客户端与服务器之间的通信是可信的(或加密不可破解)。然而,这两个假设在实际中都存在漏洞,为黑客留下了破解空间。 1.2 网络验证的三种典型架构架构一:简单令牌验证客户端发送凭证,服务器返回一个令牌(Token),客户端后续每次操作都携带该令牌。这是最轻量级的方案。 [size=12.573px]text
[客户端] --(用户名/密码)--> [服务器][客户端] <--(Token)-------- [服务器][客户端] --(Token + 操作)--> [服务器][客户端] <--(操作结果)------ [服务器]
弱点:如果Token的有效期很长,黑客只需窃取一次Token即可长期使用。如果Token是本地生成的(如JWT未校验签名),黑客可直接伪造。 架构二:会话保持验证客户端与服务器建立持久连接(或使用Session ID),服务器维护会话状态。典型的Web应用模型。 [size=12.573px]text
[客户端] --(登录请求)--> [服务器][服务器] 创建Session,返回Set-Cookie[客户端] --(Cookie)--> [服务器][服务器] 根据Session验证权限
弱点:Session ID如果可预测,黑客可以暴力猜解;如果传输未加密(HTTP),可被中间人窃取。 架构三:离线许可+定期心跳客户端定期(如每7天)连接服务器验证一次,其余时间离线工作。这是为了解决网络不稳定问题的妥协方案。 [size=12.573px]text
[客户端] <--(每7天)--> [服务器] 验证授权[客户端] 本地存储“授权有效”标志,有效期7天
弱点:黑客可以修改本地存储的有效期,或阻断心跳请求但让客户端认为服务器返回“有效”。 1.3 网络验证的破解可能性分类
[td]验证模式 | 客户端保存的信息 | 黑客能否完全破解(离线使用) | 攻击难度 | | 纯在线(每次操作都需要服务器) | 无 | 不能(只能盗号) | ★★★★★ | | 首次验证后本地缓存 | 授权文件/注册表 | 能(伪造本地缓存) | ★★☆☆☆ | | 定期心跳 | 有效期+本地标志 | 能(修改有效期) | ★★★☆☆ | | 本地验证+远程辅助 | 大部分逻辑在本地 | 能(同本地验证) | ★☆☆☆☆ |
核心结论:如果软件在不联网时也能运行(绝大多数商业软件如此),那么黑客一定能够通过修改客户端或模拟服务器来达成破解。唯一的“绝对安全”是软件必须在联网状态下使用且核心计算在云端(SaaS模式)。 第二章 网络验证的通信协议逆向在破解网络验证之前,黑客必须先理解客户端与服务器之间“说了什么”。这个过程称为协议逆向。 2.1 HTTP/HTTPS流量捕获对于使用HTTP/HTTPS的客户端(绝大多数),最简单的协议逆向方法是使用中间人代理。 工具选择: Burp Suite:Web安全测试的行业标准,支持拦截、修改、重放HTTP/HTTPS请求。 Charles Proxy:界面友好,支持Windows/macOS/Linux,可模拟限速、断网等场景。 Fiddler:Windows平台经典,脚本扩展能力强。 Wireshark:底层抓包,适用于非HTTP协议(TCP/UDP自定义协议)。
配置HTTPS中间人(以Burp Suite为例): 在Burp中设置代理监听端口(如127.0.0.1:8080)。 将客户端的代理设置为Burp地址。 在浏览器或应用中导入Burp的CA证书(如果是系统级代理,需要将证书安装到Windows受信任的根证书颁发机构)。 对于Android/iOS应用,需要在设备上安装CA证书(某些应用会检测并拒绝自定义证书,需要后续绕过)。
注意:部分软件使用证书固定(Certificate Pinning),即客户端内置服务器证书的公钥或哈希,只信任该证书,不接受Burp生成的证书。绕过证书固定的方法见后续章节。 2.2 解析请求参数捕获到HTTP请求后,黑客需要分析每个参数的含义。一个典型的网络验证请求可能如下: [size=12.573px]text
POST /api/v1/validate HTTP/1.1Host: license.software.comContent-Type: application/json{ "product": "SuperApp", "version": "5.2.1", "machine_code": "A1B2-C3D4-E5F6-G7H8", "license_key": "XXXX-YYYY-ZZZZ-AAAA", "timestamp": 1700000000, "sign": "d41d8cd98f00b204e9800998ecf8427e"}
黑客的分析重点: 固定参数:product、version → 可原样重放 动态参数:timestamp → 可能与时间验证相关 绑定参数:machine_code → 可能与硬件绑定,需要计算或修改 加密/签名参数:sign → 需要逆向签名算法 目标参数:license_key → 黑客希望构造一个有效的license_key
2.3 非HTTP协议(自定义TCP/UDP)某些游戏或专业软件使用自定义二进制协议。分析这类协议的工具: Wireshark:捕获原始报文,查看十六进制内容。 Packet Sender:构造和发送自定义TCP/UDP包。 x64dbg + WSASend/recv断点:在客户端发送/接收函数上下断,直接查看内存中的原始数据。
分析流程: 用Wireshark捕获客户端启动到验证完成的全过程。 识别出验证相关的数据包(通常第一个请求-响应对)。 分析包结构:是否包含固定魔数(如0xDEADBEEF)?是否有长度字段?是否加密? 在x64dbg中对send/recv设置断点,查看数据在内存中的原始形态,尝试定位构造数据包的代码。
第三章 本地服务器模拟——最彻底的离线破解3.1 原理概述本地服务器模拟的核心思想是:让客户端认为自己在与真实的验证服务器通信,实际上所有请求都被转发到一个伪造的本地服务器上,该服务器返回客户端期望的成功响应。 这种方法不需要修改客户端的一行代码(理论上),因此可以完美避开代码完整性校验。黑客只需要做两件事: 3.2 修改hosts文件这是最简单、最常用的域名劫持方法。 Windows系统:C:\Windows\System32\drivers\etc\hosts
Linux/macOS:/etc/hosts 添加一行: [size=12.573px]text
127.0.0.1 license.software.com
此后,客户端对license.software.com的任何请求都会被解析到本地IP(127.0.0.1)。黑客在本地启动模拟服务器监听80/443端口(或其他端口),即可接管所有请求。 局限: 如果客户端使用IP地址直连而非域名,hosts无效。 如果客户端使用DNS over HTTPS(DoH),hosts会被绕过。 某些软件会检查hosts文件是否被修改(较少见)。
3.3 使用Fiddler/Burp的AutoResponder功能对于HTTP/HTTPS协议,黑客可以不需要编写代码,直接用代理工具的AutoResponder功能返回预设的响应。 步骤(Fiddler): 配置Fiddler作为系统代理。 运行客户端,捕获一次验证成功的响应(需要拥有正版账号或借用别人的账号)。 在Fiddler中选中该请求,右键→Save→Response→Response Body,保存为文件。 让客户端重新发起请求,Fiddler会自动拦截并返回预设的成功响应。
这种方法适用于:客户端不验证响应中的动态内容(如时间戳),或者验证逻辑较弱。 3.4 编写自定义模拟服务器当AutoResponder无法满足需求(如协议为TCP二进制、响应中需要包含动态挑战值)时,黑客需要编写自定义模拟服务器。Python是首选语言。 示例:模拟一个简单的HTTP JSON验证服务器 [size=12.573px]python
from flask import Flask, request, jsonifyimport timeapp = Flask(__name__)@app.route('/api/v1/validate', methods=['POST')def validate(): data = request.get_json() print(f"Received: {data}") # 无论收到什么,都返回授权成功 response = { "status": "success", "expires": int(time.time()) + 365 * 86400, # 一年后过期 "user_type": "premium", "features": ["export", "print", "batch", "signature": "fake_signature_here" } return jsonify(response)@app.route('/api/v1/heartbeat', methods=['POST')def heartbeat(): return jsonify({"status": "active"})if __name__ == '__main__': app.run(host='0.0.0.0', port=443, ssl_context=('cert.pem', 'key.pem'))
对于非HTTP协议,使用socket库即可: [size=12.573px]python
import socketdef handle_client(conn): data = conn.recv(1024) # 根据协议格式构造响应 response = b'\x01\x00\x00\x00success\x00' conn.send(response)server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(('127.0.0.1', 9999))server.listen(5)while True: conn, addr = server.accept() handle_client(conn)
3.5 挑战-响应协议的模拟更复杂的网络验证协议会使用挑战-响应机制:服务器发送一个随机挑战数,客户端用密钥加密后返回,服务器验证。这种设计旨在防止简单的“重放攻击”(Replay Attack)。 流程: 如果没有正确的密钥,模拟服务器无法生成客户端期望的挑战。但黑客可以绕过挑战机制:直接修改客户端,使其跳过挑战步骤,或者硬编码一个固定的挑战值。 高级模拟方法:提取客户端的加密密钥。如果密钥硬编码在客户端(常见),黑客可以通过逆向找到它,然后在模拟服务器中使用相同的密钥,实现完整的离线验证。 第四章 中间人攻击——动态篡改网络通信4.1 中间人攻击(MITM)简介中间人攻击是指在客户端与服务器之间的通信链路上插入一个代理,该代理可以: 窃听通信内容 篡改请求/响应 延迟或丢弃包 注入虚假数据
对于网络验证破解,中间人攻击主要用于:将服务器返回的“失败”响应篡改为“成功”。 4.2 使用Burp Suite进行实时篡改步骤: 配置Burp作为系统代理。 启用Intercept(拦截)功能。 客户端发起验证请求,Burp拦截后,点击Forward放行(通常不修改请求)。 服务器返回响应,Burp拦截。 修改响应内容:将"status":"invalid"改为"status":"valid",调整过期时间等。 点击Forward,客户端收到篡改后的响应,认为验证成功。
自动化篡改:使用Burp的Match and Replace规则,自动替换响应中的字符串。 [size=12.573px]text
类型: Response body匹配: "status":"invalid"替换: "status":"valid"
4.3 绕过证书固定(Certificate Pinning)证书固定是网络验证对抗中间人攻击的最强手段。它的实现方式: 黑客绕过证书固定的方法: 方法一:使用Frida HOOK验证函数 [size=12.573px]javascript
// Frida脚本示例:绕过Android证书固定Java.perform(function() { var TrustManagerFactory = Java.use('javax.net.ssl.TrustManagerFactory'); var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); // 劫持证书验证方法,使其信任所有证书 X509TrustManager.checkServerTrusted.implementation = function(chain, authType) { console.log("Bypassing certificate check"); return; };});
方法二:patch客户端二进制,替换内置证书 使用十六进制编辑器或IDA,找到客户端中存储的证书公钥数据,将其替换为Burp CA证书的公钥。需要保持长度一致(或调整引用)。 方法三:使用Xposed模块(Android)
SSLUnpinning Xposed模块可以一键绕过大多数证书固定。 方法四:QEMU用户态模拟+自定义CA
对于Linux程序,可以用QEMU模拟运行,并添加虚拟的CA证书到系统的信任链。 4.4 重放攻击与时间戳绕过即使服务器返回加密响应,如果响应不绑定时间戳或请求ID,黑客可以通过重放攻击(Replay Attack)重复使用一个之前捕获的有效响应。 防御:服务器在响应中包含时间戳或nonce(一次性随机数),并要求客户端在后续请求中携带该值。 黑客绕过方法:修改客户端代码,使其忽略时间戳验证,或者将本地系统时间修改到有效期内(与第二章的时间修改方法相同)。 第五章 API Hook——不碰网络,直接修改客户端逻辑5.1 API Hook的思想如果服务器返回的响应经过强加密,且客户端使用证书固定,中间人攻击变得非常困难。这时,黑客可以转向API Hook:直接在客户端内部,拦截处理网络响应数据的关键函数,将“失败”状态替换为“成功”。 API Hook不需要破解网络协议,不需要模拟服务器,甚至不需要客户端联网(可以断开网络运行)。它的本质是:把网络验证降级为本地布尔值修改。 5.2 定位关键函数API Hook的第一步是找到客户端中处理网络响应的函数。定位方法: 方法一:字符串搜索
在IDA中搜索status、success、error_code等字符串,这些字符串很可能出现在响应解析代码中。 方法二:API断点
在x64dbg中对JSON/XML解析函数下断点(如json_object_get、xmlNodeGetContent),追踪响应数据的流向。 方法三:内存搜索
当客户端收到响应后,在内存中搜索"invalid"字符串,然后逆向找到引用该字符串的代码。 5.3 编写Hook代码(DLL注入)对于Windows程序,黑客可以编写一个DLL,注入到目标进程,在运行时修改关键函数的行为。 示例:Hook一个简单的验证函数 假设通过逆向发现,客户端有一个函数IsLicenseValid(),返回布尔值。原始逻辑中,该函数通过网络验证结果决定返回值。Hook代码可以强制返回true。 [size=12.573px]cpp
#include <windows.h>#include <detours.h>typedef BOOL (WINAPI *IsLicenseValid_t)();IsLicenseValid_t Original_IsLicenseValid = NULL;BOOL WINAPI Hooked_IsLicenseValid() { // 总是返回真 return TRUE;}BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { if (reason == DLL_PROCESS_ATTACH) { Original_IsLicenseValid = (IsLicenseValid_t)GetProcAddress(GetModuleHandle(NULL), "IsLicenseValid"); if (Original_IsLicenseValid) { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)Original_IsLicenseValid, Hooked_IsLicenseValid); DetourTransactionCommit(); } } return TRUE;}
编译为hook.dll,使用注入工具(如Process Hacker)注入目标进程。 5.4 使用Frida进行动态Hook(跨平台)Frida支持Windows、macOS、Linux、Android、iOS,无需编译DLL,用JavaScript脚本即可完成Hook。 示例:Hook Android应用中的验证函数 [size=12.573px]javascript
Java.perform(function() { var LicenseManager = Java.use("com.software.LicenseManager"); LicenseManager.isValid.implementation = function() { console.log("isValid called, returning true"); return true; };});
运行:frida -U -l hook.js com.software.app 5.5 无限试用——修改剩余天数很多软件采用首次验证后缓存有效期的机制(例如试用30天)。黑客可以通过Hook获取缓存数据的函数来无限延长试用期。 典型场景:程序从注册表或文件中读取剩余天数,比较是否大于0。Hook该读取函数,强制返回999。 [size=12.573px]javascript
// Frida脚本var ReadFile = Module.findExportByName("kernel32.dll", "ReadFile");Interceptor.attach(ReadFile, { onLeave: function(retval) { // 找到存储天数的缓冲区,修改其内容 var buffer = this.context.arg2; // 根据调用约定调整 Memory.writeUtf8String(buffer, "30"); }});
第六章 实战案例(一):破解HTTP JSON网络验证6.1 目标程序6.2 捕获分析使用Fiddler捕获请求: [size=12.573px]text
POST /api/check HTTP/1.1Content-Type: application/json{"license":"ABCD-EFGH-IJKL-MNOP","machine":"CPU:Intel;HDD:WDC1234"}
响应(未激活时): [size=12.573px]json
{"status":"invalid","message":"License key not found","code":404}
6.3 获取一次正版响应通过购买7天试用,获得一个有效license key,捕获其响应: [size=12.573px]json
{"status":"valid","expires":"2025-01-01","features":["premium","signature":"sha256:abc123..."}
6.4 AutoResponder模拟在Fiddler AutoResponder中: 之后启动NetApp.exe,即使输入任意序列号,客户端都收到“valid”响应,成功破解。 验证:拔掉网线,程序启动后显示“网络错误”而非“已激活”——说明程序每次启动都联网验证,无法离线使用。黑客需要进一步修改客户端,移除网络检查。 6.5 最终补丁在x64dbg中找到处理网络错误的代码段,将jne改为jmp,强制跳转到已激活分支。完成离线破解。 第七章 实战案例(二):TCP自定义协议模拟7.1 目标程序GameClient.exe——一款网络游戏客户端,登录时需要验证账号是否付费。使用自定义TCP协议,端口8888。 7.2 Wireshark抓包分析启动Wireshark,过滤tcp.port==8888。捕获登录过程的数据包: [size=12.573px]text
发送 (客户端→服务器): 00 08 00 00 01 02 03 04 05 06 07 08接收 (服务器→客户端): 00 04 00 01 00 00 00 00
初步分析:前2字节可能是长度(大端序)。第一个包长度0x0008=8字节,之后4字节0x01020304可能是某种ID。响应包长度0x0004=4字节,其后4字节0x01000000可能是状态码(1表示成功)。 7.3 编写模拟服务器[size=12.573px]python
import socketimport structdef handle_client(conn): # 接收长度字段 len_data = conn.recv(2) if not len_data: return pkt_len = struct.unpack('>H', len_data)[0 data = conn.recv(pkt_len) print(f"Received: {data.hex()}") # 构造成功响应:长度0x0004,状态码0x01000000(成功) response = struct.pack('>HI', 0x0004, 0x01000000) conn.send(response) conn.close()server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(('127.0.0.1', 8888))server.listen(5)print("Fake server listening on 8888...")while True: conn, addr = server.accept() handle_client(conn)
7.4 劫持流量修改hosts(如果游戏使用域名),或直接修改游戏客户端的配置文件中的服务器IP为127.0.0.1。如果服务器IP硬编码,可以用x64dbg修改内存中的IP地址,或通过路由器做DNAT。 运行模拟服务器,游戏客户端成功进入付费界面。 第八章 实战案例(三):JWT篡改与本地验证绕过8.1 JWT简介JSON Web Token(JWT)是一种自包含的令牌格式,包含三个部分:Header、Payload、Signature,用Base64编码后用点号连接。 [size=12.573px]text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc19wcmVtaXVtIjp0cnVlfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
解码后的Payload: [size=12.573px]json
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "is_premium": true}
签名由服务器使用密钥HMAC-SHA256(header + "." + payload, secret)生成,用于防止篡改。 8.2 黑客的攻击面如果客户端没有验证签名(即直接解析Payload并信任is_premium字段),黑客可以: 如果客户端验证签名,但签名密钥硬编码在客户端中(如某移动应用),黑客可以提取密钥,然后自己签名任意Payload。 提取签名密钥:在x64dbg或IDA中搜索JWT签名函数(如HMAC_SHA256、jwt.verify),追踪密钥来源。如果密钥是固定字符串(如"mySecretKey123"),直接复制使用。 伪造JWT脚本(Python): [size=12.573px]python
import jwtpayload = { "user_id": 10086, "is_premium": True, "exp": 4102444800 # 2099年过期}secret = "mySecretKey123"token = jwt.encode(payload, secret, algorithm="HS256")print(token)
将伪造的token替换客户端存储的token,实现永久高级权限。 第九章 服务端校验的盲点与绕过9.1 只校验不执行某些开发者的错误设计:服务器只负责返回“允许”或“不允许”,但客户端本身可以完全离线运行,网络验证仅用于“激活”。这种情况下,黑客只需伪造一个激活成功的标志,就能永久使用。 案例:Adobe系列软件的早期激活机制——服务器返回一个激活码,客户端将其存储在本地。此后每次启动只检查本地激活码是否存在,不再联网。黑客只需要截获一次激活响应,或者生成任意格式的激活码,即可完成破解。 9.2 本地缓存篡改许多软件在首次验证成功后,会在本地存储一个“授权文件”(.lic, .key, .dat)。黑客可以: 根据正版账户生成一个授权文件(使用正版账号登录后复制文件)。 分析授权文件的格式(可能是加密的XML或二进制)。 如果加密密钥在客户端中,提取密钥后自行生成授权文件。
具体步骤: 9.3 时间校验绕过如果服务器返回的授权包含过期时间,客户端将本地时间与过期时间比较。黑客可以: Frida Hook示例: [size=12.573px]javascript
var time = Module.findExportByName("libc.so", "time");Interceptor.attach(time, { onLeave: function(retval) { // 返回一个固定的、在有效期内的Unix时间戳 retval.replace(1609459200); // 2021-01-01 }});
第十章 高级话题:使用机器学习辅助协议逆向这是一个前沿领域。某些复杂协议使用自定义加密,逆向难度极大。研究者开始尝试用机器学习自动推断协议格式和消息类型。 工具: 局限:目前仅适用于未加密或简单加密的协议,且需要大量样本。对于单次验证场景不适用。黑客仍以手动逆向为主。 第十一章 防御建议——如何设计抗破解的网络验证如果你是软件开发者,希望设计难以被破解的网络验证系统,请遵循以下原则: 11.1 核心原则尽量将核心逻辑放在服务器:如果软件离线时无法运行,破解就失去了意义(除非盗版者模拟整个后端)。SaaS模式是终极方案。 不要信任客户端的任何数据:包括时间、硬件信息、用户输入。任何客户端可修改的数据都不应用作授权依据。 使用证书固定:防止中间人攻击。但请注意,证书固定可以绕过,只能增加难度。 响应动态化:每次成功的验证响应中包含不同的nonce,并确保该nonce在后续请求中被使用。可以防止简单的重放攻击。 多因素验证:结合硬件ID、IP地理位置、行为分析,使模拟完整环境变得困难。
11.2 实现细节消息签名:所有服务器响应使用私钥签名,客户端用公钥验证。即使黑客修改响应内容,签名将失效。注意:公钥在客户端中,黑客可以替换公钥——所以应定期更新公钥并强制客户端在线获取。 加密通信:使用TLS 1.3,配置HSTS和HPKP(已废弃,改用Expect-CT)。避免使用HTTP明文。 反调试与完整性校验:在验证通信的代码周围加入反调试和代码哈希校验,防止黑客直接Hook。 定期更换密钥:客户端内置的公钥定期更换,旧版本强制升级。即使黑客提取了公钥,也只能破解旧版本。
11.3 现实建议实际上,没有网络验证是100%无法破解的。开发者的目标应该是:让破解的成本高于购买价格。对于售价50美元的软件,花50小时破解是不值得的。对于售价1000美元的专业软件,黑客有动机投入数周时间。 最有效的防破解措施往往是法律手段和社区支持——让破解版本无法获得更新和技术支持,降低盗版体验。 第十二章 总结本文以超过一万四千字的篇幅,全面深入地讲解了网络验证破解的各项技术:从协议逆向、本地服务器模拟、中间人攻击篡改,到API Hook、JWT伪造、授权文件替换,再到服务端校验盲点分析和高级话题。四个实战案例覆盖了HTTP、TCP、JWT和本地缓存等典型场景。 网络验证是目前软件保护的主流方向,但它在设计上存在诸多可被黑客利用的弱点:本地缓存、时间戳验证、弱签名校验、证书固定的绕过等。理解这些弱点,不仅有助于黑客破解,更有助于开发者设计抗破解的授权系统。 后续本系列将继续深入探讨移动端逆向(Android/iOS)、硬件加密锁破解等高级专题。 关键词:网络验证破解;中间人攻击;本地服务器模拟;API Hook;黑客;破解软件;JWT伪造;证书固定绕过
|