Unbricking an LG G4 Stylus (MT6592M): Reverse-Engineering MediaTek's DA Write Protocol & Disassembling Little Kernel
One of the boards in my Phone Revival Lab is an LG G4 Stylus (LG-H540) built around the octa-core MediaTek MT6592M (8× Cortex-A7 @ 1.4 GHz). When I first powered it on, the screen flashed the LG logo with:
Security Error: Authentication Fail #9
and immediately shut off. LG's Download Mode (laf) partition was unreachable because the bootloader halted before loading boot or recovery, leaving only the raw MediaTek Preloader USB interface.
What looked like a routine firmware reflash turned into two deep reverse-engineering sessions:
- Reconstructing MediaTek's legacy DA Stage 2 (
0x61) write protocol after upstreammtkclientfailed to write to theMT6592eMMC. - Disassembling the ARM Thumb-2 Little Kernel (
lk.bin) bootloader inradare2to understand why the unbricked board still powered off after 3 seconds when running on bench power without a battery.
1. Catching LG's Custom MediaTek Preloader (1004:61f1)
Standard MediaTek devices expose USB VID/PID 0e8d:2000 in Preloader mode. LG customized the Preloader on the LG-H540 to enumerate as 1004:61f1 (LG Electronics, Inc. G2 / Optimus Android Phone) for roughly 2 seconds upon USB insertion before timing out.
To prevent Linux's ModemManager from probing the serial port with AT commands during that 2-second window and to bind the custom VID/PID to the USB serial driver, I added /etc/udev/rules.d/99-lg-mtk.rules:
SUBSYSTEM=="usb", ATTR{idVendor}=="1004", ATTR{idProduct}=="61f1", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1"
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1004", ATTR{idProduct}=="61f1", RUN+="/sbin/modprobe option", RUN+="/bin/sh -c 'echo 1004 61f1 > /sys/bus/usb-serial/drivers/option1/new_id'"
With the udev rule active, mtkclient caught the Preloader handshake (HW code: 0x6592), disabled the hardware watchdog (0x10007000), and uploaded the MT6592 Download Agent (MTK_AllInOne_DA.bin).
Before attempting any writes, I backed up the board's unique calibration and security partitions (nvram, pro_info, protect_f, protect_s, seccfg) using mtk r.
2. Why mtkclient Failed to Flash MT6592—and Reversing the 0x61 DA Protocol
Reading partitions with mtk r worked flawlessly. However, every attempt to write a partition with mtk w failed immediately (Error on writing ...).
Inspecting mtkclient/Library/DA/mtk_dalegacy.py showed why: for legacy MediaTek chips, mtkclient's writeflash() calls sdmmc_write_data(), which sends opcode 0x60 followed by a parameter structure intended for newer MT67xx Download Agents. On the MT6592 Stage 2 DA inside MTK_AllInOne_DA.bin, opcode 0x60 is rejected or misinterpreted.
Disassembling the MT6592 DA Stage 2 Command Loop
I extracted the MT6592 Stage 2 binary from MTK_AllInOne_DA.bin and traced its main command dispatch loop in radare2. Right next to the working read handler (0x65), opcode 0x61 (SDMMC_WRITE_IMAGE_CMD) and opcode 0x62 (SDMMC_WRITE_DATA_CMD) handled eMMC writes.
Tracing every uart_read / usb_read and usb_write call inside the 0x61 handler revealed the exact wire framing expected by the MT6592 DA:
- Command & 36-Byte Header (
0x61):- Host sends
0x61(1 byte). - Host sends a 36-byte header:
storage_type:0x00(1 byte, eMMC)part_type:0x08(1 byte,EMMC_PART_USER)addr: 64-bit big-endian byte offset on eMMC (8 bytes)length: 64-bit big-endian total byte length (8 bytes)packet_length: 32-bit big-endian chunk size =0x00100000(1 MiB, 4 bytes)padding: 14 zero bytes (bringing the header to 36 bytes)
- DA responds with
0x5A(ACK).
- Host sends
- Data Transfer Initiation (
0x62):- Host sends
0x62(SDMMC_WRITE_DATA_CMD, 1 byte) +0x00(1 byte).
- Host sends
- Chunk Loop (per 1 MiB block):
- DA sends
0x69(CONT_CHAR) when its SRAM/DRAM buffer is ready. - Host streams up to
1,048,576bytes in 4 KB USB bulk writes. - Host computes a 16-bit word sum over the chunk:
# Sum every 16-bit little-endian word, masked to 16 bits, sent big-endian words = struct.unpack(f"<{len(chunk)//2}H", chunk) checksum = sum(words) & 0xFFFF usb_write(struct.pack(">H", checksum)) - DA verifies the checksum, writes the 1 MiB block to eMMC, and replies with
0x69.
- DA sends
- Completion Sync:
- After the final chunk, DA sends
0x5A(ACK), flushes the eMMC cache, and sends a final0x5A.
- After the final chunk, DA sends
Flashing All 16 Partitions with unbrick_flash.py
I unpacked the official H54010d_00.kdz → H54010d_00.dz firmware (merging 81 sparse chunks into a 2.08 GB raw system.img) and wrote unbrick_flash.py implementing the 0x61/0x62 protocol on top of mtkclient's DA session bootstrap.
All 16 partitions flashed cleanly at ~4.1 MB/s:
MBR,EBR1,EBR2(partition tables)uboot(lk.bin@0x02fc0000)boot.img(0x03080000),recovery.img(0x04080000)secro.img(SEC_RO@0x04980000) — Restoring this partition resolvedSecurity Error: Authentication Fail #9!logo.bin,cust.img,cache.img, andsystem.img(0x09800000, 2,181,038,080 bytes)
3. Why Does the Unbricked Board Shut Down Without a Battery? Disassembling lk.bin
With Security Error #9 gone, the board vibrated, displayed the clean LG boot logo, and then powered down ~3 seconds later whenever powered solely over USB without a battery installed.
To find out whether this was a brownout or a deliberate software shutdown, I loaded uboot_393216.bin (strip off the 512-byte MediaTek header → lk.bin, loaded at base address 0x81E00000) into radare2 (r2 -a arm -b 16 -m 0x81e00000).
How LG's Thumb-2 Little Kernel Resolves Strings
Standard cross-reference searches (axt) in radare2 initially showed zero references to the error strings in lk.bin. Inspecting the Thumb-2 functions revealed why: the compiler uses PC-relative offset tables. A function loads a 32-bit signed offset from its literal pool (ldr r0, [pc, #imm]) and adds pc (add r0, pc) to compute the string address at runtime.
By scanning all Thumb-2 literal pools for (literal_value + add_pc_addr + 4) == target_string_addr, I mapped every boot-mode and battery check inside lk.bin.
Shutdown Trigger 1: KPOC (Kernel Power Off Charging) Mode
At 0x81E0DFD4 (boot_mode_select), when the board powers up from USB VBUS insertion:
It checks whether a charger is attached and logs:
Power Off Charging Mode, Check Power KeyUnless the physical Power button is held continuously across the check window, it sets
g_boot_mode = 8(KERNEL_POWER_OFF_CHARGING_BOOT/chargerlogo).Inside
mt65xx_bat_init()(0x81E0B6F8), before drawing the charging animation, it checksmtk_detect_key(8)(Power Key). If the power key was released, it hits0x81E0B97C:PowerKey is released before display chargerlogo : Power Off!!and immediately calls
mt6575_power_off()(0x81E02EB8), pulling the PMICBBPUpower-hold bit low.
Shutdown Trigger 2: [LGBM] Battery Temperature ADC Fault
Even if the power key is held, mt65xx_bat_init() calls 0x81E0B298 (lgbm_get_battery_temp_adc), which reads the battery NTC thermistor through the MediaTek PMIC AUXADC:
With no battery connected, the NTC line floats open-circuit.
The loop at
0x81E0B2C8retries 5 times, printing:[LGBM] battery temperature ADC read fail,retry count = %dAfter 5 failed reads, control returns to
0x81E0B83C, which prints:BATTERY OVER TEMPERATURE Or FAIL Reading Battery Temperatureand executes
mt6575_power_off().
Takeaway
Hardware recovery on older consumer boards rarely fails for just one reason. On the LG G4 Stylus (LG-H540), clearing the eMMC cryptographic state required reverse-engineering the MT6592 DA Stage 2 0x61 wire protocol, while diagnosing the post-unbrick shutdown required static binary analysis of the Thumb-2 bootloader (lk.bin) to separate PMIC brownouts from intentional LGBM NTC thermistor lockouts.