Repository navigation
Why my locked Galaxy A10e won't take LineageOS - a full firmware teardown #7872
CassieFox23
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TL;DR: I wanted to put LineageOS on an old Galaxy A10e and turn it into a pet cam, but this one's locked down way too hard for that. So I had Claude Code read the firmware with REA: the real wall is a locked bootloader plus verified boot. Claude also tripped over a sneaky bug, and a blind second pass caught it - that's my favorite part.
Edit (Oct 28): a blind review flagged a few claims that were too strong. The verified boot, Knox Guard and heimdall bits got the biggest fixes.
How this started
So I had a spare Galaxy A10e sitting in a drawer. The plan was to make it a pet cam so I can check on the dogs while I'm at work. First step was putting a clean custom ROM on it. I've done that before - two of my Galaxy Tab A8 tablets run a LineageOS GSI (a generic Android system image) and that went fine. So I figured the A10e would be the same.
Nope. The bootloader's locked, and the OEM unlock toggle is off on this carrier model. I can still run a camera app on stock Android, and honestly that's the plan now. But I really wanted to know why this phone is so locked down when my tablets weren't. So I asked Claude Code to go through the firmware and show me.
Small note on who did what: I own the phone, I asked for this, and I'm writing up what it found. Claude Code did the hands-on reverse engineering through REA. That was mostly Opus, with Sonnet helpers on some side passes. It also made the mistake further down. A blind second pass in another session flagged it, and Claude confirmed it. I mostly just pointed it at the problem lol
Everything in here is static analysis, just a hobby project. I'm not affiliated with Samsung in any way, either. The phone's an SM-A102U on T-Mobile, build
A102USQSDAVD1, Android 9. The firmware came straight from Samsung's servers, plus two 2023 builds from a mirror. Nothing got flashed, no lock got bypassed and no partition got written. The phone only got two read-only checks: an adb sweep and one trip into download mode. heimdall could see the phone but never got the partition table, and nothing got flashed.Heads up, it's a long one since I put all the findings in. The deep stuff is folded up, so click a section when you want the full read.
The scripts, the full write-up and the diagram source are all in a10e-firmware too.
The whole chain from boot to apps looks like this:
the locks at boot, then a fence inside the OS
The locks at a glance
The first two are about flashing and booting. TEEGRIS and the SELinux fence in front of it guard what's inside the running phone, so they don't really block a ROM.
So could it run LineageOS?
Not happening - there are two separate things in the way. This is the part my tablets got past and this phone doesn't.
Wall 1 is getting in at all. The bootloader's locked and OEM unlocking isn't available:
oem_unlock_supported=1butoem_unlock_allowed=0. One catch: that=0only proves the toggle is off. US carrier models like this one are known for not exposing a working toggle. That's known about the model in general, these files don't prove it. Either way, while the bootloader's locked, AVB rejects an unsigned image.Wall 2 is Treble's VINTF check, which tests that a system image fits the phone's vendor side. It only pairs them on an exact level match. This vendor is level 3, which is Android 9. The LineageOS 21 GSI is Android 14 and only ships matrices for levels 4 to 8. So there's no level-3 matrix to match, and by the rule the pair should get refused. That's what the rule says should happen, not a boot test. Two smaller details came from a later review of the GSI, and the GSI isn't on disk to re-check them. It does still ship the VNDK-28 runtime. On the kernel side it wants patch level
4.4.302, and this phone is on4.4.111.My Tab A8s only got past wall 1 because they allow OEM unlock, and on this carrier A10e that toggle is off.
The firmware package
Samsung ships the firmware as six
.tar.md5bundles: BL, AP, CP, CSC, HOME_CSC and USERDATA. All six MD5 trailers checked out. The partition map (PIT) lists 38 partitions, but this firmware only writes 17 of them. The other 21 get left alone. EFS, SEC_EFS and CPEFS hold your radio identity and calibration. PERSISTENT holds the lock that kicks in after a factory reset. The modem's calibration areas stay put too. So a stock reflash should leave all of that just as it was. That's going by the PIT, since nothing got flashed.The chip is an Exynos 7885-class part, but the names are kinda all over the place. The strings say
exynos7885andexynos7904, and the device-tree overlays name Exynos 7884B. That last one is most likely the real chip. Why the other names show up isn't clear. Samsung sharing one base across sibling chips would explain it, but that's a guess.Here's the hardware the device tree describes. The full list is in the repo's hardware appendix.
Lock #1 - verified boot (AVB), the one that matters
vbmetais basically Android's signed table of contents for the boot chain. Here's what Claude pulled out of it:e553992a79ab561a4805371cce123d1ee73db21b. avbtool agrees, and so does a by-hand recompute over the 1032-byte blob.vbmetaroot and every chained partition:recovery,dtb,dtbo,product.vbmetacarries SHA-256 hashes forbootloader,cmandboot. It also has dm-verity hashtrees forsystemandvendor, with FEC (error-correction data).0x12b070and0x322410. So the key ships inside the bootloader image, and nothing showed it burned into a hardware fuse.In the code, the gate is a call to libavb's key check,
validate_vbmeta_public_key, at file0xa7a34. A.datapointer at file0x140b28holds the key's address. The key gets loaded at file0xaf334, right before the Samsung check that logsAVB pubkey is not matched with vbmeta. With the wrong key, the path ends atPublic key used to sign data rejectedand boot fails.A GSI changes
systemorboot, so the hash stops matchingvbmeta. And you can't re-signvbmetawithout Samsung's private key. That's what stops a custom ROM while the bootloader's locked. Unlocking would relax it, and the other locks are kinda extra on top.To make sure this really is the firmware on my phone, avbtool boils the whole signed set down to one digest:
5d354bfd…a7e6. That matches the live phone's own digest. So that's strong evidence the boot chain, system and vendor images Claude read are what the phone runs.Lock #2 - Knox Guard, and the engineering-token hatch
Download mode, the Odin flashing screen, has a second gate on top of verified boot. This one's all about the carrier and financing. Some of the strings are literally
KG locked. Not allowed to flash.andblocked due to remaining installment balance.The Odin command handler is
usbd3_rdx_processat file0xc0ecc. It collects 1024-byte command blocks and hands each one toprocess_packetat file0x6cfc8, which routes the Odin commands. Class100is session and control,0x65is PIT,0x66is per-partition flash,0x67is reboot and0x69is device info.The actual flash verdict happens in the writer at file
0x6f7ec, where theKG lockedstring sits. The lock state comes from a KG engine at file0x74968. It reads the Knox "vaults" from RPMB, which is secure storage you can't rewind. Then it hash-checks the vaults and works out a state from them. So the flag isn't just a plaintext bit sitting out in the open. Heads up tho, Claude never read this phone's own KG state. It lives in RPMB, which isn't readable without root.There's a hatch for a Samsung-signed engineering token - that's the
This is KNOX TEST binary. Please download EM-token first!string. It's a certificate chain that sboot checks, tied to the chip ID and read from theSTEADYpartition. The structure's clear, but Claude didn't trace the actual RSA verify. So "can't be forged" is how it's designed, not something that got proven here.There also looks to be a signed-image check that's separate from KG. That's going by its strings, since it wasn't traced. sboot prints a rollback check too,
SW REV. CHECK FAIL, along withCustom binary blocked by CC Mode.andMDM MODE. CAN'T DOWNLOAD.Lock #3 - the TEEGRIS secure world
There are three small ARM64 ELF programs tucked inside the bootloader. Since they're real ELF files, they decompile cleanly once you carve them out.
teegris1(about 60 KB) is the CSMC "BFHandler" trusted app. It uses RSA to check that a Knox kernel module is signed, and it reads the boot status fields. It prints thebl_secure_infofields as debug lines, likecc_mode,em_token,frp_lockandwarranty_bit. That's just a readout tho - it was never caught setting those bits.teegris3(about 78 KB) is the secure-OS loader,startup_loader. It opensstartup.tzar.lz4, unpacks it and loads the trusted apps.teegris2(about 9 KB) is just a small stub.This is where Claude got stuck.
startup.tzar.lz4is the TEEGRIS OS body, and it's a separate file on the secure filesystem, outside sboot. It's not in this download, so the OS body and the trusted apps it bundles can't be pulled. The 21 trusted apps in/vendor/teedo ship, tho. There's noTZSWpartition in the map either, so Claude couldn't pin down where the OS body lives. Separately,cm.binis a signed container with several modules in it. Those includeCryptoManagerV40andsrpmb_fw, taggedSignerVer02.Inside the running OS - what SELinux enforces
The locked bootloader is what stops flashing and booting, and SELinux is the fence once the OS is up. The boot cmdline actually asks for
permissive, which looks pretty alarming at first. But Android init ignores that on a user build. The kernel also has an always-enforce backstop, so the phone runs Enforcing, and the props back that up:ro.boot.selinux=enforcing.Claude pulled the compiled policy out of
vendor.imgand read it with setools:allowrules.execmemgoes to 47 domains: app sandboxes, the TEE and Knox app domains, and some system daemons. So it's scoped, but broader than just apps.The fence around the secure world holds. A normal app (
untrusted_app) can't find any of the 13 secure services and has no access to the TrustZone device nodes. The only secure node it can touch is Knox DAR,/dev/dek_req. The fence matters a lot here, since most of those tz nodes are mode 0666. So SELinux is pretty much the main thing gating them.Behind the fence it's TEEGRIS - the kernel has
CONFIG_TZDEV=yand no Trustonic at all. The real services each have their own domain, and all of them are 32-bit. They'retzdaemon, the KnoxprocaHAL,secure_storage_daemon, keymaster, gatekeeper and Widevine. There are 21 trusted apps in/vendor/tee. Some of the policy is just leftover from a shared template too. The Trustonic, HDCP andsecure_elementdomains are fenced off, but this firmware doesn't ship a binary for any of them.The method, and the bug
Ok so this is the bit I'd read if you're into RE.
sboot.binis a 4 MB ARM64 image with no symbols and no obvious load address, and Ghidra stalled on it. With no valid entry, Ghidra couldn't tie the "blocked" strings to any code.Now the bug. ARM64 code finds its own data with an
ADRP+ADDpair.ADRPis page-relative, so it builds an address from the current page plus an offset. That means you need the load address mod 4 KB, which is just the low bits, not the whole base.Claude's first pass assumed those low bits were zero, which is the natural guess, but it's wrong here. The real value is
0x800, half a page. With the wrong phase, every reference lands 2 KB off on a different string. The code still disassembles fine. The references just point at the wrong text, so nothing looks broken.The fix is to measure the phase instead of guessing. Count how many pairs land exactly on the start of a string, once at phase 0 and once at
0x800. The right one stands way out:0x800(correct)With the wrong phase, one function looked like a Knox Guard check that wanted a token magic,
0x12349876. With the right phase, it's just the partition-table parser -0x12349876is the magic at the top of a Samsung PIT file. The code reads a header, checks that magic and loops over the entries. So the "carrier lock" was really a partition table, and a real constant just got matched to the wrong strings.Fixing the phase sorted out the load base too. An early guess chased
0x8f000000, but that turned out to be a jump literal in the code. The real base is0x8ef9e800,0x61800lower, and it's pinned by a 13-entry table of partition-name pointers at file0x12b488.ADRPonly gets you the page, so for the base you need absolute pointers.A blind second pass in another session flagged the mistake. Claude then re-checked it and confirmed it. Getting fooled by your own first read is pretty normal, so I'd always get a blind second look. REA itself was fine - the miss was in how Claude read the image.
If you're poking at a raw image yourself, measure the phase instead of guessing zero. A few more things that helped:
The ADRP bug, drawn out:
Does it hold up? Cross-checks
Claude also checked against other builds and Samsung's own source. These compare string sets and keys, and the big 2023 rewrite wasn't disassembled. So read "same locks" as "same keys and lock strings".
[SECLIB] Moonstonetag. Claude hashed that stage across four sboots. It's identical within an Android version and across carrier and unlocked, and it only changes at the A9 to A11 bump. So it's a per-version stage, not a per-device secret.USA:SINGLEvsUSA:OPEN. So it points to the lock coming from how the phone was provisioned, instead of a crippled bootloader. The unlock getter itself is the same code in both builds. One catch: the rest of that 16% difference wasn't gone through.permissivecmdline just gets ignored.Oh and the modem, real quick: it's a Samsung Shannon baseband, and the A10e is sold as a 4G LTE phone. The modem code does carry some 5G (EN-DC) types from Samsung's shared codebase. So the files alone don't settle 5G. The 2022 to 2023 modem is the same family, just a refresh.
How much the bootloader changed per pair, with the AVB key the same in all three:
And sboot.bin's entropy. The encrypted pre-boot stage is the flat band at the start. The other high band is a dense data table, not encryption.
What it couldn't get to
cm.bin's payload and sboot's[SECLIB]body are locked up, and no key ships in the firmware. So the plaintext can't be recovered from here, and the OTAfota.zipis encrypted too.So, the pet cam
No Lineage then, but the pet cam's still happening. It'll just run a camera app on stock Android 9, and that was always enough for watching the dogs. And now I actually know why Lineage was never an option here when it was fine on my tablets. That's what I really wanted out of all this :3
Tools
All free and local:
All in, that was several hundred Ghidra calls through REA.
🤖 Written with Claude Code (Opus 4.8 and Opus 5.5, blind-reviewed by Sonnet 5.5), posted with my OK 🦊
All reactions