Let me start out by saying that you should by no means run any of the code posted here. I (Nathan Ramella) and remix.net accept no liability for the results of your experimentation on your own computer. If you do something that causes any damage or loss to your or someone else's computer, you accept full responsibility. You should not use these scripts under any circumstances. Python is a full programming language and has all sorts of commands that could render your computer useless if you make a mistake. If you understand and accept that risk read on, if not go find a different webpage to read.
I would also like to say that this effort has nothing to do with 'cracking' Ableton in any form, this is purely an attempt to gain access to the Python framework that Ableton utilizes so that native solutions could be found for problems that packages like Gustavo Bravetti's excellent pushandpull
solve. I'm a registered user and hope if you use Ableton you are as well.
I make no claims to the validity of any of this information, I am not a Python or computer expert. If you see any errors please let me know and I will be happy to fix them -- feedback is always appreciated.
- nar(ATSIGN)remix.net
Ableton Live is written in Python and C++. C++ we're all pretty familiar with at least by name but Python might be new to some people. I don't know how much Python is used, or where its used. So my initial goal was to get the lay of the land.
Python is a GPL licensed object oriented scripting language, if I had to draw parallels I'd say its somewhere between Java and Perl, easier to program and comes with a robust standard library including threading and communication libraries.
You can get compiled Python installers for almost every operating system, OSX ships with it. I used the <a href="http://www.cygwin.net">Cygwin</a> port of Python on Windows. The programs I wrote for this effort should be portable to both Windows and OSX. My work was performed on Windows XP sp2 using version 6.0.1 of Ableton. I wrote and ran each one of these scripts on my own machine to verify that they worked as expected before trying to take them into Ableton.
I know Ableton has some Python in it, but not what amount. There's also the question of how to gain access to it. I'm not even sure how useful it will be, given that an application who's users demand single digit milisecond latency may not respond well to outside tampering. But, you never know until you try. It would appear that all of the Python content is compiled into the Ableton binary with none available on disk. Well.. Almost none.
C:\Program Files\Ableton\Resources\MIDI Remote Devices, or MRD directory as I'll refer to it later in this document has directories with compiled Python bytecode. These files are recognizable by the extension <b>.pyc</b>. Similar to Java, Python can generate compiled bytecode that allows for faster execution by not recompiling every time you run it, it'll just pick up the bytecode and run it in the Python Virtual Machine or PVM.
The MRD/_Generic directory appears to have the skeleton of Python modules that all remote MIDI devices may have, while the named directories such as MRD/Axiom and MRD/ BCF2000 appeared to have a subset of these .pyc files. I guess that these stock .pyc files only cover the differences from _Generic and that each instrument is considered generic until its loaded and its unique characteristics override _Generic behavior. I don't know enough about this mechanism to say for sure.
This might give us an injection point for our code. Python will examine source code during import that isn't byte compiled, it'll compile it for you on the fly and put the .pyc file in the same directory. Given the dynamic nature of this process if we remove a .pyc file and substitute a .py file with the same basename it might compile and run it.
I started my work in the MRD/Axiom directory. I backed it up so I wouldn't lose anything and then removed Axiom.pyc and put my replacement Axiom.py in the directory.
This script will create a file named "DEADBEEF.txt" in Ableton's current working directory at the time of code execution and write the line 'remix.net 1 2 3' followed by a newline.
Code: Select all
# CODE START
# Replacement Axiom.py - File write test
import sys
myFile = open('DEADBEEF.txt', 'a')
myFile.write('remix.net test 1 2 3\n')
myFile.close
# CODE END
Ableton was even usable after startup, although when I went to the MIDI Mapping menu and selected 'Axiom', it crashed with a C++ error. My substitute Axiom.py isn't trying very hard to look like the objects that previously made up the Axiom MIDI mapping, so this isn't suprising. It is interesting though, as it means that code that is loaded through the MIDI mapping initialization may be accessable once Ableton is up and running. It also could just mean that there was an error when Ableton tried to access the MIDI mapping.
We've managed to get our code executed and into the embedded PVM but there are still many unknowns. The PVM could just be an initialization trick to allow for dynamic custom MIDI mapping support and might be destroyed when Ableton hits running mode. To get more information on Python's behavior in Ableton I modified the first script to be a blocking test. It will never die it. It will just do a loop forever, writing to our file and sleeping a second between every loop.
Code: Select all
# CODE START
import time
while 1:
myFile = open('DEADBEEF.txt', 'w')
myFile.write('remix.net test 1 2 3\n')
myFile.close
time.sleep(1)
# CODE END
Threads in the context of programming are a way for multiple tasks to be undertaken by a program simultaniously. You can't really run multiple threads simultaniously but a program will switch execution context between threads so quickly it appears simultanious to the user. The best we can hope for in this case is to have a thread that steals a little CPU time when necessary to interact with the user and deliver those messages to the Ableton engine. I'd chalk this up to one of the main reasons why Ableton haven't allowed users this API access, intellectual property concerns aside if people started creating add-ons that talk directly to the API it would be a tech support nightmare.
Imagine being a mechanic and having to diagnose car trouble over the phone, if the car was stock from your factory you'd be able to get some pretty good guesses of whats going wrong by someone just describing them to you. However if they had replaced the engine with a bunch of spare parts they scavenged from a junkyard, all of your previous experience diagnosing problems would be almost useless as all variables would be changed. But, I digress.
I wrote one more script to give me an idea of what was going on inside the PVM before trying out the background thread idea. Once our code is loaded into the PVM we might be able to get access to other modules in memory as well as examine PVM environment.
This code will give us the version of Python thats running and dump out the list of builtin modules and dynamically loaded modules. Builtin modules contain the basic things that Python can do but sys.modules contains everything thats been loaded into the runtime engine. There may be more interesting things to get out of the PVM but I'm new to Python.
Code: Select all
# Python Environment Dumper
import sys
myFile = open('ableton_dump.txt', 'a')
myFile.write('Python version:' + sys.version + '\n')
myFile.write('sys.modules.builtin:\n**************'**\n')
for builtinModule in sys.modules.builtin:
myFile.write('\t' + sys.modules.builtin[builtinModule] + '\n')
myFile.write('sys.modules:\n**************'**\n')
for loadedModule in sys.modules:
myFile.write('\t' + sys.modules[loadedModule] + '\n')
myFile.close()
Other useful information from this is that Ableton's PVM doesn't have the newer 'threading' module. It only has the older 'thread' module. That means we have to use older Python thread programming techniques and don't get the benefits of the high level interface.
My next script is a little more complex and has two components, a client and server pair. The server will be created in a background thread which in a pure Python environment would run along side the main application. It will accept client requests and return results, requests will be Python code snippets. This is possible because of try/except in Python, if we try to execute some code and it fails we can catch the exception and ignore it.
This makes it simple to experiment as we won't have to restart Ableton every time we want to try a new snippet of code and we won't crash it if we try something that would cause a runtime error. By not including the blocking module Ableton will spawn our thread and then initialize to full running state. If we're lucky we'll be able to connect and issue commands. We start out without using the blocking module to see if it works at all.
For all intents and purposes this client/server pair is a backdoor similar to a rootkit and should be treated as such. I hard-coded the the scripts to only work with 'localhost' but that doesn't mitigate the danger of running code of this nature. But, I'm a <i>fearless risk taker</i>.
Long story short is, it works BUT not while Ableton is capable of playing music. You're stuck in initialization mode. If you run it background threaded the thread never gets execution focus until Ableton quits then only for one or two instructions and it dies too.
Someone on the #python channel suggested the thread freezing behavior may be caused by Python's GIL. The GIL is the Global Interface Lock for threads. As it turns out, Python isn't thread safe. So the GIL is used to manage thread context. If the GIL doesn't give your thread execution time, you can't take it. As well, if C/C++ has control of the GIL then you're at its mercy for getting thread focus. Normally the GIL will release and reaquire the lock every 100 bytecode instructions. You can modify this value by using sys.setcheckinterval(), but this didn't have any effect on the remix-server.py. This is a difficult roadblock. Once we start venturing into compiled C++ things get more difficult and less elegant than a pure Python solution would be.
I tried playing round with overwriting some of the objects in memory with little luck. At this point I think I'm at the limit that I could go without cracking out ollydbg and that would go against the spirit of this effort.
Here's the ta+gz file of the code I used for the client/server. Instructions are provided inside. If you find anything interesting I'd be happy to hear about it. And as always, feedback is appreciated!
http://www.remix.net/RemixShell.tar.gz
-nar