diff --git a/docs/index.md b/docs/index.md index f3d8444b0d..04da816f05 100644 --- a/docs/index.md +++ b/docs/index.md @@ -50,7 +50,7 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md). | Task | Guides | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) | -| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [PE certificates](pe-certificates.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) | +| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [PE certificates](pe-certificates.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) | | Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) | | Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) | | Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) | diff --git a/docs/pe-certificates.md b/docs/pe-certificates.md index e4c3723807..eda3f3ff0f 100644 --- a/docs/pe-certificates.md +++ b/docs/pe-certificates.md @@ -39,7 +39,11 @@ image is unsigned in every catalog or overlay sense. The selected file is limited to 64 MiB by default (512 MiB maximum). At most eight `WIN_CERTIFICATE` entries are examined by default. Malformed PE headers, truncated certificate entries, and PKCS#7 that is not structurally signedData -fail without partial success. +fail without partial success. A security directory that ends in fewer than +eight header bytes, including zeros, is a truncated `WIN_CERTIFICATE` and +fails the same way. SignedData must contain at least one `SignerInfo`, and +each `SignerInfo` must carry a non-empty signature value. Decoded certificate +name text shares a 64 KiB budget per PKCS#7 payload. This workflow does not verify signatures, certificate chains, timestamps, or Authenticode digest coverage of image bytes. diff --git a/docs/readme/README_ko.md b/docs/readme/README_ko.md index ac0fa11ad1..57ec8c547b 100644 --- a/docs/readme/README_ko.md +++ b/docs/readme/README_ko.md @@ -153,7 +153,7 @@ REA에는 Node.js 22.x(>=22.19), 24.x(>=24.11) 또는 26+와 npm이 필요합니 | 오프라인 ELF 레이아웃 | 섹션, 세그먼트, 원본 심볼/재배치 정보, 정적 분석으로 얻은 보안 완화 기법 후보 | Linux x64에서 호출자가 제공하는 pwntools; [바이너리 진단](../binary-diagnostics.md) | | Go 바이너리 | 내장된 컴파일러 버전, 모듈, `replace` 지시어, 빌드 설정 | REA 내장 ELF/PE/thin Mach-O 정적 검사; Go 설치 불필요 | | Rust 바이너리 | cargo-auditable이 내장한 크레이트 이름, 버전, 소스, 의존성 관계 | REA 내장 ELF/PE/thin Mach-O 정적 검사; [Rust 바이너리](../rust-binaries.md) | -| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | +| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | | WASM 아티팩트 | 유효성 검사, 섹션, import/export의 정확한 WAT 형식, 디코딩된 WAT | 호출자 제공 WABT 1.0.42; `inspect_wasm_artifact` | | EVM 바이트코드 | 디스패치 선택자, 바이트 오프셋, 추론한 인자와 상태 변경 가능 여부 | Linux x64의 로컬 원시 바이트/16진수 입력 파일; [오프라인 EVM 가이드](../evm-bytecode.md) | | SQLite 데이터베이스 | 테이블, 열, 인덱스, 뷰, 트리거, 선택한 일반 테이블의 행 | 로컬 데이터베이스/WAL 스냅샷; Node.js 24.15 이상의 24.x 또는 26 이상(네이티브 SQLite 한도 적용) | diff --git a/docs/readme/README_ru.md b/docs/readme/README_ru.md index 3d7ddc9201..bb8a78d88f 100644 --- a/docs/readme/README_ru.md +++ b/docs/readme/README_ru.md @@ -145,7 +145,7 @@ REA требует Node.js 22.x (>=22.19), 24.x (>=24.11) или 26+, а так | Структура ELF без запуска | Секции, сегменты, исходные символы/релокации и возможные механизмы защиты по данным статического анализа | pwntools, предоставленный вызывающей стороной, в Linux x64; [диагностика бинарных файлов](../binary-diagnostics.md) | | Байт-код EVM | Селекторы диспетчеризации, смещения байтов, предполагаемые аргументы и изменяемость состояния | Локальные необработанные байты или шестнадцатеричное представление в Linux x64; [руководство по EVM без запуска](../evm-bytecode.md) | | Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) | -| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | +| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | | Записанные сбои Linux | Необработанные записи note, регистры/сигналы каждого записанного потока и необязательные кандидаты сопоставления с памятью | pwntools, предоставленный вызывающей стороной, в Linux x64; необязательные GDB/pwndbg; [записанные сбои](../recorded-crashes.md) | | Установленные настольные приложения | Возможные среды выполнения, файлы с доказательствами, компоненты и дальнейшие шаги | Ограниченная проверка каталогов и заголовков;[Настольные приложения](../desktop-applications.md) | | JavaScript / Electron | Модули, импорты, карты исходного кода, маршруты, IPC и связи с нативными дополнениями | Node.js и npm; [анализ приложений](../javascript-application-workflows.md) | diff --git a/docs/readme/README_vi.md b/docs/readme/README_vi.md index 87251df4ae..b14d2cabaf 100644 --- a/docs/readme/README_vi.md +++ b/docs/readme/README_vi.md @@ -145,7 +145,7 @@ REA yêu cầu Node.js 22.x (>=22.19), 24.x (>=24.11) hoặc 26+, cùng với np | Bố cục ELF ngoại tuyến | Các section, segment, ký hiệu/thông tin tái định vị gốc và các biện pháp giảm thiểu có thể có từ phân tích tĩnh | pwntools do bên gọi cung cấp trên Linux x64; [chẩn đoán tệp nhị phân](../binary-diagnostics.md) | | Bytecode EVM | Bộ chọn điều phối, độ lệch byte, tham số suy luận và khả năng thay đổi trạng thái | Đầu vào cục bộ chứa byte thô hoặc dạng thập lục phân trên Linux x64; [hướng dẫn EVM ngoại tuyến](../evm-bytecode.md) | | Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) | -| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | +| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | | Sự cố Linux đã ghi lại | Bản ghi note thô, thanh ghi/tín hiệu của mọi luồng được ghi lại và các ứng viên ánh xạ tùy chọn | pwntools do bên gọi cung cấp trên Linux x64; GDB/pwndbg tùy chọn; [sự cố đã ghi lại](../recorded-crashes.md) | | Thư mục cài đặt ứng dụng máy tính | Các runtime có khả năng được dùng, tệp bằng chứng, dấu hiệu thành phần và bước tiếp theo | Kiểm tra thư mục và phần đầu tệp có giới hạn;[Ứng dụng máy tính](../desktop-applications.md) | | JavaScript / Electron | Mô-đun, các khai báo import, source map, tuyến, IPC và quan hệ với tiện ích mở rộng mã máy | Node.js và npm; [phân tích ứng dụng](../javascript-application-workflows.md) | diff --git a/docs/readme/README_zh-TW.md b/docs/readme/README_zh-TW.md index 0d71c0b02a..5bca9aa5e7 100644 --- a/docs/readme/README_zh-TW.md +++ b/docs/readme/README_zh-TW.md @@ -141,33 +141,33 @@ AI 代理通過 MCP 呼叫 REA,檢查目標並追蹤相關程式碼。REA 返 REA 需要 Node.js 22.x(>=22.19)、24.x(>=24.11)或 26+,以及 npm。額外工具和主機支援取決於分析目標: -| 目標 | REA 返回的內容 | 要求與指南 | -| --------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| 原生二進位檔案 | 偽程式碼、組合語言、字串、符號、呼叫和引用 | Hopper、Ghidra 或 IDA;[原生分析](../native-investigation.md) | -| 離線 ELF 佈局 | 節、段、原始符號/重定位資訊及靜態防護機制候選項 | Linux x64 上由呼叫方提供的 pwntools;[二進位診斷](../binary-diagnostics.md) | -| Go 二進位檔案 | 內嵌編譯器版本、模組、替換項和建置設定 | 內建靜態 ELF/PE/thin Mach-O 檢查;無需安裝 Go | -| WASM 工件 | 驗證、節、精確的匯入/匯出 WAT 形式及解碼後的 WAT | 呼叫方提供的 WABT 1.0.42;`inspect_wasm_artifact` | -| EVM 位元組碼 | 分派選擇器、位元組偏移、推斷的參數和狀態可變性 | Linux x64 上的本地原始位元組/十六進位輸入載體;[離線 EVM 指南](../evm-bytecode.md) | -| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) | +| 目標 | REA 返回的內容 | 要求與指南 | +| --------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| 原生二進位檔案 | 偽程式碼、組合語言、字串、符號、呼叫和引用 | Hopper、Ghidra 或 IDA;[原生分析](../native-investigation.md) | +| 離線 ELF 佈局 | 節、段、原始符號/重定位資訊及靜態防護機制候選項 | Linux x64 上由呼叫方提供的 pwntools;[二進位診斷](../binary-diagnostics.md) | +| Go 二進位檔案 | 內嵌編譯器版本、模組、替換項和建置設定 | 內建靜態 ELF/PE/thin Mach-O 檢查;無需安裝 Go | +| WASM 工件 | 驗證、節、精確的匯入/匯出 WAT 形式及解碼後的 WAT | 呼叫方提供的 WABT 1.0.42;`inspect_wasm_artifact` | +| EVM 位元組碼 | 分派選擇器、位元組偏移、推斷的參數和狀態可變性 | Linux x64 上的本地原始位元組/十六進位輸入載體;[離線 EVM 指南](../evm-bytecode.md) | +| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) | | PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | -| 已記錄的 Linux 崩潰 | 原始 note 記錄、每個已記錄執行緒的暫存器/訊號及可選的對映候選項 | Linux x64 上由呼叫方提供的 pwntools;可選的 GDB/pwndbg;[已記錄崩潰](../recorded-crashes.md) | -| 桌面安裝目錄 | 執行階段候選、支持檔案、元件標記與後續操作 | 有界的目錄與檔頭檢查;[桌面應用](../desktop-applications.md) | -| JavaScript / Electron | 模組、匯入、source map、路由、IPC 和原生附加元件關係 | Node.js 和 npm;[應用分析](../javascript-application-workflows.md) | -| 網站 | 頁面結構、腳本、網路觀察結果和按請求獲取的螢幕擷取畫面 | Chrome 系瀏覽器;[瀏覽器分析](../browser-observation.md) | -| 已保存的網路擷取 | 請求、回應、可訪問的酬載和來源位置 | HAR;原生 mitmproxy 擷取需要 Linux 上的 mitmdump;[擷取指南](../web-network-captures.md) | -| 封包擷取 | 端點、DNS、TLS SNI、通訊協定分佈和 HTTP 請求 | 呼叫端提供的 tshark 和 capinfos;[pcap 指南](../pcap-analysis.md) | -| .NET 程式集 | 中繼資料、CIL 指令、聲明的原生依賴和建置對比 | 靜態檢查;[托管程式碼指南](../managed-code-analysis.md) | -| PyInstaller | CArchive, PYZ, Python bytecode | Static inspection; [Python packaged applications](../python-packaged-applications.md) | -| Android APK | 清單聲明、類別、反編譯的方法和引用 | Linux/macOS/Windows x64 上的無界面 JADX 和完整 JDK;[Android 指南](../android-analysis.md) | -| Android 裝置 | 裝置、套件、程序、日誌、傾印、螢幕擷取以及 APK/檔案傳輸 | 呼叫端提供的 adb(模擬器或實體裝置);[ADB 指南](../adb-device-analysis.md) | -| JEB 專案 | 引擎支援的專案單元、成品摘要和型別/方法的虛擬碼 | 由呼叫端啟動、提供 MCP 的 JEB 用戶端,透過 REA_JEB_MCP_URL 選擇;[JEB 指南](../jeb-analysis.md) | -| Android 資源 | 解碼後的清單、字串表、語系和版本資訊 | 呼叫端提供的 Apktool 啟動器;[資源指南](../apktool-resource-analysis.md) | -| Flutter 應用 | Dart 快照雜湊、依 ABI 的程式庫雜湊及引擎建置來源 | 無(靜態 APK 解析);[Flutter 指南](../flutter-analysis.md) | -| 韌體 | 區域、提取結果和轉交原生分析的內容 | Linux 上的 Binwalk / Unblob;[韌體指南](../firmware-analysis.md) | -| 封包記錄 | 指定欄位、重複值、篩選條件與解碼規則 | Linux: TShark 4.x + prlimit; [封包分析指南](../packet-captures.md) | -| 錄製的邏輯訊號 | 解碼註解、樣本範圍與診斷資訊 | Linux: sigrok-cli 0.7.2 + prlimit; [訊號記錄指南](../signal-recordings.md) | -| 軟體包與資源 | 檔案清單、摘要、plist、Apple bundle 結構和提取的資源 | [製品與 JavaScript 指南](../javascript-artifact-reconstruction.md)、[Apple 應用](../apple-application-analysis.md) | -| 處理程序行為 | 終端輸出、互動、退出和檔案系統觀察結果,以及執行對比 | 支援原生 PTY 的 Linux/macOS;[處理程序擷取](../process-capture.md) | +| 已記錄的 Linux 崩潰 | 原始 note 記錄、每個已記錄執行緒的暫存器/訊號及可選的對映候選項 | Linux x64 上由呼叫方提供的 pwntools;可選的 GDB/pwndbg;[已記錄崩潰](../recorded-crashes.md) | +| 桌面安裝目錄 | 執行階段候選、支持檔案、元件標記與後續操作 | 有界的目錄與檔頭檢查;[桌面應用](../desktop-applications.md) | +| JavaScript / Electron | 模組、匯入、source map、路由、IPC 和原生附加元件關係 | Node.js 和 npm;[應用分析](../javascript-application-workflows.md) | +| 網站 | 頁面結構、腳本、網路觀察結果和按請求獲取的螢幕擷取畫面 | Chrome 系瀏覽器;[瀏覽器分析](../browser-observation.md) | +| 已保存的網路擷取 | 請求、回應、可訪問的酬載和來源位置 | HAR;原生 mitmproxy 擷取需要 Linux 上的 mitmdump;[擷取指南](../web-network-captures.md) | +| 封包擷取 | 端點、DNS、TLS SNI、通訊協定分佈和 HTTP 請求 | 呼叫端提供的 tshark 和 capinfos;[pcap 指南](../pcap-analysis.md) | +| .NET 程式集 | 中繼資料、CIL 指令、聲明的原生依賴和建置對比 | 靜態檢查;[托管程式碼指南](../managed-code-analysis.md) | +| PyInstaller | CArchive, PYZ, Python bytecode | Static inspection; [Python packaged applications](../python-packaged-applications.md) | +| Android APK | 清單聲明、類別、反編譯的方法和引用 | Linux/macOS/Windows x64 上的無界面 JADX 和完整 JDK;[Android 指南](../android-analysis.md) | +| Android 裝置 | 裝置、套件、程序、日誌、傾印、螢幕擷取以及 APK/檔案傳輸 | 呼叫端提供的 adb(模擬器或實體裝置);[ADB 指南](../adb-device-analysis.md) | +| JEB 專案 | 引擎支援的專案單元、成品摘要和型別/方法的虛擬碼 | 由呼叫端啟動、提供 MCP 的 JEB 用戶端,透過 REA_JEB_MCP_URL 選擇;[JEB 指南](../jeb-analysis.md) | +| Android 資源 | 解碼後的清單、字串表、語系和版本資訊 | 呼叫端提供的 Apktool 啟動器;[資源指南](../apktool-resource-analysis.md) | +| Flutter 應用 | Dart 快照雜湊、依 ABI 的程式庫雜湊及引擎建置來源 | 無(靜態 APK 解析);[Flutter 指南](../flutter-analysis.md) | +| 韌體 | 區域、提取結果和轉交原生分析的內容 | Linux 上的 Binwalk / Unblob;[韌體指南](../firmware-analysis.md) | +| 封包記錄 | 指定欄位、重複值、篩選條件與解碼規則 | Linux: TShark 4.x + prlimit; [封包分析指南](../packet-captures.md) | +| 錄製的邏輯訊號 | 解碼註解、樣本範圍與診斷資訊 | Linux: sigrok-cli 0.7.2 + prlimit; [訊號記錄指南](../signal-recordings.md) | +| 軟體包與資源 | 檔案清單、摘要、plist、Apple bundle 結構和提取的資源 | [製品與 JavaScript 指南](../javascript-artifact-reconstruction.md)、[Apple 應用](../apple-application-analysis.md) | +| 處理程序行為 | 終端輸出、互動、退出和檔案系統觀察結果,以及執行對比 | 支援原生 PTY 的 Linux/macOS;[處理程序擷取](../process-capture.md) | 靜態 JavaScript 和 .NET 檢查讀取提供的檔案,不執行應用。執行階段擷取會以你的使用者權限執行目標或與之互動;各執行階段指南說明瞭具體影響。 diff --git a/docs/readme/README_zh.md b/docs/readme/README_zh.md index dbb127099f..0a8ff88076 100644 --- a/docs/readme/README_zh.md +++ b/docs/readme/README_zh.md @@ -141,33 +141,33 @@ npx rea-agents@latest setup REA 需要 Node.js 22.x(>=22.19)、24.x(>=24.11)或 26+,以及 npm。额外工具和主机支持取决于分析目标: -| 目标 | REA 返回的内容 | 要求与指南 | -| --------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| 原生二进制文件 | 伪代码、汇编、字符串、符号、调用和引用 | Hopper、Ghidra 或 IDA;[原生分析](../native-investigation.md) | -| 离线 ELF 布局 | 节、段、原始符号/重定位信息及静态防护机制候选项 | Linux x64 上由调用方提供的 pwntools;[二进制诊断](../binary-diagnostics.md) | -| Go 二进制文件 | 内嵌编译器版本、模块、替换项和构建设置 | 内置静态 ELF/PE/thin Mach-O 检查;无需安装 Go | -| WASM 工件 | 验证、节、精确的导入/导出 WAT 形式及解码后的 WAT | 调用方提供的 WABT 1.0.42;`inspect_wasm_artifact` | -| EVM 字节码 | 分派选择器、字节偏移、推断的参数和状态可变性 | Linux x64 上的本地原始字节/十六进制输入载体;[离线 EVM 指南](../evm-bytecode.md) | -| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) | +| 目标 | REA 返回的内容 | 要求与指南 | +| --------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| 原生二进制文件 | 伪代码、汇编、字符串、符号、调用和引用 | Hopper、Ghidra 或 IDA;[原生分析](../native-investigation.md) | +| 离线 ELF 布局 | 节、段、原始符号/重定位信息及静态防护机制候选项 | Linux x64 上由调用方提供的 pwntools;[二进制诊断](../binary-diagnostics.md) | +| Go 二进制文件 | 内嵌编译器版本、模块、替换项和构建设置 | 内置静态 ELF/PE/thin Mach-O 检查;无需安装 Go | +| WASM 工件 | 验证、节、精确的导入/导出 WAT 形式及解码后的 WAT | 调用方提供的 WABT 1.0.42;`inspect_wasm_artifact` | +| EVM 字节码 | 分派选择器、字节偏移、推断的参数和状态可变性 | Linux x64 上的本地原始字节/十六进制输入载体;[离线 EVM 指南](../evm-bytecode.md) | +| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) | | PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) | -| 已记录的 Linux 崩溃 | 原始 note 记录、每个已记录线程的寄存器/信号及可选的映射候选项 | Linux x64 上由调用方提供的 pwntools;可选的 GDB/pwndbg;[已记录崩溃](../recorded-crashes.md) | -| 桌面安装目录 | 运行时候选、支持文件、组件标记和后续操作 | 有界的目录与文件头检查;[桌面应用](../desktop-applications.md) | -| JavaScript / Electron | 模块、导入、source map、路由、IPC 和原生扩展关系 | Node.js 和 npm;[应用分析](../javascript-application-workflows.md) | -| 网站 | 页面结构、脚本、网络观察结果和按请求获取的截图 | Chrome 系浏览器;[浏览器分析](../browser-observation.md) | -| 已保存的网络捕获 | 请求、响应、可访问的载荷和来源位置 | HAR;原生 mitmproxy 捕获需要 Linux 上的 mitmdump;[捕获指南](../web-network-captures.md) | -| 数据包捕获 | 端点、DNS、TLS SNI、协议分布和 HTTP 请求 | 调用方提供的 tshark 和 capinfos;[pcap 指南](../pcap-analysis.md) | -| .NET 程序集 | 元数据、CIL 指令、声明的原生依赖和构建对比 | 静态检查;[托管代码指南](../managed-code-analysis.md) | -| PyInstaller | CArchive, PYZ, Python bytecode | Static inspection; [Python packaged applications](../python-packaged-applications.md) | -| Android APK | 清单声明、类、反编译的方法和引用 | Linux/macOS/Windows x64 上的无界面 JADX 和完整 JDK;[Android 指南](../android-analysis.md) | -| Android 设备 | 设备、软件包、进程、日志、转储、屏幕截图以及 APK/文件传输 | 调用方提供的 adb(模拟器或实体设备);[ADB 指南](../adb-device-analysis.md) | -| JEB 项目 | 引擎支持的项目单元、工件摘要和类型/方法的伪代码 | 由调用方启动、提供 MCP 的 JEB 客户端,通过 REA_JEB_MCP_URL 选择;[JEB 指南](../jeb-analysis.md) | -| Android 资源 | 解码后的清单、字符串表、语言区域和版本信息 | 调用方提供的 Apktool 启动器;[资源指南](../apktool-resource-analysis.md) | -| Flutter 应用 | Dart 快照哈希、按 ABI 的库摘要及引擎构建溯源 | 无(静态 APK 解析);[Flutter 指南](../flutter-analysis.md) | -| 固件 | 区域、提取结果和转交原生分析的内容 | Linux 上的 Binwalk / Unblob;[固件指南](../firmware-analysis.md) | -| 抓包文件 | 指定字段、重复值、过滤条件与解码规则 | Linux: TShark 4.x + prlimit; [抓包分析指南](../packet-captures.md) | -| 录制的逻辑信号 | 解码注释、样本范围和诊断信息 | Linux: sigrok-cli 0.7.2 + prlimit; [信号记录指南](../signal-recordings.md) | -| 软件包与资源 | 文件清单、摘要、plist、Apple bundle 结构和提取的资源 | [制品与 JavaScript 指南](../javascript-artifact-reconstruction.md)、[Apple 应用](../apple-application-analysis.md) | -| 进程行为 | 终端输出、交互、退出和文件系统观察结果,以及运行对比 | 支持原生 PTY 的 Linux/macOS;[进程捕获](../process-capture.md) | +| 已记录的 Linux 崩溃 | 原始 note 记录、每个已记录线程的寄存器/信号及可选的映射候选项 | Linux x64 上由调用方提供的 pwntools;可选的 GDB/pwndbg;[已记录崩溃](../recorded-crashes.md) | +| 桌面安装目录 | 运行时候选、支持文件、组件标记和后续操作 | 有界的目录与文件头检查;[桌面应用](../desktop-applications.md) | +| JavaScript / Electron | 模块、导入、source map、路由、IPC 和原生扩展关系 | Node.js 和 npm;[应用分析](../javascript-application-workflows.md) | +| 网站 | 页面结构、脚本、网络观察结果和按请求获取的截图 | Chrome 系浏览器;[浏览器分析](../browser-observation.md) | +| 已保存的网络捕获 | 请求、响应、可访问的载荷和来源位置 | HAR;原生 mitmproxy 捕获需要 Linux 上的 mitmdump;[捕获指南](../web-network-captures.md) | +| 数据包捕获 | 端点、DNS、TLS SNI、协议分布和 HTTP 请求 | 调用方提供的 tshark 和 capinfos;[pcap 指南](../pcap-analysis.md) | +| .NET 程序集 | 元数据、CIL 指令、声明的原生依赖和构建对比 | 静态检查;[托管代码指南](../managed-code-analysis.md) | +| PyInstaller | CArchive, PYZ, Python bytecode | Static inspection; [Python packaged applications](../python-packaged-applications.md) | +| Android APK | 清单声明、类、反编译的方法和引用 | Linux/macOS/Windows x64 上的无界面 JADX 和完整 JDK;[Android 指南](../android-analysis.md) | +| Android 设备 | 设备、软件包、进程、日志、转储、屏幕截图以及 APK/文件传输 | 调用方提供的 adb(模拟器或实体设备);[ADB 指南](../adb-device-analysis.md) | +| JEB 项目 | 引擎支持的项目单元、工件摘要和类型/方法的伪代码 | 由调用方启动、提供 MCP 的 JEB 客户端,通过 REA_JEB_MCP_URL 选择;[JEB 指南](../jeb-analysis.md) | +| Android 资源 | 解码后的清单、字符串表、语言区域和版本信息 | 调用方提供的 Apktool 启动器;[资源指南](../apktool-resource-analysis.md) | +| Flutter 应用 | Dart 快照哈希、按 ABI 的库摘要及引擎构建溯源 | 无(静态 APK 解析);[Flutter 指南](../flutter-analysis.md) | +| 固件 | 区域、提取结果和转交原生分析的内容 | Linux 上的 Binwalk / Unblob;[固件指南](../firmware-analysis.md) | +| 抓包文件 | 指定字段、重复值、过滤条件与解码规则 | Linux: TShark 4.x + prlimit; [抓包分析指南](../packet-captures.md) | +| 录制的逻辑信号 | 解码注释、样本范围和诊断信息 | Linux: sigrok-cli 0.7.2 + prlimit; [信号记录指南](../signal-recordings.md) | +| 软件包与资源 | 文件清单、摘要、plist、Apple bundle 结构和提取的资源 | [制品与 JavaScript 指南](../javascript-artifact-reconstruction.md)、[Apple 应用](../apple-application-analysis.md) | +| 进程行为 | 终端输出、交互、退出和文件系统观察结果,以及运行对比 | 支持原生 PTY 的 Linux/macOS;[进程捕获](../process-capture.md) | 静态 JavaScript 和 .NET 检查读取提供的文件,不运行应用。运行时捕获会以你的用户权限运行目标或与之交互;各运行时指南说明了具体影响。 diff --git a/src/cli/binaryDiagnosticsCommands.ts b/src/cli/binaryDiagnosticsCommands.ts index 0ee7c74611..205575eb46 100644 --- a/src/cli/binaryDiagnosticsCommands.ts +++ b/src/cli/binaryDiagnosticsCommands.ts @@ -52,6 +52,7 @@ export const registerBinaryDiagnosticsCommands = ( pe, "rea-pe-certificates", "inspect_pe_certificates", + signal, ), ); }), diff --git a/src/native/pe/PeCertificateDer.ts b/src/native/pe/PeCertificateDer.ts index abb9da9b33..0fee691907 100644 --- a/src/native/pe/PeCertificateDer.ts +++ b/src/native/pe/PeCertificateDer.ts @@ -113,6 +113,22 @@ export const oidName = (oid: string): string | null => OID_NAMES[oid] ?? null; export const MAX_OID_BYTES = 256; export const MAX_OID_ARC_BYTES = 32; +// One PKCS#7 payload shares this ceiling. Real issuer and subject names are +// far smaller; the cap stops one wide string from occupying the file budget. +export const MAX_DECODED_NAME_BYTES = 65_536; + +interface DecodedNameBudget { + remaining: number; +} + +const chargeDecodedName = (budget: DecodedNameBudget, bytes: number): void => { + if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > budget.remaining) + fail( + `Certificate name text exceeds the ${String(MAX_DECODED_NAME_BYTES)}-byte decoded budget.`, + ); + budget.remaining -= bytes; +}; + /** Decode an OBJECT IDENTIFIER content buffer as dotted decimal. */ export const decodeOid = (content: Buffer): string => { if (content.length === 0) return fail("Empty OBJECT IDENTIFIER."); @@ -144,41 +160,64 @@ export const decodeOid = (content: Buffer): string => { return [first, combined - first * 40n, ...rest].join("."); }; -const decodeUtf16Be = (bytes: Buffer): string => { +const decodeUtf16Be = (bytes: Buffer, budget: DecodedNameBudget): string => { if (bytes.length % 2 !== 0) return fail("BMPString length is odd."); - let text = ""; - for (let index = 0; index < bytes.length; index += 2) - text += String.fromCharCode(bytes.readUInt16BE(index)); - return text; + // Charge before the copy. Appending one character at a time retains a + // cons-string chain of about sixteen heap bytes per source byte. + chargeDecodedName(budget, bytes.length); + if (bytes.length === 0) return ""; + const decoded = Buffer.allocUnsafe(bytes.length); + for (let index = 0; index < bytes.length; index += 2) { + decoded[index] = bytes[index + 1] ?? 0; + decoded[index + 1] = bytes[index] ?? 0; + } + return decoded.toString("utf16le"); }; -const decodeUtf32Be = (bytes: Buffer): string => { +const decodeUtf32Be = (bytes: Buffer, budget: DecodedNameBudget): string => { if (bytes.length % 4 !== 0) return fail("UniversalString length is not a multiple of 4."); - let text = ""; + // Four source bytes become at most four UTF-16 bytes, so the source length + // bounds the decoded string before any code point is copied. + chargeDecodedName(budget, bytes.length); + const decoded = Buffer.allocUnsafe(bytes.length); + let at = 0; for (let index = 0; index < bytes.length; index += 4) { const codePoint = bytes.readUInt32BE(index); if (codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) return fail("UniversalString holds an invalid code point."); - text += String.fromCodePoint(codePoint); + if (codePoint > 0xffff) { + const shifted = codePoint - 0x10000; + decoded.writeUInt16LE(0xd800 + (shifted >> 10), at); + decoded.writeUInt16LE(0xdc00 + (shifted & 0x3ff), at + 2); + at += 4; + } else { + decoded.writeUInt16LE(codePoint, at); + at += 2; + } } - return text; + return decoded.subarray(0, at).toString("utf16le"); }; -const decodePrintable = (node: DerNode): string => { +const decodeDirectoryString = ( + node: DerNode, + budget: DecodedNameBudget, +): string => { switch (node.tag) { case 0x1c: - return decodeUtf32Be(node.content); + return decodeUtf32Be(node.content, budget); + case 0x1e: + return decodeUtf16Be(node.content, budget); case 0x0c: case 0x13: case 0x16: case 0x17: case 0x18: case 0x1b: + chargeDecodedName(budget, node.content.length); return node.content.toString("utf8"); - case 0x1e: - return decodeUtf16Be(node.content); default: + chargeDecodedName(budget, node.content.length); return node.content.toString("latin1"); } }; @@ -210,6 +249,7 @@ interface Attribute { const decodeName = ( node: DerNode, + budget: DecodedNameBudget, ): { readonly text: string; readonly attributes: readonly Attribute[]; @@ -231,7 +271,7 @@ const decodeName = ( attributes.push({ oid, oid_name: oidName(oid), - value: decodePrintable(valueNode), + value: decodeDirectoryString(valueNode, budget), }); } } @@ -247,7 +287,7 @@ const decodeName = ( const validityTime = (node: DerNode): string => { if (node.tag !== 0x17 && node.tag !== 0x18) return fail("Validity instant is not UTCTime or GeneralizedTime."); - return decodePrintable(node); + return node.content.toString("utf8"); }; export interface ParsedCertificate { @@ -262,7 +302,10 @@ export interface ParsedCertificate { } /** Decode one X.509 Certificate SEQUENCE at a known file-relative DER node. */ -export const parseCertificate = (node: DerNode): ParsedCertificate => { +export const parseCertificate = ( + node: DerNode, + names: DecodedNameBudget = { remaining: MAX_DECODED_NAME_BYTES }, +): ParsedCertificate => { const children = sequence(node, "Certificate"); const tbs = children[0]; const signatureAlgorithm = children[1]; @@ -294,8 +337,8 @@ export const parseCertificate = (node: DerNode): ParsedCertificate => { start: node.start, end: node.end, serial_hex: integerHex(serial.content), - issuer: decodeName(issuer), - subject: decodeName(subject), + issuer: decodeName(issuer, names), + subject: decodeName(subject, names), not_before: validityTime(notBefore), not_after: validityTime(notAfter), signature_algorithm_oid: algorithmOid(signatureAlgorithm), @@ -310,7 +353,40 @@ export interface ParsedSigner { readonly authenticated_attributes: boolean; } -const parseSigner = (node: DerNode): ParsedSigner => { +const signerIdentifier = ( + sid: DerNode, + names: DecodedNameBudget, +): { + readonly issuer: ReturnType | null; + readonly serial_hex: string | null; +} => { + if (sid.tag === 0x30) { + const name = sid.children[0]; + const serial = sid.children[1]; + if ( + sid.children.length !== 2 || + name === undefined || + name.tag !== 0x30 || + serial === undefined || + serial.tag !== 0x02 + ) + return fail("SignerInfo issuerAndSerialNumber is truncated."); + return { + issuer: decodeName(name, names), + serial_hex: integerHex(serial.content), + }; + } + if (sid.tag === 0x80) { + if (sid.content.length === 0) + return fail("SignerInfo subjectKeyIdentifier is empty."); + return { issuer: null, serial_hex: null }; + } + return fail( + "SignerInfo sid is not issuerAndSerialNumber or subjectKeyIdentifier.", + ); +}; + +const parseSigner = (node: DerNode, names: DecodedNameBudget): ParsedSigner => { const children = sequence(node, "SignerInfo"); const version = children[0]; const sid = children[1]; @@ -319,16 +395,7 @@ const parseSigner = (node: DerNode): ParsedSigner => { return fail("SignerInfo is missing version."); if (sid === undefined || digestAlg === undefined) return fail("SignerInfo is missing sid or digestAlgorithm."); - let issuer = null; - let serial_hex: string | null = null; - if (sid.tag === 0x30) { - const name = sid.children[0]; - const serial = sid.children[1]; - if (name !== undefined && serial !== undefined && serial.tag === 0x02) { - issuer = decodeName(name); - serial_hex = integerHex(serial.content); - } - } + const { issuer, serial_hex } = signerIdentifier(sid, names); let cursor = 3; const authenticated = children[cursor]?.tag === 0xa0; if (authenticated) cursor += 1; @@ -338,6 +405,8 @@ const parseSigner = (node: DerNode): ParsedSigner => { const signature = children[cursor + 1]; if (signature === undefined || signature.tag !== 0x04) return fail("SignerInfo is missing its signature OCTET STRING."); + if (signature.content.length === 0) + return fail("SignerInfo signature OCTET STRING is empty."); cursor += 2; if (children[cursor]?.tag === 0xa1) cursor += 1; if (cursor !== children.length) @@ -358,7 +427,10 @@ export interface ParsedSignedData { readonly signers: readonly ParsedSigner[]; } -const parseSignedData = (node: DerNode): ParsedSignedData => { +const parseSignedData = ( + node: DerNode, + names: DecodedNameBudget, +): ParsedSignedData => { const children = sequence(node, "SignedData"); const [version, digestSet, encapContentInfo] = children; if (version === undefined || version.tag !== 0x02) @@ -382,10 +454,16 @@ const parseSignedData = (node: DerNode): ParsedSignedData => { const signerSet = children[cursor]; if (signerSet === undefined || signerSet.tag !== 0x31) return fail("SignedData is missing signerInfos."); + if (signerSet.children.length === 0) + return fail("SignedData signerInfos contains no SignerInfo."); if (cursor !== children.length - 1) return fail("SignedData has unexpected trailing fields."); - const certificates = (certificateSet?.children ?? []).map(parseCertificate); - const signers = signerSet.children.map(parseSigner); + const certificates = (certificateSet?.children ?? []).map((certificate) => + parseCertificate(certificate, names), + ); + const signers = signerSet.children.map((signer) => + parseSigner(signer, names), + ); return { content_type_oid: "1.2.840.113549.1.7.2", digest_algorithm_oids, @@ -408,5 +486,7 @@ export const parsePkcs7SignedData = (bytes: Buffer): ParsedSignedData => { const signedData = content?.children[0]; if (content === undefined || content.tag !== 0xa0 || signedData === undefined) return fail("signedData content is missing."); - return parseSignedData(signedData); + return parseSignedData(signedData, { + remaining: MAX_DECODED_NAME_BYTES, + }); }; diff --git a/src/native/pe/PeCertificateParser.test.ts b/src/native/pe/PeCertificateParser.test.ts index b8560ceb74..2e018b6296 100644 --- a/src/native/pe/PeCertificateParser.test.ts +++ b/src/native/pe/PeCertificateParser.test.ts @@ -9,6 +9,7 @@ import { } from "./PeCertificates.fixture.js"; import { DER_NODE_BUDGET, + MAX_DECODED_NAME_BYTES, MAX_OID_ARC_BYTES, MAX_OID_BYTES, decodeOid, @@ -78,6 +79,25 @@ it("fails truncated WIN_CERTIFICATE payloads without partial success", () => { expect(() => parse(bytes)).toThrow(ArtifactReaderFailure); }); +const securityDirectoryAt = 0x98 + 96 + 4 * 8; + +it("rejects a short security table instead of reporting it complete", () => { + for (const size of [4, 7]) { + const bytes = peCertificateFixture(); + const tableOffset = bytes.readUInt32LE(securityDirectoryAt); + bytes.writeUInt32LE(size, securityDirectoryAt + 4); + bytes.fill(0, tableOffset, tableOffset + size); + expect(() => parse(bytes)).toThrow(/header is truncated/u); + } + const bytes = peCertificateFixture(); + const tableOffset = bytes.readUInt32LE(securityDirectoryAt); + const tableSize = bytes.readUInt32LE(securityDirectoryAt + 4); + const extended = Buffer.concat([bytes, Buffer.alloc(4)]); + extended.writeUInt32LE(tableSize + 4, securityDirectoryAt + 4); + expect(extended.readUInt32LE(tableOffset)).toBeGreaterThan(0); + expect(() => parse(extended)).toThrow(/header is truncated/u); +}); + it("fails PKCS#7 payloads that are not signedData", () => { const bytes = peCertificateFixture(false, Buffer.from("not-pkcs7"), 0x0002); expect(() => parse(bytes)).toThrow(ArtifactReaderFailure); @@ -152,6 +172,47 @@ it("fails a SignerInfo without its signature", () => { ); }); +it("fails a SignerInfo whose signature bytes were removed", () => { + const signer = derSeq( + derInt(1), + derSeq(derSeq(), derInt(1)), + SHA256_ALG, + SHA256_ALG, + der(0x04, Buffer.alloc(0)), + ); + const payload = contentInfo( + derSeq(derInt(1), derSet(SHA256_ALG), derSeq(DATA_OID), derSet(signer)), + ); + expect(() => parse(peCertificateFixture(false, payload))).toThrow( + /signature OCTET STRING is empty/u, + ); +}); + +it("fails signedData whose signerInfos set is empty", () => { + const payload = contentInfo( + derSeq(derInt(1), derSet(SHA256_ALG), derSeq(DATA_OID), derSet()), + ); + expect(() => parse(peCertificateFixture(false, payload))).toThrow( + /no SignerInfo/u, + ); +}); + +it("fails a truncated issuerAndSerialNumber instead of dropping the signer", () => { + const signer = derSeq( + derInt(1), + derSeq(derSeq()), + SHA256_ALG, + SHA256_ALG, + der(0x04, Buffer.from([1])), + ); + const payload = contentInfo( + derSeq(derInt(1), derSet(SHA256_ALG), derSeq(DATA_OID), derSet(signer)), + ); + expect(() => parse(peCertificateFixture(false, payload))).toThrow( + /issuerAndSerialNumber is truncated/u, + ); +}); + it("stops decoding a certificate payload at the DER node budget", () => { const values = Buffer.alloc(2 * (DER_NODE_BUDGET + 1)); for (let at = 0; at < values.length; at += 2) values[at] = 0x05; @@ -190,6 +251,66 @@ it("rejects an oversized OBJECT IDENTIFIER before growing an arc", () => { ); }); +const cn = Buffer.from([0x06, 0x03, 0x55, 0x04, 0x03]); +const directoryName = (value: Buffer) => derSeq(derSet(derSeq(cn, value))); +const namedCertificate = (issuer: Buffer, subject: Buffer) => + derSeq( + derSeq( + derInt(1), + SHA256_ALG, + issuer, + derSeq( + der(0x17, Buffer.from("250101000000Z")), + der(0x17, Buffer.from("260101000000Z")), + ), + subject, + ), + SHA256_ALG, + der(0x03, Buffer.from([0])), + ); + +it("decodes BMPString names once and shares the decoded-byte budget", () => { + const bmp = (text: string) => { + const content = Buffer.alloc(text.length * 2); + [...text].forEach((character, index) => + content.writeUInt16BE(character.charCodeAt(0), index * 2), + ); + return der(0x1e, content); + }; + const certificate = readDer( + namedCertificate(directoryName(bmp("REA")), directoryName(bmp("Fixture"))), + ); + expect(parseCertificate(certificate).subject.text).toBe("commonName=Fixture"); + const half = MAX_DECODED_NAME_BYTES / 2; + const within = readDer( + namedCertificate( + directoryName(der(0x1e, Buffer.alloc(half))), + directoryName(der(0x1e, Buffer.alloc(half))), + ), + ); + expect(parseCertificate(within).issuer.text.startsWith("commonName=")).toBe( + true, + ); + const over = readDer( + namedCertificate( + directoryName(der(0x1e, Buffer.alloc(half + 2))), + directoryName(der(0x1e, Buffer.alloc(half))), + ), + ); + expect(() => parseCertificate(over)).toThrow(/decoded budget/u); + const universal = Buffer.alloc(MAX_DECODED_NAME_BYTES + 4); + expect(() => + parseCertificate( + readDer( + namedCertificate( + directoryName(der(0x1c, universal)), + directoryName(bmp("A")), + ), + ), + ), + ).toThrow(/decoded budget/u); +}); + it("decodes UniversalString names as UTF-32BE", () => { const universal = (text: string) => { const content = Buffer.alloc(text.length * 4); @@ -214,6 +335,13 @@ it("decodes UniversalString names as UTF-32BE", () => { derSeq(tbs, SHA256_ALG, der(0x03, Buffer.from([0]))), ); expect(parseCertificate(certificate).subject.text).toBe("commonName=REA"); + const emoji = readDer( + namedCertificate( + directoryName(der(0x1c, Buffer.from([0x00, 0x01, 0xf6, 0x00]))), + directoryName(der(0x1c, Buffer.from([0x00, 0x01, 0xf6, 0x00]))), + ), + ); + expect(parseCertificate(emoji).subject.text).toBe("commonName=😀"); const odd = derSeq(derSet(derSeq(cn, der(0x1c, Buffer.from([0, 0, 0]))))); const broken = readDer( derSeq( diff --git a/src/native/pe/PeCertificateParser.ts b/src/native/pe/PeCertificateParser.ts index 48b1c74f63..5266c38afa 100644 --- a/src/native/pe/PeCertificateParser.ts +++ b/src/native/pe/PeCertificateParser.ts @@ -89,11 +89,10 @@ export const parsePeCertificates = ( let at = 0; let examined = 0; while (at < table.length) { - if (table.length - at < 8) { - if (table.subarray(at).some((byte) => byte !== 0)) - fail("WIN_CERTIFICATE header is truncated."); - break; - } + // Alignment padding sits inside the gap before the next 8-byte boundary. + // Bytes that remain after that boundary are a short header, including + // zeros at the start of the table. + if (table.length - at < 8) fail("WIN_CERTIFICATE header is truncated."); examined += 1; if (examined > input.max_entries) throw new ArtifactReaderFailure(