I have an old DTV converter sitting around gathering dust, so I thought it would be interesting to take a look inside:

As you can see, there’s not much there: a Thomson TV tuner, an IR receiver, 32MB of RAM and a 2MB flash chip (on the underside of the board). What really makes this interesting though is the LGDT1111 SoC; this is a DTV chip manufactured by LG, so it’s a little different than the Broadcom/Atheros/Ralink/etc SoCs found in a lot of other consumer devices. It is very popular with many DTV converters though, so determining its CPU architecture and reversing the underlying firmware could be interesting.

Digging around on the Internet turned up a nice block diagram of the LGDT1111 (courtesy of MVPtek):

The MVPtek web site states that the SoC uses an “AMR926EJ-STM” controller…could they mean an ARM926EJ-STM? Hmmm…

According to the block diagram the LGDT1111 does have a UART connection, and indeed there is a four pin connector on the board above the SoC with the pins nicely labeled:

The serial port settings are 115200 baud, 8 bits, no stop bits, 1 parity bit. Here’s the boot messages that are dumped over the serial connection:

V1.05.40 May 20 2008, 16:24:11 sungwee@sungwee /cygdrive/d/boot_t/src Config Baud Rate : 115200 bps System Clock Rate : 175 MHz U-Boot Mem offset : Text/Data [00e00000, 00e19e9b], BSS [00e19e9c, 00e3e7c3] RAM Configuration: Bank #0: 00000000 32 MB manufature ID : 0xc2, Device ID: 0x49 Flash: 2 MB In: serial Out: serial Err: serial Set Flash Memory Structure... Set Region for Bootrom from 00:00 0x2c000000 (131072bytes) Set Region01 for Appl from 00:05 0x2c020000 (917504bytes) cacheID : 1d0d20d2 write-back, register 7, format C cache separated D-cache 4kbytes 4-way 8 words (32bytes) I-cache 4kbytes 4-way 8 words (32bytes) Masks:: Index: c0000000, Int: 000000c0, Seg: 000003e0 Compare 00e13d8c 2c013d8c Boot from address 2c000000 Boot from flash MMU CR : 000510f8 (00004000)-> 000550fb Processing BIZ file-from Flash: 0x2c020000 Aux data is symbol table(399362 bytes), pSymTab=0x1dfa8c [Application Code] Loading[4] Image from 2c020800 to 00010000(+1512037) -- TRY 0 ==> 2298510 bytes loaded in 0.778 sec, rc=0 -- Checking CRC32[Bin ] ==> Good, in 0.158 sec Moving 7270 symbols(312102 bytes) from 0x001dfa8c to 0x0026d2c4 Load Image to 00010000... Start from 00010000... MMU CR : 000540fb (00004000)-> 000540fa cacheID : 1d0d20d2 write-back, register 7, format C cache separated D-cache 4kbytes 4-way 8 words (32bytes) I-cache 4kbytes 4-way 8 words (32bytes) Masks:: Index: c0000000, Int: 000000c0, Seg: 000003e0 MMU CR : 000550fa (00004000)-> 000550ff [37m[44mnTxtSyms = 7270[0m [37m[44mpSymTabBase = [0x26d2d8..0x2827a0)[0m [37m[44mpSymHashBase = [0x2827a4..0x274470)[0m [37m[44mpSymStrBase = [0x2b0317..0x2ceac6)[0m [37m[44mnDwarfLst = 184[0m [37m[44mpDwarfLst = [0x28607c..0x28663c)[0m [37m[44mpDwarfData = [0x28663c..0x029cdb)[0m 000.025:root ] >> InitPool ]] [0x002cf000-0x01000000] 000.026:root ] SM_MAT_POOL[ 4] = 0x002cf000(0x004000) 000.027:root ] SM_MAT_POOL[ 8] = 0x002d3000(0x00c000) 000.028:root ] SM_MAT_POOL[16] = 0x002df000(0x018000) 000.029:root ] SM_MAT_POOL[32] = 0x002f7000(0x030000) 000.030:root ] SM_MAT_POOL[64] = 0x00327000(0x0a0000) 000.031:root ] >> InitBuddy ]] Addr=0x3c7000, Size=0xc39000 000.033:root ] >> Total Free Heap Size = 0xd05000 Stack... 336860180 0023fa48 New Application booted, uart = 0xfe000000, baud = 115200 [30m[43mSystem Clock : 175MHz[0m Starting V1.6.05 , Jun 25 2008, 21:52:48, from @:/cygdrive/d/Work/Cubic2/Source/TCU/app/D2A1D Start app init task.. AppInitTask: tid 0xd, priority 0x32 App_Initialize Task.. Dmc_Init() Starting MiniShell, priority 8 ... Minishell TID 0x37 (55) running ------------------------------------------- val pad sel : 0x30001d10(0x46fc) ------------------------------------------- ------------------------------------------- val pad sel : 0x30001d10(0x46fc) ------------------------------------------- gpio 6 [GPIO_KP_POWER] reset.. Mini Shell Task Spawned.. Shell> 000.070:tInit ] [30m[43mConfiguring D2A_MODE with CVBS and S-video for LGDT1111T[0m [30m[43mConfiguring D2A_MODE [0m000.070:tInit ] creating Queue tuner0Q ... qid is 0x00240934 000.071:tInit ] creating Queue SqIR ... qid is 0x00240968 000.072:tInit ] 2 Message queues are created 000.073:tInit ] creating Sema4 VDPVSyn ... 000.073:tInit ] creating Sema4 MuteSema ... 000.073:tInit ] creating Sema4 OSDSema ... 000.073:tInit ] creating Sema4 SectFilr ... 000.074:tInit ] creating Sema4 AInfo ... 000.074:tInit ] creating Sema4 VInfo ... 000.074:tInit ] creating Sema4 NvmSem ... 000.074:tInit ] creating Sema4 IR ... 000.075:tInit ] creating Sema4 TIME ... 000.075:tInit ] 9 Semaphores are created 000.075:tInit ] 0 Partitions are created install gpio 1 (group 0, offset 1) isr 105d10 enable gpio group 0 interrupt install gpio 6 (group 0, offset 6) isr 106724 enable gpio group 0 interrupt reboot line successfully set to 'boot flash' Thomson tuner 0xc2 detected DHL_DEV_Init: manufature ID : 0xc2, Device ID: 0x49 vendor: MX, device: 29LV160B DHL_DMX_Init: 000.092:tInit ] >> InitPool ]] [0x01000000-0x01084000] 000.093:tInit ] >> InitBuddy ]] Addr=0x1000000, Size=0x084000 000.094:tInit ] >> Total Free Heap Size = 0xd78bc4 SM_SDEC_POOL inited sdec_io inited PWM inited PSI_InitSF: SDEC isr 0x10, handler changed SDEC GPB IRQ inited /////////////////////////////////////////////////////////////////// ------- Project CB2 ------- - Build date: Jun 25 2008 21:51:18 - BOARD : D2A1T (2) # Board Version : 2 # Kernel : uC/OS-II LGDT1111 1.6.05 - Core Driver version : 0.0 - DSTHAL version : 609181 - FE u-code version : 000 - HDMI driver version : 0.0 # Channel MW : 609181 - EPG MW : 707121 - Caption MW : 0 # Application Version : M5.97A_T 080625A /////////////////////////////////////////////////////////////////// DhlPrintf Sema4 created [APP tInit 13] 0001 debug trace ON DHL_DEV_Init: manufature ID : 0xc2, Device ID: 0x49 vendor: MX, device: 29LV160B [NvRam tInit 13] 0001 FlashNvmInfo: blksz 0x10000-0x20, addr[0] 0x1e0000, addr[1] 0x1f0000 [DMC 0d tIni] 0001 Config:EEPROM size 0 [APP tInit 13] 0001 [APP tInit 13] 0001 .....Loading EEPROM block from FLASH Dump 'EEPROM' 256(0x100) bytes: 00249af0: __ 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00249b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00249b10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00249b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00249b30: 00 09 20 03 23 10 00 d1 00 00 00 00 00 00 00 00 .. .#..Ñ........ 00249b40: 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 ................ 00249b50: 00 00 00 00 09 00 00 00 00 00 00 00 00 00 00 00 ................ 00249b60: 00 00 00 09 00 01 00 00 00 00 00 00 00 00 00 00 ................ 00249b70: 00 00 01 00 00 00 00 00 00 04 00 00 00 00 00 00 ................ 00249b80: 00 00 00 00 00 00 00 3c 00 00 00 00 00 00 00 00 .......<........ 00249b90: 00 00 01 01 01 00 02 09 00 01 00 00 00 00 37 06 ..............7. 00249ba0: 7f 01 00 00 00 00 00 00 00 30 30 30 30 00 00 00 .........0000... 00249bb0: 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00249bc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00249bd0: 00 36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .6.............. 00249be0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00249bf0: 00 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ ................ [APP tInit 13] 0001 Wait to Write SYSDB (After 15min) [APP tInit 13] 0001 Wait to Write SYSDB (After 15min) [APP tInit 13] 0001 [APP tInit 13] 0001 [APP tInit 13] 0001 [APP tInit 13] 0001 ConfigMask is 1663 [APP tInit 13] 0001 [APP tInit 13] 0001 [APP tInit 13] 0001 set rating passwd [APP tInit 13] 0001 Rating 766 condition same [APP tInit 13] 0001 ------------------------------------------------------ [APP tInit 13] 0001 CH 0,0 RF 2 #0, uid 0, ChannelType 0, CableStd 1 [APP tInit 13] 0001 ProgramNum 0, SourceId 0, PID pva: 0x0 0x0 0x0 [APP tInit 13] 0001 Audio vol 9, mute 0x0, lang 0 [APP tInit 13] 0001 TimeZone 1, DST 0 [APP tInit 13] 0001 CC: Active 0, Style 1, Size 1, Src 2, Mode 9 [APP tInit 13] 0001 TxtCol 1, BgCol 0, TxtOp 0, BgOp 0, Edge type 0 col 1 [APP tInit 13] 0001 PenType 0, Author 55, Pos 0, mask 0x67f [APP tInit 13] 0001 ------------------------------------------------------ [APP tInit 13] 0001 Load Air UCM ..initializing crc table.. Current: 'Air' 0-0, RF 2 #0, uid 0 ---- UCM (total 0) [pid:p/v/a hex]---- [DMC 0d tIni] 0001 Notification callback 4f1f4 registerred. [DMC 0d tIni] 0001 cmdChangeDisplay: cid 0 [DHL 0d tIni] 0001 DHL_AV_ChangeVideoFormat(4, 1, 2, 4, 0) VDP_SetOutputAspectRatio: format -1, ar 2 **** vdp arc set: arc 3, zoom 1 **** security not enabled ****

It looks like it’s using a U-Boot boot loader and running uC/OS-II, an open source RTOS. After this there is a lot of debug output as it scans channels for TV stations.

There is an interactive shell available, and typing ‘?’ (no quotes) provides you with a list of supported commands.

The ‘i’ command, displays a list of running processes:

Shell> i MiniShell: [i] call function of '0x000dd3c4' Name TID(Pr) UsPr Stack_Usage State -------------------------- ------- ---- ----------- -------------------- root 0x01 1 62 1286/ 8192 Evt 0, Msg 0 IRTask 0x17 23 40 372/ 8144 Evt 240b08, Msg 0 SysTime 0x20 32 31 327/ 8144 Evt 240ba4, Msg 0 PsipRx 0x23 35 28 293/ 8144 Evt 240d10, Msg 0 AV 0x24 36 27 301/ 8144 Evt 240c40, Msg 0 MainKey 0x26 38 25 1696/32720 Evt 2413f8, Msg 0 t_AuxCC 0x2a 42 21 356/16336 Evt 241120, Msg 0 t_DccDe 0x2b 43 20 317/16336 Evt 2410ec, Msg 0 t_DccDm 0x2c 44 19 353/16336 Evt 241188, Msg 0 t_VbiDe 0x2d 45 18 325/16336 Evt 241328, Msg 0 CC_tTim 0x2e 46 17 328/ 8144 Evt 0, Msg 0 EATask 0x2f 47 16 1370/39952 Evt 0, Msg 0 tDmc 0x30 48 15 1942/32720 Evt 240de0, Msg 0 tEpgSca 0x31 49 14 445/ 8144 Evt 241050, Msg 0 tEpgEvt 0x32 50 13 333/ 8144 Evt 2410b8, Msg 0 tTimer 0x33 51 12 2570/ 8144 Evt 240fb4, Msg 0 App_Tim 0x34 52 11 137/16336 Evt 0, Msg 0 SigMon 0x35 53 10 1752/16336 Evt 2413c4, Msg 0 miniShe 0x37 55 8 9000/16336 Evt 0, Msg 0 idle 0x3a 58 5 336/ 4048 Evt 240830, Msg 0 NwtTask 0x3b 59 4 1056/ 8144 Evt 0, Msg 0 uC/OS-II Stat 0x3e 62 1 105/ 512 Evt 0, Msg 0 uC/OS-II Idle 0x3f 63 0 80/ 512 Evt 0, Msg 0 -------------------------- ------- ---- ----------- -------------------- call 0xdd3c4 result = 0x49 (73)

And the ‘key’ command displays IR codes and their associated actions:

Shell> key MiniShell: [key] call function of '0x00052014' [APP miniShe 55] 4398 -- key string help -- [APP miniShe 55] 4398 00800000 POWER_OFF [APP miniShe 55] 4398 00800001 MUTE [APP miniShe 55] 4398 00800002 CC [APP miniShe 55] 4398 00800003 ALANG [APP miniShe 55] 4398 00800004 SCREEN [APP miniShe 55] 4398 00800005 FAV [APP miniShe 55] 4398 00800006 STILL [APP miniShe 55] 4398 00800007 INFO [APP miniShe 55] 4398 00800008 EPG [APP miniShe 55] 4398 00800009 SRS_MODE [APP miniShe 55] 4398 00800010 UP [APP miniShe 55] 4398 00800011 DOWN [APP miniShe 55] 4398 00800012 LEFT [APP miniShe 55] 4398 00800013 RIGHT [APP miniShe 55] 4398 00800018 VOL_UP [APP miniShe 55] 4398 00800019 VOL_DOWN [APP miniShe 55] 4398 0080001a CH_UP [APP miniShe 55] 4398 0080001b CH_DOWN [APP miniShe 55] 4398 00800020 SELECT [APP miniShe 55] 4398 00800021 PREV_CH [APP miniShe 55] 4398 00800022 MENU [APP miniShe 55] 4398 00800023 HELP [APP miniShe 55] 4398 00800024 DRF [APP miniShe 55] 4398 00800025 EXT_INPUT [APP miniShe 55] 4398 00800026 CH_ADD [APP miniShe 55] 4398 00800027 CH_DEL [APP miniShe 55] 4398 00800028 EXIT [APP miniShe 55] 4398 0080002e ADT [APP miniShe 55] 4398 00800030 0 [APP miniShe 55] 4398 00800031 1 [APP miniShe 55] 4398 00800032 2 [APP miniShe 55] 4398 00800033 3 [APP miniShe 55] 4398 00800034 4 [APP miniShe 55] 4398 00800035 5 [APP miniShe 55] 4398 00800036 6 [APP miniShe 55] 4398 00800037 7 [APP miniShe 55] 4398 00800038 8 [APP miniShe 55] 4398 00800039 9 [APP miniShe 55] 4398 00800043 SLEEP [APP miniShe 55] 4398 00800044 SMART_PICTURE [APP miniShe 55] 4398 00800045 SMART_SOUND [APP miniShe 55] 4398 00800046 CH_BLOCK_EDIT [APP miniShe 55] 4398 00800081 DOT [APP miniShe 55] 4398 00800120 BACK [APP miniShe 55] 4398 call 0x52014 result = 0x1 (1)

There are some other interesting commands sprinkled in there, such as the video_freeze command that allows you to freeze the on screen video without interrupting the audio (an option I couldn’t find anywhere in the menu-driven user interface), or the rating_enable command which allows you to enable/disable the parental content filters without a password (bye-bye V-chip!).

This is all interesting, but if we really want to get down and dirty with this thing we’re going to need some firmware. There are of course no firmware updates for these converter boxes, so instead we’ll remove the flash chip and dump its contents using flashbin and a Gumbi board (raw flash image can be downloaded here):

$ flashbin --chip=mx29lv160 --read=flash.bin Reading all bytes starting at address 0x0… [################################################] 100.00%

All of the strings in the dumped firmware image appear to be related to the bootloader, but a binwalk scan reveals a gzip compressed section of data at offset 0x20800:

DECIMAL HEX DESCRIPTION ------------------------------------------------------------------------------------------------------- 30216 0x7608 uImage header, header size: 64 bytes, header CRC: 0xE15744, created: Sat Jun 20 18:12:44 1970, image size: 14767988 bytes, Data Address: 0xE15790, Entry Point: 0xE157A4, data CRC: 0xE3520001, image name: \341\240@\002\341\240p\003\343\240` 101737 0x18D69 LZMA compressed data, properties: 0x80, dictionary size: 21364736 bytes, uncompressed size: 33554432 bytes 133120 0x20800 gzip compressed data, from Unix, DD-WRT date: Wed Dec 31 19:00:00 1969

Running a code scan with binwalk (option -A) against the extracted gzip contents confirms that it contains big endian ARM instructions:

DECIMAL HEX DESCRIPTION --------------------------------------------------------------- 896 0x380 ARMEB function prologue 1132 0x46C ARMEB function epilogue 1360 0x550 ARMEB function epilogue 1436 0x59C ARMEB function prologue 1464 0x5B8 ARMEB function epilogue 1472 0x5C0 ARMEB function prologue 1544 0x608 ARMEB function epilogue 1652 0x674 ARMEB function prologue 1844 0x734 ARMEB function epilogue 2224 0x8B0 ARMEB function prologue 2292 0x8F4 ARMEB function epilogue 2360 0x938 ARMEB function epilogue 2456 0x998 ARMEB function prologue 2596 0xA24 ARMEB function epilogue 2700 0xA8C ARMEB function prologue 2752 0xAC0 ARMEB function epilogue 2848 0xB20 ARMEB function epilogue 2856 0xB28 ARMEB function prologue ...

Based on this and a quick look at the strings, this is definitely looking like the OS code, so let’s get it loaded into IDA. Based on the boot messages obtained from the serial port, the gzipped data we found at offset 0x20800 gets loaded into memory at 0x10000, which is where execution starts (also note the pSymTab address…this will be useful later!):

... Boot from address 2c000000 Boot from flash MMU CR : 000510f8 (00004000)-> 000550fb Processing BIZ file-from Flash: 0x2c020000 Aux data is symbol table(399362 bytes), pSymTab=0x1dfa8c [Application Code] Loading[4] Image from 2c020800 to 00010000(+1512037) -- TRY 0 ==> 2298510 bytes loaded in 0.778 sec, rc=0 -- Checking CRC32[Bin ] ==> Good, in 0.158 sec Moving 7270 symbols(312102 bytes) from 0x001dfa8c to 0x0026d2c4 Load Image to 00010000... Start from 00010000...

Loading the extracted code into IDA at the offset 0x10000 results in a decent initial analysis:

String references also check out, and we have several promising candidates for common library functions, such as snprintf:

This is encouraging, but with almost 4,000 functions, manually identifying them will take too long. Looking at the strings in IDA, there appears to be a list of strings that correspond to function and symbol names, starting at address 0x222ADF:

Now we need to find out how to correlate these strings to their appropriate functions. The boot messages mentioned a symbol table at 0x1DFA8C, so we’ll start looking there. At address 0x1DFAAC, we find what appears to be a list of symbol structures:

The symbol structure appears to be:

struct symbol { uint32_t func_address; /* Function pointer to this symbol's function */ uint32_t next_func_address; /* Function pointer to the next symbol's function */ uint32_t symbol_name_offset; /* Offset of the symbol name in the symbol strings listing */ };

Applying this structure to the first entry in the above IDA screenshot, the func_address is 0x10000 which is the entry point for our code. Adding its symbol_name_offset value (0x1BAB2) to the address where we found all the symbol strings (0x222ADF), we get: 0x222ADF + 0x1BAB2 = 0x23E591. Here we find the string “start_code”:

This looks good! A quick IDAPython script takes care of renaming all of the functions to their appropriate symbol names:

start_names = 0x222ADF start_addrs = 0x1DFAAC addr = start_addrs symbol_addr = BADADDR while symbol_addr != 0: symbol_addr = Dword(addr) next_symbol = Dword(addr + 4) string_addr = start_names + Dword( addr + 8 ) symbol_name = GetString(string_addr) try: if GetSegmentAttr(symbol_addr, SEGATTR_TYPE) == 2: MakeFunction(symbol_addr) except: pass MakeName(symbol_addr, symbol_name) print '0x%X %s' % (symbol_addr, symbol_name) addr += (4*3)

After running this script, 96% of our functions have been named, including the function we previously identified as snprintf:

Coupling this with the available source code for the uC/OS-II RTOS, we could really go to town on figuring out how exactly this thing works if we were so inclined. But scrolling through the list of functions, one of the function names caught my attention:

App_CheckRemoteBackdoorKey? 🙂

It turns out that this function is called by the MainRemoteKeyTask function, which passes App_CheckRemoteBackdoorKey the infrared key code received from the IR remote control. If one of several special IR codes is detected by App_CheckRemoteBackdoorKey, MainRemoteKeyTask will preform several actions such as providing a service menu, running a factory test, and even performing and over the air firmware upgrade:

These are actually pretty neat little boxes, and there is a surprising amount of potential for customization and modification. I can envision a wireless microcontroller that provides an enhanced user interface via the serial port and can automate commands, such as switching the channel when your favorite show comes on. Unfortunately, DTV reception is terrible here which really takes all the fun out of any cool mods like this. Oh well, I’m off to watch some Netflix.