Reverse engineering & gaining root on a...MOTU


Kaini Industries
Nov 20, 2016
I've dabbled in music production on and off for 15 years, so I've always tried to keep decent audio interfaces for all my workstations. Last year I moved from Focusrite and picked up a Motu UltraLite AVB, mostly for the networking features. The entire control panel and config system is a web page that can be accessed anywhere over the network, and using this it can function as a standalone mixer and DSP box in live situations. The DAC & ADC performance is also exceptional on MOTU's Sabre ESS equipped models, with this one in particular measuring an output noise floor a tiny bit below -140db

Anyway, I figured with an LCD, an ethernet port, DSP and a webserver onboard, it has to be running some type of embedded OS. So, I tracked down their "offline" firmware update files (they prefer you to do updates online through the web panel). Grepping through it with binwalk reveals something not very surprising for the veterans here:

Scan Time:     2021-01-15 07:07:44
Target File:   /root/re/MOTU AVB 1.3.5+626 for UltraLite AVB and Stage-B16.update
MD5 Checksum:  c6e4ecd77376704ad985dbdc17ffcba0
Signatures:    386

131468        0x2018C         Xilinx Virtex/Spartan FPGA bitstream dummy + sync word
--------trimmed for brevity---------------
4260224       0x410180        uImage header, OS: Linux, CPU: ARM, image type: OS Kernel Image, image name: "Linux-2.6.32-rc8"
4260288       0x4101C0        Linux kernel ARM boot executable zImage (little-endian)
There's our linux kernel. Digging further, there's a tmpfs archive, making up the temp filesystem that gets embiggened on boot. I began digging through that out of curiosity - remember, the main UI of this thing is a web page, so I figured there's html content somewhere:

root@testing:~/re/motu-fw/_rootfs.extracted# ls -lha |grep -i  html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux1.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux2.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux3.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux4.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux5.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux6.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux7.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 aux9.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 group1.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 group3.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 group5.html
-rw-r--r--  1 root root 5.6K Jan 15 07:08 motu_avb.html
-rw-r--r--  1 root root 5.3K Jan 15 07:08 reverb1.html
-rw-r--r--  1 root root 1.1K Jan 15 07:08 set_update_channel.html
-rw-r--r--  1 root root  770 Jan 15 07:08 start_telnet.html
Note the last file - to quote the great danny elfman, what's this? start_telnet.html certainly isn't linked anywhere in the UI, and googling around, has never been mentioned anywhere. Its contents are pretty simple:

root@testing:~/re/motu-fw/_rootfs.extracted# cat start_telnet.html
---trimmed for brevity----
      <h1>Telnet Starter - For Internal Use Only</h1>
      <label for="channel">Click Here to start telnet:</label>
      <button id="button">Start</button>
      <script type="application/javascript">
        document.getElementById('button').addEventListener('click', function(){
          var req = new XMLHttpRequest();
'POST', "/start_telnet");
There's my favorite four words, For Internal Use Only. I'm sure you can take a guess what this button does - at least, what I hoped it did - starts a telnet daemon granting shell access to the embedded linux install. So, browsing to the IP of the device and appending /start_telnet.html, do we get a working webpage? Indeed:

Clicking the "start" button doesn't change anything on the page, or provide any kind of feedback that something has happened. But, if you then telnet to the IP of the MOTU device (port 23), you'll be granted a login prompt where you can log in as root and an empty password. Note you must have the ethernet port of the device connected to your network, you can't access the debug telnet server over the localhost port you use to manage the device over USB. Anyway:

We can see it's running an Arago-derived embedded linux build, on top of an ARM926EJ-S CPU. Poking around a bit at the storage and filesystem layout:

[    1.035116] m25p80 spi1.0: found mx25l25645g, expected s25fl256s
[    1.041632] 4 cmdlinepart partitions found on MTD device s25fl256s
[    1.048046] Creating 4 MTD partitions on "s25fl256s":
[    1.053268] 0x000000000000-0x000000590000 : "Boot"
[    1.063130] 0x000000590000-0x000000b80000 : "Linux"
[    1.072011] 0x000000b80000-0x000001380000 : "RootFs"
[    1.080736] 0x000001380000-0x000002000000 : "User_Data"
[    1.090130] spi_davinci spi_davinci.1: Controller at 0xfef0e000
[    1.099096] spi_davinci spi_davinci.0: Controller at 0xfec41000

root@UltraLite-AVB:/dev# ls -1 /dev/spidev0.*

root@UltraLite-AVB:/dev# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00590000 00010000 "Boot"
mtd1: 005f0000 00010000 "Linux"
mtd2: 00800000 00010000 "RootFs"
mtd3: 00c80000 00010000 "User_Data"

root@UltraLite-AVB:/dev# df -h
Filesystem                Size      Used Available Use% Mounted on
rootfs                   15.5M     14.3M      1.2M  92% /
/dev/root                15.5M     14.3M      1.2M  92% /
tmpfs                    25.3M    132.0K     25.1M   1% /var/volatile
tmpfs                    25.3M         0     25.3M   0% /tmp
/dev/mtdblock3           12.5M    688.0K     11.8M   5% /mnt/user

root@UltraLite-AVB:/dev# mount
rootfs on / type rootfs (rw)
/dev/root on / type ext2 (rw,relatime,errors=continue)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620)
tmpfs on /var/volatile type tmpfs (rw,relatime)
tmpfs on /tmp type tmpfs (rw,relatime)
/dev/mtdblock3 on /mnt/user type jffs2 (rw,sync,relatime)
The interesting (MOTU specific) stuff is under /opt:

root@testing:~/re/motu-fw/_rootfs.extracted/ext-root/opt# ls -lh
total 1000K
-rw-r--r-- 1 root root   22 Jan 15 07:07 DeviceDescription
drwxr-xr-x 2 root root 4.0K Jan 15 07:07 img
drwxr-xr-x 5 root root 4.0K Jan 15 07:07 motu
-rw-r--r-- 1 root root  25K Jan 15 07:07 scmd
-rw-r--r-- 1 root root  44K Jan 15 07:07 ssutil
-rw-r--r-- 1 root root 916K Jan 15 07:07 tamio
Under that /opt/motu folder is all the web content, the DSP binary, and all the other MOTU-specific stuff that makes the box work. But what are these scmd and ssutil binaries? Loading scmd into Ghidra to search for strings, we find:

fpga read/write options:
  -f, --fpga         Read or write fpga
  -e, --emif         Emif (CS 2,5)
  -s, --spi          Use SPI mem map
  -r, --read ADR     (i.e. 0x3200)
  -w, --write ADR    (i.e. 0x3200)
  -v, --val VAL      For writing (i.e. 0x1234)

  Read fpga SPI Example: scmd -f -s -r 0x05
  Write fpga SPI Example: scmd -f -s -w 0x05 -v 0x34
  Read fpga EMIF Example: scmd -f -e 2 -r 0x8030
  Write fpga EMIF Example: scmd -f -e 2 -w 0x8030 -v 0x1234

I2C read/write options:
  -i, --i2c DEV      read or write i2c
  -r, --read ADR     (i.e. 0x03)
  -w, --write ADR    (i.e. 0x03)
  -v, --val VAL      For writing (i.e. 0x12)

  Read i2c Example: scmd -i -r 0x05
  Write i2c Example: scmd -i -w 0x05 -v 0x34

CSR read/write options:
  -q, --quad           read or write csr
  -r, --read ADR     (i.e. 0x10000004)
  -w, --write ADR    (i.e. 0x10000004)
  -v, --val VAL      For writing (i.e. 0x12345678)

  Read quad Example: scmd -q -r 0x10000004
  Write quad Example: scmd -q -w 0x10000004 -v 0x12345678

Error Log Debug Level:
  -d | --debug      Set syslog level mask [0xFF]
  Example: scmd -d 0xff
So it's a nice little utility to interface with the fpga onboard, as well as i2c r/w debug etc. What about ssutil?

    -f bitmap            Set firmware update bitmap b0 = firmware update, b1 = firmware corrupt
    -fr                  Get firmware update bitmap
    -p                   Get satellite personality string
    -d                   Get satellite LCD display type string
    -hr                  Get hostname string
    -hw string           Set hostname string
    -lt string           Draw text on the LCD
    -lt X string         Draw text on LCD[X]
    -lp string percent   Draw text with progress bar on the LCD
    -lp X string percent Draw text with progress bar on LCD[X]
    -lg filename         Draw GIF on the LCD
    -lg X filename       Draw GIF on LCD[X]
    -b                   Read button state
    -r                   Ultralite power down
    -ap                  Read Auto Power Mode flag
    -mr                  Read Device UID
    -ms                  Set Max 10 dirty flag (0 or 1)
    -mg                  Get Max 10 dirty flag
    -ir chipAdr reg      i2c read byte (Use hex)
    -iw chipAdr reg val  i2c write byte (Use hex)
Well those sound fun. I wonder if I could.....

Yep, ssutil definitely works as expected. That's all for now, I'll put info on the debug UART in a thread reply under this main post. Here is a ZIP of interesting output (dmesg, u-boot, etc) for anyone who wants to poke through:
Last edited:


Kaini Industries
Nov 20, 2016
Wanting to go a step further and get into the bootloader (which at this point I was presuming was u-boot, which turned out to be correct), I began looking for some type of serial debug UART. Looking at the inside of the device:

We can see the elevated daughterboard, the ARM CPU is on the bottom of that. So I figured if there is a serial header somewhere, it's going to be on that large 24-pin connector, or worse, on one of the unpopulated vias to the left of it. I was hoping the large header would be standard ARM JTAG, but it wasn't. I hooked up a logic analyzer to all the pins, a group of 5 or 6 at a time, and captured the output while booting the device:

A lot of very fast signals and clocking, but nothing resembling ASCII boot text yet. Finally, on the last group of pins I tested, I found something familiar:

ASCII serial data at 115200 baud, spelling out U-BOOT! That has to be our debug UART port. Assuming the pin next to this transmit pin was the UART receive pin, I connected a serial UART adapter set to 3.3v, and launched putty at 115200-8-N-1:

Now, while power cycling it, what do we get on the output?

Chip initialization passed!

Booting with TI UBL
Device OPP (456MHz, 1.3V)
Booting Catalog Boot Loader
BootMode = SPI 1 Flash
Starting SPI Copy...

U-Boot 2013.07-ge7987b4 (Jul 03 2019 - 11:23:48)

I2C:   ready
DRAM:  64 MiB
WARNING: Caches not enabled
Manual Pwr Mode Detected
SF: Detected MX25L25645G with page size 64 KiB, total 32 MiB
In:    serial
Out:   serial
Err:   serial
SS AVB SOM u-boot 2.00 a3
Tamio: get mac address.
set mac handler registered

Expander ID: 7A, IDVal: 9F
Detected AVB Ultralite ES
Using fpga index 3

Setting EEPROM Flag for Pwr Mode = 0
Reading FPGA array header at 005a0000
Load main fpga
Loading fpga from index 3
SF: Detected MX25L25645G with page size 64 KiB, total 32 MiB
SF: Detected MX25L25645G with page size 64 KiB, total 32 MiB
Loading 00071aac bytes from 00730000
setting prog_b 1
setting prog_b 0
  Done pin is low
setting prog_b 1
SUCCESS (Done pin is high)
end misc_init_r
Net:   miiphy dev Initialized!
No ethernet found.
LCD initialized.
SF: Detected MX25L25645G with page size 64 KiB, total 32 MiB
SF: 768 bytes @ 0x580000 Read: OK
Hit any key to stop autoboot:  0
SF: Detected MX25L25645G with page size 64 KiB, total 32 MiB
SF: 1863072 bytes @ 0x9a0000 Read: OK
SF: Detected MX25L25645G with page size 64 KiB, total 32 MiB
SF: 6697864 bytes @ 0xb80000 Read: OK
## Booting kernel from Legacy Image at c0007fc0 ...
   Image Name:   Linux-2.6.32-rc8
---------trimmed for brevity-----------------
As expected, it's running u-boot as the bootloader, booting our linux kernel. Thankfully my guess was correct, the pin next to our found pin is indeed the UART receive pin, so my typing was seen by the box. Quickly listing the u-boot environment variables:

copy_kernel=sf probe 1:0 && sf read 0xc0007fc0 ${kernel_offset} ${kernel_size}
copy_rootfs=sf probe 1:0 && sf read 0xc1180000 ${rootfs_offset} ${rootfs_size}
init_lcd=sslcd i && sf probe 1:0 && sf read 0xc0700000 ${splash_offset} 0x300 && sslcd b 0xc0700000
spi_flash_args=setenv bootargs mem=55M console=ttyS0,115200n8 root=/dev/ram0 rw initrd=0xc1180000,16M lpj=1138688 mac=${ethaddr} phy_id=${phy_id} mtdparts=s25fl256s:5832704(Boot),6225920(Linux),8388608(RootFs),-(User_Data) ver_ssuboot=${ver_ssuboot}
spi_flash_boot=run copy_kernel; run copy_rootfs; run spi_flash_args; bootm 0xc0007fc0
nfs_args=setenv bootargs
console=ttyS0,115200n8 noinitrd rw
nfs_boot=run nfs_args; ssmac i; run copy_kernel; bootm 0xc0007fc0
preboot=run init_lcd
bootcmd=run spi_flash_boot
fwup=ssmac i && dhcp && tftp 0xc0700000 avb_som_100/update_script.img && source 0xc0700000
If you let it continue booting, it lands on the same root shell prompt as the hidden telnet server. That's all for now, behold the worst pinout diagram you'll ever see for the 24-pin debug header, this should apply to any AVB enabled MOTU model:

Last edited: