reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
128
vendor-new/rev-skills/.claude/skills/re-ghidra/SKILL.md
Normal file
128
vendor-new/rev-skills/.claude/skills/re-ghidra/SKILL.md
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
name: re-ghidra
|
||||
description: >
|
||||
Ghidra 工作流:导入→自动分析→反编译→脚本化。
|
||||
触发词:Ghidra、反编译、headless
|
||||
capabilities: [decompilation, debugging]
|
||||
---
|
||||
|
||||
# Ghidra 逆向工作流
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:需要反编译与深度静态分析;headless 批量处理;脚本化自动标注/解密循环;PE/ELF/Mach-O 全支持
|
||||
- 不用:内存 <4GB(启动即卡顿,换 [[re-radare2]]);只需快速初勘(走 [[re-triage]])
|
||||
- 不用:已有 IDA 工作流且目标明确(按 `RE_DECOMPILER` 会话变量选择)
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]]——Ghidra 为静态分析主力(免沙箱);低内存环境按平台经验换 [[re-radare2]],headless 批处理适合远程环境。
|
||||
|
||||
### Ghidra(官方安装,Java 21 要求)
|
||||
|
||||
- 下载: GitHub `NationalSecurityAgency/ghidra` releases,解压即用(无需安装)
|
||||
- JDK 21(Ghidra 11.3+ 要求):
|
||||
- Linux: `apt install openjdk-21-jdk` / `dnf install java-21-openjdk` / `pacman -S jdk21-openjdk`
|
||||
- macOS: `brew install openjdk@21` 或 `brew install --cask temurin`
|
||||
- Windows: `choco install temurin21`(或 Oracle JDK 21)
|
||||
- 启动: `./ghidraRun`(GUI)/ `./support/analyzeHeadless`(无头)
|
||||
- 验证: `java -version`(须 21+);`./support/analyzeHeadless -help` 正常输出
|
||||
- 版本: 12.x 为当前主线(核验基线 12.1.4);安装时以官方 Releases 的 Latest 为准;运行官方 release 只需 JDK 21,从源码构建 Ghidra 自身需 JDK 25 + Gradle 9.1+;11.x → 12.x 有破坏性脚本 API 变更,写脚本先查目标版本 javadoc,细节见 [[gotchas]]
|
||||
|
||||
### ghidra-bridge(Python 远程控制)
|
||||
|
||||
- 全平台: `pip install ghidra-bridge`
|
||||
- 前置: Ghidra GUI 中 Script Manager 运行 `ghidra_bridge_server.py`
|
||||
- 验证: `python3 -c "import ghidra_bridge; print(ghidra_bridge.__version__)"`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **analyzeHeadless 无头导入+分析**:
|
||||
```sh
|
||||
$GHIDRA/support/analyzeHeadless /tmp/proj sample_proj \
|
||||
-import ./sample.bin -deleteProject -log /tmp/ghidra.log
|
||||
```
|
||||
只建工程不交互时加 `-overwrite`;批处理多个样本循环调用。日志看 `-log`,分析完成标志: 无异常且工程可重新导入。
|
||||
- `-process` 模式(对已导入工程内文件重跑脚本/分析,与 `-import` 二选一):
|
||||
```sh
|
||||
$GHIDRA/support/analyzeHeadless /tmp/proj sample_proj \
|
||||
-process -postScript MyScript.java -scriptPath /tmp/scripts
|
||||
```
|
||||
- 只做符号/字符串/单函数反编译时加 `-noanalysis`(跳过自动分析,秒级完成);只读复查加 `-readOnly`;全套参数族见 [[commands]]
|
||||
|
||||
2. **GUI:导航/交叉引用/反编译**:
|
||||
- `File > Import File` → 选择样本 → `Analyze`(等左下角进度完成)
|
||||
- 跳转: `Go To`(G)输入地址;入口点已在 Program Trees 标记 `entry`
|
||||
- 交叉引用: 光标在函数/变量上按 `R`(References 窗口),或右键 > References > Show References To
|
||||
- 反编译: 在 Listing 中按 `Ctrl+E`(Decompiler 窗口,默认快捷键可能随版本/keymap 不同,以 Help > Key Bindings 为准)或右键 > Decompile Function,反编译视图可 copy
|
||||
- 函数图: 函数上按 `F`(Function Graph)看分支/循环结构,与 Decompiler 同步导航
|
||||
- 下划线地址差异: Listing 显示 VA(含 ImageBase),脚本中常用 `getAddressFactory().getDefaultAddressSpace().getAddress("0x401000")`
|
||||
|
||||
3. **重命名+类型传播标记**:
|
||||
- 函数重命名: Listing 中 `L`;变量重命名: Decompiler 中 `L`
|
||||
- 设置类型: 右键变量 > Set Data Type(`y`),如 `char *`、`DWORD`、`int (*)(int)`
|
||||
- 定义结构体: Data Type Manager > 新建 Structure,Decompiler 中引用后类型传播自动改善反编译质量
|
||||
- 标记已知库函数: 右键 > Set Data Type + `f`(Function signature),提升后续调用点参数语义
|
||||
|
||||
4. **Python 脚本(自动标注、解密循环仿真)**:
|
||||
```python
|
||||
# @category Analysis — 放 GhidraScripts 目录,Script Manager 中 Run
|
||||
from ghidra.program.model.listing import CodeUnit
|
||||
fm = currentProgram.getFunctionManager()
|
||||
for f in fm.getFunctions(True):
|
||||
body = f.getBody()
|
||||
if body.getNumAddresses() > 10000:
|
||||
print("large:", f.getName(), hex(body.getMaxAddress().getOffset()))
|
||||
```
|
||||
解密循环: 定位 XOR 循环后脚本 patch 或直接计算:
|
||||
```python
|
||||
from ghidra.program.model.mem import *
|
||||
fm = currentProgram.getFunctionManager()
|
||||
f = fm.getFunctionAt(currentAddress)
|
||||
# 示例: 从 0x403000 起 0x100 字节异或 0x55
|
||||
addr = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress("0x403000")
|
||||
for i in range(0x100):
|
||||
b = getByte(addr.add(i)) ^ 0x55
|
||||
setByte(addr.add(i), b)
|
||||
```
|
||||
- **改字节前先副本**:`setByte` 直接写当前 Program(保存后即改原始文件)——分析恶意样本必须先复制 Program(`File > Save As` 另存副本)或对临时副本操作,禁止直接 patch 原始样本,否则破坏证据链
|
||||
无头批量: `analyzeHeadless ... -postScript MyScript.java -scriptPath <dir>`
|
||||
|
||||
5. **导出反编译 C**:
|
||||
- `File > Export Program` → 格式选 `C/C++`,导出整个反编译树
|
||||
- 或 Decompiler 窗口逐函数 `Copy` 到笔记
|
||||
- **导出的是反编译结果,不是原始源码恢复**:变量名/类型推断可能错误(重命名后才会改善),不代表原始实现——只作阅读辅助,关键逻辑必须对照反汇编/Listing 验证
|
||||
- 导出后核对: C 里无符号函数名保留注释(函数地址),对照原 Listing
|
||||
|
||||
## 函数分析上下文清单
|
||||
|
||||
分析每个函数前先收集(见 [[re-analyze/analysis-contract]] 上下文清单):xrefs(谁引用它/它引用谁)、目标函数引用的字符串、caller/callee 签名、已命名符号表、已恢复 struct。一次性给足再分析,避免反复翻 xrefs;主动申请额外证据每函数不超过 8 次工具调用。
|
||||
|
||||
## 单函数深分析顺序
|
||||
|
||||
按 [[re-analyze/analysis-contract]] 的「单函数深分析顺序」五步推进(types → constants → vtables → identity → decompilation,不可跳步、禁止先反编译再倒推)。Ghidra 下的对应操作:类型用 Type Manager / Apply Function Signature,虚表用 vtable 分析,符号/常量证据可用 readelf / strings / objdump 导出辅助。
|
||||
|
||||
## 跨域联合
|
||||
- [[re-address-space]]:Ghidra 基址与运行时地址换算(load bias)
|
||||
|
||||
- [[re-binary-core]]:工作流第 5 步默认反编译器
|
||||
- [[re-malware]]:恶意样本深度分析(混淆脚本标注)
|
||||
- [[re-ctf]]:逆向题主力
|
||||
- [[re-firmware]]:固件 ELF 组件反编译
|
||||
- [[re-mobile]]:App .so 库分析
|
||||
- 与 [[re-format-pe]] / [[re-format-elf]] / [[re-format-macho]] 配合读结构;低内存环境用 [[re-radare2]]
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **自动分析漏掉混淆代码**:花指令/乱序代码段可能未被反汇编——手动选中段按 `C`(code)强制标记,必要时修复函数边界(右键 > Create Function)
|
||||
- **Java 版本不匹配启动失败**:报 `UnsupportedClassVersionError` → 确认 `java -version` 为 21+,`JAVA_HOME` 指向正确 JDK
|
||||
- **内存 <4GB 卡顿**:分析大二进制内存耗尽 → 换 [[re-radare2]] 或减小分析范围(`-analysisTimeoutPerFile`)
|
||||
- headless 默认分析选项与 GUI 有差异(缺少部分可选项)→ 用 `-postScript` 显式执行分析脚本保证一致
|
||||
- **大文件自动分析卡死**:现象——导入大二进制后自动分析长时间不结束/界面卡死;原因——大文件卡死诱因=间接调用爆炸/大型 C++ RTTI/混淆控制流/大量数据段;对策——先降低自动分析范围(限制/关闭间接调用与 RTTI 分析选项),优先定位入口/字符串/交叉引用再逐步展开,见 [[re-analyze/platform-tips]] 静态优先原则
|
||||
- **函数边界异常 → 检查反编译假象**:现象——反编译视图里函数被错误拆分/合并、多出假参数或栈变量错乱、函数体看着像垃圾数据,照此分析得出荒谬逻辑;原因——手写汇编、混淆代码、尾调用(tail call 使相邻函数被错误合并)、无 frame pointer(不用 rbp/ebp 使帧推断失真)会让 decompiler 的边界与帧推断出错;对策——先核对 Listing 汇编与真实字节,按 `C`/Create Function 手工修正边界,尾调用与无 frame pointer 的函数以汇编语义为准、不完全相信反编译输出(见 [[re-binary-core]] 分析方法论 R4)
|
||||
- **内置脚本解释器是 Jython 2.7,不是 Python 3**:现象——脚本一跑就报 `SyntaxError`(f-string/海象运算符)或 `ImportError`(找不到 pip 安装的三方库);原因——Ghidra 内置 Jython 2.7,是 Python 2 语法与标准库,不支持 Python 3 语法,也无法 import pip 装的包;对策——脚本按 Python 2 兼容语法写(不用 f-string、用 `xrange`),需要现代 Python 能力(numpy 等)时改用 ghidra-bridge 让外部 Python 3 解释器驱动分析(见工具准备)
|
||||
- **脚本/扩展选型失当 → 自动化维护成本失控**:现象——一次性批处理逻辑越写越复杂、每次手动触发很烦,或需要常驻自动化却反复手动点脚本;原因——Script(脚本:轻量单文件、手动触发、适合一次性/简单重复任务)与 Extension(扩展:可注册菜单/面板/事件监听、随工程加载自动执行、ZIP 打包团队分发)的边界没分清;对策——简单重复任务用脚本即用即走;需要 UI 集成、外部服务接入、加载时自动执行或团队标准化分发时才做扩展
|
||||
|
||||
- **脚本 API 跨版本不兼容**:现象——headless 脚本编译失败(skipping 且报 `getPermissions()`/`getExecute()` 类方法不存在);原因——Ghidra 版本间 MemoryBlock 等 API 变更(12.x 为 `isExecute()`,不同版本方法名不同);对策——写脚本前先查当前版本 javadoc(小验证脚本),编译失败先怀疑 API 差异而非业务逻辑
|
||||
- **全量分析太慢就 -noanalysis**:只做符号表/字符串/单函数反编译时,`analyzeHeadless -noanalysis` 导入后脚本可直接用——符号(含 JNI 导出)与字符串扫描不依赖 auto-analysis,秒级完成
|
||||
- 命令速查(headless 参数族 / GUI 快捷键族 / 脚本 API 族)见 [[commands]];版本差异、无头批处理与脚本 API 的更多坑见 [[gotchas]]
|
||||
@@ -0,0 +1,119 @@
|
||||
# Ghidra 命令速查与操作序列
|
||||
|
||||
两条主线:GUI(交互标注/函数级深分析)与 analyzeHeadless(无头批处理/脚本化)。脚本语言二选一:GhidraScript(Java / Jython 2.7,内置解释器)或 ghidra-bridge(外部 Python 3 驱动 GUI)。所有参数名与 API 以目标版本官方文档(help/topics/)与 javadoc 为准。
|
||||
|
||||
## analyzeHeadless 参数族(无头批处理)
|
||||
|
||||
| 参数 | 作用 |
|
||||
|---|---|
|
||||
| `-project <路径>` | 工程所在目录(自动创建) |
|
||||
| `<工程名>` | 工程名(位置参数) |
|
||||
| `-import <文件\|目录>+` | 导入并分析(可多个;目录需配 `-recursive`) |
|
||||
| `-process [<工程内文件>]` | 对已导入文件重跑(与 `-import` 二选一) |
|
||||
| `-preScript / -postScript <脚本名> [<arg>]*` | 分析前/后执行脚本,可跟参数 |
|
||||
| `-scriptPath "<路径1>[;<路径2>...]"` | 脚本搜索路径(分号分隔) |
|
||||
| `-propertiesPath` | 脚本属性文件路径 |
|
||||
| `-noanalysis` | 跳过自动分析(符号/字符串仍在,秒级导入) |
|
||||
| `-analysisTimeoutPerFile <秒>` | 单文件自动分析超时 |
|
||||
| `-processor <languageID> / -cspec <compilerSpecID>` | 强制指定语言/编译器(识别错时用) |
|
||||
| `-readOnly` | 只读打开工程(不写改动) |
|
||||
| `-overwrite` | 覆盖已存在文件 |
|
||||
| `-recursive [<depth>]` | 递归导入子目录 |
|
||||
| `-deleteProject` | 结束后删除工程 |
|
||||
| `-log <文件> / -scriptlog <文件>` | 运行日志 / 脚本日志(两个分开看) |
|
||||
|
||||
典型组合:`analyzeHeadless /tmp/p name -import a.bin -postScript X.java -scriptPath /s -deleteProject -log l.log`。
|
||||
|
||||
## GUI 快捷键族(按任务分组)
|
||||
|
||||
| 任务 | 操作 | 说明 |
|
||||
|---|---|---|
|
||||
| 导入 | `File > Import File` | 选样本后 `Analyze`,等左下角进度完成 |
|
||||
| 跳转 | `G` | Go To 地址/符号 |
|
||||
| 交叉引用 | `R` | 光标在函数/变量上按 R 开 References |
|
||||
| 反编译 | `Ctrl+E`(默认,随版本/keymap 可能不同) | 打开 Decompiler 窗口,以 Help > Key Bindings 为准 |
|
||||
| 函数图 | `F` | Function Graph(分支/循环结构) |
|
||||
| 重命名 | `L` | 函数/变量重命名(Listing 与 Decompiler 中均可用) |
|
||||
| 设置类型 | `y` | Set Data Type(如 `char *`、`DWORD`) |
|
||||
| 定义结构体 | `Data Type Manager > 右键 > New > Structure` | 定义后 Decompiler 引用改善反编译 |
|
||||
| 强制反汇编 | `C` | 选中字节标为 code(混淆段手动展开) |
|
||||
| 建函数 | `右键 > Create Function` | 修复函数边界 |
|
||||
| 撤销 | `Ctrl+Z` | 标注/改名可回滚 |
|
||||
| 导出 | `File > Export Program` | 格式选 C/C++ 导出**反编译结果**(非原始源码——变量名/类型推断可能错误,只作阅读辅助,关键逻辑对照反汇编验证) |
|
||||
|
||||
## 脚本 API 族(GhidraScript,Jython 2.7 语法)
|
||||
|
||||
```python
|
||||
# 地址/函数
|
||||
addr = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress("0x401000")
|
||||
fm = currentProgram.getFunctionManager()
|
||||
f = fm.getFunctionAt(addr)
|
||||
# 重命名/标注(USER_DEFINED 才进脚本可回滚)
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
f.setName("decrypt_buf", SourceType.USER_DEFINED)
|
||||
listing = currentProgram.getListing()
|
||||
listing.setComment(addr, listing.PLATE_COMMENT, "decrypt loop start")
|
||||
# 字节批量读写(比 getByte/setByte 逐字节快)
|
||||
mem = currentProgram.getMemory()
|
||||
buf = bytearray(0x100)
|
||||
mem.getBytes(addr, buf)
|
||||
mem.setBytes(addr, buf)
|
||||
# 反编译单函数
|
||||
from ghidra.app.decompiler import DecompInterface
|
||||
dec = DecompInterface()
|
||||
dec.openProgram(currentProgram)
|
||||
res = dec.decompileFunction(f, 60, monitor) # 超时给足
|
||||
print(res.getDecompiledFunction().getC())
|
||||
# 遍历函数
|
||||
for g in fm.getFunctions(True):
|
||||
print(g.getName(), hex(g.getEntryPoint().getOffset()))
|
||||
```
|
||||
|
||||
## 操作序列(组合套路)
|
||||
|
||||
### 1. 无头批量反编译导出流水线(多样本)
|
||||
|
||||
```
|
||||
先写收集脚本 CollectC.java(遍历函数 → decompileFunction → 写 .c 文件)
|
||||
analyzeHeadless /tmp/p name -import dir/ -recursive \
|
||||
-postScript CollectC.java -scriptPath /tmp/s -deleteProject -log l.log
|
||||
产物按样本名归档,脚本日志看 -scriptlog(与 -log 分开)
|
||||
```
|
||||
|
||||
### 2. 解密循环脚本化(XOR 例程直接出明文)
|
||||
|
||||
```
|
||||
静态定位解密循环([[re-crypto-decrypt]])→ 确认异或常量与区间
|
||||
脚本: mem.getBytes 读出密文 → python 内 xor → 写回或导出新文件
|
||||
验证: 导出明文 sha256 与沙箱实跑结果对照([[re-sandbox]])
|
||||
```
|
||||
|
||||
### 3. 大型二进制分段展开(防自动分析卡死)
|
||||
|
||||
```
|
||||
先 -noanalysis 导入(秒级)→ 脚本拿符号表/字符串/入口点
|
||||
对关键区段单独跑小范围分析(或 GUI 内关掉 RTTI/间接调用选项)
|
||||
单函数反编译推进,逐步扩大——见 [[gotchas]] 大文件分析组
|
||||
```
|
||||
|
||||
### 4. ghidra-bridge 远程驱动(Python 3 环境)
|
||||
|
||||
```
|
||||
GUI 内 Script Manager 跑 ghidra_bridge_server.py(打印端口)
|
||||
外部: b = ghidra_bridge.GhidraBridge(); b.remote_scope(); b(getFunctionManager())
|
||||
适合 numpy/现代 Python 能力,脚本与 GUI 会话同步(改标注即时可见)
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 批量字节操作用 `mem.getBytes/setBytes`,别 `getByte/setByte` 逐字节(大段慢一个数量级)
|
||||
- 内置解释器是 Jython 2.7:用 `xrange`、字符串不用 f-string;要 Python 3 能力就上 ghidra-bridge
|
||||
- `decompileFunction` 第二个参数是超时秒数,大函数给 60+,返回 `res.decompileCompleted()` 判断失败
|
||||
- 地址算术用 `addr.add(i)` / `addr.subtract(i)`,不能直接 `addr + i`
|
||||
- 标注来源用 `SourceType.USER_DEFINED`(可区分于分析器自动标注,回滚安全)
|
||||
- 脚本异常先看 `-scriptlog`,headless 的 print 只进脚本日志不进 `-log`
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态分析免沙箱([[re-analyze/platform-tips]] 静态优先);样本 sha256 与导出产物存档([[re-triage]])
|
||||
- 结论写入 [[re-analyze/analysis-contract]];脚本与工程按版本锁存([[gotchas]] 版本组)
|
||||
@@ -0,0 +1,38 @@
|
||||
# Ghidra 工具特有坑与边界
|
||||
|
||||
## 版本差异(JDK / API / 构建链)
|
||||
|
||||
- **JDK 版本**:11.3+ 运行官方 release 需 JDK 21(装错报 `UnsupportedClassVersionError`);从源码构建 Ghidra 自身需 JDK 25 + Gradle 9.1+(与"运行"是两套要求,别混)
|
||||
- **12.x 破坏性 API 变更**:函数管理类等有迁移(11.x 起持续发生)——写脚本前先查目标版本 javadoc,编译失败先怀疑 API 差异而非业务逻辑
|
||||
- **MemoryBlock 权限 API 版本间方法名不同**(如 12.x 的 `isExecute()` 与旧版命名差异):写内存段权限判断脚本时以小验证脚本确认当前版本方法名
|
||||
- **GhidraDev/Eclipse 版本配套**:GhidraDev 插件版本与 Eclipse 版本有对应表,装不上先在官方 release notes 核对组合
|
||||
- **扩展兼容范围**:Java 扩展的 extension.properties 声明适用的 Ghidra 版本范围,装到范围外版本会被拒载(见 [[re-plugin-dev]])
|
||||
|
||||
## 无头批处理坑
|
||||
|
||||
- **headless 与 GUI 分析选项有差异**:默认分析选项集不完全一致——保证一致性用 `-postScript` 显式执行分析脚本
|
||||
- **工程锁冲突**:同一工程同时被 GUI 与 headless 打开报锁(File Lock)——headless 用独立工程目录或先关 GUI
|
||||
- **-import 与 -process 互斥**:重跑已导入文件用 `-process`,重复导入需 `-overwrite`(否则跳过已存在文件)
|
||||
- **两个日志别混**:`-log` 是运行日志,`-scriptlog` 是脚本 print/异常——脚本没输出先看后者
|
||||
- **路径含空格**:`-scriptPath` 多路径分号分隔且整体加引号;参数值含空格同样引起来
|
||||
|
||||
## 脚本 / API 坑
|
||||
|
||||
- **Jython 2.7 限制**:无 f-string/海象运算,无法 import pip 包;`SyntaxError`/`ImportError` 都是这个原因——现代 Python 需求走 ghidra-bridge
|
||||
- **大循环慢**:getByte/setByte 逐字节在几 MB 区间上是数量级灾难,改 mem.getBytes/setBytes
|
||||
- **地址字符串**:脚本里 `"0x401000"` 需经 `getAddressFactory().getDefaultAddressSpace().getAddress()`,直接 `currentAddress` 之外写 `"401000"` 会歧义
|
||||
- **脚本崩溃不留痕**:headless 下脚本异常进 `-scriptlog` 但可能不影响退出码——批处理脚本结尾主动打印成功标志
|
||||
- **中文输出乱码**:print 中文在部分终端乱码是编码问题,脚本内先处理字符编码或输出 ASCII 摘要
|
||||
|
||||
## 分析结果坑
|
||||
|
||||
- **自动分析漏混淆代码**:花指令/乱序段未反汇编——手动选段 `C` 强制标记,必要时 `Create Function` 修边界
|
||||
- **反编译假象**:手写汇编/尾调用/无 frame pointer 会让函数边界与帧推断出错——以 Listing 汇编语义为准,不盲信反编译 C
|
||||
- **大文件分析卡死**:间接调用爆炸/大型 C++ RTTI/混淆控制流——先降自动分析范围(限/关间接调用与 RTTI 选项),`-analysisTimeoutPerFile` 兜底
|
||||
- **-noanalysis 的能力边界**:跳过自动分析后无函数/反编译,但符号(含 JNI 导出)与字符串扫描可用——单函数反编译仍要先跑局部分析
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态分析免沙箱;内存 <4GB 换 [[re-radare2]](启动即卡顿)
|
||||
- 样本/工程/导出产物 sha256 存证([[re-triage]]);结论入 [[re-analyze/analysis-contract]]
|
||||
- 版本相关行为(API 方法名、分析选项)以目标版本实际表现为准
|
||||
Reference in New Issue
Block a user