ICCC / tz_iccc trustlet — TZ write primitive (Track E)
Status (2026-10-23): The Samsung ICCC secure store (tz_iccc) is fully
reverse-engineered and driveable from the REE on a locked-bootloader Z Fold 6.
We have a live read channel, a documented wire format, a complete namespace/field
map, and a proven TZ write primitive — including one field that accepts arbitrary
values both directions and two fields that are monotonic one-way locks.
Nothing is flashed; nothing persists across reboots (ABL re-writes these records
each boot).
What ICCC is
ICCC = Samsung "secure component status" store. ABL writes per-component structs
over SCM (tzbsp_oem_write_iccc_bl_info, ..._rot_bl_info, ..._svb_bl_info,
..._kern_info, ..._bldp_info) into a trustlet tz_iccc. The trustlet lives in
the QSEE world (not TEEGRIS): only /dev/qseecom exists, ro.config.iccc_version=3.0,
and get_chipset_id() == 0x33. The TA is ~2 MB and has no symtab — every
readable name is a __func__ string literal attributed to functions by resolving
adrp+add references.
How we reach it (bypassing the HAL)
The HIDL service /vendor/bin/hw/vendor.samsung.hardware.tlc.iccc@1.0-service
(iccserv) is oneshot + disabled and is not running by default. We bypass it
entirely and dlopen the implementation library directly:
/vendor/lib64/vendor.samsung.hardware.tlc.iccc@1.0-impl.so
Bootstrap symbols used by our client (iccc_cli, aarch64, NDK r29):
-
p_g_tlc_tz_iccc_ctx—ctx**global -
g_tlc_tz_iccc_ctx— 24-byte ctx struct -
TZ_ICCC_initialize_TLC(tlc_tz_iccc_ctx**) -
TZ_ICCC_read_TLC(ctx, param, u32*) -
TZ_ICCC_write_TLC(ctx, param, value) -
TZ_ICCC_device_status_TLC(ctx, type, buf, len)
Object layout (verified by disassembly)
create_comm_ctx (libtlc_comm_iccc.so @0x1020) allocates 0x18 bytes and calls
the open method at vtable +0x28 (returns bit 0 set on success). On failure it
calls the destructor at vtable +0x08.
ctx = { comm(+0x00), send_buf(+0x08), recv_buf(+0x10) }
comm = *(void**)ctx
vtable[1] = +0x08 destructor -> QSEECom_shutdown_app
vtable[4] = +0x20 comm_request(comm, cmd)
TZ_ICCC_read_TLC (@0x5924) and TZ_ICCC_write_TLC (@0x59e0) both do exactly
x0 = [ctx]; x8 = [x0]; x8 = [x8+0x20]; blr x8 — confirmed.
Wire format
send[+0x10] = cmd (2 = write/save, 3 = read)
send[+0x14] = param
send[+0x18] = value (write only)
comm_request(comm, cmd)
recv[+0x00] = cmd | 0x80000000
recv[+0x18] = value (read)
recv[+0x1C] = tz return code
Buffers are 0x201c bytes. TA path /vendor/firmware_mnt/image, TA name tz_iccc.
HAL impl gates (corrected)
- Caller uid must be 0 or 1000.
-
Write allowed only in
0xFF200xxxexcept0xFF200001, which is explicitly denied (the exclusion itself is the interesting field). Reads are unfiltered. -
ret = 5="ICCC permission denied!".
Calling comm_request (vtable +0x20) directly bypasses the HAL whitelist, so
0xFF200001 becomes writable at the communication layer — but the trustlet still
rejects it (see below).
TA internals
Parameter namespace validator (TA @0x760)
w8 = param & 0xFFF00000, returns 1 = allowed, 0 = reject. Allowed:
0xFF000000 0xFF100000 0xFF200000 0xFF300000 0xFF400000 0xFFF00000.
Per-component layout (from the TA jump tables)
Records live on 0x100-byte boundaries inside the TZ ICCC buffer.
| namespace | component | fields (param -> offset/size) |
|---|---|---|
0xFF000000
|
TA | 0..6 -> 0x904..0x91C/4, 7 -> 0x920/0x20 |
0xFF100000
|
KERN |
0xFF100000 -> 0xA04/4 ; 0xFF100001 -> 0xA08/0x20
|
0xFF200000
|
SYS |
0xFF200000 -> 0xB04/4 ; 0xFF200001 -> 0xB08/4 ; 0xFF200002 -> 0xB0C/4
|
0xFF300000
|
ROT |
n=0..0xD -> 0xC04,0xC08,0xC0C,0xC10, 0xC14/0x20, ..., 0xC34,0xC38 ; 0xFF30FFFF -> whole struct 0xC00/0x3C
|
0xFF400000
|
BLDP |
0xFF400000 -> 0xD50/0x100 ; 0xFF400001 -> 0xD50/0x20
|
0xFFF00000
|
(none) | unknown type -> -1 (no handler) |
Other TA facts
-
CICCCReadData_readData@0x3ec (internal cmd0x111);CICCCSaveData_saveData@0x5bc (internal cmd0x110). -
process_update_lock_reqlogs"error! updates locked. type:%#x lock:%d". -
ABL-side SCM writers:
tzbsp_oem_write_iccc_bl_info/rot_bl_info/svb_bl_info/kern_info/bldp_infowith"BL/ROT/SYS/KERN struct size mismatch detected!".
Live read map (one representative boot)
ROT: [0]=0 [1]=1 [2]=0x40000 [3]=0x1A9 [0xC]=0x29A9 [0xD]=0x29A9
SYS: [0]=FFFFFFFF [1]=0 [2]=FFFFFFFF
KERN: [0]=0
TA: [0..5]=FFFFFFFF [6]=0
BLDP: size mismatch (0x100) 0xFFF00000: no response
Writability matrix (rawwrite bypasses the HAL whitelist)
tzrc = 0 means the trustlet accepted the write.
| field | param | read value | write result |
|---|---|---|---|
| KERN[0] |
0xFF100000
|
0 | FULLY WRITABLE — arbitrary values both directions (tested 0, FFFFFFFF, 0x12345678). tzrc=0. |
| SYS[0] |
0xFF200000
|
FFFFFFFF | MONOTONIC — FFFFFFFF->0 accepted (tzrc=0), 0->FFFFFFFF rejected (tzrc=0x1D). |
| SYS[2] |
0xFF200002
|
FFFFFFFF | MONOTONIC (same pattern as SYS[0]). |
| SYS[1] |
0xFF200001
|
0 | PROTECTED — always tzrc=0x1D (HAL also denies). |
| ROT[0..3] |
0xFF300000..03
|
0,1,0x40000,0x1A9 | PROTECTED — tzrc=0x1D. |
| KERN blob |
0xFF100001
|
(0x20 blob) | PROTECTED — tzrc=0x1D. |
| TA[0] |
0xFF000000
|
FFFFFFFF | PROTECTED — tzrc=0x2. |
| TA[1] |
0xFF000001
|
FFFFFFFF | PROTECTED — tzrc=0x2. |
| ROT[4..0xD], TA[2..6], BLDP | — | — | untested |
tzrc codes: 0 = accepted, 0x2 = rejected (TA), 0x1D (29) = rejected (protected),
0x1C (28) = invalid param (unknown field).
Key findings
- Real TZ write primitive exists: KERN[0] accepts arbitrary values both directions; SYS[0]/SYS[2] are monotonic one-way locks (FFFFFFFF->0 only).
- The sub-index is NOT an attacker-controlled offset — offsets are fixed per field in the TA jump tables. The out-of-bounds write hypothesis is NOT supported.
- Values are per-boot: ABL re-writes these records each boot (via SCM), so writes do NOT persist. Within a boot, monotonic locks stick (cannot be reversed).
- Protected fields are gated at the TZ level (tzrc 0x1D/0x2), not just the HAL.
- Channel dies after ~8-11 calls per boot (cumulative comm_request count). One process per boot, ~10 calls max. Clean shutdown (vtable[1] destructor) does not extend it.
Honest assessment
These writes will not unlock the bootloader: the bit/ARB check in download mode is fuse-based (SW REV), not ICCC-based, and ABL rewrites these records each boot. The lane's value is the reverse-engineering knowledge (transport, namespaces, wire format, TA internals) and a proven monotonic TZ write primitive (KERN[0] arbitrary; SYS[0]/SYS[2] one-way locks), which may affect kernel-related attestation/Knox state within a boot.
Tooling
-
iccc_cliv2+ (aarch64, NDK r29): verbsread write rawread rawwrite sweep status probe exp min..min10. Clean shutdown via vtable[1]. Pushed to/data/local/tmp/iccc_cli*. -
reroot-ksu.ps1: fixed (variadicInvoke-Adb, positional call sites) — re-roots in ~1 cycle.
Credits
Research by the F956B rooting project. Site: https://getyahrootback.admindashboard.dev XDA thread: (see FULL-ROOT-ACHIEVED post). KernelSU-Next: https://github.com/KernelSU-Next/KernelSU-Next GhostLock (CVE-2026-43499) primitive: original research by the project team.