12.9. Vendor diagnostic, factory-test and provisioning tools in bin/xbin¶
artifacts/vendor_a/bin and artifacts/system_a/system/bin together hold
several hundred native executables that had never been individually looked at
in this project — only the .so HAL libraries and a handful of named tools
had been. This page separates generic AOSP/toybox binaries from
Qualcomm-vendor-specific ones with an actual per-file count, then disassembles the
subset that could plausibly matter to a device with an unlocked/test-key-signed
bootloader: diagnostic-port command surfaces, factory-test entry points, and
provisioning tools that write into TZ-backed key storage.
Everything below is read statically from the shipped binaries in
artifacts/vendor_a and artifacts/system_a. No code was executed, no
device was connected, and no /dev/diag traffic was captured.
12.9.1. Inventory methodology and counts¶
artifacts/vendor_a/bin has 333 directory entries; artifacts/system_a/system/bin
has 423. Neither number is “native executables” — most entries in both
directories are 7z-extracted symlinks, and 7z represents a symlink not as
a filesystem link but as a plain regular file whose entire content is the
literal link target text (confirmed by hand: artifacts/vendor_a/bin/cat
is a 13-byte regular file containing exactly the 13 ASCII bytes
toybox_vendor, not an ELF header and not a real symlink inode). That
extraction quirk is what makes a raw ls count meaningless and is the first
thing the classification below controls for.
Counting by content (not by name) across both directories:
artifacts/vendor_a/bin (333 entries)
172 -> symlink-placeholder, target "toybox_vendor" (AOSP toybox multicall)
6 -> symlink-placeholder, target "toolbox" (legacy AOSP multicall)
23 -> real files, but shell scripts, not ELF (init.{qcom,qti,mdm,...}.sh,
init.{keybox,erase_keybox,rpmb}.sh,
qca6234-service.sh, checkpoint_gc)
2 -> directories misfiled alongside binaries (hw/, qmi-framework-tests/)
130 -> real ELF 64-bit AArch64 executables <- the actual RE surface
artifacts/system_a/system/bin (423 entries)
~233 -> symlink-placeholders/one-line wrapper scripts (toybox x188, toolbox x6,
iptables/netutils-wrapper x10,
mke2fs x3, fsck.f2fs x3,
apex linker/linker64 x5,
ziptool x2, bzip2 x2, init,
app_process64, hwservicemanager,
ethtool, and five "cmd <x> \"$@\""
one-liners for window/settings/
package/input/appops)
159 -> real ELF 64-bit AArch64 executables
~31 -> real shell scripts (recovery/init helpers, all stock AOSP)
The 130 real ELF executables in vendor_a/bin were then grepped by name for
the standard Qualcomm/BSP vocabulary (diag, ftm, qmi, rpc,
cnss, wcn, ims, qti/qcom, sensor, mdm/modem,
persist, rmnet, factory, qsee, tz, hdcp, keybox,
wigig/11ad, wfd) and by manual eyeballing of everything the grep
missed. Of the 130:
8 are stock AOSP/public tools, confirmed by
strings/imports rather than name alone:awkandsh(AOSP’s standalone one-true-awk andmkshbuilds — separate binaries from, not symlinks to,toybox_vendor),toolboxandtoybox_vendorthemselves,dumpsysandlogwrapper(plain AOSP framework/init helpers), andboringssl_self_test32/boringssl_self_test64(the public AOSP BoringSSL FIPS self-test binary, imports onlylibcrypto/standard libc symbols, no vendor libraries).122 are vendor/Qualcomm-specific by name and (for every one actually opened below) confirmed by their imported libraries and embedded strings — they link against
libdiag.so,libqmi_cci.so,libnl.so+libwigig_ftm_flows.so,libkeymasterprovision.so,libhdcp1prov.so/libhdcp2p2prov.so, QSEECom APIs, or similar Qualcomm-only libraries that don’t exist on a generic AOSP device.
artifacts/system_a/system/bin (the Android-15-vintage GSI-like system
half of this device’s The Onyx/Boox platform layer A/B split) turned up essentially
nothing in the same keyword pass beyond generic AOSP components that merely
contain a matching substring — iptables/ip6tables (netfilter,
public), sensorservice, telecom, debuggerd, charger,
recovery-persist — all stock AOSP binaries with public, documented
behaviour. No Qualcomm-vendor-named native binary exists in system/bin on
this device; the entire vendor-diagnostic/factory-test/provisioning surface
lives in vendor_a/bin, consistent with the vendor/system version split
described in The Onyx/Boox platform layer.
Of the 122 vendor-specific vendor_a/bin executables, 29 were opened
and read (file + size + imported-symbol list + full strings dump,
cross-checked against their exact .rodata string offsets where a claim
below depends on it):
ftmtest ftm_flows_test myftm mm-audio-ftm
diag_mdlog diag_klog diag_socket_log diag_uart_log
diag_callback_sample diag_dci_sample test_diag ssr_diag
fmfactorytest fmfactorytestserver fmconfig
vendor_cmd_tool irsc_util
KmInstallKeybox StoreKeybox hdcp1prov hdcp2p2prov
qseecomd qseecom_sample_client secure_ui_sample_client
vendor.qti.hardware.soter@1.0-provision qwes_cli
athdiag cnss_diag sscrpcd ssgtzd dpmQmiMgr
feature_enabler_client
Five of those (fmfactorytest, fmfactorytestserver, ftmtest,
KmInstallKeybox, hdcp1prov) were taken to full AArch64 disassembly
with llvm-objdump -d plus manual ADRP/ADD string-literal resolution,
because their strings suggested something worth confirming in code rather
than trusting the string dump alone. Those five are the findings below.
The remaining ~93 vendor-named binaries (qrtr-ns/qrtr-cfg/qrtr-lookup,
pd-mapper, netmgrd, ipacm/ipacm-diag, wpa_cli,
hostapd_cli, loc_launcher/lowi-server/xtra-daemon/
xtwifi-client, wigighalsvc/wigig_wiburn/wigignpt,
wfdvndservice/wfdhdcphalservice/wifidisplayhalservice,
thermal-engine, msm_irqbalance, power_off_alarm,
poweropt-service, rmt_storage, subsystem_ramdump,
ssr_setup, time_daemon, tloc_daemon, npu_launcher,
nn_device_test, nativechitest/nativehaltest,
chifeature2test/chiofflinepostproctest, and the ims*/imsrcsd
family, among others) are Qualcomm reference-platform daemons and test
harnesses for radio/IPA/location/thermal/display subsystems whose names,
sizes and spot-checked library dependencies are consistent with their
apparent public purpose (QMI/QRTR IPC plumbing, IMS/RCS daemons, WLAN location, thermal
mitigation, NPU inference test harness, camera HAL conformance test
binaries). They were not individually disassembled — there is no indication
in their names or on-disk footprint that they differ from well-documented
Qualcomm BSP components; individual entries for all 93 are omitted below
rather than padding this page with unverified assumptions.
12.9.2. Findings¶
12.9.2.1. FM radio factory test¶
fmfactorytest (16,024 bytes) is a tiny CLI client; fmfactorytestserver
(24,608 bytes) is the daemon it talks to over
/dev/socket/fm_factory_socket_server (string at file offset 0x9cc in
fmfactorytest’s .rodata). Full disassembly of fmfactorytest’s
single .text blob (file offset 0x1000–0x15dc, no function symbols
— the binary exports/imports only PLT stubs) shows:
0x1098 add x1, x1, #0xa2b ; "enable" (argv[1] compared via strcmp)
0x10b4 add x1, x1, #0x8f0 ; "disable"
0x1198 add x1, x1, #0x92d ; "tune"
0x120c add x1, x1, #0xa32 ; "seeknext"
argv[1] must be exactly one of those four verbs. If the client can’t
connect() to the factory socket (0x1150–0x1164), it does not just
fail — it forks and, in the child, calls ``system()`` naming the server
binary itself:
0x1268 bl fork
0x126c cbz w0, 0x12d0 ; child branch
...
0x12d0 adrp x0, 0x0
0x12d4 add x0, x0, #0x96b ; "fmfactorytestserver"
0x12d8 bl system
0x12dc mov w20, w0
0x12e0 b 0x1188 ; back to log + continue
the parent retries connect() up to 10 times with a ~15.7 ms
(0x3d40 = 15680) usleep() between attempts (0x1278–0x12c0)
before giving up. So invoking fmfactorytest with any recognised verb is
sufficient, on its own, to spawn fmfactorytestserver via a bare
system("fmfactorytestserver") call if it isn’t already running — no
absolute path, relying entirely on $PATH. This is unremarkable as a
factory tool (inferred: likely invoked by test harnesses that already
control the environment, not confirmed). But it is a concrete,
address-verified example of this device’s diagnostic tooling using
fork() + system() with a
bare (non-absolute) command name, which is exactly the pattern that matters
if $PATH/environment trust ever becomes attacker-influenced in some other
context on this device.
The server side, once connected, accepts the same four verbs as
colon-prefixed command strings, and drives them straight into the V4L2
kernel radio driver. strings on fmfactorytestserver shows
/dev/radio0 (offset 0x11fd) opened, and disassembly around the two
ioctl() call sites at file offsets 0x2388 and 0x23b8 shows the
request code built as:
0x236c mov w1, #0x561c
0x2380 movk w1, #0xc008, lsl #16 ; w1 = 0xc008561c
0x2384 str x8, [sp, #0x30] ; {control_id, value} = {0x08000003, 0x0e}
0x2388 bl ioctl
0xc008561c decodes as the standard V4L2 _IOWR('V', 28, ...) —
VIDIOC_S_CTRL (an 8-byte struct v4l2_control{__u32 id; __s32 value;},
matching the 0x08 size field) — i.e. this is a plain V4L2 tuner/radio
control-set ioctl, not anything proprietary. The FM factory-test path is,
in the call sites traced here, a userspace shim over the standard Linux V4L2
radio API, with no proprietary or hidden logic found.
12.9.2.2. WLAN “FTM” tools¶
ftmtest (34,344 bytes) and ftm_flows_test (28,568 bytes) sit right
next to the Qualcomm-diag family’s ftmdaemon/ftm_flows_test naming
convention (where “FTM” = Factory Test Mode, the RF calibration mode most
Qualcomm phones can be booted into), but are unrelated to it. Their imports (nl_socket_*, nla_*, genl_* from
libnl.so; aoa_init/aoa_process from a private libaoa.so;
wigig_ftm_flows_* from libwigig_ftm_flows.so) and usage strings
("ftm <mac_addr> [<count> <delay> [<timeout>]]: perform single-shot FTM
measurement", "aoa <mac_addr> ...: perform AOA measurement") show these
are WLAN location test tools: they drive the 802.11mc Fine Timing
Measurement protocol and Angle-of-Arrival ranging over nl80211
vendor commands, for indoor-positioning RF calibration — an entirely
different “FTM” than the Qualcomm diag/factory-test one. Naming collision,
confirmed in code, not a shared code path.
ftmtest’s command dispatch (no function symbols; offsets below are file
offsets within its single .text) does four back-to-back strcmp()
calls against the second command-line argument:
0x52d0 add x1, x1, #0x831 ; "capa" -> cbz -> 0x536c (table slot +0x00)
0x52ec add x1, x1, #0x742 ; "ftm" -> cbz -> 0x5358 (table slot +0x20)
0x5300 add x1, x1, #0x7fe ; "aoa" -> cbz -> 0x5360 (table slot +0x40)
0x5314 add x1, x1, #0x8c7 ; "ftmaoa" -> cbz -> 0x5368 (table slot +0x60)
0x5328 ... ; none matched -> "unknown command: %s", exit
each match selects one of four 0x20-byte-stride entries in a static table
(base x7000+0xd0), and the actual handler is reached through an indirect
call, blr x20 at file offset 0x53c4, after a small range check
(0x5394–0x53b0) that looks like an LLVM CFI jump-table guard rather
than application logic (it pairs with the __cfi_slowpath/__cfi_check
imports present throughout this binary — the whole vendor_a build appears
to be CFI-instrumented, consistent with Security and DRM userspace’s description of this
device’s hardening baseline). capa/ftm/aoa/ftmaoa dispatch to
“show location capabilities”, single-shot FTM ranging, AOA measurement, and
combined FTM+AOA respectively — all consistent with the usage strings and
with nothing that looks like a hidden fifth command.
12.9.2.3. Attestation-keybox provisioning tool (KmInstallKeybox)¶
KmInstallKeybox (67,944 bytes) links libkeymasterprovision.so,
libqtikeymaster4.so, libcrypto.so (X509/EVP calls) and
libqmi_cci.so, and exports the mangled C++ symbol
keymasterdevice::KeymasterKeyProvision::KeyMasterInstallKeybox(km_install_keybox_t*).
Per its own embedded usage text (which the disassembly below confirms is
exactly, and only, printed by the argument-count-mismatch path — this is
documentation, not enforced logic in this binary):
KmInstallKeybox will install the attestationkeys (both RSA and ECC) along
with all the device identifiers (brand, device, product, serial#, IMEI,
MEID (if available), model and manufacturer). [...] Once 'Provisioning
Success' state is set in KM TA / Strongbox, attestation keys / Device
ID's cannot be re-provisioned
**RMA Use Case **
For RMA use case, OEM's can set the following devcfg parameter in
'keymaster_oem_config.xml'
Please ensure to sign this debug devcfg with the serial# of the device as
this will enable re-provisioning of keys and if used incorrectly can open
up the device to be re-provisioned.
<props name="allow_reprovision" type=DALPROP_ATTR_TYPE_UINT32>
usage: LD_LIBRARY_PATH=/vendor/lib64/hw KmInstallKeybox Keybox_file Device_ID provision_device_ids(true/false) Strongbox_Keybox_file Strongbox_Device_ID strongbox_provision_device_ids(true/false)
Disassembly confirms this text (three separate literals at file offsets
0x37c3, 0x332a, 0x3e2e in .rodata — scattered inside a much
larger, heterogeneous rodata blob, with roughly 3.3 KB of interleaved,
unrelated Keymaster strings between them (KM_TAG_* names, error messages
like “SW enforced failed”, “Meid: “, “SerialNum: “, etc.), not one
contiguous help banner) is only reachable from the argc-mismatch path,
printed via three back-to-back calls
at file offsets 0xaae0, 0xaaf0 and 0xab00 into an internal
puts-style wrapper at 0x5388, immediately followed by
mov w0, #-0x1; bl exit at 0xab10–0xab14. This is Qualcomm’s own
documentation of a device-identity re-provisioning mechanism, gated (per that
same text) by a signed devcfg property rather than by anything in this
userspace binary — the signature check, if enforced, lives in the Keymaster
TA, which this static pass did not reach (see Honest limits). It is
reported here because it is a factory/RMA-only capability to re-provision
attestation identity and key material, shipped, unremoved, in a consumer
retail image, and worth knowing about for anyone reasoning about this
device’s attestation trust boundary — not because a working bypass was
demonstrated.
StoreKeybox (33,432 bytes, UBSan-instrumented, plain ifstream-based)
is the sibling tool that reads a keybox file from disk; it was string- and
import-level examined but not disassembled — its behaviour (open file, parse,
store) is unremarkable relative to KmInstallKeybox and adds nothing this
page didn’t already cover.
12.9.2.4. HDCP1 key-provisioning tool (hdcp1prov)¶
hdcp1prov (11,648 bytes) is small enough to disassemble in full. It links
only libhdcp1prov.so, libdiag.so and liblog.so, and exports
nothing beyond the CRT stubs — all logic funnels through two imports,
HDCP1_key_provision and HDCP1_key_verify. Reading main (file
offset 0x106c–0x130c):
0x1088 ldr x0, [x19, #0x8] ; argv[1]
0x1090 add x1, x1, #0x839 ; "-verify"
0x1094 bl strcmp
0x1098 cbz w0, 0x10c4 ; argv[1]=="-verify" (and argc==2) -> verify path
...
0x119c bl HDCP1_key_verify ; called with w0=1 (a fixed "type")
; --- provisioning path (argv[1] != "-verify") ---
0x1114 ldr x0, [x19, w25, uxtw #3]
0x1118 bl atoi ; "type" = atoi(argv[last])
0x111c sub w8, w0, #0x1
0x1124 cmp w8, #0x3
0x1128 b.hs 0x127c ; reject unless type in {1,2,3,4}
...
0x1144 ldr x0, [x19, #0x8] ; argv[1] = KEY_FILE
0x1150 bl fopen ; mode string at offset 0x837 ("r")
0x1170 bl fread_unlocked ; up to 1000 bytes into a static buffer
...
0x11f0 ldr x0, [x19, #0x10] ; argv[2] = DPS_FILE
0x11fc bl fopen ; mode string at offset 0x781 ("rb")
0x1214 bl fread_unlocked ; exactly 16 bytes (a "DPS" seed)
0x1218 cmp x0, #0x10
0x121c b.ne 0x12fc ; must read exactly 16 bytes
0x1238 bl HDCP1_key_provision ; (keybuf<=1000B, type 1..4, dps[16], 16)
There is no signature check, no hash comparison, no authentication of
either input file anywhere in this binary — it reads whatever bytes are at
the given paths (up to 1000 bytes of key material, exactly 16 bytes of
“DPS”, and an attacker/operator-chosen small integer selecting one of four
HDCP1 key “types”) and hands them straight to HDCP1_key_provision() in
libhdcp1prov.so. Whatever cryptographic gate exists between that library
call and actually writing HDCP1 device keys into secure storage is inside
libhdcp1prov.so and/or the TrustZone/QSEE side it may call into
(unconfirmed) — static analysis did not follow that library (see Honest
limits).
hdcp2p2prov (11,640 bytes) is the HDCP2.2 sibling with the identical
structure (HDCP2P2_key_provision/HDCP2P2_key_verify); it was string-
and import-level examined but not separately disassembled since the pattern
is the same.
12.9.3. Honest limits¶
The security-relevant gating for both the keybox-reprovisioning path and the HDCP key-provisioning path was not located. For
KmInstallKeybox, the “sign the devcfg with the device serial#” check its own help text describes is not implemented in this binary (it just prints the help text) — it must live in the Keymaster TA (libqtikeymaster4.so/ the in-TZ keymaster trustlet), which is a stripped shared library, and further in a secure-world trustlet image, neither of which was disassembled here. Forhdcp1prov/hdcp2p2prov, the equivalent question — whetherHDCP1_key_provision()insidelibhdcp1prov.soperforms any authentication before writing to secure storage, or whether that responsibility is pushed even further down into TZ — was not answered;libhdcp1prov.sowas not disassembled.Whether any of these binaries is actually reachable by an unprivileged or even an ``adb shell`` caller on this device was not determined. No SELinux policy (
vendor/etc/selinux) or executable permission bits were cross-checked; several of these look, from their names and “for RMA use” documentation, like factory-line-only tools that would normally be gated by SELinux domain transitions and/or physical factory-jig-only invocation, not general userspace reachability. This page establishes what the binaries do if invoked, not who can invoke them.93 of the 122 vendor-specific ``vendor_a/bin`` executables were classified by name/library/size only, not opened. The 29 that were opened (listed above) are the ones whose names indicate diagnostic, factory-test, or key-provisioning functionality; nothing found suggests the unopened remainder contains something those 29 don’t already represent, but that is an inference from naming convention, not a verified claim about each of those 93 files.
No live device was used anywhere in this investigation; every claim above is read directly from the static ELF images.
12.9.4. Provenance¶
- Source:
artifacts/vendor_a/bin/{fmfactorytest,fmfactorytestserver,ftmtest, ftm_flows_test,myftm,mm-audio-ftm,diag_mdlog,diag_klog,diag_socket_log, diag_uart_log,diag_callback_sample,diag_dci_sample,test_diag,ssr_diag, fmconfig,vendor_cmd_tool,irsc_util,KmInstallKeybox,StoreKeybox,hdcp1prov, hdcp2p2prov,qseecomd,qseecom_sample_client,secure_ui_sample_client, vendor.qti.hardware.soter@1.0-provision,qwes_cli,athdiag,cnss_diag, sscrpcd,ssgtzd,dpmQmiMgr,feature_enabler_client}and the fullartifacts/vendor_a/bin(333 entries) /artifacts/system_a/system/bin(423 entries) directory listings, all extracted read-only with7zfromartifacts/super/vendor_a.imgandartifacts/super/system_a.imgrespectively (ext2/ext4 images extracted with7z x).- Method:
Directory classification done by reading raw file content/size (not trusting
ls) to separate 7z’s symlink-as-text-file artifacts from real ELF binaries, then keyword-grepping and manually eyeballing the remainder. Per-binary triage usedfile, byte-size, and a macOS-strings(strings -n 6) dump of each candidate. The five deep-dived binaries were fully disassembled with the pixillvm-objdump -d --no-show-raw-insn(AArch64, resolves imported-symbol names at PLT stubs directly since these are small non-stripped-import executables with intact.dynsym/section headers, unlike the large stripped.sofiles documented elsewhere in this project). Every.rodatastring cited by file offset was independently confirmed with a short Python script reading the raw bytes at that offset out of the binary — not read off thestringsdump positionally, sincestringsdoes not report offsets by default. The project’s existingartifacts/re_static/{sodis,elfdis}.pycapstone helpers were evaluated but not used for the final excerpts above (they are built for stripped, section-header-light.sofiles; these binaries have full section headers and dynamic symbol tables, so vanillallvm-objdump -dresolves PLT call targets more directly).- Cross-refs:
Security and DRM userspace (QSEECOM/TrustZone bridge, attestation keybox and HDCP/ Widevine provisioning this page’s
KmInstallKeybox/hdcp1prov/hdcp2p2provfindings sit underneath), The Onyx/Boox platform layer (the vendor/system version split that puts the entire vendor-diagnostic surface found here on thevendor_aside and none of it onsystem_a), Userspace boot — init, SELinux, A/B updates (SELinux domain context these binaries would run under, not independently verified here).