Multicraft – Lizenz und Python Internals

Multicraft ist ein All-In-One Minecraft Hosting und Server Verwaltung. Dabei können mehrere Server über einen sogenannten Deamon gesteuert werden, mehrere Deamons werden in einem großen Control-Panel mit Webinterface zusammengefasst. In der Free-Version kann man exakt einen Server auf einem Deamon hosten und ist auf 10 Spieler für diesen einen Server beschränkt. Es gibt weitere, skalierbare Lizenzen die mehrere Server, mehrere Deamons und unbegrenzte Spielerzahlen erlauben. Das höchste aller Gefühle ist dabei die „Dynamic“ Lizenz die unbeschränkt ist und mit steigender Anzahl der Server/Deamons immer teurer wird.

Multicraft wird für Windows und Linux angeboten. Die meisten Server laufen unter Linux, für mich persönlich ist aber das Reverse Engineering auf Windows leichter. Die auf Windows gewonnenen Erkenntnisse lassen sich zum Glück einfach auf Linux übertragen. Warum? Das hat mit dem etwas wilden Aufbau von Multicraft zu tun:

Schaut man sich diese vielen in Multicraft verwendeten Technologien an wird klar dass hier viel zusammengemixed wurde. Python, PHP und NodeJS sind komplett unterschiedliche Programmiersprachen. Vorallem fällt Python ins Auge was (nach diesem Artikelname wenig überraschend) die Haupttechnologie für Multicraft ist. In dem bin-Ordner findet sich neben einer python27.dll einige vorkompilierte *.pyd Module. Die eigentliche Multicraft Binary ist aber eine normale (und riesige) Excutable. Die Entwickler haben das ganze Python-Framework direkt mitgeschickt, damit der User es nicht noch installieren muss. Natürlich auf die Kosten von einer riesigen Installation. Vielleicht kann uns IDA mehr dazu verraten:

Diese Grafik stellt IDA zur Verfügung um den Aufbau der Binary zu beschreiben. Die blauen und orangefarbenen Regionen sind Programmcode, Grau sind Daten wie Strings und statische Adressbereiche. Schwarz wiederrum findet man in kaum einer Anwendung, es handelt sich dabei um nirgends referenzierte Adressbereiche und Daten die für IDA keinen Sinn ergeben. Solche finden sich oftmals bei gecrypteter Malware die wiederrum diesen Code erst entschlüsseln.

In der Main-Funktion stoßen einem viele pinke Variablen und Funktionen ins Auge. In IDA bekommt jede externe Funktion/Variable eins solche pinke Schrift. Hier werden einige globale Python-Variablen gesetzt und anschließend Py_Initialize() aufgerufen. An diese Website der Python-C API sollte man sich als Leser direkt gewöhnen, denn wir werden noch einige Informationen aus dieser Dokumentation ziehen. Im Prinzip wird mit Py_Initialize() der Python-Interpreter initialisiert. Python in C++ eingebettet, eigentlich ganz cool ! Und da Python bekanntlich eine Skriptsprache ist die sich gut decompilen lässt war die Aufregung über eine neue Herrausforderung schnell verflogen. Man musste „nur“ den Python-Code zum Lizenzcheck finden, decompilen und einen Keygen schreiben. Leider (oder ehr zum Glück?) wurde es viel komplizierter.

Der erste Ansatz waren die sogenannten „Frozen-Module“. Das ist ein weg ein Python-Skript in eine Executable zu packen die zum starten keine installierte Python-Runtime benötigt. Auf Windows  gibt es ein Äquivalent dazu, py2exe . Vorallem auf CTFs sind solche Aufgaben verbreitet und dementsprechend gut dokumentiert. Freeze und py2exe hängen einfach die vorkompilierte Python-Datei (*.pyd bzw *.pyc) an die Binary. Daher kommt man relativ einfach an den Python-Binärcode der sich wieder in lesbaren Python-Code umwandeln lässt. Aber jegliche Tools um Frozen-Module aus der Executable zu extrahieren scheiterten kläglich. Bleibt wohl nichts anderes übrig als sich weiter im Code umzuschauen.

Und wenn die Variable Py_VerboseFlag gesetzt ist bekommt man schön viele Debug-Meldungen:

# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
import cPickle # builtin
import copy_reg # frozen
import types # frozen
import cStringIO # builtin
setup nuitka compiled module/bytecode/shlib importer
import os # considering responsibility
import os # claimed responsibility (compiled)
Loading os
import errno # considering responsibility

Anscheinend werden hier alle Python-Module von einem Loader namens „nuitka“ reingeladen. Es folgen mehrere Module die geladen werden, endweder per LoadLibraryEx von der Festplatte (wie z.B. _ssl.pyd) oder aus einer bisher unbekannten Datei. Natürlich handelt es sich um die Daten aus dem schwarzen Bereich von IDA, hier sind alle alle Module reingepackt ! Die Vorfreunde über diese Erkenntnis war relativ schnell verflogen nachdem eine Google-Suche nach „Nuitka“ folgendes enthüllte:

Right now Nuitka is a good replacement for the Python interpreter and compiles every construct that CPython 2.6, 2.7, 3.2, 3.3, 3.4 and 3.5 offer. It translates the Python into a C program that then uses libpython to execute in the same way as CPython does, in a very compatible way.

Das heißt dass der gesammte Python Code der nicht aus dem mitgelieferten *.pyd Modulen stammt in C-Code übersetzt wurde und über die oben schon angesprochene Python-C API ausgeführt wird. Wie man sich vorstellen kann ist dieser entstehende C-Code hässlich und vorallem nicht wieder in Python-Code überführbar.

Aber diese Art des Reverse Engineering Schutzes hat auch etwas gutes: Ich habe mich intensiv mit Python Internals auseinander gesetzt. Denn im Endeffekt ist der Python-Interpreter ja in C geschrieben. Zudem wäre sonst nie dieser Blogpost entstanden.

Los geht es ans eingemachte: Über die Verbose-Ausgabe sehen wir alle Module die geladen werden. Ein paar davon stechen sofort ins Auge, z.B. „McConfigParser“ und „license„, beide mit dem Kommentar „claimed responsibility (compiled)“. In „license“ versteckt sich höchstwahrscheinlich der Lizenzcheck den es zu umgehen gilt.

Bevor wir intensiv uns mit Python-C-Code auseinandersetzen braucht es einige Grundlagen zu Python Internals: Python besitzt nur ein paar wenige Typen auf denen alles Aufbaut. PyString, PyLong, PyDict, PyTuple und PyObject sind die wichtigsten davon. Jeder, wirklich jeder Typ, erbt von einem Basistyp PyObject_HEAD der (solang man Python nicht im Debug-Modus kompiliert) exakt zwei Variablen hat: Ein Reference-Counter und einen Zeiger auf den Objekttyp:

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                   \
    _PyObject_HEAD_EXTRA                \
    Py_ssize_t ob_refcnt;               \
    struct _typeobject *ob_type;

Variablen wie PyDict haben einen erweiterten Anfangsheader, PyObject_VAR_HEAD:

/* PyObject_VAR_HEAD defines the initial segment of all variable-size
 * container objects.  These end with a declaration of an array with 1
 * element, but enough space is malloc'ed so that the array actually
 * has room for ob_size elements.  Note that ob_size is an element count,
 * not necessarily a byte count.
 */
#define PyObject_VAR_HEAD               \
    PyObject_HEAD                       \
    Py_ssize_t ob_size; /* Number of items in variable part */

Was bringt uns das ? Zunächst einmal wissen wir von jedem Python-Objekt was uns beim Debuggen über den Weg läuft um was für einen Typ es sich handelt, denn der Zeiger auf den Typ enthält auch einen String mit dem Typnamen. Des weiteren können wir einfach verfolgen wann auf ein Objekt zugegriffen wird indem wir einen Memory-Breakpoint auf den Reference-Count setzen. Jedes mal wenn ein Objekt verwendet wird, wird dieser Counter erhöht und nach dem Zugriff wieder dekrementiert.

Nach diesem kleinen Exkurs in die Python-Internals können wir loslegen. Das License-Modul wird durch die Funktion Py_InitModule4 geladen. Diese nimmt eigentlich Methoden entgegen die das Modul enthält, aber leider sind die bei uns Leer:

Spannend ist allerdings die md_dict Variable eines PyModuleObjects . Hier werden alle Variablen dieses Modules reingeladen. Dank den Python-Internals wissen wir wie ein solches PyDict aufgebaut ist und können die Werte dumpen:

Entry Values: 
	 KEY: Global VALUE: type (0x03179030)
	 KEY: logging VALUE: module (0x0313a1f0)
	 KEY: License VALUE: type (0x03179208)
	 KEY: __builtins__ VALUE: dict (0x003290c0)
	 KEY: __file__ VALUE: C:\Bitnami\multicraft-2.1.1\apps\multicraft\bin\license.py
	 KEY: base64 VALUE: module (0x02f2a430)
	 KEY: __package__ VALUE: NoneType (0x1e224f84)
	 KEY: re VALUE: module (0x02df3410)

Leider nicht so spannend wie erwartet. Die Python-Kenner werden hier Konstrukte wie „__buildins__“ erkennen und auch in __file__ steht eine Python-Datei drin die es leider nicht gibt. Nur von den Funktionen ist noch nichts zu sehen!

Oberhalb der Module-Initialisierung findet sich allerdings ein Funktionsaufruf der uns interessiert:

Wer behauptet auch dass RE schön ist ? 😛 Ein wenig aufgehübscht ergibt sich:

Im Endeffekt wird hier PyCode_New aufgerufen. Diese API kann genutzt werden um dynamische Codeobjekte zur Laufzeit zu erstellen. Vorallem interessant ist die Anzahl der lokalen Variablen (PyTupleLength_X), die Anzahl der Argumente (1 oder 2) und der Funktionsname (zweites Argument). PyCode_New liefert ein PyCodeObject* zurück, was wiederrum den Python Code enthält.

Es folgt ein kleiner Einschub über Strings: Es gibt ein PyString Objekt was natürlich wie jedes andere Python-Objekt mit dem schon bekannten HEAD aufgebaut ist. Um aus einem normalen ASCII-Zeichen String einen PyString zu machen kann die Funktion PyString_FromStringAndSize verwendet werden. Alle Strings werden also zu Beginn von einer großen Funktion in viele gloable Variablen geladen und in Zukunft dieser Variable verwendet. Beim Reverse Engineering erschwert diese Art von String-Objekten natürlich die Analyse, da Strings oftmals gute Anhaltspunkte sind und im Code jetzt nur Referenzen auftreten ohne dass man den String direkt sieht. Daher habe ich einige Stunden genutzt um interessante Strings (bzw ihre Objekte) zu markieren dass mir deren Verwendung im Code aufällt. Im obigen Beispiel habe ich „parse„,“names„, „level“ und „source“ so markiert. Und weil das händlisch viel zu Aufwendig ist wurde natürlich ein Tool für das Auslesen dieser Strings geschrieben. Das Umbenennen musste allerdings weiter händisch erfolgen, dazu kann ich leider die Python IDA Schnitstelle nicht gut genug bedienen…

Dadurch konnten also die Lizenz Funktionen wie „verifyLicense“, „parse“ und „checkVerified“ ausgemacht werden. Zumindest theoretisch, denn es gibt ja keinen binären Python-Code in dem PyCodeObject* von Nuikta. Aber: es war wenigtens ein Ansatzpunkt.

Damit wissen wir aus welchen Funktionen das Lizenz-Modul aufbaut ist. Nur vom eigentlich Python-C-Mix Code ist noch nichts zu sehen. Die Suche nach diesem (absichtlich nochmal versteckten!) Code und wie dieser zu Interpretieren ist gibt es im nächsten Blogpost.

So Long
Easy

4+

3 Comments

  • Antworten {root} |

    Hey easysurfer,

    an dieser Stelle ist die Frage etwas offtopic, aber machst Du dieses Jahr wieder bei der CSCG mit?

    lg
    {root}

    0
  • Antworten {root} |

    cool, ich auch unter einem anderen Pseudonym.
    dann treffen wir uns vielleicht in Düsseldorf 😉

    0

So, what do you think ?