+-----------+ |Preamble | +-----------+c o o o o o o o oI've been debating if I should do a writeup for this crash I found in Renoise.
My hesitation is for 3 reasons:
- I was unable to leverage it and do anything cool with it.
- Renoise is closed source and I had difficulty doing triage via staring at assembly and debugging alone, therefore some of my "conclusions" at this point are only "informed assumptions".
- The crashes come from a lack of an XML parser, which Renoise is not currently interested in developing, so I was unable to merge any patches.
+-----------+ |Intro | +-----------+-Co o o o o o o oYou're collaborating with some friends on a new song in Renoise. You each take turns making changes to the track, downloading, adjusting, then reuploading the xrns file to a shared drive. Today the song has made its rounds back to you. You download the file, load it into renoise and start the song.
This plays:
Renoise crashes
An error appears:
x
Error 3:
BGGP 3
BGGP 3
This was the goal I had my eyes set on. In accordance to the bggp rules, I wanted to make a Renoise song file that was 4096 bytes at the largest, that would load, play a sample, crash Renoise, then throw an error with the number 3, and in my wildest dreams, pop a shell.
It's good to dream.
However, before I could achieve this dream I knew I had to learn more about Renoise input files, fuzzing, and actually find my first crash.
+-----------+ |xrns | +-----------+--c o o o o o o oBefore fuzzing xrns files, I wanted to figure out how they worked, and how Renoise used them. The fileformat archive summed them up pretty well here.
TL;DR
An xrns file is just a zip file with a compressed xml file in it titled "Song.xml".
To verify this information I made a new renoise song, deleted all the tracks and instruments from it to get it as small as possible, then saved it.
To see if it behaved like a zip file I tried: `unzip song.xrns`
Sure enough, it output a file titled "Song.xml", the contents of which was a bunch of mark up language stuff.
I tried manually fiddling with these files a little bit. It turned out that if I changed the name of Song.xml and rezipped it into my xrns file, Renoise would no longer take it.
It seemed like there had to be a file named Song.xml. I went ahead and changed some values in the xml file too to see what would happen. Those changes were then changed in the song as well.
From this point it seemed like my best bet for getting a crash out of renoise would be to mess with the values in this file. There was only one caveat: a crash from a malformed input file will most likely cause a crash while it's being loaded, not while the song is playing, but I was eager to get started, and to see what was possible, so I started fuzzing!
+-----------+ |Fuzzing | +-----------+---Co o o o o o oFuzzing is an automated method of testing software by generating malformed inputs in the hopes of revealing vulnerabilities. Honggfuzz is a good fuzzer to start with.
This is the basic command it suggests starting out with:
$ honggfuzz -i input/ -o output/ -x -- /usr/bin/renoise ___FILE___
This standard command takes a folder of example inputs: `-i input/` generates a malformed file based on the input, Passes this generated file to the specified program (in this case renoise): `-x -- /usr/bin/renoise ___FILE___`,
and if the program crashes, will output a file containing logs and memory information at the moment of crash into the specified output folder: `-o output/`
Unfortunately, this general case would not work for me. What I needed to happen was this:
- A file named "Song.xml" is generated
- Song.xml is zipped into a xrns file
- Renoise is called
- We wait for the file to load
- Handle a crash if there is one
$ honggfuzz -n1 -t3 -i xml/ --pprocess_cmd fuzzy_xrns -x -s -- /usr/local/bin/renoise song.xrns
This command is pretty dense. I'll unpack it below: - `-n1`: sets the number of concurrent threads. Since I'm using stdin with static file names I thought it would be a bad idea to run it multi threaded / was running into problems running multi threaded.
- `-t3`: sets the timeout in seconds. Renoise took at most 3 seconds to actually begin loading the xrns file, this gave honggfuzz enough time to catch a renoise crash if there was going to be one. This time could vary depending on one's machine?
- `-i xml/`: this input folder actually only had one Song.xml file in it, and it was the smallest properly configured xml file I knew would successfully load into renoise.
- `--pprocess_cmd fuzzy_xrns`: the "pprocess_cmd" flag allows you to call a fuzzer of your choosing to generate the malformed
file. `fuzzy_xrns` is a bash script I made that removes any files named song.xrns or Song.xml from the current directory,
takes my small Song.xml file that works, passes it through radamsa, then zips it into a new song.xrns:
#!/usr/bin/bash
rm song.xrns Song.xml
cat $1 | radamsa > Song.xml
zip song.xrns Song.xml
- `-x -s -- /usr/local/bin/renoise song.xrns`: this last chunk just says to execute `-x` `/usr/local/bin/renoise` from standard input `-s`, and instead of using the file generated by honggfuzz (since there is none) use our file: `song.xrns`.
At this point we start fuzzing, and honggfuzz finds a number of crashes. I gravitate towards the crash I'm familiar with: SIGSEGV, the error for a memory access violation.
+-----------+ |"Triage" | +-----------+----c o o o o o oI'm going to try and keep this part relatively brief.
Most of the SIGSEGV crashes had a crash log in Renoise that looked like this:
This looks to me like something is trying to grab memory at [(nil)]. Just to check and see if I can get any more information I run it through gdb as well.
At the time of the crash one can see the problem pretty clearly:
On line 0xd74143, renoise tries to run the following line of code:
mov rdi, QWORD PTR [rax]
And if we check what's in the register $rax, we can see it's 0x0, which is an inaccessible memory location. Hence the SIGSEGV.
The question for me then is: "why is that 0x0?" I then spent a month, on and off, trying to answer that question by running it through gdb, staring at the dissassembly in radare and ghidra:
This was a bit out of my reach, and is where I'm going to call it for this portion of the writeup. Please reach out to me on twitter though if you see any flaws in my logic / process though! Would love to hear any leads.
This is where a new part of the challenge began: seeing how small I could make the crash.
+-----------+ |Binary Golf| +-----------+-----Co o o o o o
Working with a zip (xrns) seemed like a good strategy for making my crash as small as possible. I started off by seeing how small I could get the physical contents of the zip. I did this via some trial and error: remove stuff from the file, recompress it, throw it into renoise, see if it crashes. Funny thing about this was it didn't end until I had just one line left in the `Song.xml` file:
<RenoiseSong></RenoiseSong>
It turned out that Renoise would attempt to load any file with the `RenoiseSong` XML tag in it, then crash.After discussing ways to see if I could get this file even smaller, Retr0id pointed out that renoise may accept self-closing tags, which it does! Allowing me to make my `Song.xml` file just the single line: `<RenoiseSong/>`.
This is small enough where I'm not even getting compression out of zipping it. Here's a hexdump of the xrns file before messing with the binary at all:
00000000 50 4b 03 04 0a 00 00 00 00 00 d0 75 1f 55 e7 f7 |PK.........u.U..| 00000010 04 74 0e 00 00 00 0e 00 00 00 08 00 1c 00 53 6f |.t............So| 00000020 6e 67 2e 78 6d 6c 55 54 09 00 03 48 74 0f 63 4b |ng.xmlUT...Ht.cK| 00000030 74 0f 63 75 78 0b 00 01 04 00 00 00 00 04 00 00 |t.cux...........| 00000040 00 00 3c 52 65 6e 6f 69 73 65 53 6f 6e 67 2f 3e |..<RenoiseSong/>| 00000050 50 4b 01 02 1e 03 0a 00 00 00 00 00 d0 75 1f 55 |PK...........u.U| 00000060 e7 f7 04 74 0e 00 00 00 0e 00 00 00 08 00 18 00 |...t............| 00000070 00 00 00 00 01 00 00 00 a4 81 00 00 00 00 53 6f |..............So| 00000080 6e 67 2e 78 6d 6c 55 54 05 00 03 48 74 0f 63 75 |ng.xmlUT...Ht.cu| 00000090 78 0b 00 01 04 00 00 00 00 04 00 00 00 00 50 4b |x.............PK| 000000a0 05 06 00 00 00 00 01 00 01 00 4e 00 00 00 50 00 |..........N...P.| 000000b0 00 00 00 00 |....| 000000b4This file is 180 bytes. To get this even smaller, I had to learn more about zip files. Fortunately, there's a bunch of resources about zips, and a lot of people have messed with them. Wikipedia was pretty great for this, as well as xcellerator's writeup for bggp2, and Ange Albertini's zip poster. To get started editing hex files you can use vim + xxd, which is a fun tip I learned from yuu! Or you can just use ghex or something like that. For brevities sake, it may be worth going ahead and clicking some of those links to get a better understanding of zip for yourself. I'm going to stick to the specific changes that were necessary for this project. I got started messing around with the different fields to see what I could get away with changing, and then followed the logic that that field may also be able to be removed. What I quickly noticed from looking at ZIP's Wikipedia page, is that zip's have a lot of required fields, but also a lot of redundency. The Local File Header even has a section called the "Extra Field", and I was finding that that data could be removed after changing around the "Extra Field Length" as well.
In our case we're looking to remove the red bytes and alter the purple bytes (if you're color blind that's remove the brown bytes and alter the blue bytes):
00000000 50 4b 03 04 0a 00 00 00 00 00 d0 75 1f 55 e7 f7 |PK.........u.U..| 00000010 04 74 0e 00 00 00 0e 00 00 00 08 00 1c 00 53 6f |.t............So| 00000020 6e 67 2e 78 6d 6c 55 54 09 00 03 48 74 0f 63 4b |ng.xmlUT...Ht.cK| 00000030 74 0f 63 75 78 0b 00 01 04 00 00 00 00 04 00 00 |t.cux...........| 00000040 00 00 3c 52 65 6e 6f 69 73 65 53 6f 6e 67 2f 3e |..<RenoiseSong/>| 00000050 50 4b 01 02 1e 03 0a 00 00 00 00 00 d0 75 1f 55 |PK...........u.U| 00000060 e7 f7 04 74 0e 00 00 00 0e 00 00 00 08 00 18 00 |...t............| 00000070 00 00 00 00 01 00 00 00 a4 81 00 00 00 00 53 6f |..............So| 00000080 6e 67 2e 78 6d 6c 55 54 05 00 03 48 74 0f 63 75 |ng.xmlUT...Ht.cu| 00000090 78 0b 00 01 04 00 00 00 00 04 00 00 00 00 50 4b |x.............PK| 000000a0 05 06 00 00 00 00 01 00 01 00 4e 00 00 00 50 00 |..........N...P.| 000000b0 00 00 00 00 |....| 000000b4Below is a hexdump after those changes. The changed values are highlighted.
00000000 50 4b 03 04 0a 00 00 00 00 00 d0 75 1f 55 e7 f7 |PK.........u.U..| 00000010 04 74 0e 00 00 00 0e 00 00 00 08 00 00 00 53 6f |.t............So| 00000020 6e 67 2e 78 6d 6c 3c 52 65 6e 6f 69 73 65 53 6f |ng.xml<RenoiseSo| 00000030 6e 67 2f 3e 50 4b 01 02 1e 03 0a 00 00 00 00 00 |ng/>PK..........| 00000040 d0 75 1f 55 e7 f7 04 74 0e 00 00 00 0e 00 00 00 |.u.U...t........| 00000050 08 00 00 00 00 00 00 00 01 00 00 00 a4 81 00 00 |................| 00000060 00 00 53 6f 6e 67 2e 78 6d 6c 50 4b 05 06 00 00 |..Song.xmlPK....| 00000070 00 00 01 00 01 00 36 00 00 00 34 00 00 00 00 00 |......6...4.....| 00000080In order by which the highlighted values appear, we've altered:
- `1c` now @offset `1c` (just a coincidence) --> `00`. This value indicates the size of the extra field for the Local File Header
- `18` now @offset `52` --> `00`. This value also indicates extra field size, but for the Central Directory
- `4e` now @offset `76` --> `36`, which is the size of the Central Directory
- `50` now @offset `7a` --> `34`, which is the offset of the start of the Central Directory.
+-----------+ |Scoring | +-----------+------c o o o o oMy total score is my file size (4096-128) + 1024 points for my writeup = 4992 points. No code exec, and no printed 3's... it's not a winner, but I learned a hell of a lot. BGGP3 opened up a huge door of side projects that I'll begin documenting on this site as well. Many thanks to everyone I've linked to above, and all the homies I didn't, you know who you are! Thanks for believing in me! <3
Also please reach out if you see any glaring errors, typos, or ways that I could make the file smaller, or anything like that at all. I really won't take it personal and I'm always down to learn something new!