Friday, December 2, 2011

Decoding the Korg Monotribe Firmware Upgrade

NOTE: My blog has now moved.  Please visit my new blog where I write about audio apps, diy synthesizers, and CNCs.

NOTE: This is still a work in progress.  Keep watching the blog & github as we figure out more of the file encoding.

Korg released a firmware update for the monotribe.  It's an audio file that you play to the sync input of the monotribe.  Interesting, yeah?  Let's take a look at the content of the file and see how far we can get towards disassembly.  This would be the first steps towards a custom firmware for the korg monotribe.

The tutorial assumes you're running ubuntu linux, but any OS should be fine.  Just use your preferred methods to get the utilities we're going to use.

The firmware is delivered as an stereo m4a file.  We want a binary object file.

First, convert to a .wav with the free utility "faad":


sudo apt-get install faad
faad -o monotribe-fw.wav MONOTRIBE_SYS_0201.m4a

Before loading it into python lets also make a raw version of the file and just glance at the data to get an idea of what's up.

faad -f 2 -o monotribe-fw.raw MONOTRIBE_SYS_0201.m4a
hexdump -C monotribe-fw.raw | head -n 40
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000700  f7 ff f7 ff 18 00 18 00  e5 ff e5 ff 24 00 24 00  |............$.$.|
00000710  e5 ff e5 ff 1d 00 1d 00  de ff de ff 12 00 12 00  |................|

First of all we see each sample is 2 bytes, and most likely little endian.  Remember that there were 2 channels?  The upload process would be very finicky if it depended on a different sample on the L and R channels, and if you look here, it definitely appears that the L and R samples are always the same.

What I'm expecting is that the wave content will mostly look like a squarewave, and that we're sending a serial data stream of 1's and 0's which the firmware reconstitutes, checksums, and writes to the onboard flash.

Looking at the wave in audacity, this appears correct:



Let's remove one channel so we can process the samples easier in python.

sox monotribe_fw.wav -c 1 monotribe_fw1.wav mixer -l

Time to fire up the python interpreter and play with some data!  We will use the wave library to decode the audio.


python
Python 2.7 (r27:82500, Apr 14 2011, 00:58:22)
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.


>>> import wave
>>> fw = wave.open("monotribe_fw1.wav", "r")


>>> fw.getframerate()
44100
>>> fw.sampwidth()
2

Yup!  Looks like our data.  Lets read the first few samples.


>>> fw.readframes(8)
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

After looking at the waveform in audacity it's clear that the wavefile is not a perfect squarewave with only values at -1 and +1.  We need our data normalized over time.  It looks like near the start of the file is a pure squarewave, probably to get the monotribe to sync to the playback rate.

We are going to ingest this waveform in a similar way that the monotribe does.  First, we'll categorize every sample as being either a 1, 0.  Then we'll group the sampled 1's and 0's to decide what the next bit is of the firmware.

Note that at this point we could be wrong and a 1 could be a 0 or vice versa.  I'm just using that as a guess right now.


# convert the variable data to either a 1 or a 0.
from struct import unpack
fw.rewind()
s = fw.readframes(1)
samplestr = ""
while not s == "":
   val = unpack("h", s)[0]
   if (val > 0):
      samplestr += "1"
   elif (val < -0):
      samplestr += "0"
   s = fw.readframes(1)
 
Let's dump this to a file in case we screw it up:

f = open("samplestr", "w")
f.write(samplestr)
f.close()

 We have to do a bit of fuzzy processing to turn (batch of 1 samples) into a single 1, and (batch of 0 samples) into a single 0.  But how long are the batches that represent a single bit?  I'm a BASH man for string processing, so into the shell we go.

Let's break the file into separate lines of sequential 1's and 0's:

cat samplestr | sed 's/10/1\n0/g' | sed 's/01/0\n1/g' > lines

Now let's build a histogram of sequence lengths:

cat lines | sort | uniq -c
     21 0
      8 00
      2 000
      3 0000
 170376 00000
      1 0000000
 160543 0000000000
     20 1
      8 11
      2 111
      4 1111
 170375 11111
      1 111111
      1 1111111
 160543 1111111111

Now before we go any farther lets look at the data.

*snip...*
11111
00000
1111111111
0000000000
11111
00000
1111111111
0000000000
1111111111
0000000000
1111111111
0000000000
1111111111
0000000000
*snip...*

Interesting.  Looks like there's two signals - a "short square wave" that lasts 10 samples, and a "long square wave" that lasts 20 samples.    Maybe a short wave is a 1, and a long wave is a 0? Or vice-versa?  First lets assume everything else is garbage and all we want is a sequence of numbers that denotes the order of "long wave, long wave, short wave, long wave"... etc.  We can get rid of all the 0's since they're duplicate data for indicating if the bit is set, so filter down to just the 1 lines.

cat lines | grep '^11111$\|^1111111111$' > bitlines
cat bitlines | sed 's/^11111$/0/' | sed 's/^1111111111$/1/' > bits

Now 'bits' is a file with either a 1 or a 0 on every line.  Let's try converting that to a binary file and see if it looks recognizable.

in Python:
from struct import pack
f = open("bits", "r")
finished = ""
i = 0
f.seek(0)
str=""
for b in f.readlines():
    if b[0] == "0":
       str = "0" + str
    elif b[0] == "1":
       str = "1" + str
    i+=1
    if i == 8:
       finished += pack("B", int(str, 2))
       i = 0
       str = ""


f = open("firmware", "w")
f.write(finished)
f.close()

Let's look at the data now...
*
000004e0  00 01 6a 2d 0d b5 1d fb  35 65 35 d5 5d 4d fb 9d  |..j-....5e5.]M..|
000004f0  6d cd 5d 00 1b e4 ff ff  00 bf 7f ff ff ff ff ff  |m.].............|
00000500  ff ff ff e1 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000510  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000006f0  00 00 00 00 00 00 00 00  b5 7f fd ff fd a7 bf ff  |................|
00000700  ff 89 bf ff ff 89 bf ff  ff 89 bf ff ff 89 bf ff  |................|
00000710  ff 89 bf ff ff bf ff ff  ff ff ff ff ff ff ff ff  |................|

I'm hoping that there's a nice header at the start that says KORG or similar.  This does not have it.  What if we were not properly aligned on a byte? What if what we think is a 0, is a 1?  What if we're using the wrong bit-endianness?

To speed up the process I wrote a quick python script that tries all of the values for the issues above, and then ran the output binaries through strings.  And did it work??? YES!  We now have the firmware file!


strings 13.bin | grep KORG
KORG SYSTEM FILE
KORG

Download and run this python script in the same directory as that "bits" file and it'll output the firmware

UPDATE: there were alignment issues with that script.  nitro2k1 uploaded a new script to his blog that fixes the alignment issues

happy hacking :D