Using Android kernel to load Debian on VNPT MyTV Smartbox 2 (S805)

A personal technical log of reviving the VNPT MyTV Smartbox 2 with Debian/Armbian by reusing its Android kernel.

Introduction

I caught A–flu one weekend and was too tired to think about anything serious. Between fever spikes and boredom, I noticed a couple of VNPT MyTV Smartbox 2 units (Amlogic S805) that had been sitting untouched for years. They originally ran Android 4.4 — far too outdated to be useful now — but still perfectly functional pieces of hardware.

I cracked one open. The inside was far better than expected for a low-cost IPTV box: clean PCB layout, solid soldering, proper shielding, stable power rails, and easy UART pads placed exactly where you want them. Once connected, the board showed a short one-second U-Boot window. If I didn’t interrupt it in time, it would simply continue into Android and drop me directly into a root shell — unexpectedly convenient.

From there, the idea was simple: turn these obsolete Android boxes into small Debian/Armbian machines. Nothing serious — just a side quest while recovering.

This article is not a step-by-step guide. It is simply a record of what I tried, what worked, and what didn’t during the process of making Debian boot on this hardware.

The Easy Part

Before things got complicated, the first board behaved perfectly. I documented both sides of the PCB and marked the pins that are useful for anyone interested in eMMC wiring — D0, CLK, CMD, and the 3.3V rail. The backside also has a neat “1701” printing, suggesting the board was manufactured in week 01 of 2017. For a cheap IPTV device, the layout is surprisingly clean and serviceable.

Board Front Board Back

For the initial test, I downloaded an image from
https://github.com/hzyitc/armbian-onecloud.

For loading Armbian manually, I stopped at the U-Boot prompt and used the following command set.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
mmc part list # To rescan mmc

setenv loadaddr     0x12000000
setenv initrd_addr  0x13000000
setenv fdt_addr     0x01000000

# Load the kernel and initrd from the SD card (Armbian image uses a separate FAT boot partition, so U-Boot sees it as mmcblk0p1)
fatload mmc 0:1 ${loadaddr} uImage
fatload mmc 0:1 ${initrd_addr} uInitrd
fatload mmc 0:1 ${fdt_addr} dtb/meson8b_m201_1G.dtb

# Set the boot arguments. Amlogic assigns the SD card as mmcblk0, and the root filesystem is on partition 2:
setenv bootargs 'console=ttyS0,115200n8 earlyprintk ignore_loglevel root=/dev/mmcblk0p2 init=/init rootwait rw fsck.repair=yes no_console_suspend vdaccfg=0xa000 clk_ignore_unused hdmimode=1080p cvbsmode=576cvbs hdmitx=cecb'

bootm ${loadaddr} ${initrd_addr} ${fdt_addr}

It booted immediately on the first try, but with major limitations: no WiFi, no GPU acceleration, and even the internal eMMC wasn’t detected. It only ran properly from the SD card, so that wasn’t good enough.

I switched to an older build instead:
Armbian_5.99_Aml-s805_Debian_buster_default_3.10.108_minimal.img, which uses the original Amlogic 3.x kernel. This one worked far better. The system booted cleanly, and after adjusting a few DTB settings, WiFi came up as well. Everything felt simple and straightforward — at that point I thought the work was easy and should be done soon.

The Unexpected Wall

The strange board

After the smooth progress on the first unit, I moved on to the second Smartbox 2 — this one had a manufacturing code 1832 (week 32 of 2018). Same board revision, same SoC, same memory chips, same layout. I assumed it would behave identically.

I was wrong.

I flashed the exact same Armbian images, used the same SD card, same UART setup, and the same U-Boot commands.
No matter how many times I tried, it refused to boot anything other than its stock Android.

The system always stopped right after U-Boot printed: Starting kernel

And then: nothing.
No HDMI output, no kernel logs, not even UART noise. Changing kernel log level, adding earlyprintk, modifying console settings, or forcing init to a busybox shell made no difference.

Boot log

Click to expand boot log
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
QA5:B;SVN:B72;POC:1DF;STS:0;BOOT:0;INIT:0;READ:0;

U-boot-00000-ge0c1103-dirty(m8b_m201_v1@) (Feb 02 2018 - 15:47:12)

clr h-ram
DRAM:  1 GiB
relocation Offset is: 2fec4000
aml_card_type=0x200
MMC:   [mmc_register] add mmc dev_num=0, port=1, if_type=6
[mmc_register] add mmc dev_num=1, port=2, if_type=6
SDIO Port B: 0, SDIO Port C: 1
power init
out reg=c110804c,value=dfffffff
IR init done!
register usb cfg[0][1] = 3ff67d4c
register usb cfg[2][0] = 3ff6ac98
NAND:  EMMC BOOT: not init nand
do not init nand : cause boot_device_flag without nand
get_boot_device_flag: init_ret -1
get_boot_device_flag EMMC BOOT:
Emmckey: Access range is illegal!
[mmc_init] SDIO Port C:1, if_type=7, initialized OK!
[mmc_get_partition_table] skip cache partition.
Partition table get from SPL is :
        name                        offset              size              flag
===================================================================================
   0: bootloader                         0            400000                  0
   1: reserved                     2400000           4000000                  0
   2: cache                        6c00000          20000000                  2
   3: env                         27400000            800000                  0
   4: logo                        28400000           2000000                  1
   5: recovery                    2ac00000           2000000                  1
   6: misc                        2d400000           2000000                  1
   7: boot                        2fc00000           2000000                  1
   8: system                      32400000          40000000                  1
   9: data                        72c00000         15f400000                  4
mmc read lba=0x12000, blocks=0x1
mmc read lba=0x12001, blocks=0x1
mmc_read_partition_tbl: mmc read partition OK!
eMMC/TSD partition table have been checked OK!
i=0,register --- emmc_key
mmc_storage_probe 493
mmc  storage node0  addr = 2600000 and node1 addr = 2640000
MMC BOOT, emmc_env_relocate_spec : env_relocate_spec 59
set_storage_device_flag: store 2
[imgread]Secure kernel sz 0x7c4730
      Multi dtb tool version: v2 .
      Multi dtb detected, support 2 dtbs.
        aml_dt soc: m8b platform: m201 variant: 1G
        dtb 0 soc: m8b                plat: m201               vari: 1G
        dtb 1 soc: m8b                plat: m201               vari: 2G
      Find match dtb: 0
vpu clk_level in dts: 3
set vpu clk: 182150000Hz, readback: 182150000Hz(0x701)
Net:   Meson_Ethernet
main_loop...
cvbs trimming.1.v5: 0xa0, 0x1
preboot...
hdmi tx power init
TV mode 576cvbs selected.
vdac open.1 = 0x1, 0x1
mode is: 4
viu chan = 1
config HPLL
config HPLL done
reboot_mode=charging
### main_loop entered: bootdelay=1

### main_loop: bootcmd="run mytvboot; if test ${bootfromnand} = 1; then setenv bootfromnand 0; saveenv; else run bootfromsd; run bootfromusb; run bootfromemmc; fi; run storeboot"
Hit Enter key to stop autoboot -- :  0
exit abortboot: 0
(Re)start USB(0)...
USB0:   dwc_usb driver version: 2.94 6-June-2012
USB (1) peri reg base: c1108820
USB (1) use clock source: XTAL input
USB (1) base addr: 0xc90c0000
Force id mode: Host
dwc_otg: No USB device found !
lowlevel init failed
USB error: all controllers failed lowlevel init

** Invalid boot device **

** Invalid boot device **

** Invalid boot device **

** Invalid boot device **
[mmc_init] SDIO Port B:0, if_type=7, initialized OK!
reading s805_autoscript

1991 bytes read
## Executing script at 01000000
reading uInitrd

4373257 bytes read
reading uImage

6608764 bytes read
reading dtb.img

** Unable to read "dtb.img" from mmc 0:1 **
[imgread]Secure kernel sz 0x7c4730
      Multi dtb tool version: v2 .
      Multi dtb detected, support 2 dtbs.
        aml_dt soc: m8b platform: m201 variant: 1G
        dtb 0 soc: m8b                plat: m201               vari: 1G
        dtb 1 soc: m8b                plat: m201               vari: 2G
      Find match dtb: 0
## Booting kernel from Legacy Image at 14000000 ...
   Image Name:   Linux-3.10.108
   Image Type:   ARM Linux Kernel Image (gzip compressed)
   Data Size:    6608700 Bytes = 6.3 MiB
   Load Address: 00208000
   Entry Point:  00208000
   Verifying Checksum ... OK
## Loading init Ramdisk from Legacy Image at 15000000 ...
   Image Name:   uInitrd
   Image Type:   ARM Linux RAMDisk Image (gzip compressed)
   Data Size:    4373193 Bytes = 4.2 MiB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
## Flattened Device Tree blob at 11800000
   Booting using the fdt blob at 0x11800000
   Uncompressing Kernel Image ... OK
uboot time: 12750841 us.
EFUSE machid is not set.
Using machid 0xf81 from environment
From device tree /memory/ node aml_reserved_end property, for relocate ramdisk and fdt, relocate_addr: 0x4435001
   Loading Ramdisk to 04009000, end 04434ac9 ... OK
   Loading Device Tree to 04000000, end 04008845 ... OK

Starting kernel ...

This behavior matched exactly what another person reported online:
https://forum.armbian.com/topic/33667-amlogic-s805-image/

The symptoms are identical:
U-Boot runs normally, then the kernel simply never starts.

At this point, I suspected something deeper than a bad SD card or wrong DTB.
Everything pointed to U-Boot itself.


Attempts to Fix It (All Failed)

Loading U-Boot via USB Burning Protocol (update tool)

For this test I used the open-source Amlogic USB burning utility from:

https://github.com/Stane1983/aml-linux-usb-burn

The tool provides a userspace binary named update, which communicates with the SoC’s USB Download Mode and can upload binaries directly into RAM and execute them.
This is the same protocol used by the official Amlogic Windows “USB Burning Tool”, but exposed in a CLI form.

Because the VNPT Smartbox 2 uses an Amlogic M8/M8B (S805) chip, I followed the procedure documented in Amlogic Update USB Tool User Guide — Section 4.1.2:

1
2
3
4
5
6
7
8
9
a > update cwr ddr_init.bin 0xd9000000 // upload ddr_init.bin into RAM
b > update run 0xd9000030 // run ddr_init.bin to init DDR/PLL

c > update write u-boot-comp.bin 0x00400000 // upload compressed SPL/TPL to RAM
d > update write decompressPara_4M.dump 0xd9010000
// parameter blob for decompression

e > update run 0xd9000030 // run ddr_init.bin again to decompress TPL
f > update run 0x001000000 // finally run U-Boot

This sequence should:

  1. Initialize DRAM
  2. Load a compressed U-Boot (TPL/SPL) blob
  3. Decompress it into RAM
  4. Execute the newly uploaded U-Boot entirely from USB

I repeated this procedure with multiple U-Boot images taken from various S805 boards:

  • MXQ S805
  • M201/M201D
  • Tronsmart S85
  • ENY M8B
  • Several community-built S805 U-Boots

Every attempt behaved the same: right after update run 0x10000000, the target board instantly rebooted. There was never any UART output from the uploaded U-Boot, and the reboot happened so fast that even the ROM banner did not change.

Burning U-Boot into SD Card for External Boot

I also tried forcing the board to boot an external U-Boot from a microSD card. First, I used AML Boot Card Maker to generate a proper Amlogic boot SD with u-boot.bin written to the first sectors. Then I repeated the process manually using dd, writing both community S805 U-Boot binaries and the bootloader dump from my working 1701 board.
No matter how the SD card was prepared, the 1832 board completely ignored it — no UART output, no fallback, no SD boot attempts at all.

Full eMMC Transplant and Bootloader Replacement

Since USB and SD card boot were both blocked, the next step was the most invasive one: desoldering the eMMC from the 1832 board to rewrite the bootloader externally. I removed the eMMC chip, dumped its contents with an external reader, and compared it against the fully working 1701 board. Amlogic stores U-Boot in three locations: boot0, boot1, and the first few megabytes of the user area (the “bootloader” partition, usually the first ~4 MB). Because all three regions must match, I replaced boot0, boot1, and the user area with the known-good data from the working device.

With the cloned bootloader installed, the board failed instantly. UART printed only the BootROM diagnostic:

1
QA5:B;SVN:B72;POC:1DF;STS:0;BOOT:0;INIT:0;READ:0;CHECK:FFFFBF00;BOOT:1;INIT:10;USB:3;

This line means:

  • BOOT:0 → try internal eMMC
  • INIT:0 → eMMC detected
  • READ:0 → read OK
  • CHECK:FFFFBF00 → signature/hash verification failed
  • BOOT:1 / INIT:10 → SD card not present
  • USB:3 → USB Burning mode available but not connected

I then tried a safer variant: keep the original boot0/boot1, only replace the user-area bootloader. This allowed U-Boot to start, but the moment I ran the kernel with bootm, U-Boot printed:

1
aml log : check is 3

At first, I don’t really understand the reason but after a few researches, I know that AML CPU has secured boot feature and it’s definitely enabled in the newer batches of the box (1832).

Digging Deeper Into U-Boot and the Amlogic Boot Process

Before moving forward, it was necessary to understand exactly how Amlogic’s boot chain works, and why every attempt to replace U-Boot or load a custom kernel fails on the secure-boot board.

U-Boot Command Set

For reference, here is the full help output from the board’s U-Boot (collapsible so the article stays clean):

Click to expand U-Boot command list
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
?       - alias for 'help'
adc     - M6 ADC test
aml_sysrecovery- Burning with amlogic format package from partition sysrecovery
amlnf   - AMLPHYNAND sub-system
amlnf_test- AMLPHYNAND sub-system
autoping- do auto ping test
autoscr - run script from memory
base    - print or set address offset
bdinfo  - print Board Info structure
bmp     - manipulate BMP image data
boardid_prefetch- boardid sub-system
bootm   - boot application image from memory
bootp   - boot image via network using BOOTP/TFTP protocol
cali    - configure clock phare
calinfo - calinfo print the chip calibration info
cbusreg - cbus register read/write
checkhw - Get the hardware revsion
clear_rebootmode- clear rebootmode
clkmsr  - measure PLL clock
cmp     - memory compare
coninfo - print console devices and information
cp      - memory copy
crc32   - checksum calculation
dcache  - enable or disable data cache
defenv  - default environment
defenv_without- defenv without environment variables
dhcp    - boot image via network using DHCP/TFTP protocol
dtbinit - init some env by reading dtb file
dtbload - load binary dtb file from a dos filesystem
echo    - echo args to console
editenv - edit environment variable
efuse   - efuse version/licence/mac/hdcp/usid read/write or dump raw efuse data commands or info(display chip efuse info)
env     - environment handling commands
ethchk  - check ethernet status
ethdbg  - set ethernet debug level
ethmode - set ethernet mac mode
ethrst  - reset ethernet phy
exit    - exit script
ext2load- load binary file from a Ext2 filesystem
ext2ls  - list files in a directory (default /)
false   - do nothing, unsuccessfully
fatexist- find the file from a dos filesystem
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls   - list files in a directory (default /)
fdt     - flattened device tree utility commands
get_img_size- get img size and save the result as a environment variable
get_rebootmode- get reboot mode
go      - start application at address 'addr'
gset    - gpio commands
help    - print command description/usage
icache  - enable or disable instruction cache
imgread - Read the image from internal flash with actual size
imxtract- extract a part of a multi-image
irdetect- Detect IR Key to start recovery system
irkey   - irkey key_value time_value
itest   - return true/false on integer compare
keyunify- key unify sub-system
kgdb    - enter gdb remote debug mode
loadb   - load binary file over serial line (kermit mode)
loadenv - load environment at address 'addr'
loads   - load S-Record file over serial line
loadx   - load binary file over serial line (xmodem mode)
loady   - load binary file over serial line (ymodem mode)
logo    - logo sub-system
loop    - infinite loop on address range
macreg  - ethernet mac register read/write/dump
md      - memory display
mdc_clk - do mdc clock
mm      - memory modify (auto-incrementing address)
mmc     - MMC sub system
mmcinfo - mmcinfo <dev num>-- display MMC info
msleep  - delay execution for some time
msr     - Meson msr sub-system
mtest   - simple RAM read/write test
mw      - memory write (fill)
netspd_f- enforce eth speed
nm      - memory modify (constant address)
phyreg  - ethernet phy register read/write/dump
ping    - send ICMP ECHO_REQUEST to network host
printenv- print environment variables
put     - put  storage 
pwm     - pwm sub-system
randomb - randomb sub-system
rarpboot- boot image via network using RARP/TFTP protocol
reset   - Perform RESET of the CPU
run     - run commands in an environment variable
saveenv - save environment variables to persistent storage
sdc_burn- Burning with amlogic format package in sdmmc 
sdc_update- Burning a partition with image file in sdmmc card
secukey - security KEY sub-system
securestore- securestore sub-system
setenv  - set environment variables
sf      - SPI flash sub-system
showvar - print local hushshell variables
sleep   - delay execution for some time
source  - run script from memory
ssecukey- NAND KEY sub-system
sstorekey- sstorekey sub-system
store   - STORE sub-system
suspend - suspend
test    - minimal test like /bin/sh
tftpboot- boot image via network using TFTP protocol
true    - do nothing, successfully
unifykey- unifykey read/write based on the driver keymanage
unpackimg- unpack imgpack to single
update  - Enter v2 usbburning mode
usb     - USB sub-system
usb_burn- Burning with amlogic format package in usb 
usb_update- Burning a partition with image file in usb host
usbbc   - test usb bc
usbboot - boot from USB device
usid_prefetch- usid sub-system
uuid_prefetch- uuid sub-system
version - print monitor, compiler and linker version
video   - video sub-system

Amlogic S805 Boot Flow (Simplified)

Amlogic uses a multi-stage secure boot chain. Even without going deep into encrypted blobs, the high-level process looks like this:

  1. BootROM (in SoC)

    • Reads boot0 (BL2) from eMMC
    • Verifies its signature
    • If valid → execute
    • If invalid → fallback to SD, then USB Burning mode
  2. BL2 (in boot0/boot1)

    • Initializes DRAM
    • Loads BL30/BL31/BL32 (ARM Trusted Firmware stack)
    • Performs another signature check
  3. BL33 (U-Boot)

    • Runs the full U-Boot shell
    • Exposes commands like mmc, imgread, bootm, etc.
    • Loads the Android boot image via Amlogic’s imgread, not via raw reads
  4. imgread kernel …

    • Decrypts & verifies the kernel header
    • Extracts kernel, initramfs, DTB
    • Copies them into fixed RAM addresses determined by Amlogic’s bootloader
  5. bootm

    • Final jump into kernel entry point
    • If the kernel is modified, unsigned, or mismatched → boot stops silently

This pipeline is strict, and every stage is cryptographically tied to the next one.


Why Replacing U-Boot Does Not Work

On the secure-boot board, BL2 and BL31 enforce authentication of BL33 (U-Boot).
If U-Boot is modified, cloned from another board, or taken from any community S805 device, BootROM/BL2 rejects it immediately.

  • Replacing boot0/boot1 → fails at BootROM check
  • Replacing user-area U-Boot → the board is still using boot0/boot1 for booting
  • Loading U-Boot via USB → rejected before execution
  • Loading U-Boot via SD → ignored completely

Why Kernel Replacement Also Fails (Two Different Failure Modes)

There are two cases someone might try to load a custom kernel, and the failure reasons are different:

Case 1 — Replace kernel inside the Android boot partition

If we replace:

  • the kernel zImage,
  • the initrd, or
  • the DTB

inside the normal Android boot image (in the user area), but keep boot0/boot1 untouched, then imgread kernel boot ${loadaddr} detects that the kernel is not the signed one.

The verification fails, the decryption failes, and U-Boot outputs:

1
aml log : check is 3

Meaning:

  • kernel signature mismatch
  • kernel header authentication failed
  • kernel is rejected BEFORE bootm is called

This proves the Android boot image is encrypted + authenticated.

Case 2 — Load a kernel manually using fatload

Example:

1
2
3
4
fatload mmc 0:1 ${loadaddr} uImage
fatload mmc 0:1 ${initrd_addr} uInitrd
fatload mmc 0:1 ${fdt_addr} dtb/meson8b_m201_1G.dtb
bootm ${loadaddr} ${initrd_addr} ${fdt_addr}

In secure mode:

  • U-Boot still performs a kernel validity check before executing bootm
  • U-Boot compares the kernel image against internal metadata (anti-rollback counters, hash records, signed kernel header)
  • A manual fatload kernel has no valid signature, so the check silently fails
  • U-Boot jumps into the kernel entry point anyway, but the secure monitor blocks execution

This is why the board is stuck at:

1
Starting kernel ...

The ROM, BL2, or TEE simply refuses to let an unsigned kernel execute, even if loaded manually into RAM.


The Android Boot Image is also encrypted

On these secure units:

  • the kernel
  • the ramdisk
  • the dtb

inside Android boot.img are not plain zImage + cpio like normal Amlogic builds.
They are encrypted by the vendor signing tool and validated by U-Boot/TEE.

This forms a complete, tamper-proof chain: BootROM → BL2 → BL31 → U-Boot → encrypted kernel → Android userspace. This is exactly what we observe on the 1832 secure-boot board.

Finding Out the Initramfs Exploit

At this stage I had already accepted that replacing U-Boot or injecting a new kernel was cryptographically impossible. So I shifted focus to understanding what happens after U-Boot loads the encrypted boot image, hoping to find a weakness in the later stages.


Failed Attempts at Decoding or Rebuilding the Encrypted Boot Image

Before discovering the real exploit path, I spent some more days trying to work around the encryption:

  • Extracting and analyzing the boot partition with standard tools (abootimg, mkbootimg, unmkbootimg)
  • Brute-inspecting the image structure to locate kernel headers
  • Searching for LZO/LZ4 gzip signatures inside boot.img
  • Trying to recover the AES/secure header by diffing multiple dumps
  • Copying encrypted kernels between devices
  • Attempting to decrypt via the running system’s /dev/ubootenv or efuse maps
  • Searching through U-Boot for hardcoded decrypt routines

None of it worked. Actually there are someone who successfully bypassed the AML secured boot: https://fredericb.info/2016/10/amlogic-s905-soc-bypassing-not-so.html, but it’s too complicated for me and this limited time project.


A Crucial Observation: U-Boot Always Loads the Initramfs to a Fixed Address

Through multiple UART captures, I noticed a repeating pattern: the Android ramdisk always appeared at the same address in RAM.

Here is the relevant portion of a successful Android boot log:

Click to expand Android boot log
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Booting....
pos 509 value is 20aml_keys: version 0 can not be init 3ff6ae94
current storer:emmc_key
ERR(v2_common/optimus_download_key.c)L473:failed to query key state, rc 0, keyIsBurned=0

ANDROID Format IMAGE
Booting kernel from Legacy Image at 12000000 ...

Image Name: Linux-3.10.33
Image Type: ARM Linux Kernel Image (lzo compressed)
Data Size: 5927628 Bytes = 5.7 MiB
Load Address: 00208000
Entry Point: 00208000
Verifying Checksum ... OK
Ramdisk start addr = 0x125a8000, len = 0x20f1df
Multi dtb tool version: v2 .
Multi dtb detected, support 2 dtbs.
aml_dt soc: m8b platform: m201 variant: 1G
...
Loading Ramdisk to 04009000, end 042181df ... OK

The important part: Ramdisk start addr = 0x125a8000, len = 0x20f1df

This means:

  • U-Boot decrypts the ramdisk from the encrypted boot image
  • It places the plaintext ramdisk at 0x125a8000 in RAM
  • The kernel later copies it to another region (0x04009000), but only after U-Boot already puts a decrypted version in place

This was a breakthrough. Even though the boot image is encrypted on disk, the decrypted ramdisk exists in RAM in plaintext before the kernel uses it. If we can overwrite that region after U-Boot loads the encrypted Android ramdisk but before the kernel processes it, we control early boot.


First Exploit Test: Overwriting the Decrypted Initramfs in RAM

With this theory in mind, I tried something simple:

  1. Let U-Boot load the Android boot image normally
  2. Interrupt the process after decryption
  3. Manually overwrite the RAM area at 0x125a8000 with my own Linux initramfs
  4. Let the boot continue

The result?

It dropped me directly into the initramfs shell.

This confirmed everything:

  • The bootloader enforces secure boot ONLY for the image stored on eMMC
  • But once the ramdisk is decrypted into RAM, there is no further signature check
  • If we overwrite that memory region before bootm hands control to the kernel, we can inject our own early userspace

This is the fundamental vulnerability that makes the entire Debian-on-Secure-Boot-S805 project possible.

In other words:

We cannot decrypt or rebuild the encrypted Android boot image,
but we can hijack the decrypted initramfs in RAM before the kernel uses it.

This discovery became the foundation for the two-stage initramfs technique described later.

Using Linux ramfs to load Debian

Once the initramfs-overwrite trick in RAM was confirmed to work, the next challenge appeared immediately: the size limit.

When U-Boot loads the decrypted Android ramdisk into memory at 0x125a8000, it places it right next to the DTB and other boot structures. If I load a larger custom initramfs at that same address, it simply overwrites the device tree, and the kernel panics instantly.

The stock Android ramdisk is tiny — about 2.1 MB.
My Debian/Armbian initramfs was 4.2 MB, almost double. Stripping it is quite challenging. Fortunately, this board has mmc/sdcard driver integrated inside the kernel, so I decided to strip udevadm and kmod from the ramfs. If the disk driver is not integrated inside the kernel, we have another way to strip the ramfs:

  • Create a small ramfs with minimal binary (that has switch_root)
  • Use Uboot fatload to load the full ramfs (raw, not gz) into a specific RAM address (Eg: 0x13000000) with specific size (Eg: 0x00800000)
  • Mount a temporary file system and dd the above address to tmpfs
  • Switch root to the new root

After stripping binary, here is the sample init.

Click to expand Example code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh

# 1. Basic setup
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev

# 2. Parameters of the big CPIO in RAM
BIG_ADDR=$((0x13000000))          # physical address where big CPIO starts
BIG_SIZE=$((0x00800000))          # example: 8 MiB, change to real size

# 3. Create target root in RAM
mount -t tmpfs -o size=128M tmpfs /newroot

# 4. Unpack CPIO from RAM into /newroot
dd if=/dev/mem bs=1 skip="$BIG_ADDR" count="$BIG_SIZE" \
  | gzip -dc \
  | ( cd /newroot && cpio -idmv )

# 5. Final switch to the new root
exec switch_root /newroot /sbin/init

Then, we can boot the board using our new uinitrd_tiny

1
2
3
4
5
setenv bootargs 'console=ttyS0,115200n8 earlyprintk root=/dev/mmcblk0p2 init=/sbin/init rootwait rw fsck.repair=yes no_console_suspend vdaccfg=0xa000 clk_ignore_unused hdmimode=1080p cvbsmode=576cvbs hdmitx=cecb'
mmc part list

fatload mmc 0:1 0x125a8000 uinitrd_tiny
bootm

Building rootfs for Debian

Change init system from systemd to sysvinit

I took a prebuilt rootfs that is known to works on S805 box. It’s a Debian 10 (Armbian 5.99), which is quite outdated, but for demonstration, it’s still good. Although we changed the ramfs to load Debian, the kernel is still Android. Android uses different init system that is not compatible with systemd. Therefore, we will remove the systemd and install the older one: sysvinit. We will use qemu-user-static to mount rootfs of Armbian/Debian image to the current Ubuntu system to install:

1
2
3
4
5
6
apt-get install qemu-user-static binfmt-support
cp /usr/bin/qemu-arm-static /mnt/rootfs/usr/bin/
mount -o bind /dev /mnt/rootfs/dev
mount -o bind /sys /mnt/rootfs/sys
mount -t proc proc /mnt/rootfs/proc
chroot /mnt/rootfs /bin/bash

Now we are in the root of the Armbian/Debian. It’s time to get rid of systemd and go back to sysvinit:

1
2
3
4
5
apt-get update
apt-get install sysvinit-core sysvinit-utils sysv-rc initscripts login ifupdown iproute2 net-tools nano htop curl wget openssh-server ca-certificates
apt-get purge systemd systemd-sysv
# We will fix some issues
echo 'T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100' >> /etc/inittab #To fix the login using UART

To unmount correctly:

1
2
3
4
exit
sudo umount /mnt/rootfs/dev
sudo umount /mnt/rootfs/sys
sudo umount /mnt/rootfs/proc

The Android user group

Android also uses Linux UIDs/GIDs, but its permission model is very different from a classic GNU/Linux system.
Instead of relying purely on Linux capabilities (CAP_NET_*, CAP_SYS_*, …), Android defines a fixed list of Android IDs (AIDs) that represent system roles and kernel-visible capabilities.
The canonical list is defined in AOSP here:

https://android.googlesource.com/platform/system/core/+/master/libcutils/include/private/android_filesystem_config.h

Of particular interest are the 3000-series groups. As documented in the source, these groups are supplemental groups only and are explicitly checked by the Android kernel to grant access to sensitive subsystems such as networking. For example:

  • inet (3003) – permission to create IPv4/IPv6 sockets
  • net_raw (3004) – permission for raw sockets (e.g. ping)
  • net_admin (3005) – permission to configure network interfaces and routing

Why we must recreate these groups in Debian

The VNPT Smartbox 2 still boots with the vendor Android kernel (3.10.33).
When Android userspace is replaced by Debian, the Android AID groups no longer exist in /etc/group. As a result, even root may lack the supplemental groups that the kernel expects, and basic operations such as DNS resolution or opening network sockets can fail.

To fix this, we must:

  1. Recreate the required Android groups with the same GID numbers
  2. Add our Debian users (including root or service users) to those groups

Once this is done, networking and other kernel-gated features start working normally.

Creating the Android groups and adding users

Below is the group set I use for normal operation (networking, optional Bluetooth, optional Android-style storage).
This creates the groups and adds the chosen user to them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# replace this with your main user if needed
U=root

# Create Android capability groups (kernel-aware on many Android kernels)
# 3001/3002 = bluetooth sockets (only if you use BT)
# 3003      = AF_INET/AF_INET6 sockets (internet)
# 3004      = raw inet sockets (ping, some DHCP tools)
# 3005      = interface/routing control (ifup/route tools for non-root)
# 3006/3007 = bandwidth stats/accounting (often optional)
# 1015/1028 = Android sdcard write/read (only if you use /sdcard-style storage)

groupadd -g 3001 net_bt_admin 2>/dev/null || true
groupadd -g 3002 net_bt       2>/dev/null || true
groupadd -g 3003 inet         2>/dev/null || true
groupadd -g 3004 net_raw      2>/dev/null || true
groupadd -g 3005 net_admin    2>/dev/null || true
groupadd -g 3006 net_bw_stats 2>/dev/null || true
groupadd -g 3007 net_bw_acct  2>/dev/null || true
groupadd -g 1015 sdcard_rw    2>/dev/null || true
groupadd -g 1028 sdcard_r     2>/dev/null || true

# Add _apt for apt to work
usermod -aG sdcard_rw,sdcard_r _apt
# Add user to the required groups (adjust as needed)
usermod -aG inet,net_raw,net_admin,net_bw_stats,net_bw_acct,sdcard_rw,sdcard_r $U

# Reboot to apply
reboot

In practice, inet is the most important group. The others should be added only when the corresponding functionality (raw sockets, interface management, Bluetooth, or Android /sdcard access) is actually needed.

Fix other things

Fix apt-get

On many Android kernels (especially older 3.10 vendor trees like Amlogic S805), the networking path used by glibc resolver is partially incompatible with unprivileged processes, so we run apt as root.

1
2
3
cat > /etc/apt/apt.conf.d/00sandbox-root << 'EOF'
APT::Sandbox::User "root";
EOF

Fix time

This box doesn’t have RTC module so it has to get time from Internet. Therefore, we use ntpdate to automatically sync time from the Internet.

Install and configure ntpdate
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apt-get install ntpdate
ntpdate -u pool.ntp.org
echo "0 3 * * * root /usr/sbin/ntpdate -u pool.ntp.org >/dev/null 2>&1" \
  > /etc/cron.d/ntpdate
cat > /etc/rc.local <<'EOF'
#!/bin/sh -e
# /etc/rc.local

(
  tries=0
  while [ "$tries" -lt 10 ]; do
    sleep 30

    if ip route | grep -q '^default'; then
      /usr/sbin/ntpdate -u pool.ntp.org >/dev/null 2>&1 && break
    fi

    tries=$((tries + 1))
  done
) &

exit 0
EOF

chmod +x /etc/rc.local

Fix locale

1
2
3
4
apt install -y locales
sed -i 's/^# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8

Hold sysvinit and prevent accidently installed systemd

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apt-mark hold systemd systemd-sysv libpam-systemd libnss-systemd
apt-mark hold sysvinit-core sysvinit-utils sysv-rc initscripts
cat > /etc/apt/preferences.d/no-systemd <<'EOF'
Package: systemd*
Pin: release *
Pin-Priority: -1

Package: libpam-systemd
Pin: release *
Pin-Priority: -1

Package: libnss-systemd
Pin: release *
Pin-Priority: -1
EOF

Getting drivers to work

On Android TV boxes, drivers are the hardest part when replacing Android with a normal Linux distribution.
Most peripherals rely on vendor-specific, out-of-tree kernel modules that are tightly coupled to the Android kernel and device tree.

For the VNPT Smartbox 2 (Amlogic S805), the most important drivers are:

  • Wi-Fi (RTL8189ES, SDIO)
  • Mali GPU (for graphics acceleration, optional)
  • HDMI / display (already handled by the vendor kernel)

Wifi (RTL8189ES)

For Wi-Fi to work, we normally need:

  • a kernel module (*.ko)
  • a firmware blob (*.bin) loaded by the driver at runtime

The first step is to inspect the working Android system and reuse exactly what it uses.

To find the kernel module and firmware on Android, use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# find the kernel module
find / -type f -name "*8189*ko" 2>/dev/null

# search for firmware files
find / -type f \( \
  -iname "*8189*.bin" -o \
  -iname "*rtl*.bin" -o \
  -iname "*wifi*.bin" -o \
  -iname "*wlan*.bin" \
\) 2>/dev/null

# check kernel logs for firmware requests
dmesg | grep -i -E '8189|rtl|firmware'

In my case, the result was: /system/lib/8189es.ko. No external firmware files were found, and Android boot logs showed no request_firmware() messages. This means the firmware is embedded inside the kernel module, which is very common for older Realtek drivers on Android 3.10 kernels.

Since the Wi-Fi firmware is embedded inside the driver, we only need to copy the 8189es.ko to Debian. We will place all the third-party modules under /lib/modules/<kernel-version>/extra. Then, we install it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
KVER=$(uname -r)

mkdir -p /lib/modules/$KVER/extra
cp 8189es.ko /lib/modules/$KVER/extra/8189es.ko
chmod 0644 /lib/modules/$KVER/extra/8189es.ko
: > /lib/modules/$KVER/modules.order # To silent the warning
: > /lib/modules/$KVER/modules.builtin # To silent the warning
depmod -a $KVER
modprobe 8189es # Test it
# Enable driver loading on boot
mkdir -p /etc/modules-load.d
cat > /etc/modules-load.d/8189es.conf << 'EOF'
8189es
EOF

At this point, ip link should show at least 1 wlan interface. We will bring it up and set it to be automatically enabled on boot

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1) Bring it up right away
ip link set wlan0 up

# 2) Ensure the Wi-Fi kernel module is auto-loaded at boot
mkdir -p /etc/modules-load.d
cat > /etc/modules-load.d/8189es.conf << 'EOF'
8189es
EOF


# 3) Ensure wlan0 is brought UP at boot (sysvinit / ifupdown)
# This does NOT configure Wi-Fi security; it only raises the interface.
cat >> /etc/network/interfaces << 'EOF'

# Wi-Fi (RTL8189ES)
auto wlan0
iface wlan0 inet dhcp
    pre-up modprobe 8189es
    pre-up ip link set wlan0 up
    wpa-driver nl80211
    wpa-conf /etc/wpa_supplicant/wpa_supplicant-wlan0.conf
    metric 10
EOF

Now, we can connect to wifi using wpa_supplicant.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# create wpa config
cat > /etc/wpa_supplicant/wpa_supplicant-wlan0.conf <<'EOF'
ctrl_interface=/run/wpa_supplicant
update_config=0
country=VN

network={
    ssid="YOUR_SSID"
    psk="YOUR_PASSWORD"
}
EOF
chmod 600 /etc/wpa_supplicant/wpa_supplicant-wlan0.conf

# start wpa_supplicant
killall wpa_supplicant 2>/dev/null || true
rm -rf /run/wpa_supplicant
mkdir -p /run/wpa_supplicant
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant-wlan0.conf

# get DHCP lease
apt-get install -y isc-dhcp-client
dhclient -v wlan0

HDMI & Mali driver

Due to the nature of the Android kernel, making it works is difficult so I leave it not working until I have more time

Install Debian rootfs into eMMC

Understanding the partition table and U-boot command

Before touching the eMMC, it is important to clearly understand what layout we are dealing with.
On VNPT Smartbox 2 (Amlogic S805), the eMMC uses Amlogic proprietary Android partitioning, not a standard GPT/MBR layout.

Below is the original partition table detected by U-Boot / SPL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        name                        offset              size              flag
===================================================================================
   0: bootloader                         0            400000                  0
   1: reserved                     2400000           4000000                  0
   2: cache                        6c00000          20000000                  2
   3: env                         27400000            800000                  0
   4: logo                        28400000           2000000                  1
   5: recovery                    2ac00000           2000000                  1
   6: misc                        2d400000           2000000                  1
   7: boot                        2fc00000           2000000                  1
   8: system                      32400000          40000000                  1
   9: data                        72c00000         15f400000                  4

On Amlogic devices (Meson8b / S805), the eMMC layout is defined in two places, but only one is authoritative. This is why repartitioning with tools like amparthttps://github.com/7Ji/ampart often behaves unexpectedly on secure-boot devices.

1. Bootloader DTB (master)

  • The real partition table lives in the U-Boot DTB.
  • U-Boot generates the EPT from this DTB at boot.
  • On secure-boot boards, this DTB is encrypted and signed, so ampart cannot modify it.

2. reserved partition (EPT copy)

  • Contains a writable copy of the EPT used by Android tooling and ampart.
  • This copy exists for update and maintenance purposes only.
  • It is not authoritative.

At every cold boot, U-Boot:

  1. Loads the bootloader DTB
  2. Rebuilds the EPT from the DTB /partitions node
  3. Overwrites the EPT stored in the reserved partition

Since the U-boot is encrypted in the secure boards, ampart can successfully modify the reserved partition. On the next reboot, U-Boot restores the original layout from the encrypted DTB. Therefore, repartitioning is impossible without bypassing secure boot.

Reuse existing partitions

Instead of modifying the partition table (which is unreliable on secure-boot devices), the existing eMMC partitions can be reused and repurposed as follows:

  • /dev/data : This is the largest partition on the eMMC so it will be used as the Linux root filesystem (/).
  • /dev/system: Basically, it won’t be used in Debian so it can be kept for Android as a fallback, or reformatted and used as a secondary Linux partition (e.g. /usr, /opt, or /var).
  • /dev/boot : Kept unchanged, as it contains the signed Android kernel required for booting.
  • /dev/env : A raw partition used by U-Boot to store environment variables. The actual U-Boot environment size is only 64 KB, so the remaining space is reused to store a small ramfs / initramfs.
  • /dev/cache : Used to store TWRP. Due to secure boot, TWRP can only run as an application and cannot be installed into the recovery partition. By placing it in cache, TWRP can be launched anytime without requiring external storage.

This approach avoids repartitioning while still providing a flexible and practical Linux setup on a secure Amlogic device.

DD to eMMC

  1. For the rootfs
1
2
3
4
5
6
7
# write partition 2 (rootfs) from image to /dev/data

SECTOR_START=270336
SECTOR_COUNT=1085954

dd if=VNPTSMB2-S805-Debian-Buster.img of=/dev/data \
   bs=512 skip=$SECTOR_START count=$SECTOR_COUNT conv=fsync status=progress
  1. For ramfs file
1
2
3
mmc part list
fatload mmc 0:1 0x13000000 uinitrd_tiny #Load the uinitrd_tiny into address 0x13000000
store write env 0x13000000 0x0 0x100000 #Use the AML's write command to write it to the offset 1MB of env partition
  1. Fix U-boot to load the Debian from eMMC
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1) make the initramfs (/init) always run, then it will switch_root to /dev/data
setenv initargs 'init=/init no_console_suspend'

# 2) replace storeargs so preboot will build Debian bootargs (instead of Android ones)
setenv storeargs 'setenv bootargs ${initargs} console=${console} earlyprintk rootwait rw fsck.repair=yes clk_ignore_unused vdaccfg=${vdac_config} hdmimode=1080p hdmitx=${cecconfig}'

# 3) make storeboot always refresh ramfs from env@+1MiB before bootm
setenv storeboot 'echo Booting Debian...; if unifykey get usid; then setenv bootargs ${bootargs} androidboot.serialno=${usid}; fi; imgread kernel boot ${loadaddr}; store read env 0x125a8000 0x100000 0x1f05d6; bootm; run recovery'

# 4) keep normal boot flow
setenv bootcmd 'run storeboot'

# 5) persist
saveenv
  1. Remove U-boot auto reset env
1
2
3
4
5
6
7
# On this board, it always reset env to default when reboot into recovery(call devenv). We remove it.
# 1) Remove the defenv path from preboot (keep everything else)
setenv preboot 'echo preboot...; if itest ${upgrade_step} == 3; then run prepare; run storeargs; run update; fi; run prepare; run storeargs; get_rebootmode; clear_rebootmode; echo reboot_mode=${reboot_mode}; run update_ir; run switch_bootmode'

# 2) Force upgrade_step to a safe value and persist
setenv upgrade_step 2
saveenv
  1. Fix fstab
1
2
3
4
LABEL=ROOTFS  /  ext4  defaults,noatime,nodiratime,commit=600,errors=remount-ro,nofail  0  0
/dev/data  /  ext4  defaults,noatime,nodiratime,commit=600,errors=remount-ro,nofail  0  0
LABEL=BOOT /boot vfat defaults 0 2
tmpfs /tmp tmpfs defaults,nosuid 0 0

Conclusion

Due to the limitations imposed by secure boot on some VNPT MyTV Smartbox 2 (S805) boards, it is not possible to fully replace the original Android boot chain. The practical approach is to reuse the stock Android kernel together with a custom initramfs to start a Debian userspace. As a result, we get Armbian (Debian 11) running with sysvinit on top of the Android kernel.

Despite this constraint, the system is a fully functional Armbian environment and behaves like a normal Debian-based server for most real-world use cases. Stability is good, performance is acceptable for its class of hardware, and all essential kernel features required for server workloads are available.

In practice, this setup is perfectly usable as a small home server. Software and services that run well include:

  • LEMP stack (Nginx, MariaDB/MySQL, PHP)
  • Node.js 18 (APIs, bots, small web services)
  • OpenVPN and WireGuard-Go
  • Pi-hole (network-wide DNS filtering and ad blocking)
  • FTP / SFTP server
  • Transmission (torrent box)
  • SSH for remote administration
  • File server / NAS-like usage over LAN
  • Lightweight Docker containers (with reasonable expectations)
  • Various cron-based automation and monitoring tools

With this approach, the VNPT MyTV Smartbox 2 is no longer just an Android TV box but a compact, low-power Linux machine that can reliably handle many everyday server tasks at home.

From Hanoi with love
Built with Hugo
Theme Stack designed by Jimmy