Skip to content

Instantly share code, notes, and snippets.

@petergeraghty
Last active February 18, 2023 14:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petergeraghty/666c52d8215c7f56429d to your computer and use it in GitHub Desktop.
Save petergeraghty/666c52d8215c7f56429d to your computer and use it in GitHub Desktop.
Investigating RFX9400

Investigating the RFX9400

Note that some of the conclusions here are incorrect and have been updated in part 2

The firmware file

The firmware can be downloaded from Remote Central
Unzipping the download gives a single file, extfw-1.4.7-philips.bin
Despite the name, this is just another zip.

$ file extfw-1.4.7-philips.bin 
extfw-1.4.7-philips.bin: Zip archive data, at least v2.0 to extract

Unzipping this gives

parts.config            
zImage-1.4.7            
zImage-fu-1.4.7         
rootfs-1.4.7.jffs2      
sndr-1.4.7.bin  

So the RFX9400 runs Linux and rootfs-1.4.7.jffs2 is the filesystem image that we can mount on another Linux box (details to follow). Doing so and taking a look through the contents yields some items of interest

/opt/run.sh looks like this

#!/bin/sh

if [ -e "/opt/backdoor_open" ]
then
    echo -n "Opening backdoor..."
    ip link set eth0 up
    ip addr add 192.168.42.22/24 dev eth0
    telnetd
    echo "done."
fi

So, if we can somehow create a file at /opt/backdoor_open then we can telnet to the RFX9400 and have a poke around. We could modify the jffs2 image, repack everything and flash the firmware but it turns out there is an easier and less risky way

/usr/share/httpd-philips/html/cgi-bin/open_sesame

#!/bin/sh

touch /opt/backdoor_open

echo "Content-Type: text/plain"
echo ""
echo "Backdoor is open -- remember to close it!"

So, all you need to do is go to http://<ip of rfx>/cgi-bin/open-sesame and the backdoor is opened for anyone to telnet in! Fortunately the web server is only running on the RFX when you have the setting switch on the back set to configuration mode, but this is still mildly terrifying. Still, useful for us...

BTW, you can close the backdoor again either by telnetting in and deleting the file, or by going to http://<ip of rfx>/cgi-bin/close-sesame

The live system

Having opened the backdoor you can simply telnet in as the root user, there is no password

$ telnet <rfx ip>
Trying <rfx ip>...
Connected to <rfx ip>.
Escape character is '^]'.

(none) login: root
~ # 

What can we find out? (My RFX9400 doesn't actually have the latest firmware on it, so some of these results my be different for you)

It's an ARMv5 system running a 2.4 series kernel

~ # uname -a
Linux (none) 2.4.20_mvl31-mx21ads #1 Mon May 14 10:06:57 CEST 2007 armv5EJl unknown

It's based on BusyBox

~ # ls /bin/busybox 
/bin/busybox

So that means we can resort to using nc (netcat) to transfer files on or off. That may be useful if we had a suitable crosscompiler environment

In addition to the basic tools provided by the busybox binary, we also have tcpdump which is an pleasant surprise - no need to set up a socat proxy to view traffic between the RFX and the TSU

/ # ls /usr/sbin/tcpdump 
/usr/sbin/tcpdump

Running processes include

/usr/local/sbin/ssdpd

which seems like it may be for the discovery process, and several instances of

/usr/local/sbin/nhcpd

which is for communication with the TSU remote. Trying to see if there is a help option for this

/ # /usr/local/sbin/nhcpd -h
/usr/local/sbin/nhcpd: invalid option -- h
Usage: nhcpd [-d] [-l address]

reveals a -d flag which turns out to be a handy debug flag.

One other useful binary is /usr/bin/dsw

/ # dsw         
DSW RFX9400/9600
DSW Version 0.1
  (use 'list' to see available commands)
list
ACK

  Available commands :

    cmd    alias              synopsis
    ---    -----              --------
    BOG1   bog1               Bogus command
    blnk   blink_led          blnk <busy|eth|wifi|serial1|serial2|serial3|serial4> <red|green|yellow|on|off>
    boot   reboot             Soft reset
    emcf   cfg_emc            Config emc
    emof   stop_emc           Stop emc
    emon   start_emc          Start emc
    exit   quit               Exit dsw
    getn   get_serial         Get serial number [flash]
    girv   get_ir_versions    Get IR versions
    gver   get_version        Get versions [component]
    help   ?                  Show help
    irpi   irpick             Select the IR port
    list   list               List commands
    lpba   rs232_loop         lpba
    nfbd   nand_check_bad     Check the nandflash for bad blocks
    rinp   read_input         rinp [<port>]
    rspi   rs232_pick         rspi <port>
    rssp   rs232_speed        rssp <speed>
    rsst   rs232_stop         rsst
    rstx   rs232_send         rstx <data>
    setn   set_serial         Set serial numbers
    slee   delay              Wait a bit
    snet   stop_network       Stop transmitting over network <device>
    snfl   stop_nandflash     Stop the nandflash test
    sram   stop_ram           Stop the SDRAM test
    srel   set_relay          srel <port> <on|off>
    stir   irc_stop_ir        Stop direct IRC IR transmission
    sttr   stop_ecf           Stop IR transmission
    tnet   transmit_network   Transmit data over network <device> <packet size>
    tnfl   test_nandflash     Test nandflash
    tram   test_ram           Test SDRAM [Background]
    tsws   test_dials         tsws <dial> <timeout> <final position>
    txec   send_ecf           Transmit ECF <Duration> <DeviceID> <ECF>
    txir   irc_send_ecf       Transmit raw ECF direct via IRC <Duration> <ECF>
    wfcf   config_wifi        Load wifi config <device> <path/to/configfile>

LIST 00
help txir
ACK

  txir (irc_send_ecf) [I X] : Transmit raw ECF direct via IRC <Duration> <ECF>
  
Transmit the specified IR code (bypassing RIS).
Duration is in milliseconds.
ECF code must be specified as a sequence of hexadecimal digits.

HELP 00
help txec
ACK

  txec (send_ecf) [I I X] : Transmit ECF <Duration> <DeviceID> <ECF>
  
Transmit the specified ECF IR code (using RIS).
Duration is in milliseconds, 0 means send continuously until STTR.
DeviceID is the unique number of the receiving device.
ECF code must be specified as a sequence of hexadecimal digits.

HELP 00
help irpick
ACK

  irpi (irpick) [I] : Select the IR port
  Pick the IR port from which to send codes. The port is a number from 1 to 5

HELP 00
quit
ACK

So dsw is a test harness that can control the front LEDs, send serial commands (on the RFX9600), send IR commands and perform other diagnostic functions. Currently I'm only interested in the IR functions which are

txir

Transmits an IR code in ECF format (first time I've seen this mentioned anywhere). You can specify a duration in milliseconds or zero to send continuously until you request it is stopped.

txec

Similar to txir, but takes a device id which I do not know the meaning of. References using RIS and looking in libris.so there are references to encryption so I believe this may be used for sending protected codes from the Philips database

stir

Stops IR transmission started with txir

irpick

Selects the IR port for ftransmission

An example of using dsw to send an IR code is (the source of this ECF code will be discussed next)

/ # dsw
DSW RFX9400/9600
DSW Version 0.1
  (use 'list' to see available commands)
irpick 1
ACK
IRPI 00
txir 0 0060010000080a04da61d1c040747a60747040746060747040746060e8e040748a60747040746060e8e040748a60747040746060747040746060e8e040748a607470407460607470407460607470407460607470505000400016c3000000 
ACK
TXIR 00
stir
ACK
STIR 00
quit
ACK

Having this test harness available is incredibly useful as it's an easy way to test ECF codes we generate ourself or manipulated existing codes to see when they break

Initial communications analysis

Find the running nhcpd processes and kill them, then re-launch in debug mode

~ # /usr/local/sbin/nhcpd -d
Starting nhcpd
Got RFX9400-111 hardware
Got IRCVersion_V7.1
Got IRCLibVersion_V0.6
Binding to 0.0.0.0
Creating receiver thread
Creating sender thread
Creating executor thread

Also, we want to see the raw traffic going to and from nhcpd (which runs on port xxx) so run tcpdump in another terminal

~ # tcpdump -s 400 -x "udp port 65442"
tcpdump: listening on eth0

A quick press on a button on the TSU remote gives the following output from nhcpd

32 bytes received from 192.168.0.27:1024
LOCK <30000> <Untitled1>
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
124 bytes received from 192.168.0.27:1024
Sending 16 bytes to 192.168.0.27:1024 [ACK]
IR_SEND <0> <1> <0> <0x00> <0x00>
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
IR_SEND evaluated to TRUE
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
28 bytes received from 192.168.0.27:1024
UNLOCK
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]

and this from tcpdump

01:29:00.539787 192.168.0.27.1024 > 192.168.0.28.65442:  udp 32 (DF) [tos 0xfc] 
                         45fc 003c 0000 4000 4011 b82d c0a8 001b
                         c0a8 001c 0400 ffa2 0028 0088 0000 0000
                         0000 0000 0000 0000 3000 0012 0000 7530
                         556e 7469 746c 6564 3100 0000
01:29:00.558027 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7eb9 0000 0020
                         0000 0000 0051 7d8a 0051 7d8d
01:29:00.558763 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 794e 0000 0040
                         0000 0000 0000 0005 0100 0000
01:29:00.572582 192.168.0.27.1024 > 192.168.0.28.65442:  udp 124 (DF) [tos 0xfc] 
                         45fc 0098 0000 4000 4011 b7d1 c0a8 001b
                         c0a8 001c 0400 ffa2 0084 7786 0000 0100
                         0000 0000 0000 0000 4000 0070 0100 0000
                         0000 0000 0000 0000 ffff 0060 0100 0008
                         0a04 da61 d1c0 4074 7a60 7470 4074 6060
                         7470 4074 6060 e8e0 4074 8a60 7470 4074
                         6060 e8e0 4074 8a60 7470 4074 6060 7470
                         4074 6060 e8e0 4074 8a60 7470 4074 6060
                         7470 4074 6060 7470 4074 6060 7470 5050
                         0040 0016 c300 0000
01:29:00.577800 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7d90 0000 0120
                         0000 0000 0051 7d9f 0051 7da1
01:29:00.607561 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7d87 0000 0020
                         0100 0000 0051 7d8a 0051 7dbf
01:29:00.608387 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 784e 0000 0040
                         0100 0000 0000 0005 0100 0000
01:29:00.627581 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7c5e 0000 0120
                         0100 0000 0051 7d9f 0051 7dd3
01:29:00.657566 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7c55 0000 0020
                         0200 0000 0051 7d8a 0051 7df1
01:29:00.658386 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 774e 0000 0040
                         0200 0000 0000 0005 0100 0000
01:29:00.677565 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7b2c 0000 0120
                         0200 0000 0051 7d9f 0051 7e05
01:29:00.715662 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 784e 0000 0140
                         0000 0000 0000 0005 0100 0000
01:29:00.722158 192.168.0.27.1024 > 192.168.0.28.65442:  udp 28 (DF) [tos 0xfc] 
                         45fc 0038 0000 4000 4011 b831 c0a8 001b
                         c0a8 001c 0400 ffa2 0024 72c4 0000 0200
                         0000 0000 0000 0000 3100 000e 556e 7469
                         746c 6564 3100 0000
01:29:00.726347 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7b68 0000 0220
                         0000 0000 0051 7e33 0051 7e35
01:29:00.726982 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 774e 0000 0240
                         0000 0000 0000 0005 0100 0000
01:29:00.767503 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 774e 0000 0140
                         0100 0000 0000 0005 0100 0000
01:29:00.775363 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7a36 0000 0220
                         0100 0000 0051 7e33 0051 7e67
01:29:00.776410 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 764e 0000 0240
                         0100 0000 0000 0005 0100 0000
01:29:00.817579 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 764e 0000 0140
                         0200 0000 0000 0005 0100 0000
01:29:00.825379 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 7904 0000 0220
                         0200 0000 0051 7e33 0051 7e99
01:29:00.826377 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 754e 0000 0240
                         0200 0000 0000 0005 0100 0000

A longer press gives

32 bytes received from 192.168.0.27:1024
LOCK <30000> <Untitled1>
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
124 bytes received from 192.168.0.27:1024
Sending 16 bytes to 192.168.0.27:1024 [ACK]
IR_SEND <1500> <1> <0> <0x00> <0x00>
IR started <len=96 duration=0 eqptid=0>
IR_SEND evaluated to TRUE
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
24 bytes received from 192.168.0.27:1024
Sending 16 bytes to 192.168.0.27:1024 [ACK]
IR_RESUME <0xe00> <1500>
IR_RESUME evaluated to TRUE
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
20 bytes received from 192.168.0.27:1024
Sending 16 bytes to 192.168.0.27:1024 [ACK]
IR_STOP
IR_STOP evaluated to TRUE
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
28 bytes received from 192.168.0.27:1024
UNLOCK
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]
Sending 16 bytes to 192.168.0.27:1024 [ACK]
Sending 16 bytes to 192.168.0.27:1024 [REPLY]

and

04:21:28.946567 192.168.0.27.1024 > 192.168.0.28.65442:  udp 32 (DF) [tos 0xfc] 
                         45fc 003c 0000 4000 4011 b82d c0a8 001b
                         c0a8 001c 0400 ffa2 0028 f387 0000 0d00
                         0000 0000 0000 0000 3000 0012 0000 7530
                         556e 7469 746c 6564 3100 0000
04:21:28.950903 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 a1aa 0000 0d20
                         0000 0000 00ef 64f4 00ef 64f6
04:21:28.951514 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 6c4e 0000 0d40
                         0000 0000 0000 0005 0100 0000
04:21:28.962501 192.168.0.27.1024 > 192.168.0.28.65442:  udp 124 (DF) [tos 0xfc] 
                         45fc 0098 0000 4000 4011 b7d1 c0a8 001b
                         c0a8 001c 0400 ffa2 0084 64aa 0000 0e00
                         0000 0000 0000 0000 4000 0070 0100 0000
                         0000 05dc 0000 0000 ffff 0060 0100 0008
                         0a04 da61 d1c0 4074 7a60 7470 4074 6060
                         7470 4074 6060 e8e0 4074 8a60 7470 4074
                         6060 e8e0 4074 8a60 7470 4074 6060 7470
                         4074 6060 e8e0 4074 8a60 7470 4074 6060
                         7470 4074 6060 7470 4074 6060 7470 5050
                         0040 0016 c300 0000
04:21:28.967385 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 a089 0000 0e20
                         0000 0000 00ef 6505 00ef 6506
04:21:28.972504 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 6b4e 0000 0e40
                         0000 0000 0000 0005 0100 0000
04:21:29.007556 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 a071 0000 0d20
                         0100 0000 00ef 64f4 00ef 652f
04:21:29.008185 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 6b4e 0000 0d40
                         0100 0000 0000 0005 0100 0000
04:21:29.016378 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9f57 0000 0e20
                         0100 0000 00ef 6505 00ef 6538
04:21:29.022400 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 6a4e 0000 0e40
                         0100 0000 0000 0005 0100 0000
04:21:29.057551 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9f3f 0000 0d20
                         0200 0000 00ef 64f4 00ef 6561
04:21:29.058185 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 6a4e 0000 0d40
                         0200 0000 0000 0005 0100 0000
04:21:29.066356 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9e25 0000 0e20
                         0200 0000 00ef 6505 00ef 656a
04:21:29.072383 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 694e 0000 0e40
                         0200 0000 0000 0005 0100 0000
04:21:29.745470 192.168.0.27.1024 > 192.168.0.28.65442:  udp 24 (DF) [tos 0xfc] 
                         45fc 0034 0000 4000 4011 b835 c0a8 001b
                         c0a8 001c 0400 ffa2 0020 169b 0000 0f00
                         0000 0000 0000 0000 4100 000c 0000 0e00
                         0000 05dc
04:21:29.749272 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 996d 0000 0f20
                         0000 0000 00ef 6813 00ef 6814
04:21:29.753500 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 6a4e 0000 0f40
                         0000 0000 0000 0005 0100 0000
04:21:29.798413 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 983b 0000 0f20
                         0100 0000 00ef 6813 00ef 6846
04:21:29.803389 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 694e 0000 0f40
                         0100 0000 0000 0005 0100 0000
04:21:29.848419 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9709 0000 0f20
                         0200 0000 00ef 6813 00ef 6878
04:21:29.853360 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 684e 0000 0f40
                         0200 0000 0000 0005 0100 0000
04:21:30.047244 192.168.0.27.1024 > 192.168.0.28.65442:  udp 20 (DF) [tos 0xfc] 
                         45fc 0030 0000 4000 4011 b839 c0a8 001b
                         c0a8 001c 0400 ffa2 001c 1a83 0000 1000
                         0000 0000 0000 0000 4200 0008 0000 0e00
04:21:30.050909 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9612 0000 1020
                         0000 0000 00ef 6940 00ef 6942
04:21:30.087245 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 694e 0000 1040
                         0000 0000 0000 0005 0100 0000
04:21:30.106174 192.168.0.27.1024 > 192.168.0.28.65442:  udp 28 (DF) [tos 0xfc] 
                         45fc 0038 0000 4000 4011 b831 c0a8 001b
                         c0a8 001c 0400 ffa2 0024 63c4 0000 1100
                         0000 0000 0000 0000 3100 000e 556e 7469
                         746c 6564 3100 0000
04:21:30.110449 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 94d6 0000 1020
                         0100 0000 00ef 6940 00ef 697e
04:21:30.111044 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 949b 0000 1120
                         0000 0000 00ef 697b 00ef 697e
04:21:30.111649 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 684e 0000 1140
                         0000 0000 0000 0005 0100 0000
04:21:30.137569 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 684e 0000 1040
                         0100 0000 0000 0005 0100 0000
04:21:30.177549 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9393 0000 1020
                         0200 0000 00ef 6940 00ef 69c1
04:21:30.178170 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9358 0000 1120
                         0100 0000 00ef 697b 00ef 69c1
04:21:30.178776 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 674e 0000 1140
                         0100 0000 0000 0005 0100 0000
04:21:30.187355 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 674e 0000 1040
                         0200 0000 0000 0005 0100 0000
04:21:30.227556 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 9226 0000 1120
                         0200 0000 00ef 697b 00ef 69f3
04:21:30.228373 192.168.0.28.65442 > 192.168.0.27.1024:  udp 16 (DF)
                         4500 002c 0000 4000 4011 b939 c0a8 001c
                         c0a8 001b ffa2 0400 0018 664e 0000 1140
                         0200 0000 0000 0005 0100 0000

For the moment we concentrate on the longest packet in each of these captures as that should contain the IR data. After stripping off the UDP headers for the short press this is

0000 0100 0000 0000 0000 0000 4000 0070 0100 0000
0000 0000 0000 0000 ffff 0060 0100 0008 0a04 da61
d1c0 4074 7a60 7470 4074 6060 7470 4074 6060 e8e0 
4074 8a60 7470 4074 6060 e8e0 4074 8a60 7470 4074 
6060 7470 4074 6060 e8e0 4074 8a60 7470 4074 6060
7470 4074 6060 7470 4074 6060 7470 5050 0040 0016
c300 0000

and for the long press

0000 0100 0000 0000 0000 0000 4000 0070 0100 0000
0000 05dc 0000 0000 ffff 0060 0100 0008 0a04 da61 
d1c0 4074 7a60 7470 4074 6060 7470 4074 6060 e8e0 
4074 8a60 7470 4074 6060 e8e0 4074 8a60 7470 4074 
6060 7470 4074 6060 e8e0 4074 8a60 7470 4074 6060 
7470 4074 6060 7470 4074 6060 7470 5050 0040 0016 
c300 0000

These are identical except for the value in the 12th word, so it seems that just like for the dsw test harness a value of zero is sent to indicate the code should be sent once and a timeout should be specified if the code is to be repeated for some period of time.

Sending some different codes and further analysis gives the message structure

Unknown Packet id Unknown Type Length Port Unknown Timeout Unknown Payload
0000 01 00 0000 0000 0000 0000 4000 0070 01 00 0000 0000 05dc 0000 0000 The rest

Packet id

Incremented on each packet sent by TSU, returned in status responses from RFX. RFX will ignore as duplicate packets with the same id if they are received within some period of time

Type

4000 represents IR data. Other values are used for control messages and presumably for serial data

Port

Zero indexed IR output port number to use

Timeout

Number of milliseconds to repeat the code for, in this case 0x05dc is 1500ms

Payload

This is the IR data in ECF format

Decoding the ECF format

What I tried to send in Pronto HEX format fromt the TSU was

Type Period Initial burst length Repeat burst length Burst data
0000 0067 0000 000D 0060 0018 0018 0018 0018 0018 0030 0018 0018 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0018 0018 0018 0018 0018 041F

What was sent to the RFX was

ffff 0060 0100 0008 0a04 da61 d1c0 4074 7a60 7470 
4074 6060 7470 4074 6060 e8e0 4074 8a60 7470 4074 
6060 e8e0 4074 8a60 7470 4074 6060 7470 4074 6060 
e8e0 4074 8a60 7470 4074 6060 7470 4074 6060 7470
4074 6060 7470 5050 0040 0016 c300 0000

Which looked so different at first I thought it might be encrypted. However, by varying the input slightly and noting the different output a pattern starts to emerge

The first word, ffff, seems to indicated a learned code in the same way as 0000 does for Pronto HEX. Database codes are prefixed with eecf
There are 96 bytes in the payload and 0x60 is 96 The burst pairs, after the preamble are all either equal or one is double the other. At first there seems to be nothing like that in the ECF data, but with a little rearranging

ffff 0060 0100 0008 0a04 da6

1d1c04 0747a6
074704 074606
074704 074606
0e8e04 0748a6
074704 074606
0e8e04 0748a6
074704 074606
074704 074606
0e8e04 0748a6
074704 074606
074704 074606
074704 074606
074705 050004

0 0016 c300 0000

So the burst pairs are there, it's just that they're now much larger values stored as 3 bytes each, and they are not exactly equal/double

Looking at the first value in the ECF burst data, 0x1d1c04 = 1907716 whereas in Pronto HEX it was 0x60=96. Dividing these gives approximately 1907716/96 = 19872. This is suspiciously close to the value in the word preceeding the burst data, 0x4da6 = 19878. So let us assume that 0x4da6 represents the period and that instead of the burst pairs being length in terms of number of periods as in Pronto HEX it is instead a length in the same units as the period itself

Not quite

That close match was enticing, but the numbers never quite looked right. Each of the first half pair was a slightly different multiple of the corresponding value in the Pronto HEX. 1d1c04 is not exactly 4 times 074704 as it should be and 0e8e04 is not exactly twice 074704. It's possble there could be some inaccuracy in the conversion, but there is no pattern to the conversion. Also the pairs that should have equal values are different (074704 and 074606), it could be that the pair sums are massaged to all be multiples of the same value to prevent clock drift, but there is no pattern here either.

After persisting for some time and getting nowhere I went back looking for a different pattern. After testing a number of working codes and tweaking them slightly I found that the fact the pairs end in 4, 5 or 6 was critical. So a bit of rearranging gives

ffff 0060 0100 0008 0a
04da

6 1d1c0 4 0747a
6 07470 4 07460
6 07470 4 07460 
6 0e8e0 4 0748a
6 07470 4 07460
6 0e8e0 4 0748a
6 07470 4 07460
6 07470 4 07460
6 0e8e0 4 0748a
6 07470 4 07460
6 07470 4 07460
6 07470 4 07460
6 07470 5 05000

40 0016 c300 0000

So now we have a period of 0x04da = 1242
Each burst pair starts with a 6 that we will interpret as "on" or "start pulse" and then has a 4 before the second half for "off". This seems unnecessary for PWM, but maybe this is so they can also support RC5 codes. The 5 before the final half pair seems to be "end" and the value after that is not burst data at all.

Checking some values...
The period 1242 = 12.058 * 103 which is the period from the HEX format. It also happens that 50000000/1242 =~ 40257 which is the frequency. That value of 50 million also pops up later
The use of 2 and a half bytes for each burst value is odd, but that gives a maximum of just over a million which ties in well with Philips' claim of being able to support 1MHz codes. It also saves a little space which may have been deemed important
The values in the first burst look promising. 1d1c0 is exactly 4 times 07470 which is exactly half 0e8e0. They're also all multiples of the perion 1242
The second values look a little suspicious, but summing the burst pairs gives (decimal) values of 149050, 59600 and 89450 which have a common deniminator of 50. Given that the period seems to be set in terms of the inverse of 50 million and the device supports 1MHz output then synchronising each pair to units of 50 seems to be a potential requirement

The second half of the final burst pair looks to have been declared redundant and replaced. Through testing with dsw it appears this is the time to send the code for, although the rules for calculation of this value are not yet apparent

Another code

The other values are a mystery and there is nothing that appears to correspond to the initial/repeat burst length values in Pronto HEX. The lack of an initial burst in the HEX probably isn't helping, so trying again with two pairs of initial burst added to the above HEX yields

ffff 006c 0100 0008 0a

04da

6 07470 4 07460
6 07470 4 07460

6 1d1c0 4 0747a 
6 07470 4 07460
6 07470 4 07460
6 0e8e0 4 0748a
6 07470 4 07460
6 0e8e0 4 0748a
6 07470 4 07460
6 07470 4 07460
6 0e8e0 4 0748a
6 07470 4 07460
6 07470 4 07460
6 07470 4 07460
6 07470 5 05000

40 0016 c300 000f

The only change other than the additional pairs and the length is last byte 0x0f = 15, which is the number of bytes from the a0 before the period to the start of the repeat burst, so there are no actual lengths for the burst data, just this offset

The ECF format

Putting everything together gives

Field Width Example Description
Type Word ffff ffff = Learned code, eecf = Database code
Length Word 006c Count of bytes in ECF
Unknown 5 bytes 0100 0008 0a Presumably more than one field, but not known how many
Period Word 04da Units of 1/50000000 s, Period = 50000000/frequency
Burst pairs 6 bytes per pair See below Final half is total duration, not a burst
Unknown 5 bytes 40 0016 c300 May also actually be more than one field
Burst offset Word 000f 0 = no initial burst, otherwise nunber of bytes in initial burst + 3

Burst pair (except final)

Field Width Example Description
Control 4 bits 6 6 = switch on (oscillate)
First burst 20 bits 07470 Units of 1/50000000 s
Control 4 bits 4 4 = switch off
Second burst 20 bits 07460 Units of 1/50000000 s. Adjusted such that (first + second) is a multiple of 50

Final burst pair

Field Width Example Description
Control 4 bits 6 6 = switch on (oscillate)
First burst 20 bits 07470 Units of 1/50000000 s
Control 4 bits 5 5 = end
Code duration 20 bits 05000 Units of 1 microsecond. Sum of all burst lengths divided by 50 where instead of the final burst length the value 2000 is used (or perhaps it is limited to a maximum of 2000)

Converting Pronto HEX to ECF

We're now at a point where we can convert existing HEX codes to ECF

ECF field Value
Type ffff
Length Number of burst pairs + 24
Unknown Hardcode to 0100 0008 0a0
Period 12.058 * Pronto HEX period (rounded to nearest integer)
Burst pairs Each burst pair value in Pronto HEX, multipled by ECF period and stored as 20 bits per value. Control bits set as definition. Final burst is duration (calculated as per definition) and has a control value of 5
Unknown Hard code to 00 0000 0000
Burst offset 0 or number of initial burst bytes + 3

Example conversion

This Pronto HEX

0000 0067 0000 000D 0060 0018 0018 0018 0018 0018 0030 0018 0018 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0018 0018 0018 0018 0018 041F

converts to

FFFF 0060 0100 0008 0A04 DA61 D1C0 4074 7A60 7470 
4074 6060 7470 4074 6060 E8E0 4074 8A60 7470 4074 
6060 E8E0 4074 8A60 7470 4074 6060 7470 4074 6060 
E8E0 4074 8A60 7470 4074 6060 7470 4074 6060 7470 
4074 6060 7470 4050 0040 0000 0000 0000

while a real TSU/RFX gives

ffff 0060 0100 0008 0a04 da61 d1c0 4074 7a60 7470 
4074 6060 7470 4074 6060 e8e0 4074 8a60 7470 4074
6060 e8e0 4074 8a60 7470 4074 6060 7470 4074 6060 
e8e0 4074 8a60 7470 4074 6060 7470 4074 6060 7470 
4074 6060 7470 5050 0040 0016 c300 0000

This is identical, except for the final 3 words whose derivation we have not found. Some other codes give results that don't quite match due to differences in how the rounding to nearest 50 is handled, but this is getting very close

The RFX protocol

Heading back to the message dumps/logs we see there are 2 potential scenarios for communication between the TSU and RFX when sending an IR code

Short press

When a button is pressed briefly enough to be interpretted as "send once" the following messages are sent from the TSU

LOCK
IR_SEND
UNLOCK

The LOCK message has a duration which is always 30000 milliseconds and a string to identify the owner which is the Project Name in PEP
The IR_SEND has a duration of zero to indicate "send once"
The UNLOCK is sent once the RFX has replied that the IR_SEND is complete

Press and hold

The messages become

LOCK
IR_SEND
IR_RESUME
IR_STOP
UNLOCK

The LOCK message again has a duration which is always 30000 milliseconds and a string to identify the owner which is the Project Name in PEP
The IR_SEND has a duration of 1500 milliseconds
If the button is held for more than around a second then roughly every second an IR_RESUME will be sent with a duration of 1500 milliseconds to indicate the IR code sending should continue
An IR_STOP is sent to stop sending
The UNLOCK is sent once the RFX has replied that the IR_STOP is complete

If a button is held past the 30 second lock timeout then the lock is expired on the RFX and the busy light goes out. Commands can still be sent after this but it is unknown if the RFX processes them

Return messages

Going back from the RFX to the TSU there are ACK and REPLY messages

Message structure

By analysing the tcpdump output the structure is found

LOCK

Field Width Example Description
Unknown Word 0000 Appears to always be zero
Packet id Byte 0d Incremented on each message sent from TSU to RFX, included in each response from RFX to TSU
Unknown 9 bytes 00 0000 0000 0000 0000 Probably more than one field, but always seem to be zeros
Type Word 3000 3000 = LOCK
Length Word 0012 Number of bytes in lock name + 9
Unknown Word 0000 Appears to always be zero
Timeout Word 7530 Lock timeout in milliseconds, appears to always be 30000
Lock name Variable 556e 7469 746c 6564 31 ASCII encoded lock name, in this case 'Untitled1'. Matches project name in PEP

IR_SEND

As covered above

Field Width Example Description
Unknown Word 0000 Appears to always be zero
Packet id Byte 0e Incremented on each message sent from TSU to RFX, included in each response from RFX to TSU
Unknown 9 bytes 00 0000 0000 0000 0000 Probably more than one field, but always seem to be zeros
Type Word 4000 4000 = IR_SEND
Length Word 0070 Number of bytes in payload + 16
Port Byte 00 Zero indexed IR port number to send on
Unknown 5 bytes 00 0000 0000 Probably more than one field, but always seem to be zeros
Timeout Word 05dc Number of milliseconds to send for, always 1500
Unknown 2 words 0000 0000 Probably more than one field, but always seem to be zeros
Payload Variable IR code in ECF format

IR_RESUME

Field Width Example Description
Unknown Word 0000 Appears to always be zero
Packet id Byte 0f Incremented on each message sent from TSU to RFX, included in each response from RFX to TSU
Unknown 9 bytes 00 0000 0000 0000 0000 Probably more than one field, but always seem to be zeros
Type Word 4100 4100 = IR_RESUME
Length Word 000c Always 12?
Unknown Word 0000 Appears to always be zero
Send id Byte 0e Packet id of the IR_SEND being resumed
Unknown 3 bytes 00 0000 Appears to always be zero
Timeout Word 05dc Number of milliseconds to continue for, always 1500

IR_STOP

Field Width Example Description
Unknown Word 0000 Appears to always be zero
Packet id Byte 10 Incremented on each message sent from TSU to RFX, included in each response from RFX to TSU
Unknown 9 bytes 00 0000 0000 0000 0000 Probably more than one field, but always seem to be zeros
Type Word 4200 4200 = IR_STOP
Length Word 0008 Always 8?
Unknown Word 0000 Appears to always be zero
Send id Byte 0e Packet id of the IR_SEND being stopped
Unknown Byte 00 Appears to always be zero

UNLOCK

Field Width Example Description
Unknown Word 0000 Appears to always be zero
Packet id Byte 11 Incremented on each message sent from TSU to RFX, included in each response from RFX to TSU
Unknown 9 bytes 00 0000 0000 0000 0000 Probably more than one field, but always seem to be zeros
Type Word 4200 3100 = UNLOCK
Length Word 000e Number of bytes in lock name + 5
Lock name Variable 556e 7469 746c 6564 31 ASCII encoded lock name, in this case 'Untitled1'. Matches project name in PEP
Send id Byte 0e Packet id of the IR_SEND being stopped
Unknown 3 bytes 00 0000 Appears to always be zero
@Marius-Alexandru
Copy link

Hello! Did you tried to integrate RFX9600 into Home Assistant?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment