以下根据您提供的框架,逐节补充完整内容。标题层级严格遵循:一/二/三为二级标题(##),1/2/3为三级标题(###),a/b/c为四级标题(####)。关键信息已加粗,冒号前的文字(如“关键说明:”)已加粗。


一、基础知识与环境搭建

关键说明: 本节是所有逆向工作的起点,环境配置错误会导致后续无法调试。

1、HTTP/HTTPS 协议基础

  • 请求/响应结构:

    • 请求行:方法(GET/POST)、URL、协议版本

    • 请求头:Host、User-Agent、Cookie、Referer、Content-Type

    • 请求体:FormData、JSON、明文或加密数据

    • 响应状态码200(成功)、301/302(重定向)、401(未授权)、403(禁止)、500(服务器错误)

  • 常见请求方式: GET(参数在URL)、POST(参数在Body)、PUT、DELETE、OPTIONS

  • Cookie与Session机制: HTTP无状态,Cookie维持会话。Cookie可能由服务器下发(Set-Cookie)或前端JS生成(document.cookie赋值),逆向时需区分来源。

2、浏览器开发者工具使用

  • 元素面板: 查看DOM结构,定位加密入口(如按钮的onclick绑定、表单提交事件)

  • 控制台: 执行JS代码、Hook关键函数(见第六节)、输出调试日志

  • 网络面板: 捕获XHR/Fetch请求,查看请求参数、响应数据。右键请求 → Copy → Copy as cURL 可导出为命令行,方便重放。

  • 源代码面板: 设置断点、格式化混淆代码(点击{}按钮)、查看调用栈、Scope变量、Watch表达式

3、抓包工具配置与使用

  • Fiddler: 配置HTTPS解密(Tools → Options → HTTPS → Decrypt HTTPS traffic),使用bpu命令断点修改请求/响应。

  • Charles: Map Local功能可将远程JS替换为本地修改版;Rewrite功能可批量修改请求头或响应体;Throttle Settings模拟弱网环境。

  • Wireshark: 极少情况用于分析非HTTP协议(如WebSocket帧、TCP流重组),需要过滤表达式 tcp.port == 443

4、Node.js 与 Python 环境

  • Node.js: 安装LTS版本,使用npm init创建项目。本地执行加密片段时,需补齐浏览器环境(见第八节)。vm模块可创建沙盒运行不信任代码。

  • Python: 安装execjs(依赖Node)或subprocess调用Node脚本。推荐将加密逻辑封装成Node模块,由Python通过subprocess调用,稳定且易调试。

  • 虚拟环境: Python用venvconda;Node用nvm管理版本。

5、常用逆向辅助工具

  • 油猴脚本(Tampermonkey): 编写注入脚本,在页面加载前执行Hook代码,如:

    javascript

    // ==UserScript==
    // @name         Hook JSON.stringify
    // @namespace    http://tampermonkey.net/
    // @version      0.1
    // @description  try to take over the world!
    // @author       You
    // @match        *://target.com/*
    // @grant        none
    // ==/UserScript==
    (function() {
        const original = JSON.stringify;
        JSON.stringify = function(obj) {
            console.log("JSON.stringify called:", obj);
            return original(obj);
        };
    })();
  • Requestly: 修改请求/响应、重定向JS文件至本地服务器(如http://127.0.0.1:8080/hooked.js)。

  • ReRes(Chrome插件): 将远程JS资源映射到本地文件,无需自建服务器。


二、JavaScript 核心语法速查

关键说明: 不理解JS闭包、原型链、异步,无法读懂混淆后的代码。

1、作用域与闭包

  • 词法作用域: var函数作用域,let/const块级作用域。混淆代码常大量使用var和立即执行函数(IIFE)制造独立作用域

  • 闭包形成与作用: 函数内部返回函数,内部函数持有外部函数变量。闭包常用于封装私有变量和模块模式,逆向时可查找return { ... }暴露的方法。

  • 经典反调试利用: 通过闭包隐藏加密函数,只暴露加密结果。定位加密函数需在调用栈中找到闭包内的具体实现

2、原型链与 this 指向

  • 原型继承: __proto__(对象内部指针)与prototype(构造函数属性)。Hook原型方法可以监控所有实例的调用,如:

    javascript

    const originalPush = Array.prototype.push;
    Array.prototype.push = function(...args) {
        console.log("Array push:", args);
        return originalPush.apply(this, args);
    };
  • this绑定规则: 默认(全局/undefined)、隐式(对象调用)、显式(call/apply/bind)、new绑定、箭头函数(继承外层this)。混淆代码中常见的var that = this是为了保存外层this

  • Hook原型方法: 常用于拦截数组操作、字符串处理等基础行为。

3、异步编程

  • 回调地狱: 多层嵌套的function老旧混淆可能将加密逻辑拆散在多个回调中,需按顺序跟踪。

  • Promise: then/catch链,async/await是语法糖。破解时若遇到Promise,需关注resolve传入的值

  • 生成器与async/await: function*yield,反混淆时可将await还原为Promise.then形式以便理解。

4、常见内置对象与函数

  • Object: Object.defineProperty用于定义属性访问器,常用于实现数据双向绑定或隐藏敏感属性。逆向时可改写getter/setter输出日志。

  • Function: new Function('a','b','return a+b')动态创建函数,混淆代码常用来延迟执行或规避静态分析。Hook Function构造函数可捕获动态生成的内容。

  • 正则表达式: 提取加密参数时经常用matchreplace例如提取URL中的sign=([a-f0-9]{32})


三、调试与定位技巧

关键说明: 找到加密入口是逆向的核心,本节技巧能节省80%的时间。

1、断点调试

  • XHR断点: Sources面板 → XHR/fetch Breakpoints → 添加包含URL关键字的断点。当请求URL匹配时自动断下,可快速定位发请求的代码。

  • DOM断点: 元素面板右键 → Break on → attribute modification / subtree modifications。当加密参数被写入DOM时触发

  • 事件监听器断点: Sources右侧 → Event Listener Breakpoints → 选择clicksubmit等。点击登录按钮时断下,查看调用栈

2、调用栈分析与日志输出

  • 调用栈(Call Stack): 断点后右侧面板显示调用链,从下往上追溯:最下面是触发事件,最上面是当前执行函数。加密函数通常在中间某层。

  • console.log: 在Sources中修改代码,插入console.log('value:', variable),然后右键保存修改。注意修改的是临时内存代码,刷新后失效,可用Overrides持久化。

  • console.trace: 在控制台或注入代码中执行,打印当前调用堆栈,适用于追踪函数调用来源。

3、全局搜索与代码注入

  • 搜索关键词: 使用Sources面板下的Search all files(Ctrl+Shift+F),搜索encryptsigntokenpasswordkeyivmodepadding等。如果被混淆,可搜索=return等通用符号

  • 代码注入: 在控制台直接重写函数,例如:

    javascript

    window.originalEncrypt = window.encrypt;
    window.encrypt = function(data) {
        console.log("encrypt input:", data);
        let result = window.originalEncrypt(data);
        console.log("encrypt output:", result);
        return result;
    };
  • 保存JS文件本地替换: Sources → 打开JS文件 → 右键 → Save for overrides,然后修改代码保存,刷新后生效。

4、条件断点与日志断点

  • 条件断点: 右键行号 → Add conditional breakpoint,输入表达式如data.length > 16,只有满足条件时才暂停。

  • 日志断点: 右键行号 → Add logpoint,输入"参数值为:" + param不会暂停执行,只打印日志,适合高频调用的函数。

5、内存漫游

  • 查找全局变量: 控制台输入Object.keys(window),观察是否有可疑的全局对象(如_0x1234encryptModule)。

  • 定位隐藏函数: 如果加密函数挂载在某个对象下,可递归打印属性:

    javascript

    function printProps(obj, prefix) {
        for(let k in obj) {
            console.log(prefix + k, typeof obj[k]);
            if(typeof obj[k] === 'object') printProps(obj[k], prefix + '  ');
        }
    }
    printProps(window, '');
  • 调用匿名函数: 如果遇到(function(){...})(),可通过arguments.callee在函数内部获取自身引用,但更简单的方法是在代码中搜索(function并打断点


四、网络请求逆向

关键说明: 最终目标是模拟加密请求,因此需要完整还原参数构造过程。

1、请求参数加密

  • URL参数加密:?data=xxx&sign=yyysign通常由其他参数拼接后计算得出。定位方法:在Network面板找请求,复制URL,再在Sources中搜索sign

  • POST FormData / JSON: body内的嵌套加密字段。注意:有时整个body是一个加密字符串,需要先解密才能看到内部结构。

  • 加密位置: 可能在XMLHttpRequest.sendfetch调用之前。断点打在send方法或fetch函数上,查看调用栈。

2、响应数据解密

  • 识别加密响应: 响应内容为无意义乱码、Base64长串、或十六进制字符串。常见于图片验证码、敏感数据接口

  • 解密位置: 通常在response拦截器中,或then回调里,或者重写了XMLHttpRequest.prototype.onreadystatechangeHook JSON.parse可以捕获解密后的对象

3、Cookie/Session 生成逻辑

  • Cookie来源:

    • 服务器通过Set-Cookie下发(无需逆向,直接携带即可)。

    • 前端JS生成:例如通过document.cookie = 'token=' + encrypt(data)逆向方法:Hook document.cookie的setter

  • Hook示例:

    javascript

    Object.defineProperty(document, 'cookie', {
        set: function(val) {
            console.log('Setting cookie:', val);
            debugger; // 断点
            return val;
        },
        get: function() {
            return originalCookie;
        }
    });

4、Token(JWT、自定义)逆向

  • JWT三段式结构: 头部、载荷、签名。可直接复制JWT字符串使用,无需破解签名,除非需要伪造内容。注意JWT可能带有过期时间。

  • 自定义Token: 通常由时间戳、用户ID、随机数等拼接,再经哈希或对称加密生成。逆向目标:找到生成函数

5、请求签名(Sign)与时间戳

  • 常见签名方式:

    1. 将所有请求参数(包括timestamp)按key排序

    2. 拼接成key1=value1&key2=value2...格式

    3. 末尾加上固定密钥(secret)

    4. 进行MD5或SHA256得到签名

  • 时间戳校验: 服务器会检查timestamp与当前时间差,超过范围则拒绝。需要同步服务器时间(可从响应头Date字段获取)。

  • 加粗关键点: Sign的构造顺序(参数排序、拼接方式、密钥位置)决定了是否能重放成功。稍有偏差就会验签失败。


五、常见加密算法识别与还原

关键说明: 能一眼看出MD5、AES、RSA是逆向的基本功。

1、哈希(MD5、SHA 系列)

  • 特征: 固定长度32/40/64位十六进制,不可解密。常见调用方式:CryptoJS.MD5(str).toString()crypto.createHash('md5').update(str).digest('hex')

  • 识别技巧: 搜索md5sha1sha256createHashdigest如混淆,搜索输出长度32或64的十六进制字符串

2、对称加密(AES、DES、3DES)

  • 识别特征: 出现ivmodepaddingCryptoJS.AES.encryptcrypto.createCipherivAES的密钥长度常见128/256位

  • 关键参数:

    • 密钥(Key):可能硬编码、从服务器获取、或通过固定算法生成。

    • 初始向量(IV):CBC模式需要IV,ECB模式不需要。

    • 模式与填充:CBC、ECB、CFB等,填充方式PKCS7、Zero等。

  • 加粗提醒: IV通常不能固定,需要从请求或响应中动态提取。如果IV固定,可硬编码。

3、非对称加密(RSA、ECC)

  • 特征: 公钥加密私钥解密,JS中常见公钥硬编码。搜索-----BEGIN PUBLIC KEY----------BEGIN RSA PUBLIC KEY-----或hex格式的公钥

  • 提取公钥: 可能在JS变量中,或从接口动态获取。RSA加密后的数据长度等于密钥长度(如1024位密钥加密后128字节)

  • 还原方法: Node.js使用crypto.publicEncrypt(publicKey, buffer)模拟。如果使用JSEncrypt库,可提取其getKey方法

4、Base64 与变种

  • 标准Base64: 字符集A-Za-z0-9+/,末尾可能有=填充。识别特征:长度是4的倍数,仅包含上述字符

  • 变种:

    • 自定义字母表:例如-_代替+/(URL-safe)。

    • 去除填充:末尾无=

    • 多次嵌套:先Base64再URL编码等。

  • 还原方法: Node.js Buffer.from(str, 'base64'),自定义表需手动替换。

5、自定义编码与混淆算法

  • 举例: 循环移位、查表替换、位运算组合(异或、与、或)。

  • 还原思路:

    • 动态调试,输入'abc'得到输出'def',推测映射规律。

    • 如果算法简单,可手工重写为JS函数。

    • 如果涉及复杂循环,可直接将加密函数复制出来,补全依赖。


六、Hook 与拦截技术

关键说明: Hook是篡改程序行为的利器,也是动态分析的基础。

1、关键函数 Hook

  • eval: 拦截动态执行代码,常见于反混淆(将字符串当成代码执行)。

    javascript

    const origEval = eval;
    window.eval = function(code) {
        console.log('eval called:', code);
        // debugger;
        return origEval(code);
    };
  • Function: 拦截new Function()创建的函数,方法同上。

  • setTimeout/setInterval: 阻止定时器反调试,可替换为空函数或立即执行。

2、原型链 Hook

  • toString: 某些检测会调用Function.prototype.toString判断函数是否为原生。可Hook使其返回function () { [native code] }

    javascript

    const origToString = Function.prototype.toString;
    Function.prototype.toString = function() {
        if(this === fakeFunction) return 'function () { [native code] }';
        return origToString.apply(this);
    };
  • JSON.stringify/parse: 监控数据序列化过程,可打印参数。

3、属性描述符 Hook

  • 使用Object.defineProperty重写getter/setter,例如监控localStorage

    javascript

    const ls = localStorage;
    Object.defineProperty(window, 'localStorage', {
        get: function() {
            console.trace('localStorage accessed');
            return ls;
        }
    });
  • 案例: Hook document.cookie(见四-3),Hook navigator.webdriver(改为false)。

4、浏览器环境 Hook

  • navigator: 修改userAgentplatformlanguages,绕过检测。

  • window: 伪造window.chrome(非headless浏览器的特征)。

  • document: 改写document.getElementById,监控特定元素的获取。

5、使用外部工具 Hook

  • Frida: 注入JS到Chromium内核,可Hook底层C++函数,适合对抗重度反调试。

  • Oil(浏览器插件): 持久化Hook脚本,类似油猴但更底层。


七、AST(抽象语法树)与代码混淆

关键说明: 对付高强度混淆(如obfuscator.io)必须掌握AST还原。

1、AST 基础

  • Babel全家桶:

    • @babel/parser:将JS代码解析成AST。

    • @babel/traverse:遍历AST节点,进行增删改。

    • @babel/generator:将AST生成回代码。

  • 节点类型: Identifier(变量名)、Literal(字面量)、FunctionDeclarationCallExpression等。

2、常见混淆手法

  • 字符串加密: 原始字符串变为"\x68\x65\x6c\x6c\x6f" 或 调用解密函数_0x1234('0x1'),解密函数从数组取值。

  • 控制流平坦化: 将顺序代码转为while-switch结构,有一个状态变量控制跳转。还原时需记录状态变化,合并基本块

  • 死代码注入: 插入永远不会执行的垃圾语句,如if(false){...},可直接删除。

  • 逗号表达式混淆: a=1,b=2,c=3 代替多行语句,可展开为多个表达式语句。

3、反混淆实战

  • 还原字符串:

    • 若字符串通过函数调用返回,可在运行时执行该函数获得真实字符串,然后AST替换。

    • 示例脚本:使用vm模块在Node中执行解密函数,得到映射表,然后替换所有CallExpression

  • 简化控制流:

    1. 识别while循环和switch结构。

    2. 记录状态变量(如_state)的取值变化。

    3. 按顺序提取有效语句,重组为基本块。

  • 删除死代码: 常量传播,如if(false)分支删除。

4、自动化反混淆脚本编写

  • 模板:

    javascript

    const parser = require('@babel/parser');
    const traverse = require('@babel/traverse').default;
    const generate = require('@babel/generator').default;
    const code = `...混淆代码...`;
    const ast = parser.parse(code);
    traverse(ast, {
        CallExpression(path) {
            // 处理字符串解密调用
        }
    });
    const output = generate(ast).code;
    console.log(output);
  • 常用插件: @babel/plugin-transform-xxx可辅助简化。


八、补环境与模拟执行

关键说明: 当无法直接本地运行加密JS时,需要补全浏览器环境。

1、浏览器环境模拟

  • 缺失变量: windowdocumentnavigatorlocationlocalStoragescreenhistory

  • 补齐策略:

    • 从真实浏览器导出API快照:在控制台执行JSON.stringify(window, null, 2)(注意循环引用问题)。

    • 使用globalThis赋值:globalThis.window = globalThis;,但很多API无法直接模拟。

    • 推荐使用PuppeteerJSDOM

2、使用 PyExecJS、Node.js vm 模块

  • PyExecJS: 简单调用,但性能差,不支持大型库(如CryptoJS可能报错)。适合轻量加密。

  • Node.js vm:

    javascript

    const vm = require('vm');
    const context = { window: {}, document: {}, console };
    vm.createContext(context);
    const script = new vm.Script(encryptedJS);
    script.runInContext(context);
    console.log(context.window.encrypt('test'));

3、使用 JsDom、Puppeteer 实现半模拟

  • JsDom: 模拟DOM API,但性能较低,部分现代特性不支持(如WebGL)。

    javascript

    const jsdom = require('jsdom');
    const { window } = new jsdom.JSDOM('<!DOCTYPE html>');
    global.window = window;
    global.document = window.document;
    // 再执行加密JS
  • Puppeteer: 启动真实浏览器执行,速度慢但最准确。适合调试时使用,生产环境不推荐。

4、完美补环境框架

  • Proxy陷阱: 使用Proxy拦截任意属性读写,返回伪造值或记录日志。

    javascript

    const fakeWindow = new Proxy({}, {
        get: (obj, prop) => {
            console.log('Getting window.' + prop);
            return undefined; // 或返回特定伪造值
        }
    });
  • getter/setter模拟: 精确还原浏览器API行为,如localStorage需实现存储。

  • 指纹绕过: 补充canvas、webgl、时区、音频上下文等。可复制真实浏览器的指纹值。

5、指纹对抗

  • Canvas指纹: 固定toDataURL返回值。可通过Hook HTMLCanvasElement.prototype.toDataURL返回预设base64。

  • WebGL指纹: 固定getParameter返回值。Hook WebGLRenderingContext.prototype.getParameter

  • 时区与语言: 设置Intl.DateTimeFormat().resolvedOptions().timeZone为目标时区,navigator.language为目标语言。


九、RPC 与进程通信

关键说明: 如果加密过于复杂,可以通过RPC直接调用浏览器内已解密的函数。

1、WebSocket 即时通信

  • 原理: 在页面注入WebSocket客户端,本地起一个WebSocket服务端。客户端接收参数,调用页面内加密函数,将结果返回服务端。

  • 实现步骤:

    1. 油猴脚本注入WebSocket客户端。

    2. 本地Node.js起WebSocket Server。

    3. 发送{id:1, data:"hello"},客户端调用window.encrypt(data)后返回{id:1, result:"..."}

2、基于页面注入的 RPC

  • sekiro: 开源RPC框架,基于Netty,支持多客户端管理。客户端注入sekiro.js,服务端接收HTTP请求并分发。

  • BrowserRPC: Chrome扩展,暴露页面函数给本地服务,通过chrome.runtime.connectNative与本地程序通信。

3、配合安卓逆向

  • 应用场景: App内嵌WebView加载H5,加密逻辑在JS中。可通过addJavascriptInterface暴露Java对象给JS,或在WebView中注入JS代码。

  • 方法:

    1. 使用Frida hook WebView的loadUrl,注入RPC客户端。

    2. 通过console.log输出加密结果,用logcat捕获。


十、实战案例分类

关键说明: 每一种反爬手段都有对应的突破套路,建议按类别建立笔记。

1、登录参数逆向

  • 密码加密: RSA公钥加密、AES对称密钥(密钥可能由RSA加密传输)、SM系列国密。

  • 验证码处理: 图片识别(OCR/打码平台)、滑块轨迹加密(需逆向轨迹生成算法)。

  • 加粗提醒: 注意登录时的公钥可能每次请求都变化,需从HTML或接口动态获取。

2、数据采集接口逆向

  • 翻页参数: pageNolimit,以及对应的sign变化规律。注意sign通常包含页码和时间戳

  • 时间戳防重: ts_ttimestamp,服务器会校验时间窗口。

3、反爬虫机制突破

  • 极验(Geetest): 轨迹、指纹、w参数还原。w参数逆向极为复杂,可考虑使用RPC调用。

  • 瑞数(RiverSafe): VM保护、Cookie后缀(__jsl_clearance)、动态JS。需处理首次请求的302跳转和JS计算

  • 阿里系: 无痕环境、设备指纹(umid)、滑块验证。

4、小程序/公众号逆向思路

  • 获取源码: 解密wxapkg包(工具:wxappUnpacker),反编译得到JS文件。

  • 定位加密: 搜索encryptsign等关键词,注意小程序使用wx.request发送请求。

5、WebAssembly 初步逆向

  • 识别: JS中调用WebAssembly.instantiate,加载.wasm文件。

  • 分析方法:

    • 提取wasm文件,使用wasm2wat转换为文本格式(S表达式)。

    • 理解导出的函数签名,使用wat2js或直接动态调用传入参数观察输出。

    • 也可将wasm反编译为伪C代码(如wasm-decompile),但可读性较差。


十一、工具与自动化

关键说明: 熟练掌握以下工具能极大提升逆向效率。

1、抓包修改重放工具

  • Burp Suite: Repeater重放请求、Intruder批量测试参数、Decoder编解码。

  • Postman: 环境变量管理,可将加密结果写入变量供后续请求使用。

2、JS 代码格式化与美化

  • 在线工具: beautifier.iojsnice.org(提供变量名猜测)。

  • 本地IDE: VSCode + Prettier,WebStorm自带格式化。

3、调试器与反调试绕过

  • 禁用断点: Deactivate breakpoints按钮(Chrome Sources面板右侧),所有断点失效。

  • 清除定时器: 控制台执行for (let i=1; i<9999; i++) clearInterval(i);clearTimeout(i); 清除所有定时器。

  • 绕过无限debugger:

    • 右键断点行 → Never pause here。

    • 或者Function.prototype.constructor = function(){}; 注意这会破坏所有函数构造,谨慎使用。

4、自动化脚本

  • Python + execjs: 适合简单加密(如MD5、简单AES)。

  • Node.js 调度: 直接require加密JS模块,但需补环境。可将加密函数挂载到global对象上。

  • Puppeteer/Playwright: 完全模拟浏览器行为,适合需要频繁页面交互的爬虫,但速度慢。


十二、反逆向对抗与绕过

关键说明: 网站也会升级防御,你需要了解反爬是如何检测你的。

1、反调试手段

  • 无限debugger: 通过setInterval反复执行debugger;eval("debugger;")绕过方法:禁止断点或替换eval

  • 控制台检测: 检测console.log是否被篡改、toString是否返回原函数字符串。Hook检测函数

  • 断点检测: 计算函数执行时间差,若超过阈值则认为有断点。绕过:不在该函数内断点,或修改时间检测逻辑

2、代码自校验与时间戳检测

  • 完整性校验: 对自身代码求哈希,存于闭包中,执行前对比,不一致则拒绝。绕过:Hook哈希函数或直接禁止校验

  • 时间戳陷阱: 记录关键函数的执行时间,超时(调试时)则终止。绕过:在函数开头重置计时变量

3、环境检测与特征识别

  • 检测浏览器插件: navigator.plugins长度,headless浏览器通常长度很小。

  • 检测headless: navigator.webdriver(正常应为false)、window.chrome(正常应存在)。

  • 检测非人类交互: 鼠标移动轨迹、按键频率。模拟时需添加随机延迟和轨迹

4、常见对抗绕过思路

  • 替换检测函数: 重写console.logRegExp.prototype.test(拦截检测正则)。

  • 使用Proxy伪造所有环境属性,使任何属性访问都返回正常值。

  • 直接patch掉反调试代码: 通过本地替换(Overrides)删除相关逻辑,例如删除setInterval的调用。


十三、资源与持续学习

关键说明: 技术更新快,保持学习习惯至关重要。

1、优质博客与论坛

  • 国内: 看雪论坛(逆向版块)、先知社区、CSDN逆向专栏、掘金(搜索JS逆向)。

  • 国外: crackmes.one(破解练习)、Reddit的r/netsec、r/ReverseEngineering。

2、GitHub 优质项目

3、逆向练习靶场

4、法律法规与道德边界

  • 加粗警告: 不得利用逆向技术窃取数据、破解付费、入侵系统

  • 合法范围: 个人学习、安全测试需获得授权、漏洞挖掘遵守SRC规则。逆向代码不得用于商业爬虫或数据倒卖