樣本:SonoiQ-2.2.15_260113 (1).apk(299 MB)· SHA-256 867c136a…60a2c1 生成:2026-09-03 · 授權:離線樣本靜態分析 · 21 節合併全景
01

🗺️ App 全景圖 — The Whole Picture

flowchart TB
    subgraph HW["🔬 探頭硬件 Probe Hardware"]
        FPGA[["Efinix Trion T55 FPGA<br>波束形成 · 掃描轉換"]]
        MCU[["TI CC3220/CC3235 WiFi MCU<br>AP 模式 192.168.254.1"]]
        USB[["Cypress FX USB<br>VID 0x04B4 · EP6 bulk"]]
        AUTH[("授權存儲<br>EEPROM / SD block 2-12<br>B·C·PW·DICOM·Vet 位")]
        PIEZO[壓電陣元 16ch/32ch]
    end
    subgraph XPORT["📡 傳輸層"]
        USBIF[UsbProbeManager<br>FPGA 地址映射 0x20000/0x40000/0x60000]
        WLANIF[WlanProbe / CmdParser<br>0xFE 頭命令碼 · UDP 8081]
    end
    subgraph NATIVE["🧠 Native 引擎 (arm64)"]
        DOP[libdoppler 15.4MB<br>DSP · DSC · DICOM 棧 · hash 授權]
        SOCK[libHSocketIf · libh264rtp]
        AIENG[PyTorch 76MB · TFLite · Vosk · OpenCV · FFmpeg]
    end
    subgraph JAVA["☕ Java 核心"]
        PUMP[kernel.MainHandler<br>JavaGetMessage 輪詢泵]
        CORE[Ultrasys 單例<br>ImagePlayer · Cine · MITI]
        AISVC[ai.* 16 診斷類<br>膀胱·心臟·甲狀腺·乳腺…]
        DICJ[JDicom* 服務<br>Storage·Worklist·MPPS·Print]
        MEAS[preset · measurement<br>OB_table 177 表]
    end
    subgraph UI["🖥️ UI 層"]
        SPLASH[SplashActivity 登錄<br>Admin/12345678 MD5]
        MODELS[18 OEM 機型<br>LandQ6 · PortQ61 · Lianmed…]
        VET[人/獸醫模式 0-6 級]
    end
    subgraph EXT["🌐 外部世界"]
        PACS[["醫院 PACS / DICOM"]]
        HIS[["HIS 系統<br>SHA-1 簽名 API"]]
        SDK[["第三方 App<br>ThirdPartyManageActivity"]]
    end
    subgraph DATA["📦 資產"]
        PID[["PID03.zip<br>77 探頭型號 + 13 AI 模型"]]
        FW[["fw/board v2/v3<br>FPGA + CC32xx 固件"]]
    end
    PIEZO --> FPGA
    FPGA --> USB
    USB --> USBIF
    MCU --> WLANIF
    USBIF --> DOP
    WLANIF --> DOP
    DOP <--> SOCK
    DOP --> PUMP
    PUMP --> CORE
    CORE --> AISVC
    AISVC --> AIENG
    CORE --> DICJ
    CORE --> MEAS
    CORE --> UI
    SPLASH --> MODELS
    MODELS --> VET
    DICJ --> PACS
    AUTH -.-> DOP
    HIS -.->|HttpProtocol_hle| DICJ
    SDK -.->|exported| SPLASH
    PID -.-> DOP
    FW -.-> FPGA
02

執行摘要 — 靜態分析報告

SonoiQ 2.2.15 — 靜態逆向分析報告

項目 內容
樣本 SonoiQ-2.2.15_260113 (1).apk
SHA-256 867c136abf74211da7eaa511f0d5a1de022064421d7b2a5410d105f32060a2c1
大小 299,234,800 bytes(解壓 482 MB,2,182 檔案)
包名 com.SonoiQ.uhandprobe
標籤 SonoiQ,launcher com.Com.handprobe.SplashActivity
授權 離線樣本靜態分析(case sono-iq,auth=granted,network_profile=offline)
工具 jadx 1.5.6、apktool 3.0.3、OpenJDK 17(tool-index 路徑)
日期 2026-09-03

1. 樣本定性

手持超聲波診斷設備的 Android 宿主 App(OEM/ODM 白牌方案)。連接 USB / Wi-Fi 超聲探頭,提供 B/M/Color/PW 成像、AI 輔助診斷、DICOM、報告打印等完整臨床工作流。

高置信度依據(Evidence 鏈):

  • E-01:assets/pid/ 含 FPGA bitstream ×6(FPGA_ch16*.rbf,Intel/Altera RBF)與探頭描述庫 PID03.zip(140.8 MB)
  • E-02:lib/arm64-v8a/libdoppler.so(15.4 MB)+ HPidIf.GetProbeElementNumber() 等探頭 API
  • E-03:handprobe.application.ai.* 16 個診斷類(BladderMeas、CardiacAutoEF、ThyroidDiagnose…)
  • E-04:DICOM 全家桶(JDicomServer/Storage/Worklist/MPPS/Print
  • E-05:MyApplication 內 18 個 OEM_*_ID 常量(ESAOTE、BD、ECCOSCAN、SONOSTAR…)

2. 技術構成

dex(Java 層)

  • classes.dex 9.5 MB / classes2.dex 2 KB / classes3.dex 4.5 MB
  • jadx 1.5.6 處理 6,076 個 class,18 個反編譯錯誤(0.3%)
  • 錯誤分佈:第三方庫 9 檔(mockito、apache-poi、okhttp3 Http2Connection、android.support GridLayoutManager/Flow、zxing Code128Reader),自有代碼僅 1 檔 handprobe/components/file/fileselect/utils/FileUtils.java(type-inference overflow,單一超大 method,非混淆/加固)
  • 主要第三方:xUtils(ORM)、OkHttp 3、OpenCV、PyTorch、TFLite、Vosk、zxing(條碼)、mp4parser、JNA(libjnidispatch

native(lib/arm64-v8a,僅此 ABI)

分類
超聲核心 libdoppler(PID/波束/前端/DSC/DICOM/系統控制)
傳輸 libHSocketIf(USB/串口/GL 視圖)、libh264rtp
影像 libopencv_java4libhopencvif_liblibfpgargb_lib
AI libpytorch_jni(76 MB)、libtensorflowlite_jni+GPU、libvosk
視頻/文檔 FFmpeg 56 系列(avcodec/avformat/avfilter/avutil/swscale/swresample/postproc)、libx264libmodpdfium/libjniPdfium
其他 libjnidispatch(JNA)、libc++_sharedlibfbjnilibmodft2/libmodpng

資產

  • assets/pid/ 147.9 MB:PID03.zip(探頭描述庫)、FPGA bitstream ×6、BMP/PMP/VMP/dPMP.bin(模式參數表)、Tintmap.bin(色調映射)、gsl3670.fw(觸摸屏固件)、FileCopy.bat/FileCopyAll.bat(開發殘留)
  • assets/vosk/ 139.2 MB:中文 vosk-model-small-cn-0.22 + 英文 vosk-model-small-en-us-0.15
  • assets/OB_table/ 9.6 MB:177 個產科參考表(CHINA、CHITTY、ASUM、BERTAGNOLI…)
  • assets/user/:說明書 PDF(英/俄/中)
  • assets/fw/board/:板卡固件 4 MB
  • assets/BodyMark/:261 個體表標記

3. 架構

探頭(FPGA) ──USB/WiFi──> UsbProbeManager/WlanProbe ──> jnitojava ──> libdoppler 等 native
                                                                │ JavaGetMessage 輪詢
                     kernel.MainHandler(主線程廣播)──> Ultrasys 單例 ──> UI (Activities/Fragments)
                                                                ├──> AI (PyTorch/TFLite)
                                                                └──> DICOM (native 棧)

詳細:../docs/architecture.md(含 Mermaid 圖)。

4. 安全觀察(靜態)

# 觀察 嚴重度 依據
S-01 android:allowBackup="false" Manifest 34 行
S-02 ThirdPartyManageActivity exported + 回傳第三方結果 Manifest 50 行;僅設備廠商集成場景
S-03 兩 Activity 聲明 sharedUserId="android.uid.system"(需系統簽名) 信息 Manifest 48-49 行
S-04 android.permission.INSTALL_PACKAGES 聲明(非系統簽名無法使用) Manifest 11 行
S-05 崩潰處理器重啟 Launcher 並 AppUtil.exitApp(1)(設備級行為,無隱蔽持久化) 信息 MyApplication.handleException
S-06 FileCopy.bat 開發腳本殘留(無憑證/密鑰內容) 信息 assets/pid/
S-07 網絡:OkHttp 標準 TLS;未見自定義 CertificatePinner(Java 層) 信息 okhttp3 反編譯未見 pin 邏輯
S-08 未發現:加固殼、dex 動態加載、反射調用敏感 API、遠程 code exec 模式 jadx 全量掃描
S-09 USB 影像流無加密:「EncImg」= BC 影像縮寫;幀首 byte 為方向標記(負值→Reserse 反轉) 信息 UsbProbeManager.ReadEncImgReadBCImagemImgBuf[0]<0 分支
S-10 功能授權存於探頭 EEPROM(B/PW/Color/DICOM/人獸醫 + OEM ID 匹配);vendor request 24/81 寫授權通道 中(商業敏感) HSUsbProbe EEPROM 地址表 + WriteAuthCode
S-11 HSUsbProbeCh16.VerifyAuth() 直接 return true(ch16 探頭無認證) HSUsbProbeCh16.java 全文
S-12 libHSocketIf.InputUtils 可注入系統級鼠標/鍵盤事件 信息(OEM 設備特性) Java_jni_InputUtils_* 8 導出
S-13 未見 Java/native 層明文 AES/DES/RC4 標識(strings 證據) libdoppler 53K strings 掃描
S-14 ThirdPartyManageActivity exported 無保護:任意 App 可啟動、注入病患 PHI 入 DB、經 SAVEIMG_PATH/IMG_PATH 外洩影像/PDF/絕對路徑 docs/deepdive/thirdparty-sdk-interface.md
S-15 HIS API:SHA-1+Base64 簽名、ts 硬編碼、TLS 憑證驗證完全關閉(中間人可攔截 HIS 憑證) HttpProtocol_hle.java:95-123
S-16 WLAN 探頭協議無 checksumCmdHandler.varify() 空 stub),僅靠 0xFF 終止符 docs/deepdive/wlan-command-codes.md

未做動態驗證(離線範圍內):上述結論均為靜態推斷,動態行為(Frida 驗證網絡/SSL、探頭協議)不在本次授權。

5. 深度分析進度(2026-09-03 更新)

三線深入已完成靜態部分:

  1. native 深挖docs/native-analysis.md):rabin2 重建 381 個 JNI 導出全表、關鍵函數地址、DICOM 棧證實、私有 UID root 1.2.156.162458、hash 函數族
  2. USB 協議重構docs/usb-protocol.md):VID 0x04B4(Cypress)、EP6 bulk、FPGA 地址映射(B@0x20000/H264@0x40000/T@0x60000)、影像方向標記、EEPROM 功能授權體系
  3. 動態分析(Frida):腳本已備妥(frida/hooks.js,10 個 hook section),阻塞於無設備連接adb devices 為空)

6. 待設備就緒後(需授權設備)

  1. frida -U -f com.SonoiQ.uhandprobe -l frida/hooks.js --no-pause 驗證:USB 流捕獲、消息泵語義、VerifyAuth 行為
  2. 硬件在環:探頭 USB 流量抓取(Wireshark usbmon / Frida)驗證協議重構
  3. libdoppler 算法深挖(JavaGetMessage 消息表、CPreProc)——IDA/Ghidra(本機 IDA Pro 未裝,Ghidra 可用)

附錄 A:重現命令

cd /Volumes/End-Gsme/untra-sound/sonoiq-reverse
jadx -d jadx_out --show-bad-code "…SonoiQ-2.2.15_260113 (1).apk"
apktool d -f -o apktool_out "…SonoiQ-2.2.15_260113 (1).apk"
shasum -a 256 "…SonoiQ-2.2.15_260113 (1).apk"

附錄 B:資產清單統計腳本

import re, collections
rows = []
with open('_apk_listing.txt', encoding='latin-1') as f:
    for line in f:
        m = re.match(r'\s*(\d+)\s+\d{2}-\d{2}-\d{4}\s+\d{2}:\d{2}\s+(.+)$', line.strip())
        if m: rows.append((int(m.group(1)), m.group(2)))
c = collections.defaultdict(lambda: [0, 0])
for s, n in rows:
    k = '/'.join(n.split('/')[:2]) + '/'
    c[k][0] += 1; c[k][1] += s
for k, (cnt, sz) in sorted(c.items(), key=lambda x: -x[1][1]):
    print(f"{k:30s} {cnt:5d} files {sz/1e6:8.1f} MB")
03

授權範圍

Scope — SonoiQ 2.2.15 Static Analysis

Field Value
Case ID sono-iq
Sample /Volumes/End-Gsme/untra-sound/SonoiQ-2.2.15_260113 (1).apk
SHA-256 867c136abf74211da7eaa511f0d5a1de022064421d7b2a5410d105f32060a2c1
Package com.SonoiQ.uhandprobe
auth.status granted
network_profile offline
Authorization Offline static analysis of a locally owned sample. No network targets, no dynamic instrumentation against any device, no repackaging/distribution.
Created 2026-09-03 (case-init.sh, reverse-skill router)
Router skills/scripts/master-route.sh → PRIMARY apk-reverse/ (R1)

Permitted actions (executed)

  • jadx static decompilation → jadx_out/
  • apktool d unpack → apktool_out/
  • Manifest / assets / smali inspection
  • Code wiki + markdown documentation generation
  • git init + commits of analysis artifacts

Out of scope

  • Any dynamic hooking (Frida) on a device
  • Repackaging, signing, or installing a modified APK
  • Any network interaction with the vendor or third parties
04

專案總覽

SonoiQ 2.2.15 — Android APK 靜態逆向分析項目

手持式超聲波診斷設備 App 的反編譯代碼庫 + Code Wiki。 樣本:SonoiQ-2.2.15_260113 (1).apk(299 MB,SHA-256 867c136a…60a2c1) 授權範圍:離線靜態分析(詳見 scope.md

SonoiQ 是什麼

SonoiQ 是手持超聲波探頭(handheld ultrasound probe)的 Android 宿主 App(package com.SonoiQ.uhandprobe)。它連接 USB 或 Wi-Fi 超聲探頭(內含 FPGA 波束形成器),在 Android 設備上實現完整的超聲成像工作流:

  • 成像模式:B / M / Color Doppler / PW(脈衝多普勒)等,含 DSC(數字掃描轉換)、cine 回放
  • AI 輔助診斷:膀胱體積、心臟 EF、肺部 B 線、甲狀腺、多囊卵巢(PCOS)、神經、皮膚、靜脈曲張等(PyTorch + TensorFlow Lite 推理)
  • 語音控制:Vosk 中/英文離線語音識別
  • 臨床工作流:患者管理、測量、報告、DICOM(Storage / Worklist / MPPS / Print)、PDF 報告、打印
  • OEM 白牌MyApplication 內有 18 個 OEM ID(ESAOTE、BD、ECCOSCAN、SONOSTAR…),同一代碼庫派生多個品牌機型 UI(LandQ6 / PortQ61 / Lianmed / ccgc / Anor 等)
  • 獸醫模式HAnimalMng / AnimalExam + def_vet 資源開關

規模一覽

指標 數值
APK 大小 299 MB(解壓 482 MB,2,182 檔案)
dex 3 個(classes.dex 9.5 MB + classes2/3)
Java 檔(jadx) 6,025(自有代碼 ~1,050,其餘第三方庫)
smali 檔 11,997(12 萬行級)
native libs 28 個 .so,149.6 MB,僅 arm64-v8a
最大資產 assets/pid/PID03.zip 140.8 MB(探頭描述庫)

Key Concepts

  • Probe / PID — 探頭識別與參數庫(libdoppler 提供 HPidIf/HBeParamIf/HFeParamIfsetPidFilesPath() 指向 assets/pid/PID03.zip
  • jnitojava 消息泵 — C++ native 經 JavaGetMessage(long[]) 輪詢向 Java 層推送 what/arg1/arg2 消息(kernel.MainHandler
  • Ultrasys — 中央單例,統籌 DSC、影像播放、cine、preset、MITI(機械/熱指數)
  • ConnectType — 7 種探頭連接:SIMU / WLAN / WUSB / USB / UWLAN / RGBIF / BROADCAST
  • ExamMode / Preset — 檢查模式與掃描參數預設體系(assets/presetPara/assets/SonoiQ6*/
  • FPGA bitstreamassets/pid/FPGA_ch16*.rbf(Intel/Altera RBF)在探頭連接時編程

Entry Points

檔案 角色
com.Com.handprobe.SplashActivity LAUNCHER 入口:登錄、DICOM 服務啟動、機型路由
com.Com.application.MyApplication Application:3 個 SQLite DB、crash handler、TaskManager、OEM 常數
com.Com.handprobe.BaseActivity 主 Activity 基類(mUltrasysApp 宿主)
kernel.MainHandler C→Java 消息泵(單例,主線程)
handprobe.application.ultrasys.Ultrasys 超聲核心單例

High-Level Architecture

Java UI 層(Activities/Fragments)→ Ultrasys 核心(Java)→ JNI(jni.*)→ native 引擎(libdoppler/libHSocketIf/OpenCV/PyTorch/TFLite)→ 探頭(USB/Wi-Fi,FPGA 採集)。詳見 docs/architecture.md

Module Map

Module Purpose
ultrasys 超聲核心:DSC、影像、cine、MITI
kernel-jni C↔Java 消息橋 + native 接口
com-ui UI Activities / OEM 機型派生
components DICOM、檔案、顯示、輸入等組件
ai AI 輔助診斷 + 語音
network-probe 探頭連接(USB/WLAN)+ DICOM 網絡
preset-measurement 預設參數、檢查模式、測量
data-assets 資產、FPGA 固件、DB、OEM 配置
native-analysis libdoppler/libHSocketIf JNI 面、符號、strings
usb-protocol USB 影像流協議、FPGA 地址映射、EEPROM 授權體系
feature-auth 功能授權體系完整解碼(hash 算法、漏洞)
additional-findings 硬件身份、77 探頭型號、13 AI 模型、40+ 檢查模式
deepdive/ 7 項深挖:消息泵反匯編、WLAN 命令碼、ThirdParty SDK、DispMode、INF 格式、CC32xx 固件、Efinix bitstream
frida/hooks.js Frida 動態 hook 套件(10 個 section,待設備)

Getting Started

工具鏈重現步驟見 docs/getting-started.md

目錄結構

sonoiq-reverse/
├── jadx_out/sources/     # jadx Java 反編譯(6,025 檔)
├── apktool_out/          # apktool 解包:smali + AndroidManifest.xml
│   ├── smali*            # 12K smali 檔(3 dex)
│   └── AndroidManifest.xml
├── docs/                 # Code Wiki(架構、模組、圖)
├── reports/              # 靜態分析報告
├── scope.md              # 授權範圍
└── README.md
05

系統架構 Architecture

Architecture — SonoiQ

系統形態

SonoiQ 是典型的醫療超聲嵌入式宿主架構:超聲探頭(硬體,含 FPGA 波束形成器與前端模擬電路)通過 USB 或 Wi-Fi 連接到 Android 主機。App 分四層:

  1. UI 層com.Com. Activities + handprobe.application.gui. Fragments)— 掃描界面、測量、菜單
  2. 核心層handprobe.application.*)— Ultrasys 單例統籌影像管線;preset/measurement/patient/cine/network 子系統
  3. JNI 橋jni.kernel.)— libdoppler / libHSocketIf 的 Java 接口 + C→Java 消息泵
  4. Native 引擎(28 個 .so)— 超聲信號處理(doppler)、影像處理(OpenCV)、AI 推理(PyTorch/TFLite)、語音(Vosk)、視頻(FFmpeg/x264)

數據入口是探頭(RF/影像流經 FPGA 採集),出口是屏幕顯示、DICOM 存儲/打印、PDF 報告與本地檔案。

Components

  • Probe hardware(FPGA) — 探頭內固件由 assets/pid/FPGA_ch16*.rbf(Intel/Altera bitstream)+ PID03.zip 探頭描述庫定義;App 側通過 HPidIf.setPidFilesPath() / InitPidSys() 初始化
  • UsbProbeManager / WlanProbe — 傳輸層:USB bulk 讀寫(UsbWriteDwords/UsbReadDwords/WUsbReadImg)或 Wi-Fi 探頭(handprobe.application.wlan.*
  • Ultrasys — Java 核心單例:DscCtrl(數字掃描轉換)、FeUltrasysCtrl(前端控制)、ImagePlayerCinePlayerPresetServer、MITI 熱指數
  • kernel.MainHandler — native 消息泵:jnitojava.JavaGetMessage(long[4]) 輪詢 → 廣播給註冊的 Doppler Handler
  • DICOM servicesJDicomServer/Storage/Worklist/MPPS/Printhandprobe.components.dicom.*
  • AI engineshandprobe.application.ai.*(診斷算法)+ handprobe.components.ai.{pytorch,tensorflow}(推理封裝)
  • Storage — xUtils SQLite:UserInfo/sonoiq.dbOperatorAuth/sonoiq.dbPatientInfo/sonoiq.db + 外部存儲檔案系統(PodFilesSys

System Diagram

flowchart TD
    Probe[["Ultrasound Probe<br>FPGA beamformer"]] -->|"USB bulk / WiFi"| Xport[UsbProbeManager / WlanProbe]
    Xport -->|jnitojava| Native[["Native engines<br>libdoppler · libHSocketIf<br>OpenCV · PyTorch · TFLite<br>Vosk · FFmpeg"]]
    Native -->|"JavaGetMessage(what,arg1,arg2)"| Pump[kernel.MainHandler]
    Pump -->|broadcast| Core[Ultrasys singleton]
    Core --> DSC[DscCtrl]
    Core --> Img[ImagePlayer / DispSurface]
    Core --> Cine[CinePlayer / CinePartitionBuffer]
    Core --> MITI[MITIData]
    Img --> UI[Activities / Fragments<br>com.Com.handprobe · gui.*]
    UI --> Dicom[DICOM Server/Storage/Worklist/MPPS/Print]
    Dicom --> Net{{"PACS / Printer<br>(external)"}}
    UI --> AI[ai.* diagnosis]
    AI --> Native
    UI --> DB[("SQLite ×3<br>xUtils")]
    Core --> Preset[PresetServer / ExamMode]
    Preset --> Assets[["assets/<br>PID03.zip · presetPara · BodyMark"]]

Data Flow

  1. 採集 — 探頭 FPGA 採集 RF/影像,App 經 UsbProbeManager.Read() / ReadEncImg() 拉取(jnitojava.WUsbReadImg
  2. 處理 — native 完成波束形成/掃描轉換/多普勒處理;結果經 JavaGetMessage 上拋
  3. 顯示Ultrasys 分發 → ImagePlayer/DispSurface(OpenGL,HGLImageJNIViewLib)渲染到 UI
  4. AI — 用戶觸發測量 → ai.* 調用 PyTorch/TFLite 模型 → 結果疊加顯示
  5. 存儲/輸出 — 圖像/報告寫本地(PodFilesSys)、經 DICOM 上傳 PACS、打印或輸出 PDF

Key Design Decisions

  • 單一 arm64-v8a ABI — 現代設備專用,壓縮體積(探頭 App 一般跑在專用平板)
  • 探頭即數據庫 — 探頭參數(元素數、半徑、頻率)由 PID03.zip 描述,native 側查詢(GetProbeElementNumber() 等),App 換探頭即可切換參數
  • 7 種連接類型共存ConnectType(SIMU/WLAN/WUSB/USB/UWLAN/RGBIF/BROADCAST)由 HSysctrlIf.SetProbeConnType() 同步到 native
  • OEM 由常數+資源派生 — 同一 dex 經 MyApplication.OEM_*_IDdef_vet 等資源開關切換品牌/人獸醫模式,無需分叉代碼
  • C→Java 採用輪詢而非直接 JNIEnv 回調JavaGetMessage 阻塞輪詢 + 主線程 Handler 廣播,避免 JNI 跨線程回調複雜性
  • 資源全面混淆res/ 檔名短隨機化(如 xY.png9_.jpg),但 assets/ 保持明文目錄結構
06

模組 — ultrasys 超聲核心

Module: ultrasys — 超聲核心

handprobe.application.ultrasys(+ 少量 handprobe.components.ultrasys)是整個 App 的影像核心。Ultrasys 是中央單例,上游接 native 引擎(經 jni/jnitojava),下游餵 UI 層。

Responsibilities

  • 統籌成像管線:B / M / Color / PW 模式切換與參數分發
  • DSC(數字掃描轉換):algm.DscCtrl 把探頭極坐標數據轉為屏幕直角坐標
  • 影像顯示與回放:ImagePlayer / TwoImagePlayer / CinePlayer / MBCinePlayer
  • MITI(機械指數 MI / 熱指數 TI):MITIData + MITIObservable 實時計算與顯示
  • 前端控制:algm.FeUltrasysCtrl(頻率/深度/焦點)
  • 探頭模式管理:probe.ExamMode.HExamTypeprobe.ConnectType

Key Files

  • Ultrasys.java — 中央單例(Ultrasys.Instance()),持有 DscCtrl / FeUltrasysCtrl / ImagePlayer / CinePlayer / PresetServer / MITIData 引用
  • ParaCtrl.java — 參數控制(從 UI 參數變化同步到 native)
  • HFuncId.java — 功能 ID 常量(按鍵/菜單/功能碼)
  • algm/DscCtrl.java — 掃描轉換控制(SetBFrameCorrCoef / SetCFrameCorrCoef
  • image/DispModeDispSurfaceImageDefineImagePlayer(Base)TwoImagePlayer
  • parameter/ExamTypesIdFuncidMapInspectionObjectType
  • probe/ConnectType(7 種連接)、ExamMode/podsys/(探頭系統)、PidInterface
  • cine/ — cine 錄製

Public API(Java 側關鍵入口)

  • Ultrasys.Instance() — 取單例
  • Ultrasys.setMITIData(float[]) — native 回調更新熱指數(由 jnitojava.UpdateMITI 調用)
  • Ultrasys.GetPattern(int, int) — 取模式數據
  • Ultrasys.GetProbeConType() — 探頭連接類型
  • ConnectType.SetConnType(int) — 切換連接類型並同步 HSysctrlIf.SetProbeConnType()

Internal Structure

  • algm/(algorithm)— 與 native 配對的算法控制:DSC、前端
  • image/ — 顯示鏈:player → surface → 模式定義
  • parameter/ — 檢查類型/功能碼映射
  • probe/ — 探頭體系:連接、模式、pod 系統(poddef 定義)
  • cine/ — cine 環形緩衝與錄製

Dependencies

  • Used by: com.Com.handprobe.BaseActivity(宿主)、gui.fragment.ImageDisplayFragmentai.*
  • Uses: jni.(HBeParamIf/HFeParamIf/HPidIf/HDscClassIf/HSysctrlIf/HPresetIf)、kernel.handprobe.application.usb.handprobe.application.wlan.components.ultrasys.*(FrameLineData、CinePartitionBuffer、B/C/M/PW 掃描參數類)

Notable Patterns / Gotchas

  • Ultrasys 大量字段是 public 的直接暴露(mDscCtrl 等),跨包直接操作——無嚴格封裝
  • 掃描參數按模式分族:BScanPara/CScanPara/MScanPara/PWScanPara + 對應 *DspPara
  • FrameLineData.SimDataBuffer 表明有模擬數據路徑(SIMU 連接模式,用於無探頭演示/培訓)
  • MITI 數據由 native 側計算後上拋(UpdateMITI(float[])),Java 只負責顯示
07

模組 — kernel + jni C↔Java 橋

Module: kernel + jni — C↔Java 橋

kernel(6 檔)與 jni(14 檔)是 Java 層與 native 引擎(libdopplerlibHSocketIflibh264rtplibhopencvif_liblibfpgargb_lib)的接口。

Responsibilities

  • C→Java 消息推送:native 產生的 what/arg1/arg2 消息泵入主線程
  • Java→C 調用:探頭參數查詢、系統控制、DICOM、DSC、預設、影像
  • 串口/USB Socket 訪問(libHSocketIf
  • FPGA RGB 輸出(libfpgargb_lib)、H.264 視頻解碼(libh264rtp)、OpenCV 封裝(libhopencvif_lib

Key Files

  • kernel/MainHandler.java — 主線程單例 Handler:起一個輪詢線程不停調 jnitojava.JavaGetMessage(long[4]),有消息就廣播給 mDopplerMsgHandlerVector 所有註冊 Handler
  • kernel/DopplerHandler.java — Doppler 專用 Handler 封裝
  • kernel/HObserver.java / HObservable.java — 觀察者模式基類(UI 訂閱影像/參數事件)
  • kernel/FocusResMng.javakernel/WinRect.java — 焦點/窗口矩形輔助
  • handprobe/application/usb/jnitojava.java — native 回調 Java 的固定簽名入口(JavaGetMessageSetJniEvnUsbWriteDwordsUsbReadDwordsWUsbReadImgUpdateMITIGetPatternCineDataCapture…)

Native 庫加載映射(System.loadLibrary 實測)

Java 類
HPidIf HBeParamIf HFeParamIf HDicomIf HDscClassIf HPresetIf HSysctrlIf libdoppler(核心)
SerialPort MeasFormulaParser InputUtils HGLImageJNIViewLib libHSocketIf(USB/串口/GL)
HH264PlayerIf libh264rtp
HOpencvIf libhopencvif_lib
fpgargb_lib libfpgargb_lib
IDReader.DecodeWlt DecodeWlt(中國身份證讀卡)

Public API 精選

  • HPidIf — 探頭查詢:GetProbeId(String)GetProbeName(int)GetProbeElementNumber()GetProbeRadius()GetProbeType(int)InitPidSys()setPidFilesPath(String)
  • HSysctrlIf — 系統控制:SetProbeConnType(int) 等(190+ 行,最大接口之一)
  • HGLImageJNIViewLib — OpenGL 影像視圖
  • SerialPort — 串口讀寫
  • MeasFormulaParser — 測量公式解析(native)

Internal Structure

兩條消息路徑:

native → jnitojava.JavaGetMessage (輪詢) → kernel.MainHandler (主線程廣播) → 註冊 Handler
Java  → jni.* static native → libdoppler / libHSocketIf

Dependencies

  • Used by: handprobe.application.(幾乎所有核心包)、com.Com.handprobe.
  • Uses: Android System.loadLibrarylibc++_shared(native 依賴)

Notable Patterns / Gotchas

  • 輪詢而非回調JavaGetMessage 在後台線程忙輪詢(Tools.Sleep(20)),避免 JNIEnv 跨線程 attach 複雜度
  • 消息為固定 4×long 佈局([0]=what, [1]=arg1, [2]=arg2),擴展性有限
  • 多個 JNI 類載入同一個 libdoppler——native 側按類名分派(靜態註冊)
  • jnitojava 的方法名固定為 native 側硬編碼的回調點(改 Java 方法名會斷鏈)
08

模組 — com.Com UI 層 / OEM 機型

Module: com.Com — UI 層 / OEM 機型

com.Com(67 檔)是 Android UI 層:入口 Activity、主 Activity 基類、以及按 OEM 機型派生的整套界面。

Responsibilities

  • App 啟動與登錄(SplashActivity
  • 主掃描界面骨架(BaseActivity
  • OEM 機型界面派生(Land / Port / Lianmed / ccgc / Anor)
  • 第三方 App 集成入口(ThirdPartyManageActivity,exported)
  • 崩潰處理與桌面返回

Key Files

  • application/MyApplication.java — Application:初始化 xUtils、3 個 SQLite(UserInfo / OperatorAuth / PatientInfo)、crash handler(記錄後重啟 Launcher)、TaskManager 後台線程、時間/日期系統設置
  • handprobe/SplashActivity.java — LAUNCHER:用戶登錄(spinner 選用戶 + 密碼 + 自動登錄 + 緊急按鈕)、DICOM 服務啟動、機型路由
  • handprobe/BaseActivity.java — 主 Activity 基類:持有 mUltrasysApp,消息處理(AMSG_CONNECT_CHANGE 等)
  • handprobe/LandFeature/LandActivity.java — 橫屏機型
  • handprobe/LandQ6Feature/ — Q6 橫屏系列:LandQ6ActivityLandQ6MidActivityLandQ62ActivityLandQ6BMVActivityQ62SonoStarActivity(SonoStar OEM)、SSKeydataHandleHKPKeyboardAPP(硬體鍵盤)
  • handprobe/PortQ61Feature/PortQ61Activity.java — 便攜 Q61
  • handprobe/LianmedFeature/LianmedActivity.java — Lianmed OEM
  • handprobe/ccgc/LandCCGCActivity.java — ccgc OEM
  • handprobe/AnorActivity.java + UI/AnorTopFuncFrag.java/AnorBottomFuncFrag.java — Anor 機型
  • handprobe/PortActivity.java — 便攜基礎版
  • handprobe/ThirdPartyManageActivity.java — 第三方 App 調起接口(授權結果回傳)
  • handprobe/FsActivity.javaCrashActivity.javaMyInstalledReceiver.java — 輔助

OEM 白牌機制

MyApplication 定義 18 個 OEM ID 常量(實測源碼):

OEM_YOUKEY_ID=1  OEM_ESAOTE_ID=2  OEM_INTEGRA_ID=3  OEM_BMV_ID=4
OEM_VIS_ID=6     OEM_D8NOTEBOOTK_ID=7  OEM_SMART_ID=8  OEM_ECCOSCAN_ID=9
OEM_CCOSON_ID=10 OEM_IVET_ID=11  OEM_HBKQ_ID=12  OEM_ECHONECT_ID=13
OEM_FASVET_ID=14 OEM_DRAMINSKIVET_ID=15  OEM_SONOSTAR_ID=16  OEM_OPU_ID=17
OEM_MADINA_ID=18 OEM_BD_ID=19

機型行為差異由資源開關控制,例如獸醫模式:R.string.def_vet"on"GetPatientMng() 返回 HAnimalMnggetExamByID()AnimalExam

Dependencies

  • Used by: manifest(SplashActivity 為 LAUNCHER;BaseActivity/LandActivity/… 全部 exported=falseSplashActivityThirdPartyManageActivity
  • Uses: handprobe.application.handprobe.components.kernel.jni.UI.*

Notable Patterns / Gotchas

  • 全部主界面 screenOrientation="landscape"configChanges 全開(旋轉不重建)
  • ThirdPartyManageActivity 是唯一顯式 exported 的 Activity(Theme.Translucent),支持第三方 App 嵌入超聲功能——MyApplication.returnThirdParty() 回傳結果
  • LandQ62Activity/LandQ6ActivitysharedUserId="android.uid.system"(需系統簽名安裝——典型 OEM 預裝設備)
  • Crash handler 會 finish() 所有 Activity 並啟動 com.android.launcher3,醫療設備場景下防止卡死在崩潰畫面
09

模組 — components 可重用組件

Module: components — 可重用組件

handprobe.components(319 檔)是與超聲成像解耦的可重用子系統:DICOM、檔案、顯示、輸入、串口、音頻、網絡等。

Responsibilities

按子包:

子包 職責
dicom/ DICOM 服務全家桶:Server、Storage、Worklist、MPPS、Print、Local + JDicom* JNI 包裝 + UnitConverter
file/ 檔案系統抽象:PodFilesSys(探頭數據目錄)、AssetsFile、檔案選擇器 fileselect.FileManage
display/ 第二顯示器支持(SecondDislay
input/ 鍵盤/軌跡球/觸摸輸入
serialport/ 串口設備(掃描槍/身份證等外設)
usb/ USB 設備:HS5pRGBIf(RGB 輸出機型)
audio/ 音頻:AudioPlayer + voice/vosk/VoskSpeechRecognizer(語音輸入)
opencv/ OpenCV Java 封裝
network/ 網絡工具
wifi/ Wi-Fi 管理
BroadCast/ 廣播接收
PatientInfo/ UserInfo/ 患者/用戶數據模型與 DB(ExamAnimalExamUserLoginInfo
task/ TaskManager 後台任務線程
permission/ 權限申請封裝
ultrasys/ 成像共享部件:FrameLineData(幀線數據 + FileManager)、cine/CinePartitionBufferCinePlayerMBCinePlayerICineIteratorMCineRollBuffer)、parameter/BScanPara/CScanPara/MScanPara/PWScanPara/MITIData…)
utils/ AppUtilFileOperateLanguageUtilLogUtilQlogUtilScreenUtilToastUtil
widget/ 自定義控件(HEditTextHArrayAdapter…)

Key Files

  • dicom/JDicomServer.java — DICOM 服務 JNI 入口(native libdoppler 內建 DICOM 棧)
  • file/PodFilesSys.java — 探頭數據目錄管理(App 外置存儲佈局)
  • file/fileselect/fileselect/FileManage.java — manifest 註冊的檔案管理 Activity
  • PatientInfo/db/Exam.javaAnimalExam.java — xUtils ORM 患者檢查表
  • ultrasys/cine/MCineRollBuffer.java — cine 環形緩衝(錄製回放)
  • ultrasys/parameter/MITIData.java — MI/TI 熱指數數據 + Observable

Dependencies

  • Used by: handprobe.application.com.Com.
  • Uses: org.xutils(ORM)、jni.(JDicom)、org.opencv、Vosk(org.vosk

Notable Patterns / Gotchas

  • DICOM 棧實際在 nativeJDicom* 全是 native 方法),Java 側只是參數對象(DicomPresetParaDicomMPPSServicePara)與生命周期管理
  • 患者數據庫同時支持人(Exam)與動物(AnimalExam)兩套表,獸醫模式在查詢層切換
  • MCineRollBuffer 是內存敏感的環形緩衝——掃描過程全幀緩存於 RAM(largeHeap="true" 為此開)
  • 語音識別走 org.vosk(離線),模型在 assets/vosk/(中/英 small 模型,~139 MB)
10

模組 — AI 輔助診斷與語音

Module: ai — AI 輔助診斷與語音

handprobe.application.ai(算法/診斷)+ handprobe.components.ai(推理引擎封裝)實現了完整的 AI 輔助診斷功能集。

Responsibilities

  • 診斷測量:單次觸發 → 模型推理 → 輪廓/數值疊加顯示
  • 推理引擎封裝:PyTorch(libpytorch_jni)與 TensorFlow Lite(libtensorflowlite_jni + GPU delegate)
  • 離線語音控制:Vosk 中/英文識別,語音指令操作掃描界面

診斷功能清單(application/ai 實測)

功能
BladderMeas 膀胱體積測量
CardiacAutoEF 心臟射血分數(EF)自動計算
LungBLineDetector 肺部 B 線檢測(新冠肺炎/肺水腫場景)
ObHcMeas 產科頭圍(HC)測量
PcosDiagnose 多囊卵巢綜合徵診斷
PwAutoPositionCarotid(0) 頸動脈 PW 自動取樣門定位
ThyroidDiagnose 甲狀腺診斷
NerveSegment 神經分割(麻醉導引)
SkinSegment 皮膚層分割
VaricosityMeas 靜脈曲張測量
AssistDiagnose 通用輔助診斷
AiFuncMng / AiMeasBase AI 功能管理 / 測量基類
UltraSpeechRecognizer 語音識別入口(包裝 Vosk)

Key Files

  • application/ai/AiFuncMng.java — AI 功能註冊/調度
  • application/ai/UltraSpeechRecognizer.java — 語音指令識別(+ components/audio/voice/vosk/VoskSpeechRecognizer.java 實際 Vosk 調用)
  • components/ai/AiModelBase.java — 模型基類(加載/推理生命周期)
  • components/ai/MaskProcessor.java + Mask3Diam / MaskInnerDiameter / MaskUtils / LineSegment — 分割掩膜後處理(直徑/輪廓擬合)
  • components/ai/pytorch/components/ai/tensorflow/ — 引擎適配
  • components/ai/ImageUtils.java / AiData.java — 影像預處理

推理鏈

UI 觸發 → AiFuncMng → AiMeasBase 子類(ROI 選擇、預處理)
        → MaskProcessor/ImageUtils → pytorch / tensorflow 引擎
        → libpytorch_jni / libtensorflowlite_jni
        → Mask 後處理(擬合直徑/面積)→ 數值疊加顯示

Dependencies

  • Used by: gui.fragment.ImageDisplayFragment(影像界面集成)
  • Uses: org.pytorchorg.tensorfloworg.voskultrasys(取當前幀)

Notable Patterns / Gotchas

  • 模型體積:libpytorch_jni.so 達 76 MB(打包了 PyTorch 運行時)
  • TFLite 帶 GPU delegate(libtensorflowlite_gpu_jni.so),OpenCL 加速
  • Vosk 模型在 assets/vosk/:中文 vosk-model-small-cn-0.22、英文 vosk-model-small-en-us-0.15(含 graph/HCLr/Gr.fst、am/final.mdl、ivector)
  • AI 測量在 UI 線程外異步跑,結果以 Observer 回調刷新
11

模組 — 探頭連接與網絡

Module: network-probe — 探頭連接與網絡

探頭連接(USB / Wi-Fi)與 DICOM 網絡是 SonoiQ 的兩條外部通路。

探頭連接(handprobe.application.usb + wlan

7 種連接類型(ultrasys.probe.ConnectType):

常量 含義
NONE_CONNECT_TYPE 0 未連接
SIMU_CONNECT_TYPE 1 模擬(無探頭演示)
WLAN_CONNECT_TYPE 2 Wi-Fi 探頭
WUSB_CONNECT_TYPE 3 無線 USB
USB_CONNECT_TYPE 4 USB 有線
UWLAN_CONNECT_TYPE 5 另一種無線模式
RGBIF_CONNECT_TYPE 6 RGB 接口機(HS5pRGBIf
BROADCAST_CONNECT_TYPE 7 廣播

切換時 ConnectType.SetConnType() 同步 HSysctrlIf.SetProbeConnType(int) 並刷新 UI 搜索按鈕狀態、發送 AMSG_CONNECT_CHANGE

Key Files

  • usb/UsbProbeManager.java — USB bulk 傳輸單例(Write/Read/ReadEncImg/GetImgPack
  • usb/jnitojava.java — native 回調入口(見 kernel-jni 模組)
  • wlan/protocol/ — 探頭通信協議(ToolsJsonParam
  • wlan/wlanprobe/WlanProbe.java + ProbeWifiInfo.java — Wi-Fi 探頭發現/連接
  • wlan/wifi/ — Wi-Fi 管理

DICOM 網絡(handprobe.components.dicom

全部 DICOM 邏輯在 native libdoppler 內(JDicom* 類為 JNI 包裝):

  • JDicomServer — 本地 DICOM 服務
  • JDicomStorageService — 存儲(SCU 上傳 PACS)
  • JDicomWorklistService — 工作列表(Modality Worklist)
  • JDicomMPPSService — 檢查狀態回報(MPPS)
  • JDicomPrintService — DICOM 打印
  • JDicomLocal / JDicomService — 本地/服務基類
  • 參數:handprobe.application.preset.DicomPresetParaDicomMPPSServicePara

其他網絡

  • application/network/ — 通用網絡(激活/升級類)
  • components/network/ — 網絡工具
  • Manifest 權限:INTERNET、CHANGE_WIFI_STATE、ACCESS_WIFI_STATE、CHANGE_NETWORK_STATE(無 BLUETOOTH 實際使用——聲明遺留)

Dependencies

  • Used by: Ultrasys(連接狀態)、SplashActivity(啟動 DICOM 服務)、BaseActivity(offWifi)
  • Uses: jni.HSysctrlIfkernel.*

Notable Patterns / Gotchas

  • MyApplication.onActivityDestroyed 會在 BaseActivity 銷毀時 mMainActivity.offWifi()——離開主界面即斷 Wi-Fi 探頭
  • ThirdPartyManageActivity 回傳時 ProbeWifiInfo.DisconnectWifi() 強制斷開,防止第三方 App 期間佔用探頭
  • ReadEncImg 表明 USB 影像流是加密/編碼傳輸(WUsbReadImg),細節在 native 側
12

模組 — 預設與測量

Module: preset-measurement — 預設與測量

掃描參數預設(preset)與臨床測量(measurement)是超聲設備的兩大業務核心。

Preset(handprobe.application.preset

  • PresetServer.java — 預設服務(加載/保存/分發)
  • PresetData.java / PresetParams.java — 預設數據模型;MITIObservable 熱指數觀察
  • PresetFile.java / PresetFileManipulation.java / SystemPresetFile.java — 預設檔案持久化
  • ExamMode/ + ExamModeAliases.java — 檢查模式(腹部/心臟/產科/血管…)與別名
  • ExamToPid.java — 檢查模式 → 探頭 PID 映射
  • ExamImg/ExamImgParaPerMode.java — 每模式影像參數(含 ProbeParameter 嵌套)
  • DicomPresetPara.java / JDicomPresetPara.java / DicomMPPSServicePara.java / NetworkPresetData.java — DICOM/網絡配置
  • DisableProbeBtnPreset.java — OEM 禁用按鈕配置

預設資產:assets/presetPara/(16 檔)、assets/SonoiQ6/assets/SonoiQ6Vet/assets/SonoiQVet/assets/SonoiQ/(型號默認預設)。

Measurement(handprobe.application.measurement

  • MeasManager.java — 測量總管
  • MeasureLib.java / VetMeasureLib.java — 人/獸醫測量庫(MeasurePackage/VetMeasurePackage/
  • MeasurePreset.java / MeasOBCalcPreset.java — 測量預設
  • MeasAutoCalc.java / MeasVolCalc.java — 自動計算/體積計算
  • MeasResultMng.java — 結果管理(報表輸出)
  • ExpressionParser.java — 測量公式解析(Java 側;native 版見 jni.MeasFormulaParser
  • MeasureView/measurebase/measMenu/ — 測量 UI
  • MEASID.java — 測量 ID 常量
  • obInfo/ — 產科信息:OBInfoMngOBTableUtilTestOBTable(GA 孕周/生長曲線查表)
  • PWAutoCalc/ — PW 多普勒自動計算

產科表資產

assets/OB_table/ 177 檔(9.6 MB),按標準命名:CHINACHITTY(DERIVED)CHITTY(O-O)CFEFBERTAGNOLIASUM(澳洲超聲醫學會)等——各國/各學會胎兒生物測量參考表。

Dependencies

  • Used by: Ultrasys(MITI、PresetServer)、gui.*(測量 UI)、報告系統
  • Uses: jni.HPresetIf / HPidIf(native 側預設/探頭查詢)、xutils(JSON 序列化)

Notable Patterns / Gotchas

  • 預設檔案在 native 與 Java 兩側都有概念:Java 管持久化/UI,native(HPresetIf)管實際掃描參數生效
  • VetMeasureLibMeasureLib 平行——獸醫測量套件獨立維護
  • 產科孕周計算依賴 OB_table 參考表,跨地區合規需正確選表
13

模組 — 資產、固件與數據庫

Module: data-assets — 資產、固件與數據庫

APK 解壓 482 MB 中,assets/(298 MB)承載了探頭描述庫、FPGA bitstream、AI/語音模型與臨床參考表。

資產目錄實測統計

路徑 大小 內容
assets/pid/ 147.9 MB 探頭描述庫與 FPGA 固件(見下)
assets/vosk/ 139.2 MB Vosk 中/英文離線語音模型
assets/OB_table/ 9.6 MB 177 個產科參考表
assets/user/ 7.1 MB 說明書 PDF(English / Russian / Chinese)
assets/fw/board/ 4.0 MB 板卡固件
assets/SonoiQ6*/SonoiQVet/SonoiQ/ ~1.3 MB 各型號默認預設配置
assets/presetPara/ 0.4 MB 預設參數
assets/DigTplXmlEs/En/Ch 0.8 MB 報告模板 XML(西/英/中)
assets/BodyMark/ 0.1 MB 261 個體表標記 PNG(腹部/心臟/產科/血管…)
assets/CommentMenu/ 0.1 MB 註釋菜單
assets/dicom/ 0.1 MB DICOM 配置
assets/reports/ 報告模板

assets/pid/ — 探頭固件核心

PID03.zip              140.8 MB   探頭描述庫(HPidIf.setPidFilesPath 目標)
FPGA_ch16.rbf           718 KB    Intel/Altera FPGA bitstream v1
FPGA_ch16_2/3/4/6.rbf   718 KB×4  同族 bitstream
FPGA_ch16_5.rbf          3.5 MB   (最大版本)
BMP.bin / PMP.bin / VMP.bin / dPMP.bin   B/M/PW 模式參數表
Tintmap.bin                       色調映射表
gsl3670.fw                         觸摸屏控制器固件(Silead GSL3670)
GlobalVersion.txt                  全局版本號
FileCopy.bat / FileCopyAll.bat     (Windows 工具殘留——開發者拷貝腳本)

Native 庫(lib/arm64-v8a,149.6 MB)

大小 用途
libpytorch_jni.so 76.1 MB PyTorch 推理
libopencv_java4.so 19.9 MB OpenCV
libdoppler.so 15.4 MB 超聲核心(PID/參數/DICOM/DSC/系統控制)
libvosk.so 8.9 MB 語音識別
libavcodec-56.so 等 FFmpeg 全家 ~12 MB 視頻編解碼(cine 導出)
libmodpdfium.so / libjniPdfium.so 6.1 MB PDF 渲染(報告)
libtensorflowlite_jni.so + GPU 7.3 MB TFLite
libh264rtp.so 108 KB H.264 RTP
libhopencvif_lib.so 3.2 MB OpenCV JNI 封裝
libfpgargb_lib.so 14 KB FPGA RGB 輸出
libHSocketIf.so 14 KB USB/串口 Socket
libjnidispatch.so 158 KB JNA

數據庫(運行時創建,非 APK 內)

DB 位置 用途
sonoiq.db UserInfo/database/ 用戶與登錄
sonoiq.db OperatorAuth/database/ 操作者授權
sonoiq.db 外部存儲(FileStorageMng 患者/檢查記錄(xUtils ORM)

Notable Patterns / Gotchas

  • PID03.zip 佔 APK 近半——單一探頭描述庫壓縮包(zip 內未壓縮的位圖/參數表)
  • FileCopy.bat/FileCopyAll.bat 隨 assets 打包是明顯的開發者疏忽(含拷貝命令,可用於推測出廠燒錄流程)
  • FPGA bitstream 同族多版本(_2/_3/_4/_5/_6)暗示現場可切換 FPGA 配置
  • 資源目錄 res/ 全面混淆(短隨機檔名),assets/ 未混淆——敏感內容(PID/固件/模板)都在 assets
14

USB 探頭協議

USB 探頭協議(靜態重構)

來源:UsbProbeManagerHUsbProbeInterfaceHSUsbProbe(jadx)+ libdoppler/libHSocketIf/libh264rtp 導出符號。純靜態推斷,未經硬件驗證。

1. USB 設備層

證據
VID 0x04B4(1204) HUsbProbeInterface.PROBE_VID
PID 0x1004(4100) HUsbProbeInterface.PROBE_PID
VID 含義 Cypress Semiconductor 0x04B4 為 Cypress 註冊 VID → FX2/FX3 系 USB 控制器
控制傳輸 EP0,setup 8 B / buffer 64 B USB_EP0_BUF_LEN=64USB_SETUPDATA_LEN=8
數據傳輸 EP6 bulk,FIFO 2,048 B USB_EP6_FIFO_SIZE=2048(Cypress FX3 特徵)
設備枚舉 動態掃描(不硬編碼 VID/PID 過濾),分三路 ScanHUsbProbe/ScanHSUsbProbe/ScanHRProbe

探頭驅動類譜系(handprobe.components.usb):

HUsbProbe (基類)
├── HSUsbProbe ── HSUsbProbeCh16   (FPGA ch16 系列,mProbeType=4)
├── HRUsbProbe                     (R 系列)
├── HRTLUsbProbe                   (Realtek 方案)
└── HCyRTLUsbProbe                 (Cypress/Realtek 組合?)

2. FPGA 地址空間(影像流讀寫直接定址)

常量 用途
FPGA_BIMG_ADDR 131,072(0x20000) B 模式影像
FPGA_CIMG_ADDR 131,072 C(Color)影像
FPGA_H264_ADDR 262,144(0x40000) H.264 編碼流
FPGA_TIMG_ADDR 393,216(0x60000) T/M 模式影像

影像幀尺寸:B/C 各 131,072 B(128 KB);M 8,192 B;PW 2,048 B;T 10,240 B。

RF 採集模式(mRFGather)讀 2,097,152 B(2 MB 全緩衝 mImgBuf)。

3. 讀取循環(USB_ImgReadThread,priority=10)

loop:
  CheckImgUpload() → bitmask
    bit0 (1): B 影像就緒
    bit1 (2): M/PW 就緒
    bit2 (4): C 影像就緒
  ConType 3 (WUSB):
    ReadBCImage → HH264PlayerIf.WUsbSignalBCImg   (H.264 流解碼路徑)
  ConType 4 (USB):
    probeType==6: GetImgPack → HH264PlayerIf.RUsbSignalImgReady
    else:
      Read(0x20000, mImgBuf, RF?2MB:128KB)
      mImgBuf[0] < 0  →  HDscClassIf.Reserse(buf, 131072)   ← 方向標記!
      (SIMU 模式: simdat++ 填 ramp 假數據)
  DispMode 分支:
    5  → Read(0x60000, mTImgBuf, 2048)   → PW 隊列 → HH264PlayerIf.PackD
    9  → Read(0x60000, mMImgBufCache, 10240) → 4×2048 分包 → HH264PlayerIf.PackM
    8/55 → Read(0x20000, mCImgBuf, 131072) → 拼入 mImgBuf[131072..262144]

「EncImg」真相

jnitojava.WUsbReadImgUsbProbeManager.ReadEncImgmUsbInterface.ReadBCImage()

「Enc」= BC(B+C 合成)影像的縮寫,不是加密。安全結論:USB 影像流為明文位元組流;幀首 byte 最高位是影像方向(正/反)標記(負值 → HDscClassIf.Reserse 整幀反轉),無加密層。

4. 寫入路徑(FPGA 寄存器/參數)

  • Write(addr, data, len) → 隊列 UWlanWriteQueueElem{addr,data,len} → 寫線程:
  • USB(ConType 4):直接 mUsbInterface.Write
  • UWLAN(ConType 5):WlanProbe.mICmdHandler.UWriteFPGA(addr,data,len)TCP_PARA 時走 UWriteFPGA_TCP(TCP 通道傳 FPGA 寄存器寫)
  • WLAN 讀取:UReadFPGA(addr, out, len) / UReadFPGA_TCP,1.5 s 信號量超時

5. 探頭授權體系(EEPROM 功能位)

HSUsbProbe 定義探頭 EEPROM 授權地址表:

EEPROM 地址 內容
32,764 探頭序列號(EEPROM_SN_ADDR
28,160 探頭 SN(64 B 讀取)
28,192 板卡 SN
28,312 OEM ID(App 側 CheckOEMId(GetProbeOemId()) 強制匹配)
28,224 DICOM 功能授權
28,240 PW 多普勒授權
28,256 Color 授權
28,288 B 模式授權
28,308 人/獸醫模式授權
  • VerifyAuth():讀 EEPROM SN + 探頭 SN(首 byte 合法性檢查 !=-1 && !=204)→ 拼接 SN-XXXX 標識 → 授權
  • WriteAuthCode() / vendor request VR_USB_SET_AUTH=24 → 寫回授權(廠商售後解鎖通道)
  • HSUsbProbeCh16.VerifyAuth() 直接 return true(ch16 探頭無 SN 認證——可能 ch16 系無 EEPROM 或授權移至 App 側)
  • UpdateDicomAuth():DICOM 授權經 USB 通道更新

商業模式解讀

功能授權(B/C/PW/DICOM/人獸醫)儲存於探頭硬件 EEPROM,而非 App:換 App/設備不能解鎖高階功能;廠商經 vendor request 寫碼解鎖。OEM ID 鎖死探頭與白牌 App 的匹配(探頭不能跨品牌用)。

6. Wi-Fi 探頭協議(handprobe.application.wlan

  • 命令框架:CmdParser / CmdInfo / BaseCmdHandlerCmdHandler / WlanCmdHandler
  • 傳輸:WlanHost / WlanClient / WlanProbe(多主機 GetMultiHost()
  • 序列化:protocol.Tools(Hex/ASCII 轉換、StrToHex_rIntToByte_o——大小端雙向)
  • 雙通道:UDP 命令 + 可選 TCP_PARA(TCP 參數通道)
  • JsonParam 標籤:Tag_probe_id 等 JSON 配置項

7. 未解(需硬件/動態)

  1. CheckImgUpload 的具體實現(寄存器 or EP 中斷)
  2. H.264 流格式(WUsbSignalBCImg 輸入的碼流封裝)
  3. vendor request 完整碼表(除 24/81 外)
  4. WLAN 命令碼錶(CmdInfo 枚舉值)
15

Native 分析

Native Analysis — libdoppler / libHSocketIf(靜態)

工具:rabin2 6.2.0 + strings(macOS)。樣本為 stripped aarch64 ELF,但 JNI 導出符號完整保留,無需 IDA 即可重建完整 JNI 面。

libdoppler.so(15.4 MB,超聲核心)

  • ELF aarch64,dynamically linked,stripped(BuildID 157510dc…43b0
  • 導出符號 35,618 個,其中 381 個 JNI 導出Java_*),按類分佈:
JNI 類 導出數 職責
jni.HSysctrlIf 91 掃描系統控制(增益/焦點/TGC/頻率/功率)
jni.HBeParamIf 77 波束形成參數(B 模式)
jni.HDscClassIf 69 掃描轉換/影像處理
jni.HFeParamIf 54 前端參數
jni.HPidIf 34 探頭描述(PID)
jni.HDicomIf 33 DICOM 棧
jni.HPresetIf 8 預設
jnitojava 2 JavaGetMessage(@0x0061dd28, 244B)+ SetJniEvn
jni.MeasFormulaParser 1 測量公式(tonibolan

關鍵符號(地址實測)

Java_handprobe_application_usb_jnitojava_JavaGetMessage  0x0061dd28  244 B
Java_jni_HDscClassIf_ImgBufToDispBuf                     0x0062464c  804 B
Java_jni_HDscClassIf_CPreProc                            0x006229d8  1536 B
Java_jni_HDscClassIf_PwPostProcessAndSaveCine            0x0062342c  592 B
Java_jni_HDscClassIf_SearchPwMaxOfArea                   0x006262c8  332 B
Java_jni_HDscClassIf_hash_1JSHash_1forHuman              0x006251dc  136 B
Java_jni_HDicomIf_GenPatientID                           0x006616a0  228 B
Java_jni_HDicomIf_StorageServiceCommit                   0x00660774  116 B
Java_jni_MeasFormulaParser_tonibolan                     0x007be2d4  496 B

Strings 證據

  • DICOM 棧完整內嵌:數百個 DICOM tag 名(AcquisitionContextDescriptionCurveDataDescriptor…)+ 標準 UID(1.2.840.10008.*
  • 私有 UID root1.2.156.162458.1.1.0.11.2.156.162458 為廠商私有註冊)
  • 字符串 hash 全家桶JSHash/FNVHash/DJBHash/DEKHash/BPHash/BKDRHashHDscClassIf.hash_*)——經典 hash 表 lookup(資源名/配置 key 匹配),暗示 native 側有大量字符串驅動的配置表
  • 未發現明文字符串級別的 AES/DES/RC4 標識(crypto grep 命中的全為 DICOM tag 名)

libHSocketIf.so(14 KB)

  • 導出:Java_jni_SerialPort_open/close/config/constructFileDescriptor(1.2 KB 的 open 是主體)+ Java_jni_InputUtils_*(8 個:moveMouse/clickMouse/wheelMouse/AbsMouse/PressMoveMouse/sendKeyCodeUp/Down/open/close)
  • SerialPort_open"Opening serial port %s with flags 0x%x"/dev/tty* 串口 + Ljava/io/FileDescriptor 回 Java
  • InputUtils設備級輸入注入(鼠標/鍵盤事件注入系統)——OEM 專用設備(如軌跡球面板)場景
  • 編譯鏈:Android clang 9.0.9(based on LLVM 9.0.9svn)

其他 native 庫要點

觀察
libpytorch_jni.so(76 MB) PyTorch 運行時全打包,AI 推理主引擎
libtensorflowlite_jni.so + _gpu_jni TFLite CPU + GPU(OpenCL)雙後端
libvosk.so(8.9 MB) Kaldi 系語音識別
libh264rtp.so HH264PlayerIf:USB 影像流 H.264 解碼(WUsbSignalBCImg/RUsbSignalImgReady/PackD/PackM
libfpgargb_lib.so fpgargb_lib:FPGA RGB 輸出 + bindToCpu(7)(LandQ6 系列綁定 CPU7)+ byteswap(RBF 位元組序轉換)
FFmpeg 56 系列 cine 導出視頻編碼(x264 在內)
libmodpdfium/libjniPdfium PDF 渲染(說明書/報告)

未解部分(需 IDA/Ghidra 動態或深挖)

  1. JavaGetMessage 內部消息協議(what/arg1/arg2 的語義表)
  2. 波束形成/掃描轉換核心算法(CPreProc 1.5 KB 等大函數)
  3. hash_*forHuman 系列用途(hash 表 key 生成)
  4. DICOM 棧的具體實現(自研 vs 內嵌第三方——tag 名集合顯示是完整自研/集成棧)
16

功能授權體系

Feature Authorization System — 功能授權體系完整解碼

靜態重構(jadx + native 符號)。授權算法分佈:**驗證邏輯 Java 側,hash 算法 native 側(HDscClassIf.hash_*)**。

1. 總覽:三種探頭硬件,三種授權存儲

探頭類 硬件 授權存儲 驗證方式
HSUsbProbe(S 系列 USB) Cypress USB EEPROM 地址表 hash(SN) == EEPROM 授權碼
HRUsbProbe(R 系列 RGB 機) RGB 接口 + SD SD 卡 block 2-12 hash(SN) == SD 授權碼
HRTLUsbProbe(Realtek 系) Realtek 寄存器ProbeReg(4) 單 byte) bitmask 直讀
HUsbProbe(基類)/ HSUsbProbeCh16 VerifyAuth() 硬編碼 return true

2. 授權算法(HRUsbProbe / HSUsbProbe 通用)

授權碼 = 對探頭 SN 字符串計算 hash(native libdoppler 內實現):

功能 hash 存儲位置(R: SD block / S: EEPROM addr)
B 模式 DJBHash block 6 / EEPROM 28,288
Color BKDRHash block 4 / EEPROM 28,256
PW 多普勒 FNVHash block 3 / EEPROM 28,240
DICOM BPHash block 2 / EEPROM 28,224
人/獸醫切換 JSHash block 8 / EEPROM 28,308
使用時間/計次 DEKHash block 12(隨機數+計數器)

全部係經典公開 hash 算法(DJB2/BKDR/FNV-1/BP/JS 均為標準教科書算法)。SN 格式:"--"

3. 關鍵漏洞/後門(靜態證據)

3.1 SN 清零自動全授權(HRUsbProbe.VerifyAuth)

boolean z = b == 0 && bArr3[1] == 0;   // SN 頭兩字節為 0 = 未初始化
int iReadAuthCode = ReadAuthCode(4);   // Color 授權碼
if (iReadAuthCode == 0 && z) {
    WriteAuthCode(4, iHash_BKDRHash_forColor2);  // ← 自動寫入正確授權碼!
}

Color/DICOM/PW 三項都有同樣邏輯:授權位為 0 且 SN 未初始化 → 自動寫入 hash(SN) 全功能授權。(推測:出廠自檢/新 SN 燒錄流程,但可被利用——清 SN 頭 2 字節 + 清授權位 = 全解鎖)

3.2 VerifyAuth 硬編碼 true

  • HUsbProbe.VerifyAuth()(基類):return true
  • HSUsbProbeCh16.VerifyAuth()return true(ch16 型號完全無 SN 認證)
  • 但功能位仍由 EEPROM hash 驗證(HSUsbProbe.VerifyAuth 內 680-706 行)——ch16 跳過嘅係 SN 格式檢查,唔係功能位

3.3 萬能 OEM ID = 255

public Boolean GetOEMAuth(int probeOemId) {
    if (probeOemId != 255) { ... if (probeOemId != appOemId) return false; return true; }
    return true;   // ← 255 對任何 App 一律通過
}

另有 IsCheckOEMID(appOemId) 開關:App 側可配置完全跳過 OEM 驗證(此時 appOemId 視為 1)。

3.4 授權寫入通道公開

  • vendor request VR_USB_SET_AUTH=24WriteAuthCode() 明文寫入,無簽名無加密
  • WriteEEprom(81, addr, code, 4):S 系列寫通道
  • WriteSD(block, ...):R 系列寫通道
  • 31-byte bulk 命令格式已解碼:{GET_EEPROM_REG3, 'S','B','C', 24,24,24,24, len32, 0x80,0,10,40,0, addr32, 0, len16, 0...}

4. 隱藏功能清單

功能 證據 狀態
使用時間/計次授權(租賃模式) Readrandomandnum(12)SetUseTimeNumhash_DEKHash_forUseTime;Ultrasys.mUseTimeAuthCode 檢查點在 native(Java 層只有 set/get)
獸醫等級 0-6 ReadVetIdbArr[0]-48(ASCII '0'-'6')→ SetVet(i) + WLAN msg 100;RTL 用寄存器 bit3-5 7 級獸醫模式
NoExpand(影像擴展鎖) RTL mNoExpand = (b & 128)>>7 + WLAN msg -95 硬件位
FPGA 數據寬度 8/16 bit SendMsg 16/17 → SetDataWidth(8/16) RGB 機型
FPGA 電源/配置控制 SendMsg 18-21 → SetFPGANConfig/CheckFPGANStatus/CheckFPGANConfigDone/SetFPGAPower RGB 機型
RF 原始數據採集 mRFGather → 讀 2,097,152 B 原始 RF 工程/研究模式
多主機模式 WlanProbe.GetMultiHost() == 2 一探頭多設備(教學?)
FPGA 現場升級 外部 FPGA_ch16_5.rbf 覆蓋 assets 版 + byteswap 升級通道
模擬模式 IsSUsbSimOpen() → ramp 假數據 無探頭演示
第三方 App 集成 ThirdPartyManageActivity(exported)+ returnThirdParty SDK 模式
DICOM 獨立授權更新 UpdateDicomAuth() 經 USB 售後 DICOM 開通

5. 安全評估

  • 保護強度:授權碼演算法為公開經典 hash + SN 明文 → 可完全離線計算。真正的防護只係「物理訪問探頭存儲」嘅門檻
  • 無簽名、無非對稱加密、無挑戰應答 → 無防重放、無防偽造
  • UseTime 計時授權若在 native 檢查,需進一步動態驗證(Frida hook SetUseTimeAuthCode 後觀察 native 行為)
  • 商業影響:EEPROM/SD 讀寫器 + SN 讀取 → 全功能解鎖;OEM ID 255 或關閉 IsCheckOEMID → 跨品牌通用
17

JNI API Reference

API Reference — JNI / Native Surface

SonoiQ 的「公共 API」本質上是 jni.* 包暴露的 native 接口。以下按功能分組(全部實測自 jadx 反編譯)。

探頭與 PID — jni.HPidIf(→ libdoppler

public static native boolean InitPidSys();
public static native boolean setPidFilesPath(String path);      // assets/pid/PID03.zip
public static native int GetProbeId(String str);
public static native String GetProbeName(int i);
public static native int GetProbeType(int i);
public static native int GetProbeElementNumber();               // 陣元數
public static native float GetProbeRadius();                    // 凸陣半徑
public static native float GetProbeElementDistance();           // 陣元間距
public static native int GetProbePowerType();
public static native float GetFreqValue(int i, int i2);         // 頻率
public static native float GetPwFreq(int i);
public static native float GetFocusDepth(int i);
public static native int GetBSSR(int i, int i2);                // B 模式採樣率
public static native int GetMaxScanArea();
public static native float GetDeflectAngle(int i, int i2);      // 偏轉角
public static native float GetCardiacExpand();
public static native float GetExpandAngle();
public static native int GetDblPlanFlag();

掃描系統控制 — jni.HSysctrlIf(→ libdoppler

public static native void DopplerAppInit();
public static native void DoStartScan();
public static native void DoStopScan();
public static native void Freeze();
public static native void CalcScanFrame();
public static native void RFGather();                           // RF 採集
public static native float GetFrameRate();
public static native void ParaChangeStart(int i);               // 參數批量變更 begin/end
public static native void ParaChangeEnd(int i);
public static native void ParaSave(short s, short s2, String str);
public static native void ImgPresetInit();
public static native int ReadFPGAParam(int i);                  // FPGA 參數讀取
public static native void SetAPowerLevel(int i);                // 聲功率
public static native void SetBFocus(int i);
public static native void SetBFreq(int i);
public static native void SetBGainLevel(int i);
public static native void SetBDynamicLevel(int i);
public static native void SetBDTgc(int[] iArr);                 // TGC 曲線
public static native void SetBExpand(boolean z);
public static native void SetBImgEnhanceLevel(int i);
public static native void SetBSpaceCompSwitch(boolean z);
public static native void SetBFrameCorrLevel(int i);
public static native void SetAdAdjust(int i);
// …(B/C/M/PW 全套 setter,190 行級)

掃描轉換與影像處理 — jni.HDscClassIf(→ libdoppler

public static native void BCJoint(byte[], byte[], int, int[], int[], int[], int);
public static native boolean BEnhance(byte[], byte[], int, int, int, int);
public static native boolean BFrameCorr(byte[], byte[], int, int, int, int);
public static native boolean BreathNoiseDepress(byte[], int, int, int);   // 呼吸噪聲抑制
public static native boolean CFDenoise(byte[], byte[], int, int, int, int);
public static native boolean CFrameCorrelation(byte[], byte[], byte[], int, int, int, int, int);
public static native boolean ColorMap(byte[], int[], int[]);              // 色調映射
public static native void CancelDscTableCalc(boolean z);
public static native int ClipImg(byte[], byte[], int, int, int, int, int, int);

DICOM — jni.HDicomIf(→ libdoppler,native DICOM 棧)

public static native void Init();
public static native void AddStorageServer(int, String, String, String, int);
public static native void RemoveStorageServer(String);
public static native void ResetStorageServerList();
public static native void AddWorkListServer(int, String, String, String);
public static native void RequestWorklist(int, String, String, String, String);
public static native void AddMppsServer(int, String, String, String);
public static native boolean SendMPPS(String aet, String host, String port, String..., int...);
public static native void CommitPrepare(byte[], int, int, int, byte[], int, boolean);
public static native int GenPatientID(byte[]);
public static native void GetUID(HExamInfo examInfo);
public static native void SetHospitalName(String);
public static native void SetDeviceInfo(String, String, String, String);
public static native void SetDateAndTime(String, String);
public static native void AddDefaultServer(String);

USB / 消息橋 — handprobe.application.usb.jnitojava(native 回調入口)

public static native int JavaGetMessage(long[] out);            // out[0]=what out[1]=arg1 out[2]=arg2
public static native void SetJniEvn();
public static boolean UsbWriteDwords(int, byte[], int);         // → UsbProbeManager.Write
public static boolean UsbReadDwords(int, byte[], int);          // → UsbProbeManager.Read
public static int WUsbReadImg(byte[], int);                     // → ReadEncImg(加密影像)
public static int UsbGetImgPack(byte[], int[]);                 // → GetImgPack
public static void UpdateMITI(float[]);                         // → Ultrasys.setMITIData
public static int GetPattern(int, int);                         // → Ultrasys.GetPattern
public static void CineDataCapture();                           // → AppProc.DicomCineDataCapture

其他

  • jni.SerialPort(→ libHSocketIf)— 串口 open/close/read/write
  • jni.HH264PlayerIf(→ libh264rtp)— H.264 解碼播放
  • jni.HOpencvIf(→ libhopencvif_lib)— OpenCV 操作封裝
  • jni.fpgargb_lib(→ libfpgargb_lib)— FPGA RGB 輸出
  • jni.HGLImageJNIViewLib(→ libHSocketIf)— OpenGL 影像視圖
  • jni.MeasFormulaParser(→ libHSocketIf)— 測量公式解析
  • jni.InputUtils(→ libHSocketIf)— 輸入工具
  • jni.HPresetIf / HBeParamIf / HFeParamIf(→ libdoppler)— 預設/波束參數/前端參數
18

附加發現

Additional Findings — 硬件生態與臨床功能全景

第二輪深挖(PID03.zip 內容、固件、HIS API、FPGA bitstream 元數據、hash 反匯編)。全部靜態證據。

1. 硬件身份鎖定

發現 證據
FPGA = Efinix Trion T55F324(非 Intel/Altera) FPGA_ch16_5.rbf 明文頭:Family: Trion / Device: T55F324 / Width: 16 / Mode: passive
FPGA 項目名 YK2100 RBF 頭:Project: D:\Project\FPGA\YK2100_FPGA_T55_new\03_pnr(開發者 Windows 路徑殘留)
品牌 = Youkey OEM_YOUKEY_ID = 1 + YK2100 項目名 → SonoiQ 原始品牌 Youkey,其餘 17 OEM 為白牌
板卡 FPGA 固件 `fw/board/v2 v3/fpga/16 32/UFPGA.bin`(raw bitstream,0xFF 開頭無頭)
WiFi MCU = TI CC3220R / CC3235S `fw/board/v2 v3/mcu/cc32{20r,35s}/mcuimg2.bin;頭 0030 0020` = Cortex-M4 向量表(SP=0x20003000)
固件版本 fpga/16 v3 = 191204001.3.18;PID Global = P25122602_004(2025-12-26 構建)
RBF 工具鏈 Version: 2022.1.226Generated: Wed Jul 31 11:07:04 2024(Quartus/Efinity 2022.1)

2. 探頭型號全集(PID03.zip,77 個型號目錄 + Global + AIModel,6,294 檔 344 MB)

類別 型號例 用途
凸陣 C 03C5-2Ds、04C5-2Ds、05C5-2Vs、06C15s、07C15s、08C15s、C5-2Ds… 腹部/產科
微凸高頻 05C60h、07C50h、07C60h、08C50h、08C60h(h 後綴) 高頻微凸
線陣 L L10-4Fs、L11-4Ds、L12-4Hs、06L40s、07L25h、07L40s、08L40h… 淺表/血管/肌骨
相控陣 P P4-2Fs、04P4-2Ds、05P4-2Ds、07P20s、08P4s 心臟
腔內 E E10-4Ds、E10-4Ks、06E10s、08E10s 婦科/泌尿
複合 05L25E10 特殊
Global BGrayMap / DGrayMap / DXNode / ApodizeCurveLib / CMMParaLib 全局灰階映射+切趾庫

每型號 pattern0/Normal/ 含全套 DSP 參數(.INF 201KB 主描述 + ApodizePara、BlindArea、CodingLut、CosLutPara、DRPara、DSFilterPara、EmitFocusPara…)——波束形成/DSC 完整參數集,換型號=換參數集

3. AI 模型全集(PID03/AIModel/,13 個 .pt,~165 MB)

模型 大小 對應功能
ResUNet_Bladder.pt 23.9 MB 膀胱分割
UNet_Sample400_SegClass_breast_lite.pt 23.9 MB 乳腺分割(ExamTypesId.BREAST_EXAM 有入口)
UNet_Sample180_256x192_Carotid_recognition_v&l.pt 17.7 MB 頸動脈
skin_segment.pt 17.5 MB 皮膚分割
cardiac_ef.pt 13.8 MB 心臟 EF
lung_bline_detector.pt 13.4 MB 肺 B 線
UNet_ob_hc.pt 7.9 MB 產科頭圍
UNet_Sample7000_256x192_Thyroid.pt 7.9 MB 甲狀腺分割
UNet_Varicosity.pt 7.9 MB 靜脈曲張
ovary_pcos_classifier.pt 6.5 MB 多囊卵巢分類
mobilenet_v3_classifier_3_n190_128x96_quantized.pt 4.6 MB MobileNetV3 量化分類器
Thyroid_classifier_3_n7000_256x192_lite.pt 4.6 MB 甲狀腺分類

模型架構命名暴露訓練細節(n190/n7000 = 訓練樣本數、256x192 = 輸入尺寸、v&l = 血管+縱向)。

4. 臨床檢查模式全集(ExamTypesId,40+ 模式)

腹部(成人/兒科/新生兒/穿透)、心臟(成人/兒科/新生兒/胎兒)、產科(OB1/OB23)、婦科、乳腺、頸動脈、IMT(內中膜厚度)、眼科前列腺睪丸、泌尿、腎臟、肌骨、骨科、神經、甲狀腺、外周動脈/靜脈、上肢動脈/靜脈、經顱 TCI、急診套件(FAST/OB/ABD/血管/淺表)。

5. HIS 集成 API(HttpProtocol_hle)

  • 用途:醫院資訊系統對接(basicUserInfo 用戶驗證、addCheckItem 回傳檢查項)
  • 認證:appKey + appSecret + ts + signsign = SHA-1(MessageDigest.getInstance("SHA"))+ Base64
  • 弱點:
  • SHA-1 已過時(碰撞可行)
  • ts=168236588304 硬編碼(2023-04-25,非實時時間戳)
  • HTTPS 證書驗證完全關閉checkServerTrusted 空實現 + hostnameVerifier 恆 true)——中間人可攔截 HIS 憑證
  • 配置:用戶可在 NetWorkPresetFrag 設置服務器/帳號/密碼(http/https 可選)

6. Hash 算法反匯編確認(libdoppler, arm64)

功能 算法 反匯編證據
B 模式 DJB2 mov w21, 0x1505(=5381)+ add w10, w21, w21, lsl 5(×33)+ add w21, w10, c
Color BKDR(乘加) mul w11, w21, w10 + add w21, w11, c
PW FNV-1(乘異或) mul w12, w21, w10 + eor w21, w12, w11

授權碼 = 對 SN 字符串計算以上公開算法,可完全離線重現(Java 一行代碼)。

7. 深挖完成狀態(2026-09-03,7 項全部完成 → docs/deepdive/)

  1. java-getmessage-semantics.md — 消息泵反匯編:元素 {int32 what; int32 arg1; int64 arg2}、std::deque、GetLongArrayElements(0x5e0)/ReleaseLongArrayElements(0x620)、Java 側 arg2 截斷 int32
  2. wlan-command-codes.md — 命令碼全集:0xFE 頭 + cmdid + dir + 2B LE 長度 + 0xFF 尾;D_ 設備碼 128-202 / W_ 參數碼 0-127;端口 8081/8082/8083/8093;hex-ASCII vs raw 編碼;無 checksum
  3. thirdparty-sdk-interface.md — SDK 接口:7 extras key、5 錯誤碼、SAVEIMG_PATH/IMG_PATH 雙通道、PHI 注入/外洩風險(高)
  4. dispmode-values.md — DispMode 全集:0=B 1=M 5=PW 8=BC 9=BM 16/17 雙幅 50/51 多幅 55=BPower 57/58/59=PW 疊加變體;值兼任硬件 scan-mode ID
  5. pid03-inf-format.md — INF 格式:GBK XML 標籤 + 二進制 payload 混合,65 個標籤(發射/接收/波束形成/DSC/PW/MI-TI 全套 DSP 參數結構)
  6. cc32xx-firmware.md — CC32xx:run-from-SRAM 影像(SP=0x20003000,reset 在 SRAM)、serial flash 檔(probeid.txt/uhandprobe.bin/sleep.bin)、AP 命令、v2/v3 差異 49%
  7. efinix-bitstream.md — Efinix:_5 係完整未壓縮 T55 bitstream(3,459,240 B = 27.67Mbit,AN006 max 27.675Mbit 吻合);5 個 718,569 B raw RBF 係 5 個不同板版本(~4.8:1 壓縮/截斷);fpgargb_lib.byteswap 證實為 16-bit 字序反轉
19

深挖 1 — 消息泵反匯編

JavaGetMessage 消息泵反匯編 (C→Java Message Pump Disassembly)

對象:libdoppler.so Java_handprobe_application_usb_jnitojava_JavaGetMessage(0x0061dd28,244 B,arm64) 方法:r2 線性反匯編(避免 full-analysis 卡死),結合 Java 側 kernel.MainHandler 交叉驗證。

一、完整解碼(高置信度)

函數主體 = 從全局單例的消息隊列 pop 一個 16-byte 元素,轉換寫入 Java long[4]

// 語義等價 C 偽代碼
int Java_jnitojava_JavaGetMessage(JNIEnv* env, jclass, jlongArray out) {
    G* g = s_szInstanceNumber;              // 0xe2e000 全局單例指針
    if (g->queue_ready == 0)                // [x22+0x28] == 0
        return 0;                            // 隊列未初始化
    jlong* buf = env->GetLongArrayElements(out, NULL);   // vtable[0x5e0]
    Msg m = g->queue.pop_front();           // 16-byte 元素: d8(2×int32) + x23(int64)
    buf[0] = (jlong)(int32_t)m.what;        // sshll v0.2d, v8.2s → 符號擴展
    buf[1] = (jlong)(int32_t)m.arg1;        // 同一 SSHLL 指令寫兩路
    buf[2] = (jlong)m.arg2;                 // x23 原始 int64 直接存 [x21+16]
    buf[3] 未寫(Java 側只用 0-2)
    env->ReleaseLongArrayElements(out, buf, 0);          // vtable[0x620], mode=0
    return 1;
}

關鍵指令證據

地址 指令 含義
0x61dd40 adrp x22, reloc.s_szInstanceNumber 全局單例(隊列宿主)
0x61dd48-4c ldr x8,[x22,0x28]; cbz x8, 0x61de00 隊列空/未初始化 → return 0
0x61dd60 ldr x8,[x8,0x5e0]; blr x8 GetLongArrayElements(JNI vtable 0x5e0)
0x61dd6c-94 ldr q0,[x22,0x20] + lsr x10,x9,5 + and x10,x10,0x7fff...f8 std::deque map 索引(x>>5 & ~7 每 8 元素一 block)
0x61dd98-9c ldr d8,[x9]; ldr x23,[x9,8] 元素兩字段:d8=2×int32、x23=int64
0x61ddb0 bl operator_delete pop 後析構元素
0x61ddd0 sshll v0.2d, v8.2s, 0 int32→int64 符號擴展(what/arg1 為 signed)
0x61ddd4-98 str q0,[x21]; str x23,[x21,0x10] 寫 buf[0]=what、buf[1]=arg1、buf[2]=arg2
0x61ddec ldr x8,[x8,0x620]; blr x8 ReleaseLongArrayElements(mode=0)
0x61ddf8 mov w0, 1 有消息返回 1

Java 側消費(kernel/MainHandler.java:36-45,已知)

if (jnitojava.JavaGetMessage(jArr) != 0) {
    message.what = (int) jArr[0];
    message.arg1 = (int) jArr[1];
    message.arg2 = (int) jArr[2];   // ← 注意:int64 截斷為 int32!
    // 廣播給 mDopplerMsgHandlerVector 所有註冊 Handler
}

二、結論

  1. 消息元素:C 側 {int32 what; int32 arg1; int64 arg2}(16 B,deque 隊列)
  2. 隊列:std::deque(每 block 8 元素,由 lsr 5 + &~7 證明)
  3. 全局單例s_szInstanceNumber(0xe2e000)——隊列 + 就緒標誌 + 容器頭
  4. 返回值:0=無消息/未初始化,1=成功取出一條
  5. arg2 類型不對稱:native 傳 int64,Java 截斷成 int32((int) jArr[2])——arg2 實際有效位 ≤ 32
  6. what/arg1 為 signed int32 語義(符號擴展證據)

三、缺口(無法靜態確定)

  • 消息生產者:邊個函數 push 入呢個 deque?(需 xrefs to s_szInstanceNumber 或對 0xe2e000 引用做反向分析——stripped binary 下成本高)
  • what 值語義表:消息碼枚舉喺 native,Java 側只係轉發。可以從 Java 側 handleMessagecase 反向歸納(BaseActivityAMSG_* 常量是 UI 側消息,未必同源)
  • 隊列元素構造器未定位(operator_new 16B 分配點可用 axt 0x61dd98 追)

四、對 Frida 的意義

  • Hook JavaGetMessage 的 onLeave + 讀 out 數組(Frida 可以讀 Java 數組)即可獲得 what/arg1/arg2 全流——無需理解 native 隊列
  • 或 Hook MainHandler.handleMessage(Java 側)更簡單:所有消息必經此處
  • 若要篡改消息(例如屏蔽凍結狀態),直接改 jArr 內容即可
20

深挖 2 — WLAN 命令碼

WLAN 探頭命令代碼總表 (WLAN Probe Command-Code Reference)

來源:jadx_out/sources/handprobe/application/wlan/(jadx 反編譯 Java 協定層)。 檔案簡稱對照:protocol/CmdInfo.javaprotocol/CmdParser.javaprotocol/BaseCmdHandler.javaprotocol/CmdHandler.javaprotocol/Tools.javawlanprobe/WlanProbe.javawlanprobe/WlanHost.javawlanprobe/WlanClient.javawlanprobe/WlanCmdHandler.javawlanprobe/JsonParam.javawifi/HWifiReceiver.java

一、封包框架 (Packet Framing)

1.1 傳送端(App → 探頭)— BaseCmdHandler.excute() (BaseCmdHandler.java:52-90)

偏移 欄位 說明
[0] 0xFE W_HEAD = -2 封包頭 (BaseCmdHandler.java:35)
[1] cmdid 命令代碼(1 byte,& 0xFF
[2] dir 方向:0=SET、1=GET、2=DAT、3=PACK
[3..4] len 資料長度(little-endian,len % 256 / len / 256
[5..] payload 見下方編碼規則
末位 0xFF W_END = -1 封包尾 (BaseCmdHandler.java:34)

編碼規則 (payload encoding)

  • dir ∈ {0 (SET), 1 (GET)}:payload 為 hex-ASCII 編碼(每個原始 byte → 兩個 ASCII 十六進位字元),長度 = 2 × len。回傳總長 = 2×len + 6 (BaseCmdHandler.java:64-87)。
  • dir ∈ {2 (DAT), 3 (PACK)}:payload 為 原始二進位,長度 = len。並在結尾把 dir 欄位 [2] 清零 (BaseCmdHandler.java:80-89)。
  • dir == 1 (GET):長度欄位強制 [3]=1 (BaseCmdHandler.java:61-63)。

1.2 接收端(探頭 → App,WLAN UDP)— CmdHandler.PackProcThreadInit (CmdHandler.java:245-280)

注意:接收端「沒有」0xFE 封包頭,與傳送端不對稱。
偏移 欄位
[0] cmdid
[1] dir
[2..3] len(little-endian,mRecvData[2] + (mRecvData[3] << 8)
[4..] data:dir==3 時原始複製;其餘 hex-ASCII 解碼 (Tools.StrToHex)

長度上限檢查:s <= 8189 && s != 0 (CmdHandler.java:247)。

1.3 接收端(探頭 → App,USB 通道)— CmdHandler.WUsbProcThreadInit (CmdHandler.java:494-521)

USB 通道以 0xFE 為同步字節掃描,框架為 [0xFE][cmdid][dir][len_lo][len_hi][data(hex-ASCII)],且資料為 hex-ASCII(每 byte 兩個字元),下一個封包偏移 = 5 + 2×len (CmdHandler.java:519)。

1.4 TCP 通道框架(UWLAN,ConType==5)

TCP 在 UDP 框架前再加 2-byte little-endian 長度前綴[len_lo][len_hi][0xFE][cmdid][dir][len_lo][len_hi][data]。證據:UReadFPGA_TCP / UWriteFPGA_TCP (CmdHandler.java:913-1031) 中 mData[0]=len_lo, mData[1]=len_hi, mData[2]=0xFE, mData[3]=cmdid

1.5 校驗 (Checksum)

無 checksumvarify(DatagramPacket) 為空實作,恆回傳 true (CmdHandler.java:65-67)。完整性僅靠 0xFF 結束字節與長度欄位。

二、通道、埠號與位址 (Channels, Ports & Addresses) — BaseCmdHandler.java:25-35

常數 用途
REMOTE_IP 192.168.254.1 探頭預設位址
REMOTE_HOST_2_IP 192.168.254.199 第二主機位址
REMOTE_BROADCASTER_IP 192.168.254.255 廣播位址
REMOTE_PORT 8081 命令 UDP/TCP 埠
LOCAL_PORT 8082 本機命令接收埠
LOCAL_DAT_PORT 8083 本機資料接收埠(FPGA 讀寫回應)
WLAN_PORT 8093 主機發現/廣播埠

連線型態 (ConnectType.java:10-17):0=無、1=SIMU、2=WLAN(SSID)、3=WUSB、4=USB、5=UWLAN(+SSID+)、6=RGBIF、7=BROADCAST。UWLAN (5) 走 TCP,其餘走 UDP。

三、方向與目標代碼 (Direction & Target Codes)

3.1 方向 CmdInfo.CmdDir (CmdInfo.java:6-14)

常數 意義
W_SET 0 寫入/設定參數
W_GET 1 讀取/請求參數
W_DAT 2 資料(二進位)
W_PACK 3 封包/批量資料(二進位)

3.2 目標 CmdHandler 常數 (CmdHandler.java:41-44)

BROADCASTER_TARGET=255DEVICE_TARGET=0HOST_1_TARGET=1HOST_2_TARGET=2。在 CmdHandler.excute(i, i2, i3, bArr, i4)i4 即目標:2wlanSendToHost2()255wlanBroadcaster()、其餘 → wlanSend() + wusbSend() (CmdHandler.java:87-102)。

四、命令代碼總表 (Command-Code Table)

代碼以十進位表示;(byte) 負值對應無號值:例如 -95 = 0xA1 = 161。 方向縮寫:→P = App→Probe、←P = Probe→App、 = 雙向。

4.1 裝置類命令 D_*(Device,128–202)

代碼 常數名稱 方向 意義 證據
128 D_SET_AP_SSID_NAME →P 設定 AP SSID 名稱 CmdInfo.java:59
129 D_SET_AP_SSID_PWD →P 設定 AP SSID 密碼 CmdInfo.java:60
130 D_SET_BULE_LED →P 設定藍色 LED(藍芽指示燈) CmdInfo.java:61
131 D_SET_WHITE_LED →P 設定白色 LED CmdInfo.java:72
132 D_SET_MCUIMG_CONTENT →P 寫入 MCU 韌體內容 CmdInfo.java:66
133 D_SET_MCUIMG_CONTENT_END →P MCU 韌體內容結束 CmdInfo.java:67
134 D_SET_MCUIMG_SIZE →P MCU 韌體大小 CmdInfo.java:68
135 D_SET_IMX51_IMG_CONTENT →P 寫入 i.MX51 映像內容 CmdInfo.java:63
136 D_SET_IMX51_IMG_CONTENT_END →P i.MX51 映像內容結束 CmdInfo.java:64
137 D_SET_IMX51_IMG_SIZE →P i.MX51 映像大小 CmdInfo.java:65
138 D_SET_PID_CONTENT →P 寫入 PID(產品 ID)內容 CmdInfo.java:69
139 D_SET_PID_RETURN_CONTENT ←P PID 回傳內容 CmdInfo.java:70
140 D_SET_PID_RETURN_FINISH ←P PID 回傳完成 CmdInfo.java:71
141 D_PROBE_FIRMWARE_VERSION ⇄(GET) 探頭韌體版本 CmdInfo.java:51
142 D_WIFI_FIRMWARE_VERSION ⇄(GET) Wi-Fi 韌體版本 CmdInfo.java:83
143 D_HOST_CHECK 主機偵測 CmdInfo.java:44
144 D_WIFI_CHANNEL ⇄(GET) Wi-Fi 通道 CmdInfo.java:82
145 D_PROBE_PID_VERSION ⇄(GET) 探頭 PID 版本 CmdInfo.java:54
146 D_PROBE_FPGA_VERSION ⇄(GET) 探頭 FPGA 版本 CmdInfo.java:52
147 D_UPROBE_FPGA_ADDR →P FPGA 暫存器位址 CmdInfo.java:74
148 D_UPROBE_FPGA_DAT FPGA 暫存器資料讀/寫 CmdInfo.java:75
149 D_PROBE_SN ⇄(GET) 探頭序號 CmdInfo.java:55
150 D_UPROBE_PRODUCTID ⇄(GET) 產品 ID(配對握手) CmdInfo.java:77
151 D_UPROBE_PROBEID ⇄(GET) 探頭 ID CmdInfo.java:76
152 D_SET_CHARGE →P 設定充電狀態 CmdInfo.java:62
153 D_ENABLE_SECOND_HOST 啟用第二主機 CmdInfo.java:40
154 D_IP_ADDR IP 位址 CmdInfo.java:46
155 D_BOARD_VERSION ⇄(GET) 電路板版本 CmdInfo.java:34
156 D_BOARD_SN ⇄(GET) 電路板序號 CmdInfo.java:32
157 D_UPROBE_BOARDID ⇄(GET) 探頭電路板 ID CmdInfo.java:73
158 D_DICOM_AUTH →P DICOM 授權 CmdInfo.java:37
159 D_PW_AUTH →P PW 授權 CmdInfo.java:56
160 D_COLOR_AUTH →P 彩色(Colour)授權 CmdInfo.java:36
161 D_PRJ_OPTION ⇄(GET) 專案選項旗標 CmdInfo.java:50
162 D_LIANYIN_DATA ←P 聯影(Lianmed)資料 CmdInfo.java:47
163 D_WRITE_FLASH_FILE →P 寫入 Flash 檔案 CmdInfo.java:84
164 D_READ_FLASH_FILE 讀取 Flash 檔案 CmdInfo.java:57
165 D_GET_MCUIMG_CONTENT 取得 MCU 映像內容 CmdInfo.java:41
166 D_GET_MCUIMG_CONTENT_END MCU 映像內容結束 CmdInfo.java:42
167 D_GET_MCUIMG_SIZE 取得 MCU 映像大小 CmdInfo.java:43
168 D_HUMAN_VET_AUTH →P 人醫/獸醫授權 CmdInfo.java:45
169 D_DUAL_ACTIVE →P 雙探頭啟動切換 CmdInfo.java:38
170 D_POWERSAVE_TIME ⇄(GET) 省電時間 CmdInfo.java:49
171 D_OEM_ID OEM ID CmdInfo.java:48
172 D_USETIME_AUTH →P 使用期限授權 CmdInfo.java:79
173 D_USETIME_TIME ⇄(GET) 使用期限剩餘時間 CmdInfo.java:81
192 D_BOARD_TEMPERATURE ←P 電路板溫度 CmdInfo.java:33
193 D_BATTERY_LEVEL ←P 電池電量 CmdInfo.java:31
194 D_ADC_PROBE_ID ←P ADC 探頭 ID CmdInfo.java:29
195 D_BUTTON_ID ←P 探頭按鍵 CmdInfo.java:35
196 D_4DPOS_ID ←P 4D 定位 CmdInfo.java:28
197 D_PROBE_ID ←P 探頭 ID CmdInfo.java:53
198 D_USB_INSERT_ID ←P USB 插入事件 CmdInfo.java:78
199 D_AP_CONNECT_ID ←P AP 連線事件 CmdInfo.java:30
200 D_DUAL_PROBES_ID ←P 雙探頭 ID CmdInfo.java:39
201 D_USETIME_ID 使用期限 ID CmdInfo.java:80
202 D_SEND_HOST_2 →P 轉送給第二主機 CmdInfo.java:58

4.2 影像參數命令 W_*(App 命令,0–127)

代碼 常數名稱 意義 證據
0 W_PROBEID_BEGIN 探頭 ID 起始(0) CmdInfo.java:155
1 W_PROBEID_CMD 探頭 ID CmdInfo.java:156
2 W_FREEZE_CMD 凍結/解凍 CmdInfo.java:127
3 W_POLLING_CMD 輪詢/心跳 CmdInfo.java:143
4 W_DEPTH_CMD 深度 CmdInfo.java:120
5 W_TGC_CMD TGC CmdInfo.java:189
6 W_B_GAIN_CMD B 增益 CmdInfo.java:95
7 W_B_FREQ_CMD B 頻率 CmdInfo.java:94
8 W_B_TSI_CMD B TSI CmdInfo.java:101
9 W_B_POWER_CMD B 功率 CmdInfo.java:98
10 W_B_DYNAMIC_CMD B 動態範圍 CmdInfo.java:89
11 W_B_EXPAND_CMD B 展開 CmdInfo.java:91
12 W_B_FOCUSPOS_CMD B 焦點位置 CmdInfo.java:92
13 W_B_ENHANCE_CMD B 增強 CmdInfo.java:90
14 W_B_SPACECOMP_CMD B 空間複合 CmdInfo.java:99
15 W_B_STEER_CMD B 偏轉 CmdInfo.java:100
16 W_B_FRAMECORR_CMD B 幀相關 CmdInfo.java:93
17 W_B_POSTEFFECT_CMD B 後處理 CmdInfo.java:97
18 W_B_LR_CMD B 左右翻轉 CmdInfo.java:96
19 W_B_UD_CMD B 上下翻轉 CmdInfo.java:102
20 W_B_COLORMAP_CMD B 彩色圖譜 CmdInfo.java:88
21 W_THI_GAIN_CMD THI 增益 CmdInfo.java:191
22 W_THI_DYNAMIC_CMD THI 動態範圍 CmdInfo.java:190
23 W_THI_POSTEFFECT_CMD THI 後處理 CmdInfo.java:192
24 W_FRAMECORR_CMD 幀相關 CmdInfo.java:126
25 W_C_GAIN_CMD C 增益 CmdInfo.java:113
26 W_C_SCALE_CMD C 量程 CmdInfo.java:116
27 W_C_SPEED_REVERSE_CMD C 速度反向 CmdInfo.java:117
28 W_C_FREQ_CMD C 頻率 CmdInfo.java:112
29 W_C_WALLFILTER_CMD C 壁濾波 CmdInfo.java:119
30 W_C_BASELINE_CMD C 基線 CmdInfo.java:106
31 W_C_FRAMECORR_CMD C 幀相關 CmdInfo.java:111
32 W_C_PRIORY_CMD C 優先權 CmdInfo.java:114
33 W_C_PSEUDOCOLOR_CMD C 偽彩 CmdInfo.java:115
34 W_C_STEER_CMD C 偏轉 CmdInfo.java:118
35 W_C_DISP_LINE_RANGE_CMD C 顯示線範圍 CmdInfo.java:109
36 W_C_DISP_POINT_RANGE_CMD C 顯示點範圍 CmdInfo.java:110
37 W_POWER_GAIN_CMD Power 增益 CmdInfo.java:147
38 W_POWER_SCALE_CMD Power 量程 CmdInfo.java:150
39 W_POWER_SPEED_REVERSE_CMD Power 速度反向 CmdInfo.java:151
40 W_POWER_WALLFILTER_CMD Power 壁濾波 CmdInfo.java:152
41 W_POWER_FRAMECORR_CMD Power 幀相關 CmdInfo.java:146
42 W_POWER_PRIORY_CMD Power 優先權 CmdInfo.java:148
43 W_POWER_PSEUDOCOLOR_CMD Power 偽彩 CmdInfo.java:149
44 W_POWER_DYNAMIC_CMD Power 動態範圍 CmdInfo.java:145
45 W_M_GAIN_CMD M 增益 CmdInfo.java:134
46 W_M_SPEED_CMD M 速度 CmdInfo.java:138
47 W_M_DYNAMIC_CMD M 動態範圍 CmdInfo.java:133
48 W_M_COLORMAP_CMD M 彩色圖譜 CmdInfo.java:132
49 W_M_POSTEFFECT_CMD M 後處理 CmdInfo.java:137
50 W_M_LINECORR_CMD M 線校正 CmdInfo.java:135
51 W_M_TIME_MARK_CMD M 時間標記 CmdInfo.java:139
52 W_M_LINENUM_CMD M 線數 CmdInfo.java:136
53 W_PW_BASELINE_CMD PW 基線 CmdInfo.java:164
54 W_PW_SPEED_CMD PW 速度 CmdInfo.java:181
55 W_PW_QUICKANGLE_CMD PW 快速角度 CmdInfo.java:178
56 W_PW_ANGLE_CMD PW 角度 CmdInfo.java:162
57 W_PW_COLORMAP_SWITCH_CMD PW 彩色圖譜切換 CmdInfo.java:167
58 W_PW_TIMEMARK_CMD PW 時間標記 CmdInfo.java:183
59 W_PW_REVERSE_CMD PW 反向 CmdInfo.java:179
60 W_PW_COLORMAP_CMD PW 彩色圖譜 CmdInfo.java:166
61 W_PW_VOLUME_CMD PW 音量 CmdInfo.java:185
62 W_PW_DYNAMIC_CMD PW 動態範圍 CmdInfo.java:169
63 W_PW_AUTOCALC_CMD PW 自動計算 CmdInfo.java:163
64 W_PW_TRACERANGE_CMD PW 追蹤範圍 CmdInfo.java:184
65 W_PW_STEER_CMD PW 偏轉 CmdInfo.java:182
66 W_PW_FREQ_CMD PW 頻率 CmdInfo.java:171
67 W_PW_SCALE_CMD PW 量程 CmdInfo.java:180
68 W_PW_GAIN_CMD PW 增益 CmdInfo.java:172
69 W_PW_FILTER_CMD PW 濾波 CmdInfo.java:170
70 W_PW_GATESIZE_CMD PW 取樣門大小 CmdInfo.java:174
71 W_PW_GATEPOS_CMD PW 取樣門位置 CmdInfo.java:173
72 W_PW_LINENUM_CMD PW 線數 CmdInfo.java:175
73 W_PW_QOPT_CMD PW Q 優化 CmdInfo.java:177
74 W_PW_POSTEFFECT_CMD PW 後處理 CmdInfo.java:176
75 W_PW_ACSPWR_CMD PW 聲功率 CmdInfo.java:161
76 W_EXAMMODE_CMD 檢查模式 CmdInfo.java:124
77 W_B_CMD 切換至 B 模式 CmdInfo.java:87
78 W_C_CMD 切換至 C 模式 CmdInfo.java:107
79 W_M_CMD 切換至 M 模式 CmdInfo.java:131
80 W_POWER_CMD 切換至 Power 模式 CmdInfo.java:144
81 W_PW_CMD 切換至 PW 模式 CmdInfo.java:165
82 W_UPDATE_CMD 更新 CmdInfo.java:194
83 W_ALL_PARAM_CMD 讀取全部參數 CmdInfo.java:85
84 W_NOFPGADSC_CMD 無 FPGA DSC CmdInfo.java:141
85 W_SAVE_PARAM_CMD 儲存參數 CmdInfo.java:186
86 W_EXAMIMGPARA_CMD 檢查影像參數 CmdInfo.java:123
87 W_UIMSG_CMD UI 訊息 CmdInfo.java:193
88 W_C_DISP_LINEANDDOT_CMD C 顯示線與點 CmdInfo.java:108
89 W_PWB_B_CMD PW/B 切換 CmdInfo.java:158
90 W_PWC_C_CMD PW/C 切換 CmdInfo.java:159
91 W_PWC_POWER_CMD PW/Power 切換 CmdInfo.java:160
92 W_PWBC_PW_CMD PW 切換 CmdInfo.java:157
93 W_UPDATE_IMGMODE_CMD 更新影像模式 CmdInfo.java:195
94 W_MITI_CMD MITI CmdInfo.java:129
95 W_IMGQUALITY_CMD 影像品質 CmdInfo.java:128
96 W_ENCMODE_CMD 編碼模式 CmdInfo.java:122
97 W_CONNECT_TYPE_CMD 連線型態 CmdInfo.java:104
98 W_DICOM_SWITCH_CMD DICOM 開關 CmdInfo.java:121
99 W_PROBEDID_MAPFLAG_CMD 探頭 DID 映射旗標 CmdInfo.java:154
100 W_VET_CMD 獸醫(Vet)模式 CmdInfo.java:196
101 W_EXAMMODE_ID_CMD 檢查模式 ID CmdInfo.java:125
102 W_BFGATHER_CMD B 幀收集 CmdInfo.java:86
103 W_SEARCH_HOST_CMD 搜尋主機(發現廣播) CmdInfo.java:187
104 W_CONNECT_HOST_CMD 連線主機 CmdInfo.java:103
105 W_C_APOWER_CMD C 聲功率 CmdInfo.java:105
106 W_M_APOWER_CMD M 聲功率 CmdInfo.java:130
107 W_START_PLAY 開始播放 CmdInfo.java:188
108 W_PRE_FRAME 上一幀 CmdInfo.java:153
109 W_NEXT_FRAME 下一幀 CmdInfo.java:140
110 W_PW_CW_CMD PW/CW CmdInfo.java:168
127 W_NONE_CMD 空命令(回應/ack) CmdInfo.java:142

4.3 其它 ID 群組

群組 意義 證據
BtnId.W_BTN_1..10_ID 1–10 探頭按鍵編號 CmdInfo.java:203-212
BtnAction.W_BTN_DOWN/HOLD/LONG_PRESS/UP 1/2/3/4 按鍵動作 CmdInfo.java:218-226
PollingID.P_IMAGE_RUN_CMD 128 影像執行輪詢 CmdInfo.java:231
PollingID.USB_CONNECTED_CMD 129 USB 已連線 CmdInfo.java:232
PollingID.USB_DISCONNECTED_CMD 130 USB 已斷線 CmdInfo.java:233
PollingID.PROBE_CONTINUE_TEST_CMD 131 探頭連續測試 CmdInfo.java:230
PollingID.WLAN_CONNECTED_CMD 132 WLAN 已連線 CmdInfo.java:234
PollingID.WLAN_DISCONNECT_CMD 133 WLAN 已斷線 CmdInfo.java:235
PollingID.IMAGE_SYNC_CMD 134 影像同步 CmdInfo.java:229
AppMsgId.WUSB_SET_PROBE_ID 240 設定探頭 ID CmdInfo.java:242

五、Java 端處理器映射 (Handler → Code Mapping)

核心接收處理器為 WlanProbe.mHandler.handleMessage() (WlanProbe.java:712-1332)。收到 what==1 訊息後輪詢 CmdHandler.mNetDataQueue,依 cmdElem.cmdid 分派:

代碼 無號值 處理動作 證據
-115 141 mProbeFwVerObservable(韌體版本) WlanProbe.java:742-745
-114 142 mWifiFwVerObservable(Wi-Fi 韌體版本) WlanProbe.java:746-749
-107 149 mProbeSnObservable(序號) WlanProbe.java:750-754
-106 150 m_ProductIdStr / mProductIdObservable(產品 ID) WlanProbe.java:1324-1326
-101 155 mProbeBoardVerObservable(板版本) WlanProbe.java:773-776
-100 156 mBoardSnObservable(板序號) WlanProbe.java:777-780
-95 161 SetNoExpand(Data[0]&1)(專案選項旗標) WlanProbe.java:1315-1319
-94 162 LianmedSendPacket()(聯影資料) WlanProbe.java:1313
-112 144 mCurChannel(Wi-Fi 通道) WlanProbe.java:784-789
-111 145 mProbePidVerObservable(PID 版本) WlanProbe.java:790-794
-110 146 mProbeFpgaVerObservable(FPGA 版本) WlanProbe.java:795-799
-103 153 mSeHost(啟用第二主機) WlanProbe.java:803-811
-86 170 mProbePowerSaveTime(省電時間) WlanProbe.java:812-816
-83 173 mUseTime(使用剩餘時間,≤5 提示過期) WlanProbe.java:817-829
-64 192 mTemperature / mTempObservable(溫度) WlanProbe.java:1189-1199
-63 193 mBattLevel / mBattLevelObservable(電池) WlanProbe.java:1201-1212
-62 194 mProbeId = CalProbeId()(ADC 探頭 ID) WlanProbe.java:1213-1236
-59 197 mProbeId = Data[0]-48(探頭 ID) WlanProbe.java:830-846
-56 200 mDualProbeId0/1mDualActive(雙探頭) WlanProbe.java:847-895
-16 240 mProbeId(設定探頭 ID) WlanProbe.java:896-903
2 2 Freeze()/UnFreeze()(凍結) WlanProbe.java:757-766
4 4 SetDepth()(深度) WlanProbe.java:1321
6 6 SetBGain() WlanProbe.java:769-770
7 7 SetBFreq() WlanProbe.java:771-772, 904-905
8 8 SetTsi() WlanProbe.java:907-908
9 9 SetAPower() WlanProbe.java:910-911
10 10 SetBDynamic() WlanProbe.java:913-914
11 11 SetBExpand() WlanProbe.java:916-917
12 12 SetBFocus() WlanProbe.java:919-920
13 13 SetBEnhance() WlanProbe.java:922-923
14 14 SetBFrameComp() WlanProbe.java:925-926
15 15 SetBSteer() WlanProbe.java:928-929
16 16 SetBFrameCorre() WlanProbe.java:931-932
17 17 SetBGrayMap() WlanProbe.java:934-935
18 18 setBLR() WlanProbe.java:937-938
19 19 setBUD() WlanProbe.java:940-941
20 20 SetBColorMap() WlanProbe.java:943-944
25–36 25–36 SetCGain/Scale/Invert/Freq/WallFilter/…/Steer/LineRange/DotRange WlanProbe.java:1239-1278
43 43 SetPowerColorMap() WlanProbe.java:946-947
45–47 45–47 SetMGain/MSpeed/MDynamic WlanProbe.java:1281-1289
49 49 SetMGrayMap() WlanProbe.java:949-950
52–54 52–54 SetMDispLine/PwBaseLine/PwSpeed WlanProbe.java:1292-1301
56 56 SetPwAngle() WlanProbe.java:952-956
59 59 SetPwReserse() WlanProbe.java:957-958
61–73 61–73 SetPwVolume/Dynamic/Steer/Freq/Scale/Gain/WallFilter/GateSize/GatePos/DispLine WlanProbe.java:960-999
75 75 SetAPower() WlanProbe.java:1000-1001
76 76 SetExamType() WlanProbe.java:1003-1005
77 77 SetDispMode(0) WlanProbe.java:1006-1009
78 78 SetDispMode(16/8) WlanProbe.java:1010-1016
79 79 SetDispMode(9) WlanProbe.java:1017-1019
80 80 SetDispMode(16/55) WlanProbe.java:1020-1026
82 82 SetDispMode(5)(更新) WlanProbe.java:1027-1039
86 86 SetPresetParam() WlanProbe.java:1040-1042
89–92 89–92 SetDispMode(57/58/59/5) WlanProbe.java:1043-1054
94 94 setMITISData() WlanProbe.java:1055-1057
96 96 mEncModeObservable(編碼模式) WlanProbe.java:1058-1063
98 98 SetDicomAuth/PwAuth/ColorAuth/HumanVetAuth/OEMId WlanProbe.java:1064-1105
100 100 mVet / mProbeDisabled(獸醫模式) WlanProbe.java:1106-1162
101 101 SetExamType() WlanProbe.java:1163-1165
103 103 UpdatetoHostList()(主機發現) WlanProbe.java:1166-1170
104 104 WlanHost.setConnect()(主機連線) WlanProbe.java:1171-1186

六、多主機握手 (Multi-Host Handshake)

  1. 偵測第二主機TcpSocketConnect() 檢查本機 IP 末位 == 199 時呼叫 SetMultiHost(2) (CmdHandler.java:156-160)。
  2. 啟用訊號:收到代碼 153 (D_ENABLE_SECOND_HOST) 設定 mSeHost (WlanProbe.java:803-811)。
  3. 轉送MultiHostSendCmd() 以目標 2 呼叫 excute(..., 2)wlanSendToHost2() (WlanProbe.java:1444-1477;CmdHandler.java:91-92, 688-702)。wlanSendToHost2 把封包重新包裝為 cmdid=-54(=202 D_SEND_HOST_2)後送至 REMOTE_PORT (CmdHandler.java:690-701)。
  4. 主機角色WlanClient.SetWlanRole(0/1/2) = NONE/CLIENT/HOST (WlanClient.java:26-28)。

七、主機發現與連線握手(WLAN_PORT 8093)

  • 主機廣播WlanHost.UpateParam() 以 dir=3 (PACK) 廣播代碼 103 (W_SEARCH_HOST_CMD),payload 為 JSON 參數(JsonParam:sn/ip/name/id/ex_name/ex_id/img_mode/w/h/shape/depth/scale/angle/rang)(WlanHost.java:144-188;JsonParam.java:8-22)。
  • 客戶端接收WlanClient.StartBroadCastRecv() 監聽 8093,代碼 103 → UpdatetoHostList() (WlanClient.java:156-189, 203-207)。
  • 連線:客戶端送代碼 104 (W_CONNECT_HOST_CMD) 帶本機 IP;主機回代碼 104 設 WlanHost.setConnect() (WlanClient.java:66-102;WlanProbe.java:1166-1186)。
  • 斷線:代碼 104 payload 為單字節 "0" (WlanClient.java:71)。
  • 心跳:代碼 3 (W_POLLING_CMD) 用作 keepalive (WlanHost.java:180)。

八、配對握手 (Pairing Handshake) — CmdHandler.StartWlanPair() (CmdHandler.java:325-456)

  1. 以 UDP (LOCAL_DAT_PORT 8083) 收探頭回應,超時 8 秒 (StartWlanPairTimer)。
  2. 送出代碼 150 (D_UPROBE_PRODUCTID) 請求產品 ID (CmdHandler.java:384)。
  3. 收到回應後回傳封包:0xFE, 150, 2, 1, 0, VR_GET_EEPROM_REG3(=85), 0xFF (CmdHandler.java:424-431;HSUsbProbe.java:49)。

九、FPGA 暫存器讀寫 (UReadFPGA/UWriteFPGA)

  • 位址:代碼 147 (D_UPROBE_FPGA_ADDR) 設 4-byte little-endian 位址 (CmdHandler.java:747-754)。
  • 資料:代碼 148 (D_UPROBE_FPGA_DAT) 讀/寫資料;dir=1 讀、dir=0 寫 (CmdHandler.java:760, 847)。
  • 大批量資料以 1400-byte 區塊分片(mData[3]=120, mData[4]=5 表示 1400)(CmdHandler.java:767-769)。
  • TCP 版 (UReadFPGA_TCP/UWriteFPGA_TCP) 加 2-byte 長度前綴 (CmdHandler.java:913-1031)。

十、已知缺口 (Known Gaps)

  1. 未定義代碼區間:D_* 表在 174–191 之間無常數定義(D_BOARD_TEMPERATURE=192 起才接續);190191 亦無定義。
  2. 無 checksumvarify() 為空實作,封包完整性僅靠 0xFF 結束字節。
  3. 傳送/接收框架不對稱:傳送端含 0xFE 頭,WLAN UDP 接收端卻從 [0] 起讀 cmdid(無 0xFE 頭),僅 USB 通道與 TCP 通道掃描 0xFE
  4. 方向語意待確認:多數 D_* GET 代碼的「確切回傳方向」未在反編譯碼中明示,僅由 SendGetCmdToProbe 呼叫點推斷(Ultrasys.java:2765-2799)。
  5. 代碼 190/191、174-189 語意未知:反編譯碼中無對應常數或處理器。
  6. W_UPDATE_CMD(82)、W_UIMSG_CMD(87)、W_NOFPGADSC_CMD(84)、W_BFGATHER_CMD(102)、W_PROBEDID_MAPFLAG_CMD(99) 等:僅有常數定義,未見明確的 Java 端接收處理器。
  7. HSUsbProbe 暫存器碼(VR_* 常數,HSUsbProbe.java:35-62)屬 USB 協定層,與 WLAN 命令碼為不同命名空間,此處僅列出與配對握手相關者。
21

深挖 3 — ThirdParty SDK

ThirdPartyManageActivity — 第三方 SDK 整合介面 (Third-Party SDK Integration Interface)

來源(jadx 反編譯): - com.Com.handprobe.ThirdPartyManageActivity.java(exported activity,AndroidManifest.xml 第 50 行) - com.Com.application.MyApplication.javareturnThirdParty / getThirdPartyExtras / mIsThirdPartyEntered) - handprobe.application.app.AppProc.java(影像輸出 + 回傳結果觸發) - com.Com.application.ReportFragment.java(PDF 輸出) - com.Com.handprobe.BaseActivity.javaLandFeature.LandActivity.java(病患注入、exit 回傳)

一、定位與角色 (Role)

ThirdPartyManageActivity 是一個 exported="true"、無 intent-filter、無權限保護 的透明 Activity(@android:style/Theme.Translucent.NoTitleBar)。它的作用是扮演一個 「轉送閘道 (relay)」:第三方 App 以 explicit component 呼叫它,它把第三方帶來的 extras 轉送給主 App(BaseActivity/LandActivitySplashActivity),並在主 App 完成工作後以 setResult() 把結果送回第三方,再把第三方 App 帶回前景。

它本身不做任何超音波工作,邏輯極短(約 130 行),真正的輸出邏輯分散在 AppProc(影像)與 ReportFragment(報告 PDF)。

二、啟動協定 (Launch Protocol)

2.1 Component

Manifest(第 50 行)只宣告了 activity,沒有

<activity android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
          android:exported="true"
          android:name="com.Com.handprobe.ThirdPartyManageActivity"
          android:screenOrientation="sensor"
          android:theme="@android:style/Theme.Translucent.NoTitleBar"/>

因此沒有 action / category 可以靠 implicit intent 觸發。第三方必須用 explicit component 啟動:

  • 套件名:com.SonoiQ.uhandprobe
  • 元件:com.SonoiQ.uhandprobe/com.Com.handprobe.ThirdPartyManageActivity
Intent i = new Intent();
i.setComponent(new ComponentName("com.SonoiQ.uhandprobe",
                                "com.Com.handprobe.ThirdPartyManageActivity"));
i.putExtra("APP_NAME", "MyApp");
i.putExtra("SAVEIMG_PATH", "/sdcard/myapp/images/");
i.putExtra("PATIENTID", "P001");
i.putExtra("NAME", "John Doe");
i.putExtra("GENDER", 1);
i.putExtra("AGE", 35);
i.putExtra("EXPORT_MODE", "jpeg");
startActivityForResult(i, REQ_CODE);
已確認exported="true" 且無 intent-filter(Manifest 第 50 行)。 推斷(高度確信):第三方需以 startActivityForResult + explicit component 啟動,因為代碼依賴 getCallingActivity() / getCallingPackage()setResult() 的回傳機制——這些 API 只有在以 startActivityForResult 啟動、且在同一 task 返回棧的條件下才有非 null 值。

三、Intent Extras 協定 (Intent Extras Protocol)

3.1 輸入鍵 (Input Keys)

ThirdPartyManageActivity.onCreate 讀取以下鍵,存入 public static Map mArgsMapHashMap,全行程共享):

Key 型別 讀取位置 語意
APP_NAME String getString 第三方 App 名稱(記錄用途,未見後續消費)
SAVEIMG_PATH String getString 第三方指定輸出目錄;App 會把超音波影像 PNG 與報告 PDF 直接檔案複製 到這個目錄
PATIENTID String getString 病患 ID;空字串 → 不注入病患;非空 → 建 HPatient 並注入
NAME String getString 病患姓名(setFirstName
GENDER int getInt 病患性別(setGender,無 default → 缺省為 0)
AGE int getInt 病患年齡(setAge(age, 0),缺省為 0)
EXPORT_MODE String AppProc(非 onCreate) 回傳結果格式:"jpeg"/"jpg"/"webp"/其他 → 決定 IMG_PATH 指向的檔案格式

EXPORT_MODE 與輸出鍵 IMG_PATHThirdPartyManageActivitypublic static 常數"EXPORT_MODE""IMG_PATH"),但不在 onCreate 讀取——它們在 extras 被轉送後由 AppProc 消費。

3.2 轉送 (Forwarding)

Bundle extras = getIntent().getExtras();
if (extras != null) {
    intent.putExtras(extras);        // 原封不動轉送給 MainActivity/SplashActivity
    // ...解析並填入 mArgsMap、建 mThirdPartPatient...
    startActivity(intent);
}

extras 被 putExtras 轉送到主 Activity,且 getThirdPartyExtras() 回傳的其實就是 同一個 bundle 物件mTPManageActivity.getIntent().getExtras())。這導致回傳結果與輸入共用同一 bundle 記憶體(見第四節)。

已確認mArgsMappublic static Map,在 onCreate 每次 clear() 後重填。全行程共享 → 若主 App 已存在、第三方再次進入,靜態 map 會被新呼叫覆寫。 推斷APP_NAME 僅作記錄;PATIENTID 為必填鍵——若 extras 存在但缺 PATIENTIDmArgsMap.get("PATIENTID").equals("") 會拋 NullPointerException

四、生命週期 (Lifecycle)

4.1 Activity 本身

ThirdPartyManageActivity 是透明、瞬時的:

  1. onCreateKeepFullScreen → 決定轉送目標 → 解析 extras。
  • extras 存在 → startActivity(intent) 轉送給主 App,return不 finish,留在返回棧當結果中繼)。
  • extras 為 null → 直接回傳 ERROR_Intent_has_no_extrasfinish()
  1. onNewIntent:僅 setIntent(intent)(支援 singleTop/singleTask 重入更新 extras)。
  2. onActivityDestroyed(Application 生命週期回調):mTPManageActivity = nullmIsThirdPartyEntered = false

4.2 Application 層狀態追蹤 (MyApplication)

MyApplicationActivityLifecycleCallbacks 追蹤第三方狀態:

  • onActivityCreated / onActivityStarted(命中 ThirdPartyManageActivity)→ 記錄 mTPManageActivitymIsThirdPartyEntered = true;並在 onActivityCreated 記錄 mCalledByComponentName = new ComponentName(getCallingPackage(), getCallingActivity().getClassName())
  • onActivityDestroyed → 清除狀態、mIsThirdPartyEntered = false

4.3 病患注入 (Patient Injection)

第三方提供的 PATIENTID/NAME/GENDER/AGELandActivity.UpdatePatientThird() 中生效(需 WRITE_EXTERNAL_STORAGE 權限,ISGetPremission):

if (mTPManageActivity.isPatientUpdate() && ISGetPremission) {
    HPatient p = mTPManageActivity.updatePatient();   // 只回傳一次(mPatientUpdate 會被清為 false)
    if (p == null)  AddNewPatient();
    else            AddNewPatient(p.getId()) ... setFirstName/Gender/Age/Birthday;
    // SetCurPatient + NewExam + SaveToDb
}
updatePatient() 有一次性語意:讀取後把 mPatientUpdate 設回 false

五、回傳結果機制 (Return-Result Mechanism)

5.1 進入點

有兩個重載:

  • ThirdPartyManageActivity.returnThirdParty(Intent intent, Bundle bundle) — 實際執行 setResult + finish + 喚回第三方。
  • MyApplication.returnThirdParty(Bundle bundle) — 包裝:ProbeWifiInfo.DisconnectWifi() → 主 Activity moveTaskToBack(true) + setIntent(null) → 委派給 mTPManageActivity.returnThirdParty

5.2 setResult 邏輯 (確認自反編譯)

public void returnThirdParty(Intent intent, Bundle bundle) {
    if (bundle != null) {
        if (bundle.getString(IMG_PATH) != null) {
            intent.putExtras(bundle);
            setResult(-1, intent);                      // RESULT_OK —— 成功
        } else if (bundle.getInt(ERROR) <= 0 && bundle.getString(ERROR) == null) {
            bundle.putString(ERROR, "Not save image.");
            bundle.putInt(ERROR, 3);
            intent.putExtras(bundle);
            setResult(0, intent);                       // RESULT_CANCELED —— 未存影像
        } else {
            intent.putExtras(bundle);
            setResult(0, intent);                       // RESULT_CANCELED —— 已有錯誤碼
        }
    } else {
        Bundle b = new Bundle();
        b.putString("error", "Unknown error.");
        b.putInt(ERROR, 1);
        intent.putExtras(b);
        setResult(0, intent);                           // RESULT_CANCELED —— 未知錯誤
    }
    finish();
    setIntent(null);
    if (mCalledByComponentName != null) {
        intent.setComponent(mCalledByComponentName);
        startActivity(intent);                          // 把第三方 App 帶回前景
    }
}
注意第三個分支的 bug 語意:bundle.getString(ERROR) == nullbundle.getInt(ERROR) > 0(例如只放了 int 錯誤碼、沒放字串)會落入最後分支,直接 setResult(0) 原樣回傳 bundle。 注意錯誤字串用了兩個不同 key:正常路徑用 ERROR"ERROR"),null-bundle 分支誤用 "error"(小寫)——已確認是源碼不一致。

5.3 觸發回傳的時機 (Call Sites)

觸發點 位置 行為
影像存檔完成且 EXPORT_MODE 存在 AppProc ~312–333 EXPORT_MODE 轉檔(jpeg/jpg/webp)或沿用 PNG,設 IMG_PATHreturnThirdParty
功能鍵「返回」 (FUNCID_Return_to_Lian, case 205) AppProc ~824 非 Lianmed 時 returnThirdParty(getThirdPartyExtras())
連續兩次 back (exit()) BaseActivity.exit ~866 getThirdPartyExtras() != nullreturnThirdParty
全域未捕捉例外 (crash) MyApplication uncaught handler ~153–158 ERROR="App crashed." + code 4 → returnThirdParty
getThirdPartyExtras() 回傳 null 的條件:mTPManageActivity == null || !mIsThirdPartyEntered || getIntent() == null。因此回傳只在第三方確曾進入時發生。

六、錯誤碼 (Error Codes)

ThirdPartyManageActivity 定義的常數(已確認):

常數 字串 觸發條件
ERROR_Reserved 0 (保留)
ERROR_Unknown 1 "Unknown error."(用 key "error" bundle 為 null
ERROR_Intent_has_no_extras 2 "Intent has no extras." 啟動時 getExtras() == null
ERROR_Not_save_image 3 "Not save image." 回傳時無 IMG_PATH 且無錯誤碼
ERROR_App_crashed 4 "App crashed." 全域 uncaught exception handler

回傳的 result code:

  • -1RESULT_OK:僅當 IMG_PATH 存在(成功)。
  • 0RESULT_CANCELED:所有錯誤路徑。

錯誤碼與字串都放在回傳 Intent 的 extras 裡,key 皆為 ERROR(值 "ERROR")——唯 null-bundle 分支誤用 "error"(見 5.2 註記)。

七、第三方能拿到什麼 (What the Third Party Gets Back)

兩條獨立輸出通道,互補:

7.1 SAVEIMG_PATH(檔案複製通道,被動持續輸出)

  • 影像AppProc 每次存 PNG 影像後,FileUtils.fileCopyByChannel 複製一份到 SAVEIMG_PATH + mFilename + ".png"FileDefinition.PNG_SUFFIX)。
  • 報告 PDFReportFragment 產生 PDF 後複製到 SAVEIMG_PATH + createReportName(mExam) + ".pdf"

第三方指定的是目錄,App 直接寫入多個檔案,與 Intent 回傳無關。

7.2 EXPORT_MODE + IMG_PATH(Intent 回傳通道,主動單次輸出)

  • 第三方設 EXPORT_MODE 後,App 存檔時依格式轉檔並把 單一檔案的絕對路徑 寫入 IMG_PATH,再 setResult(-1)
  • 格式對應:
  • "jpeg" / "jpg" → JPEG
  • "webp" → WEBP
  • 其他(或不設,但走了回傳路徑)→ 沿用 PNG(file2.getAbsolutePath()
  • 第三方在 onActivityResult 中讀:
if (resultCode == RESULT_OK && data != null) {
    String path = data.getStringExtra("IMG_PATH");   // 絕對路徑
}

結論:第三方拿到的不是圖片位元組,而是檔案絕對路徑(IMG_PATH),以及透過 SAVEIMG_PATH 目錄被動收到的 PNG 影像 + PDF 報告副本。

已確認IMG_PATH 存的是絕對路徑(file.getAbsolutePath());setResult(-1) 只在 IMG_PATH != null 時發生。 推斷:若第三方未設 EXPORT_MODE,App 不會在存檔時主動回傳;IMG_PATH 永遠為 null,回傳(如按返回鍵或 exit)會落入「Not save image」錯誤分支。若要拿到影像路徑,EXPORT_MODE 是必要輸入。

八、安全性分析 (Security Notes)

ThirdPartyManageActivity 是 SonoiQ 上 攻擊面最大的元件:exported、無 intent-filter 限制、無權限、無簽章/白名單驗證。

  1. 任意第三方可啟動exported="true" 且無任何 android:permission 保護;任何同裝置 App 都能用 explicit component 觸發。
  2. 無呼叫者身分驗證getCallingActivity() / getCallingPackage() 僅用於「事後把第三方帶回前景」,從不驗證呼叫者簽章、套件名或授權。
  3. 病患 PHI 注入:呼叫者可控 PATIENTID/NAME/GENDER/AGE,透過 UpdatePatientThird() 直接寫入 SonoiQ 的病患資料庫(SaveToDb)——資料完整性風險(可植入偽病患紀錄)。
  4. 資料外洩 (exfiltration) 通道
  • SAVEIMG_PATH 由第三方指定,App 會把含病患資訊的超音波影像與報告 PDF 複製到第三方可控目錄(App 有 WRITE_EXTERNAL_STORAGE + requestLegacyExternalStorage="true",可寫共享外部儲存),第三方再讀取該目錄即可取走 PHI。
  • IMG_PATH 直接回傳 App 內部檔案的絕對路徑(路徑洩漏 + 若檔案在可讀共用區則等同內容洩漏)。
  1. 靜態可變狀態mArgsMappublic static、無同步;多第三方/多實例共用,存在跨呼叫污染與競態。
  2. crash 回報洩漏:全域 uncaught handler 會把 "App crashed."(code 4)連同原本 extras 回傳給第三方,並 AppUtil.exitApp(1) 強制結束 App——第三方可用惡意 extras 反覆觸發 crash(例如缺 PATIENTID 的 NPE,見 3.2 推斷)。
  3. 喚回第三方(foreground re-entry):回傳後 App 會 startActivity(mCalledByComponentName),等於把控制權交還給第三方;若第三方本身就是惡意 App,此機制使它得以在主 App 結束後立即重新取得前景。
相較於其他 activity,LandQ6Activity / LandQ62Activity 帶有 android:sharedUserId="android.uid.system"(第 48–49 行,本身也是可疑的越權標記),但 ThirdPartyManageActivity 不帶 sharedUserId,運行於 App 一般 UID。

九、已確認 vs 推斷 (Confirmed vs Inferred)

已確認(直接源自反編譯源碼/Manifest):

  • ThirdPartyManageActivity exported、無 intent-filter、透明主題。
  • 所有 input key(APP_NAME/SAVEIMG_PATH/PATIENTID/NAME/GENDER/AGE/EXPORT_MODE)與 output key(IMG_PATH/ERROR)。
  • 錯誤碼常數 0–4 與對應字串。
  • setResult 的完整分支邏輯與 result code(-1/0)。
  • mArgsMappublic static,onCreate 中 clear() 重填。
  • 兩條輸出通道(SAVEIMG_PATH 檔案複製、EXPORT_MODEIMG_PATH 回傳)的具體行為。
  • 病患注入流程(UpdatePatientThirdAddNewPatientSaveToDb)。

推斷(邏輯推導,未直接實測):

  • 第三方需以 explicit component + startActivityForResult 啟動(因無 intent-filter,且代碼依賴 getCallingActivity/setResult)。
  • PATIENTID 為實質必填——缺省會 NPE(mArgsMap.get("PATIENTID").equals(""))。
  • GENDER/AGE 缺省為 0(getInt 無 default)。
  • 不設 EXPORT_MODE 時不會有主動回傳、IMG_PATH 恒為 null。
  • APP_NAME 僅作記錄用途,未見下游消費。
22

深挖 4 — DispMode 值

SonoiQ DispMode 特殊值解讀 (Special DispMode Values)

反編譯來源:jadx_out/sources/(JADX 反編譯的 classes.dex)。 核心類別:handprobe/application/ultrasys/image/DispMode.javaImageDefine.javaUltrasys.javahandprobe/application/usb/UsbProbeManager.javahandprobe/application/ultrasys/ParaCtrl.java

一、常數定義總表 (Constants Definition)

DispMode.java:7-20 定義了完整的模式常數。模式值並非連續,中間留有空位(保留給硬體

scan mode 對應的 ID),因此值本身即帶有語意(見第五節)。

常數名 (Constant) 字面含意 中文含意
0 B_MODE Brightness (2D grayscale) B 模式(灰階二維)
1 M_MODE Motion M 模式(時間-運動曲線)
5 PW_MODE Pulsed-Wave Doppler PW 脈衝波都卜勒
8 BC_MODE B + Color B + 彩色都卜勒(C)複合
9 BM_MODE B + M B + M 模式複合
16 TWO_BC_MODE Dual B + Color 雙幅 B+彩色
17 TWO_B_POWER Dual B + Power 雙幅 B+能量都卜勒
50 BB_MODE B + B (bi-plane) 雙 B(雙平面)
51 BBBB_MODE B+B+B+B (4-view) 四 B(四分割)
55 BPOWER_MODE B + Power Doppler B + 能量都卜勒複合
57 PWB_B_MODE PW over B PW 頻譜疊加於 B 圖
58 PWC_C_MODE PW over Color PW 頻譜疊加於彩色圖
59 PWC_POWER_MODE PW over Power PW 頻譜疊加於能量圖

二、模式 → 意義 → 資料路徑對照表 (Mode → Meaning → Data Path)

資料路徑的硬體事實 (FPGA 位址與尺寸)

UsbProbeManager.java:42-66

BIMG_SIZE = 131072        // B 影像一幀大小
CIMG_SIZE = 131072        // C(彩色)影像一幀大小
FPGA_BIMG_ADDR = 131072   // B 影像在 FPGA 的記憶體位址
FPGA_CIMG_ADDR = 131072   // C 影像位址(與 B 同值,分時/分區使用)
FPGA_H264_ADDR = 262144
FPGA_TIMG_ADDR = 393216   // "T 影像":PW 頻譜 / M 軌跡資料區
MIMG_SIZE = 8192          // M 影像
MIMG_PACK_SIZE = 2048     // M 每包 2048B
PWIMG_SIZE = 2048         // PW 頻譜每幀 2048B
TIMG_SIZE  = 10240        // T 影像(M)總量 10240B

USB 讀取分派邏輯 UsbProbeManager.java:256-305InitThread 內的 ImgReadUSBThread):

  • CheckImgUpload() 回傳 bit0=1 → 讀 B 影像 131072 bytes(UsbProbeManager.java:240-241
  • bit1=1 → 依 GetDispMode() 分派讀取第二路資料(下表)。

對照表

意義 USB 資料路徑 (evidence) Cine page size (DispMode.SetCine) SetScanMode (Ultrasys.SetDispMode)
0 B 灰階 bit0:FPGA_BIMG_ADDR 讀 131072B → mImgBufUsbProbeManager.java:240-241 131072(雙平面 262144)DispMode.java:110-119 SetScanMode(0) Ultrasys.java:1435
5 純 PW 都卜勒 bit1:FPGA_TIMG_ADDR2048BmTImgBufmUsbPwDataQueuePackDUsbProbeManager.java:258-270, 372-394 換 PW 模擬 buffer,不設 mLastMode DispMode.java:106-108 SetScanMode(5) Ultrasys.java:1430
8 B+彩色 (BC) bit1:FPGA_CIMG_ADDR(131072) 讀 131072B → mCImgBuf → 拷入 mImgBuf[131072..]UsbProbeManager.java:271-278 C_PAGE_SIZE(見下)DispMode.java:88-97 SetScanMode(8) Ultrasys.java:1422
9 B+M (BM) bit1:FPGA_TIMG_ADDR10240BmMImgBufCache,拆 4×2048 → mTImgBufPackMUsbProbeManager.java:279-296, 387-394 131072(BM 模擬 buffer)DispMode.java:77-86 SetScanMode(9) Ultrasys.java:1390
16 雙幅 B+彩色 同 8(C 影像路徑) 未在 SetCine 明確處理(落入空 switch) SetScanMode(8)
17 雙幅 B+能量 同 8(C 影像路徑) 未在 SetCine 明確處理 SetScanMode(8)
50 雙 B bit0 B 影像(雙平面各自讀取) 未在 SetCine 明確處理 SetScanMode(8)
51 四 B bit0 B 影像(四分割) 未在 SetCine 明確處理 SetScanMode(8)
55 B+能量 (BPower) bit1:同 8,FPGA_CIMG_ADDR 讀 131072B(UsbProbeManager.java:271-278,`== 55 \ \ == 8`) C_PAGE_SIZE DispMode.java:99-104 SetScanMode(8) Ultrasys.java:1426
57 PW 疊加 B bit0 B 影像;bit1 無對應讀取分支(見第七節缺口) 未處理(空 switch) SetScanMode(0) Ultrasys.java:1400
58 PW 疊加彩色 bit1:FPGA_CIMG_ADDR 讀 131072B(UsbProbeManager.java:297-304,`== 58 \ \ == 59`) 未處理(空 switch) SetScanMode(8) Ultrasys.java:1405
59 PW 疊加能量 bit1:同 58(C 影像路徑) 未處理(空 switch) SetScanMode(55) Ultrasys.java:1410
C_PAGE_SIZE 定義於 Ultrasys.java:342C_PAGE_SIZE = (ImageDefine.C_M 256) + 262144 = (512256)+262144 = 393216。 巧合的是 FPGA_TIMG_ADDR = 393216C_PAGE_SIZE 同值——B+彩色模式整頁 (C 131072 + 其餘 262144)恰等於 T 影像在 FPGA 記憶體中的起始偏移。

三、特殊值 5 / 8 / 9 的語意(單一複合模式)

  • 5 (PW):純脈衝波都卜勒。唯一走「頻譜」專用 2048B 資料路徑的模式,

UsbProbeManager.UReadMDImgHH264PlayerIf.PackD 封裝(UsbProbeManager.java:381)。

進入 PW 前若溫度過高會攔截(Ultrasys.java:1362)。SetCine 不更新 mLastMode

表示 PW 是「暫態」疊加,退出後回上一模式(DispMode.java:106-108ParaCtrl.java:721-761

Pw_lastMode 往返邏輯證實此點)。

  • 8 (BC):B + 彩色都卜勒。C 影像以第二路(bit1)上傳,SetMode 時會

ResetDropFrameCount()DispMode.java:58-60),並以 C_PAGE_SIZE 建立 cine buffer。

  • 9 (BM):B + M。M 軌跡資料同樣來自 FPGA_TIMG_ADDR,但量為 10240B(=5 幀×2048),

由獨立執行緒拆包 4×2048UsbProbeManager.java:283-296),顯示時用 PackM

四、複合/變體模式為何存在 55 / 57 / 58 / 59

55 = BPOWER(B + 能量都卜勒)

能量都卜勒(Power Doppler)與彩色(Velocity)共用同一 C 影像資料路徑與同一個

SetScanMode(8),差別在 DSP 參數(ParaCtrl.java:699-719UsePowerPara() vs

UseColorPara())。所以 55 是 8 的「能量參數變體」。

57 / 58 / 59 = PW 疊加模式(PW over B / C / Power)

這三個值是「PW 頻譜 + 底圖」的複合顯示模式,證據:

  1. 命名PWB_B_MODE(PW+B)、PWC_C_MODE(PW+C)、PWC_POWER_MODE(PW+Power)。
  2. UI 觸發WlanProbe.java:1043-1051,遠端命令 89/90/91 → 57/58/59)。
  3. 模式往返邏輯ParaCtrl.java:679-682, 702-706, 736-760):
  • 在 B(0) 或 BM(9) 下按 PW → 進 57;在 BC(8) 下按 PW → 進 58;在 BPower(55) 下按 PW → 進 59。
  • 在 57/58/59 下再按 PW → 回到純 5;再按一次 → 回上一複合模式。
  1. 底圖對應Ultrasys.java:1397-1410SetScanMode):
  • 57 → SetScanMode(0)(底圖為 B)
  • 58 → SetScanMode(8)(底圖為彩色)
  • 59 → SetScanMode(55)(底圖為能量,掃描模式 55 = 能量)
  1. SampleWinView 繪製SampleWinView.java:322-331):57 只聚焦 PW sample gate;

58/59 同時繪製彩色 sample box(mCurCSample)與 PW gate(mCurPWSample)——

即「彩色取樣框 + PW 取樣門」並存的複合畫面。

結論:55 是「能量都卜勒」的獨立 DispMode;而 57/58/59 是「PW 疊加」的複合模式,

其底圖分別為 B / 彩色 / 能量。三者的存在是為了讓 PW 頻譜能疊在任一種底圖上,

同時保留一個獨立的值給純能量模式(55)。

五、為何值不連續(55、57、58、59 的來歷)

這些值同時扮演「App 層 DispMode ID」與「硬體 scan mode ID」雙重角色。關鍵證據是

Ultrasys.SetDispMode 尾段(Ultrasys.java:1378-1436)把 DispMode 翻譯成

HSysctrlIf.SetScanMode(...) 的硬體掃描模式編號,其對應為:

DispMode → SetScanMode 硬體掃描語意(推測)
0 0 B 掃描
5 5 純 PW 掃描
8/16/17/50/51/55 8 彩色/能量流 2D 掃描(power 由 DSP 參數決定)
9 9 B+M 掃描
57 0 B + PW 交錯掃描
58 8 彩色 + PW 交錯掃描
59 55 能量 + PW 交錯掃描

HSysctrlIf.SetScanModeJNI native 方法jni/HSysctrlIf.java:171),其實際

語意在 native 層,jadx 輸出中不可見。但可推論:硬體 scan-mode 編號空間(0/5/8/9/55)是

App 層 DispMode 值的一個子集,55 是硬體「能量」掃描模式 ID,因此 DispMode 55 與 59

直接沿用了硬體編號;57/58/59 是「PW 交錯」的變體編號(底圖 + PW),55/58/59 這組值

正是「能量/彩色/B」三種底圖的 PW 疊加版本編碼。

六、getFuncid 對照(功能鍵 ID 映射)

DispMode.getFuncid()DispMode.java:24-52)把模式映射到「功能 ID」(41–45),

供硬體功能鍵使用(LandActivity.java:786 傳給 getProbebtnIndex):

DispMode funcid 功能
0, 50, 51 41 B
8, 16, 17, 58 42 Color (C)
55, 59 43 Power
5 44 PW
1, 9 45 M

這進一步佐證:58 與 8 同屬彩色功能、59 與 55 同屬能量功能、57 未列出(落入 41 = B)

七、已知缺口與未解之謎 (Gaps)

  1. M_MODE = 1 疑似未使用:全 codebase 未找到 SetDispMode(1) 呼叫;純 M 模式

在此裝置上實際以 9 (BM) 呈現。1 是定義但未啟用的遺留常數。

  1. 57 的 PW 頻譜資料路徑不明:USB 讀取分派(UsbProbeManager.java:256-305)中,

57 不在 bit1 的分派鏈(只有 5 / 55‖8 / 9 / 58‖59),故 57 模式下 PW 頻譜如何

從 FPGA 上傳未在 Java 層交代——可能由 native HH264PlayerIf 或他處處理,需進一步追蹤。

  1. SetScanMode 的精確硬體語意未知HSysctrlIf 為 JNI,掃描模式 0/5/8/9/55 對應的

實際聲學掃描序列(尤其 55 = 能量 與 8 = 彩色 在 FPGA 內的差異)無法從 Java 層證實。

  1. DispMode 55(BPower)為何走 SetScanMode(8) 而 59 走 SetScanMode(55)

推測為「能量參數可由 DSP 在 scan mode 8 上實現,但 PW+能量交錯需要專屬 scan mode 55」,

但此為推論,未在反編譯碼中直接證實。

  1. 16/17/50/51(雙幅/四幅)的資料路徑:這些模式在 USB 層只走 bit0(B 影像),

彩色部分(16/17)如何與 B 交錯上傳未有獨立分支,推測由 SetScanMode(8)

硬體交錯輸出在同一 B buffer 中完成。

23

深挖 5 — INF 格式

PID03 .INF 格式解析 (Probe Description .INF Format)

對象:PID03.zip 內每探頭型號的 pattern0/Normal/<型號>.INF(例:03C5-2Ds.INF,201,837 B) 方法:GBK 解碼 + xxd + 標籤提取。

一、格式定性

自定義「XML 標籤 + 二進制 payload」混合格式:以 GBK 編碼中文 XML 標籤分段,標籤之間是原生二進制數據(float 數組 / 字節表),非標準 XML(無轉義、payload 不可打印)。

<探头一般信息>  [0x16 header: 型號名(12B) + 版本(2B) + float 參數... + 日期戳 "20251126-094503"]
</探头一般信息>
<探头图标> ... </探头图标>
<图谱索引> ... </图谱索引>
<频率列表> float[] ... </频率列表>
<声速列表> float[] ... </声速列表>
...
<MI基准参数> ... </MI基准参数>
<TI基准参数> ... </TI基准参数>

開頭實證(hexdump):3c cc bd cd b7 d2 bb b0 ... 3e = GBK <探头一般信息>,0x74 處 閉合。

二、65 個標籤全集(GBK 解碼提取,去噪後按功能分組)

分組 標籤
基本信息 探头一般信息(型號、版本、日期戳 20251126-094503)、探头图标
幾何/聲學 图谱索引、扫描密度、频率列表、声速列表、偏转角度、穿刺
發射鏈 B/CD 发射聚焦参数、B/CD 发射波形、发射芯片控制参数
接收/波束形成 变迹相关参数、宽带合成截位数、B/CD 亮度补偿参数、盲区补偿参数、B/CD 模式 GrossTGC、接收芯片控制参数、B/CD 旋转角度表
DSC/降採樣 B/C/D 降采样滤波器参数、截位控制、IQ复合参数、加权求和参数、焦点复合参数、C矩阵乘法相关参数
影像後處理 增益范围参数、帧相关及线相关参数、对数压缩参数、B图像增强参数(+App)、背景抑制参数、KeyHole参数、彩色编码表、彩色增强参数、C能量对数压缩参数、NDR平滑阈值、速度方差能量帧相关参数
模式控制 扫描时序、B/C/BD 模式扫描控制、M 模式参数、D型成像参数
PW 多普勒 PW多普勒增益调节、PW壁滤波参数、PW谱平均参数、PW谱压缩参数、PW声音处理参数、PW音频增益系数、PW谱勾边参数、D型勾边平滑参数、PW一键优化参数
安全指數 MI基准参数、TI基准参数(機械/熱指數基準——合規必需)

(少數標籤為二進制數據被 GBK 誤解碼的噪聲,如 #=%吞=潦!,屬正常現象)

三、與 .bin 檔的對應

pattern0/Normal/ 內大量同名 .bin 正是 INF 內各段 payload 的獨立分離版(供 native 按需載入):

03C5-2DsApodizePara.bin     ← <变迹相关参数>
03C5-2DsBFTrunctbit.bin     ← <宽带合成截位数>
03C5-2DsBlindArea.bin       ← <盲区补偿参数>
03C5-2DsB_ImgEnhanceApp.bin ← <B图像增强参数App>(36 KB,最大)
03C5-2DsDSFilterPara_B.bin  ← <B降采样滤波器参数>(737 KB,超大 FIR 系數集)
03C5-2DsCosLutPara_B.bin    ← 餘弦查找表(98 KB)
03C5-2DsDRPara.bin          ← 動態範圍參數
03C5-2DsEmitFocusPara_B.bin ← <B发射聚焦参数>
...

四、結構推斷(已驗證部分)

  • 頭部 0x16 = <探头一般信息> 開標籤 + 0x66 00(0x66=102 = payload 長度?)+ 型號 ASCII(12B 對齊)
  • 型號名重複出現:主型號 03C5-2Ds + 別名 C5-2V / C5-2D(對應 GetProbeName 返回的不同名稱)
  • 日期戳格式 YYYYMMDD-HHMMSS
  • 尾部以 結束 + 0xFF padding

五、未解

  • 標籤與 payload 之間的長度前綴規則(部分段有 2B/4B 長度頭,部分靠標籤閉合定位)
  • 探头图标 段格式(圖標位圖)
  • native 解析器(HPidIf.InitPidSyssetPidFilesPath)的完整解析邏輯在 libdoppler 內
24

深挖 6 — CC32xx 固件

CC32xx MCU 固件靜態分析 (TI CC3220R/CC3235S Firmware)

對象:assets/fw/board/v2|v3/mcu/cc32{20r,35s}/mcuimg2.bin 方法:純離線 xxd/python3/strings(無 TI 工具鏈)。

一、影像身份

大小 SP(向量表[0]) Reset(向量表[1]) vec3
v2/cc3220r 140,376 B 0x20003000 0x2001bd99 0x2001d82d
v2/cc3235s 136,463 B 0x20003000 0x2001bb19 0x2001d58d
v3/cc3220r 140,376 B 0x20003000 0x2001bb61 0x2001d64d
v3/cc3235s 137,288 B 0x20003000 0x2001bd81 0x2001d8a5
  • Cortex-M4 向量表確認(SP=0x20003000)
  • Reset 向量落喺 SRAM(0x2001_xxxx)而非 flash(0x0100_0000) → 典型 TI SimpleLink「run-from-SRAM」影像:代碼由 serial flash 載入 SRAM 執行(CC32xx mcuimg 架構特徵)
  • 首 8 字節無 TI OTA 檔頭 magic(直接係向量表)→ mcuimg2.bin 係裸影像(非 OTA 封裝)

二、可讀字符串(v3/cc3235s,strings -n 5)

file error
Fprobeid.txt          ← 檔案操作:探頭 ID 檔
Fuhandprobe.bin       ← 檔案操作:主固件影像(命名同 App 包 com.SonoiQ.uhandprobe 呼應)
Fwlan_ap_start -s "   ← WLAN AP 啟動命令(-s <ssid>):探頭 MCU 開 AP 模式
uhandprobe_sleep.bin  ← 睡眠模式影像
  • F 前綴 = SimpleLink 檔案 API 名稱殘留(sl_FsOpen 等);串列顯示 MCU 的 serial flash 檔案系統佈局:probeid.txt(探頭 ID)、uhandprobe.bin(升級影像)、uhandprobe_sleep.bin(睡眠影像)
  • wlan_ap_start -s ":MCU 提供 AP 模式啟動能力(Wi-Fi 探頭的直連模式——App 連探頭 AP 192.168.254.1 的對應側)
  • 文本密度極低(大部分為二進制)→ 影像可能經 bit-reversal 或部分打包,裸 strings 不可靠

三、版本差異

  • v2→v3(cc3235s):49%(66,838/136,463)字節不同——實質性固件升級
  • 板級版本對應 fw/board/v2 vs v3(App 內 GlobalVersion P25122602_004 對應 2025-12-26 構建)

四、推斷(未驗證)

  • CC3220R / CC3235S 是同一設計的兩代 WiFi MCU(CC3220 2.4GHz、CC3235 雙頻+BLE 系),固件按板 v2/v3 分別打包 → 無線探頭有兩個硬件代次
  • uhandprobe.binmcuimg2.bin 的關係:mcuimg2 是開機引導影像(run-from-SRAM),uhandprobe.bin 可能是放在 serial flash 的完整應用影像,開機後載入

五、完整解包需求(未做,需工具)

  • TI ImageCreator / Uniflash:正式解析 SimpleLink 影像、serial flash 檔案系統
  • 若影像 bit-reversed(TI CC32xx 歷史上有位序變換),需先做 bit 反轉再 disasm
  • 完整逆向 MCU 協議(AP 握手、探頭控制命令)需 Ghidra(Cortex-M4, little-endian, 無 OS 或 TI-RTOS)
25

深挖 7 — Efinix Bitstream

Efinix Trion T55 FPGA Bitstream — 靜態分析 (Static Analysis)

分析對象:SonoiQ APK 內嵌之 Efinix Trion T55F324 FPGA 位元流檔案。 方法:純離線 (xxd / strings / python3),未執行任何 FPGA 工具鏈。 範圍:RBF 標頭/註解格式、原始位元流特性、版本差異、Java 端處理流程、變體假說、真實逆向之需求。

1. 檔案總覽 (File Overview)

1.1 探頭 FPGA (Probe FPGA) — assets/pid/FPGA_ch16*.rbf

檔案 大小 (bytes) MD5 (前 12) 起頭
FPGA_ch16.rbf 718,569 2775ed70c3b6 FF×32 …
FPGA_ch16_2.rbf 718,569 4cdf4c761b52 FF×32 …
FPGA_ch16_3.rbf 718,569 8c99e843a91c FF×32 …
FPGA_ch16_4.rbf 718,569 7a91e00ba4ba FF×32 …
FPGA_ch16_5.rbf 3,459,240 明文註解標頭 (plaintext annotation header)
FPGA_ch16_6.rbf 718,569 22f86d1bef8b FF×32 …

1.2 主板 FPGA (Board FPGA) — assets/fw/board/{v2,v3}/fpga/{16,32}/UFPGA.bin

檔案 大小 (bytes) MD5 (前 12) 起頭
v2/16/UFPGA.bin 847,677 354c9e368f4b FF×32 …
v2/32/UFPGA.bin 871,678 eefd60449eb0 FF×32 …
v3/16/UFPGA.bin 848,325 ec865c02f962 FF×32 …
v3/32/UFPGA.bin 869,410 663efd0a08a6 FF×32 …
全部 10 個位元流檔案中,僅 FPGA_ch16_5.rbf 帶有明文標頭;其餘皆為無標頭原始位元流 (raw bitstream),直接以 0xFF 開頭。

2. FPGA_ch16_5.rbf — 完整註解位元流 (Full Annotated Bitstream)

2.1 註解標頭格式 (Annotation Header Format)

FPGA_ch16_5.rbf 的前 256 bytes 為純文字註解標頭,欄位如下(以 \n 分隔,其餘以 \n 填充至 256 bytes):

Version: 2022.1.226
Generated: Wed Jul 31 11:07:04 2024

Project: D:\Project\FPGA\YK2100_FPGA_T55_new\03_pnr
Family: Trion
Device: T55F324
Width: 16
Mode: passive
PADDED_BITS: 0
\n \n \n … (padding to 256 bytes)

欄位解讀:

欄位 意義
Version 2022.1.226 Efinity IDE 版本(2022.1, build 226)
Generated Wed Jul 31 11:07:04 2024 編譯時間戳
Project D:\Project\FPGA\YK2100_FPGA_T55_new\03_pnr 原始 PnR 專案路徑(洩漏 Windows 建置機路徑)
Family Trion FPGA 家族
Device T55F324 T55, 324-ball FBGA
Width 16 組態資料匯流排寬度 = 16 bit(平行組態)
Mode passive 被動組態模式
PADDED_BITS 0 尾端未填補位元數 = 0

標頭固定 256 bytes(第 256 byte 即二進位載荷起點,值 0x16)。這份標頭是 Efinity 輸出 RBF 時可選附加的 metadata(AN 006 指出此標頭最多可增加 ~1 KB)。

2.2 原始位元流起始位置 (Raw Stream Boundary)

與其他 5 個原始 RBF(以 0xFF×32 開頭)不同_5 的二進位載荷不含 0xFF 前導序列 (preamble)

  • 標頭結束 / 載荷起點:offset 256,首 8 bytes = 16 8a 22 36 f0 33 29 35
  • 全檔 0xFF105 個(最長連續 1 byte,位於 offset 2691);不存在 0xFF 長連串,故「搜尋第一個 0xFF run」找不到傳統的 raw-stream 邊界。
  • @1@1@ 標記位於 offset 836(即 0x40 0x31 0x40 0x31 0x40),嵌於稀疏 nibble 資料區內,屬位元流內部 framing/annotation 標記,非 raw 資料起點。
  • 載荷可粗分兩段:
  • offset 256 ~ 約 2600:註解/框架區 (annotation/framing),僅 119 種相異 byte 值,含大量 0x000x04/0x05/0x08/0x0c/0x0d 及 ASCII 0 1 4 5 < @,entropy ≈ 4.30。
  • offset 約 2600 → 檔尾:密集位元流資料區,256 種相異 byte 值全出現,entropy ≈ 3.45,0x00 佔約 58%,其餘以單一位元置位的 byte (0x01/0x02/0x04/0x08/0x10/0x20/0x40/0x80) 為主——與第 3 節之原始 RBF 統計特徵一致,屬同一種編碼

2.3 位元流大小對照 (Bitstream Size vs. T55 Spec)

依 Efinix AN 006「Configuring Trion FPGAs」 Table 1:

T55 最大支援組態位元數(單一影像, single image)= 27,675,040 bits

換算與實測:

27,675,040 bits ÷ 8 3,459,380 bytes
FPGA_ch16_5.rbf 實測 3,459,240 bytes(= 27,673,920 bits)
差值 −140 bytes(−1,120 bits)
標頭扣除後載荷 3,458,984 bytes(27,671,872 bits)

_5 檔大小與 T55 單一影像最大組態位元數幾乎完全吻合(差距 140 bytes,可由「標頭填充 + PADDED_BITS: 0 不填尾端」解釋)。_5 即 T55 的完整未壓縮位元流。 反觀 718,569-byte 的原始 RBF 僅為其 ~20.8%(約 4.81:1)——見第 3.3 節。

3. 原始 RBF 位元流 (Raw RBF Bitstreams)

FPGA_ch16.rbf_2_3_4_6 五個檔大小完全相同(718,569 bytes)、無標頭、0xFF 起頭。

3.1 前導 / 結尾結構 (Preamble / Trailer)

FPGA_ch16.rbf 為例:

  • 前導0xFF × 32(idle/sync 填充),其後接 sync/命令字 0x6a
  • 共同前綴 (common prefix):五個檔共享 42 bytes 前綴:
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff   (32 × 0xFF)
6a f7 f7 f7 f7 f7 f7 f3 fb f2                     (sync + 9 bytes)
  • 結尾:檔尾 0xFF 填充 59 bytes
  • 內部長連串:offset 75 處有 255-byte 0xFF run、offset 346 處有 288-byte 0x00 run;全檔 ≥16-byte 的相同-byte run 高達 1,567 段——呈結構化區塊(sync/pad/data),非壓縮流樣貌
  • byte 統計0x00 佔 57.3%(411,790/718,569),其次為單一位元置位值 0x10/0x20/0x08/0x40/0x02/0x04/0x01/0x80;256 種 byte 值全出現,entropy ≈ 3.29。

3.2 五個變體差異 (Variant Differences)

兩兩 byte 比對(vs. FPGA_ch16.rbf,718,569 bytes):

檔案 首個差異 offset 差異 byte 數 差異比例
_2 45 364,395 50.7%
_3 42 365,685 50.9%
_4 44 365,322 50.8%
_6 42 365,539 50.9%

→ 五檔共享 42-byte 前綴後,約半數位元組互異。這不是「僅 channel 設定」的微小差異,而是五份實質不同、但同規模(同 T55、同編碼)的完整位元流,對應不同硬體主板版本(見第 5.2 節對應表)。

3.3 大小之謎:718,569 bytes vs 27.68 M bits

T55 完整位元流需 27,675,040 bits(3.46 MB),但原始 RBF 僅 718,569 bytes(5,748,552 bits,~20.8%)。

假說(未經 Efinity 專案設定確認)

  1. 位元流壓縮 (bitstream compression):Efinix 支援位元流壓縮(Efinity Programmer Guide 記載 Titanium 預設壓縮至約一半;Trion 亦具壓縮選項)。壓縮由 FPGA 內部組態控制器解壓,故檔案無標準壓縮 magic、呈稀疏/區塊結構。
  2. 或為截斷/最小化位元流(去除尾端填充與閒置 frame)。
  3. 前導 0x6a(bit-reversed 為 0x56,見第 4 節 UFPGA)為 sync 字,屬壓縮或標準位元流皆可能之結構。
兩者何者成立需對照 Efinity 專案(YK2100_FPGA_T55_new)的 COMPRESSION 設定,靜態層面無法定論——已誠實標記為未定

4. UFPGA.bin — 主板 FPGA 位元流 (Board FPGA Bitstreams)

4.1 大小 (Sizes)

主板版 16 通道 32 通道
v2 847,677 B 871,678 B(+24,001)
v3 848,325 B(+648) 869,410 B
  • 32 通道比 16 通道約大 ~24 KB(v2: +24,001;v3: +21,085)——額外通道之邏輯使然。
  • v3 與 v2 差異甚小(16ch: +648 B;32ch: −2,268 B)——主板小改版。
  • 四檔兩兩差異達 69.6 萬~78.5 萬 bytes(幾乎全檔不同),共同前綴僅 41–42 bytes。

4.2 位元反轉關係 (Bit-Reversal Relationship)

UFPGA.bin 與探頭 RBF 存在逐 byte 位元反轉 (bit-reversal) 對應:

FPGA_ch16.rbf  前導:  FF…FF  6a f7 f7 f7 f7 f7 f7 f3 fb …
UFPGA(v2/16)   前導:  FF…FF  56 ef ef ef ef ef ef cf df …
bitrev(56 ef…)        =  6a f7 f7 f7 f7 f7 f7 f3 fb …   ← 與 ch16 前 41 bytes 完全一致

0x6a↔0x560xf7↔0xef 恰為位元反轉對。意即主板 FPGA 位元流與探頭 FPGA 位元流共享同一 sync/前導字,但以相反位元序 (bit order) 儲存(典型為 SPI LSB-first vs MSB-first 載入差異)。注意兩者為不同檔案、不同 FPGA(尺寸不同),僅前導字序相反。

5. Java 程式碼處理流程 (Java Handling)

5.1 載入 (Loading) — HUsbProbeInterface 建構子

// HUsbProbeInterface(Context context) — 節錄
this.mFPGARbfBuf  = LoadFPGAFile(ReadBinAssetsFiles.mFPGA_ch16_RbfFileName);   // ch16.rbf
this.mFPGARbfBuf2 = LoadFPGAFile(ReadBinAssetsFiles.mFPGA_ch16_2_RbfFileName); // _2
this.mFPGARbfBuf3 = LoadFPGAFile(ReadBinAssetsFiles.mFPGA_ch16_3_RbfFileName); // _3
this.mFPGARbfBuf4 = LoadFPGAFile(ReadBinAssetsFiles.mFPGA_ch16_4_RbfFileName); // _4
this.mFPGARbfBuf5 = LoadFPGAFile(ReadBinAssetsFiles.mFPGA_ch16_5_RbfFileName); // _5
String str = MyApplication.App().getExternalFilesDir() + "/FPGA_ch16_5.rbf";
if (new File(str).exists()) {
    this.mFPGARbfBuf5 = FileOperate.readFileToByteArray(str);   // ★ 外部檔案覆寫
}
fpgargb_lib.byteswap(mFPGARbfBuf5.length, mFPGARbfBuf5);         // ★ 僅對 _5 做 byteswap
  • ReadBinAssetsFiles 只宣告 ch16_2_3_4_5 五個檔名常數——FPGA_ch16_6.rbf 未被任何程式碼引用(孤兒檔,遺留資產)。
  • 外部檔案覆寫路徑 (external-file override)getExternalFilesDir()/FPGA_ch16_5.rbf 若存在即取代 asset 版本——即 board v2110 的 FPGA 位元流可現場升級 (field upgrade)
  • byteswap 僅施加於 _5,其餘四檔原樣載入、不做變換。

5.2 主板版本對應 (Board-Version → Bitstream Mapping) — SetCurProbe

if (BoardVersion() == 1)      buf = ch16.rbf;
else if (BoardVersion() == 3) buf = ch16_3.rbf;
else if (BoardVersion() == 4) buf = ch16_4.rbf;
else if (BoardVersion() == 5) buf = ch16_4.rbf;      // v4/v5 共用
else if (BoardVersion() == 2110) buf = ch16_5.rbf;   // ★ byteswap + 可外部覆寫
else                          buf = ch16_2.rbf;      // 預設

5.3 byteswap 原生實作 — fpgargb_lib.byteswap(int, byte[])

System.loadLibrary("fpgargb_lib")lib/arm64-v8a/libfpgargb_lib.so)。逆向反組譯 Java_jni_fpgargb_1lib_byteswap(objdump, ARM64):

  • GetByteArrayElements → 取得陣列指標。
  • 逐對交換相鄰 bytebyte[i] ↔ byte[i+1](i = 0, 2, 4, …),即 16-bit word 位元組序交換 (16-bit endian swap);NEON ld2/st2 對 32-byte 區塊向量化。
  • ReleaseByteArrayElements,回傳 true

→ 名稱雖叫 byteswap,實為每 16-bit word 內高低位元組對調。這與 _5 標頭 Width: 16(16-bit 平行組態)吻合:Efinity 輸出之 byte 序與 16-bit 平行匯流排載入所需 byte 序相反,故載入前對調(參照 AN 006 Table 12:16-bit bus 每週期載入 Byte0(高)+Byte1(低))。此變換不作用於其他 board 版本,顯示舊板(1/2/3/4/5)採不同載入模式(如序列 SPI,不需 byte 對調),僅 board v2110 採 16-bit passive 平行載入。

6. 六個變體之假說 (Hypotheses on the 6 Variants)

依第 5 節程式碼對應與檔案特性:

檔案 大小 對應主板版 推測角色
ch16.rbf 718,569 BoardVersion 1 v1 主板(最舊)
ch16_2.rbf 718,569 預設 (else) 通用/未識別主板
ch16_3.rbf 718,569 BoardVersion 3 v3 主板
ch16_4.rbf 718,569 BoardVersion 4、5 v4/v5 主板(共用)
ch16_5.rbf 3,459,240 BoardVersion 2110 新版主板,16-bit passive 平行組態、完整未壓縮位元流、可現場升級
ch16_6.rbf 718,569 無引用 遺留/棄用資產

關鍵結論:六檔皆為 T55F324(16 通道探頭) 位元流,但屬不同硬件主板改版的組態,非「不同 channel 數」之變體(channel 差異反映在 fw/board/*/fpga/{16,32} 的 UFPGA.bin,而非 pid/ 內的 ch16 系列)。_5 為唯一由 Efinity 原始輸出之完整註解位元流(帶標頭、需 byte 對調、可外部覆寫),其餘五檔為預先處理過之無標頭位元流。

7. 真實位元流逆向需要什麼 (What Real Bitstream Reversal Requires)

誠實評估:靜態層面無法還原邏輯設計。

  • 無公開格式規範:Efinix 的 RBF/bitstream 內部格式(frame 佈局、CRAM 位址對應、routing mux 編碼)未公開;公開文件(AN 006、Efinity Programmer/Software User Guide)僅說明組態介面、位元流總大小與打包方式,不揭露位元流內容語意。目前無第三方格式規格。
  • 需要 Efinity 工具鏈:真正的逆向路徑是取得/安裝 Efinity IDE,用其 dbedit(資料庫編輯器)或把位元流載回工具、以對應 project(標頭洩漏之路徑 D:\Project\FPGA\YK2100_FPGA_T55_new\03_pnr)重新生成並 diff,才能建立「位元流 bit ↔ 邏輯 cell/routing」的對照。此工具鏈為閉源、需授權。
  • 黑箱差分 (black-box diff) 為可行中間路徑:既然有 5 個同規模、半數位元組相異的原始位元流(不同主板版)+ 1 個完整位元流,可對齊其 42-byte 前綴後做逐 frame 差分,推測哪些區段對應「主板版本相依」的 I/O/routing 區塊。但無 CRAM 佈局規格下,仍無法將位元對應至具體 LUT/FF。
  • _5 的 byteswap 是唯一已知轉換:16-bit word 位元組對調(第 5.3 節)是唯一可驗證的位元流轉換;其餘(UFPGA 的位元反轉、718KB 的壓縮/截斷)僅為統計觀察與假說。

附錄:關鍵原始十六進位 (Key Hex)

FPGA_ch16.rbf  前 64B:  FF×32  6a f7 f7 f7 f7 f7 f7 f3 fb f2 f9 fb fa f1 f8 f9
                        fd f9 f8 f8 fa fb fd f9 ff fe ff fb fc fc f9 fd …
FPGA_ch16_5.rbf 標頭(0..255): 純文字 + \n 填充;載荷起點 256 = 16 8a 22 36 f0 33 29 35 ec 64 be c0 …
FPGA_ch16_5.rbf @1@1@:  offset 836 (0x40 31 40 31 40)
UFPGA v2/16 前 64B:  FF×32  56 ef ef ef ef ef ef cf df cf 1f df cf 0f 1f 9f …
26

Class Diagram

Core Types(基於 jadx 反編譯實測)

classDiagram
    class MyApplication {
        +static final int[] OEM_*_ID (18個)
        +DbManager.DaoConfig userdaoConfig
        +DbManager.DaoConfig operatorAuthdaoConfig
        +DbManager.DaoConfig daoConfig
        +TaskManager mTaskManager
        +HPatientMng GetPatientMng()
        +Exam getActiveExam()
        +void handleException(Throwable)
    }
    class SplashActivity {
        +List~User~ allUser
        +void initDicomServices()
        +void routeToModelActivity()
    }
    class BaseActivity {
        +UltrasysApp mUltrasysApp
        +void handleMessage(int, Object)
        +void offWifi()
    }
    class Ultrasys {
        <<singleton>>
        +DscCtrl mDscCtrl
        +FeUltrasysCtrl mFeUltrasysCtrl
        +ImagePlayer mImagePlayer
        +CinePlayer mCinePlayer
        +PresetServer mPresetServer
        +MITIData mMITIData
        +static Ultrasys Instance()
        +void setMITIData(float[])
        +int GetProbeConType()
    }
    class MainHandler {
        <<singleton>>
        +Vector~Handler~ mDopplerMsgHandlerVector
        +void InitCtoJavaMessageHandler()
        +void RegistryDopplerHandler(Handler)
    }
    class jnitojava {
        +static native int JavaGetMessage(long[])
        +static boolean UsbWriteDwords(int, byte[], int)
        +static boolean UsbReadDwords(int, byte[], int)
        +static int WUsbReadImg(byte[], int)
        +static void UpdateMITI(float[])
    }
    class HPidIf {
        +static native boolean InitPidSys()
        +static native boolean setPidFilesPath(String)
        +static native int GetProbeId(String)
        +static native int GetProbeElementNumber()
        +static native float GetProbeRadius()
    }
    class ConnectType {
        <<singleton>>
        +const SIMU/WLAN/WUSB/USB/UWLAN/RGBIF/BROADCAST
        +void SetConnType(int)
    }
    class UsbProbeManager {
        <<singleton>>
        +void Write(int, byte[], int)
        +void Read(int, byte[], int)
        +int ReadEncImg(byte[], int)
        +int GetImgPack(byte[], int[])
    }
    class AiFuncMng {
        +void trigger(AiMeasBase)
    }
    class AiMeasBase {
        <<abstract>>
        +void run()
    }
    class VoskSpeechRecognizer {
        +void recognize()
    }
    class Exam {
        +int examState
    }
    class AnimalExam {
        +int examState
    }
    MyApplication --> TaskManager : owns
    MyApplication --> Exam : queries
    MyApplication --> AnimalExam : queries (vet)
    BaseActivity --> Ultrasys : hosts
    SplashActivity --> BaseActivity : routes
    Ultrasys --> MainHandler : subscribes
    MainHandler --> jnitojava : polls
    jnitojava --> UsbProbeManager : delegates I/O
    Ultrasys --> ConnectType : reads
    ConnectType --> HPidIf : native sync
    AiFuncMng --> AiMeasBase : dispatches
    AiFuncMng --> VoskSpeechRecognizer : voice control

Notes

  • Ultrasys 內部持有 DscCtrl/FeUltrasysCtrl/ImagePlayer/CinePlayer/PresetServer 等,但通過 public 字段暴露(非 getter),跨包直接訪問
  • jnitojava 是 native 側硬編碼的回調入口類——方法簽名即 ABI
  • MainHandler 是唯一的 C→Java 消息出口(輪詢 + 主線程廣播)
  • AI 類之間用 AiMeasBase 抽象:每個診斷功能一個子類,AiFuncMng 統一調度
27

Sequence Diagrams

Workflow 1: App 啟動 → 掃描界面

sequenceDiagram
    participant User
    participant Splash as SplashActivity
    participant App as MyApplication
    participant Dicom as JDicomServer
    participant Main as BaseActivity
    participant Ultrasys
    User->>Splash: 啟動 App
    Splash->>App: onCreate(DB / TaskManager / crash handler)
    Splash->>Splash: 顯示登錄(用戶列表 + 密碼)
    User->>Splash: 登錄 / 緊急模式
    Splash->>Dicom: 啟動 DICOM 服務(Storage/Worklist/MPPS/Print)
    Splash->>Main: 路由到機型 Activity(LandQ6/PortQ61/…)
    Main->>Ultrasys: Instance() 初始化成像管線
    Main-->>User: 掃描界面就緒

Walkthrough

  1. 啟動SplashActivity(manifest LAUNCHER)
  2. DB 初始化MyApplication.onCreate():xUtils 三庫 + WAL
  3. 登錄 — 用戶選擇、密碼、自動登錄、緊急按鈕
  4. DICOMJDicomServer 等 native 服務啟動
  5. 路由 — 按 OEM 資源開關跳 LandActivity / LandQ6Activity / PortQ61Activity / LianmedActivity / LandCCGCActivity / PortActivity

Workflow 2: 探頭連接與影像流

sequenceDiagram
    participant Probe as FPGA Probe
    participant Usb as UsbProbeManager
    participant Jni as jnitojava
    participant Pump as MainHandler
    participant Core as Ultrasys
    participant GL as HGLImageJNIViewLib
    Probe->>Usb: USB bulk 數據(加密影像包)
    Usb->>Jni: WUsbReadImg / GetImgPack
    Jni->>Core: JavaGetMessage 輪詢消息
    Core->>Pump: 廣播 what/arg1/arg2
    Pump->>Core: Handler 回調(主線程)
    Core->>GL: 渲染(OpenGL)
    GL-->>User: 畫面更新

Walkthrough

  1. 連接ConnectType.SetConnType(USB_CONNECT_TYPE)HSysctrlIf.SetProbeConnType(4)
  2. PID 初始化HPidIf.setPidFilesPath(assets/pid/PID03.zip)InitPidSys()
  3. 數據拉取UsbProbeManager.Read() / ReadEncImg()
  4. 消息泵MainHandler.InitCtoJavaMessageHandler() 輪詢 jnitojava.JavaGetMessage(long[4])
  5. 顯示UltrasysImagePlayer/DispSurface(OpenGL)

Workflow 3: AI 測量(膀胱體積)

sequenceDiagram
    participant User
    participant UI as ImageDisplayFragment
    participant Mng as AiFuncMng
    participant Meas as BladderMeas
    participant Pt as org.pytorch
    participant So as libpytorch_jni.so
    participant Mask as MaskProcessor
    User->>UI: 點擊膀胱測量
    UI->>Mng: trigger(BladderMeas)
    Mng->>Meas: 異步執行
    Meas->>Pt: 前向推理(當前幀)
    Pt->>So: JNI
    So-->>Pt: 分割掩膜
    Pt-->>Mask: 後處理(直徑/面積擬合)
    Mask-->>Meas: 體積結果
    Meas-->>UI: Observer 回調 → 疊加顯示

Workflow 4: DICOM 存儲

sequenceDiagram
    participant User
    participant UI as ReportFragment
    participant Dicom as JDicomStorageService
    participant Dopp as libdoppler (native DICOM)
    participant PACS as PACS Server
    User->>UI: 檢查結束 → 存儲
    UI->>Dicom: 發送圖像
    Dicom->>Dopp: native 調用
    Dopp->>PACS: C-STORE(DICOM 協議)
    PACS-->>Dopp: ACK
    Dopp-->>Dicom: 結果
    Dicom-->>UI: 狀態提示
28

Getting Started

Getting Started — 重現與擴展分析

Prerequisites

工具 版本(實測) 路徑
jadx 1.5.6 /opt/homebrew/bin/jadx
apktool 3.0.3 /opt/homebrew/bin/apktool
Java (JDK) OpenJDK 17.0.20.1 /usr/bin/java
git 系統自帶
Python 3 3.9+ 系統自帶(統計腳本用)
工具路徑以 reverse-skill tool-index.md 為準(本機已裝,無需 bootstrap)。

重現反編譯

APK="/Volumes/End-Gsme/untra-sound/SonoiQ-2.2.15_260113 (1).apk"
cd /Volumes/End-Gsme/untra-sound/sonoiq-reverse

# Java 反編譯(~3 dex,6025 檔)
jadx -d jadx_out --show-bad-code "$APK"

# 解包(smali + manifest + 資源)
apktool d -f -o apktool_out "$APK"

驗證樣本一致:

shasum -a 256 "$APK"
# 867c136abf74211da7eaa511f0d5a1de022064421d7b2a5410d105f32060a2c1

資產清單統計

unzip -l "$APK" > _apk_listing.txt
# 再跑 Python 分類腳本(見 reports/static-analysis-report.md 附錄)

常用探索命令

# 找 native 加載點
grep -rn "System.loadLibrary" jadx_out/sources/handprobe jadx_out/sources/jni \
  jadx_out/sources/kernel jadx_out/sources/com/Com --include="*.java"

# 找關鍵字(加密/簽名/證書/網絡)
grep -rln "encrypt\|decrypt\|AES\|RSA\|certificate\|trust" \
  jadx_out/sources/handprobe jadx_out/sources/com/Com --include="*.java"

# 搜 smali(patch 前)
grep -rn "invoke-static.*CheckSign\|checkSign" apktool_out/smali*/ --include="*.smali"

已知限制

  • 僅 arm64-v8a ABI——無 armeabi-v7a 模擬器支持
  • res/ 檔名全面混淆;assets/ 明文
  • 核心算法在 native(libdoppler 15.4 MB)——Java 層只有 JNI 簽名;深入需 IDA/Ghidra(reverse-skill ida-reverse/radare2 路由)
  • LandQ6Activity/LandQ62ActivitysharedUserId="android.uid.system"——重打包需系統簽名

Where to Go Next