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