Was bedeutet obfuscate?
Die sogenannte „Obfuskation“ bietet einen sehr guten Schutz gegen sämtliche Reverse Engineering-Techniken. Aktuelle Decompiler können relativ einfach den Quellcode aus einem kompilierten Code rekonstruieren. Der folgende Artikel erklärt, wie sich Code-Obfuskation in modernen OOP-Programmiersprachen realisieren lässt und warum dieses obfuscate Verfahren im Kontext des World Wide Web besonders wichtig ist.
Die Flexibilität moderner Programmiersprachen birgt Risiken
Es ist sehr einfach Klassendateien gängiger Programmiersprachen wie Java oder C# zurückentwickeln. Der kompilierte Code ist mit viele Informationen versehen, die auch im ursprünglichen Quellcode enthalten sind. Darüber hinaus bieten viele moderne Programmiersprachen ein hohes Maß an Flexibilität, um einen schnellen und vereinfachten Entwicklungsprozess zu ermöglichen. Obwohl diese Flexibilität zahlreiche Vorteile in verteilten Umgebungen bietet, birgt sie auch einige potenzielle Risiken, die sich Hacker und Exploit-Entwickler eventuell zunutze machen könnten.
Was versteht man unter Obfuskation?
Code-Obfuscate ist aktuell das beste Verfahren, um Code gegen Reverse Engineering- und Hacking-Techniken zu schützen. Durch das Obfuscate-Verfahren wird der kompilierte Code des Programms zwar kryptisch und unverständlich, funktioniert jedoch weiterhin einwandfrei nach dem ursprünglichen Quellcode. Obfuskation lässt sich sowohl manuell als auch durch den Einsatz spezieller Programme, den sogenannten „Obfuskatoren“, realisieren. Die Software ist so komplizierter zu dechiffrieren, was sie unempfindlicher gegen sämtliche Reengineering- und Exploit-Techniken macht. Durch Obfuskation wird aus einem bestimmten Satz von Klassendateien „K“ ein anderer Satz von Klassendateien „K*“. Ergebnis der Obfuskation ist, dass der Code für die beiden Klassendateien nicht mehr der gleiche ist. Als Beispiel dient uns der folgende Java-Quellcode:
class OriginalHey {
public OriginalHey() {
int number=99;
}
public String getHey(String helloname){
return helloname;
}
Nach dem Obfuscate-Verfahren werden in diesem Code alle Namen der Klasse geändert oder zerhackt und die Zeilennummern entfernt. Dadurch entsteht dann der folgende obfuskierte Code:
class x {
public static boolean x;
public x() {
int a=1;
}
public String x(String x){
return y;
}
An dem obigen Beispiel erkennen Sie, dass die OriginalHey-Klasse und ihre Methode durch das Obfuscate-Verfahren geändert wurden. Der Name der Methode „x“ ist nichtssagend und viel schwieriger zu verstehen als getHey(). Wenn Sie den Bytecode mit dem ursprünglichen Bytecode vergleichen, sehen Sie, dass sämtliche Zeilennummern und Zeilenumbrüche entfernt wurde. Dadurch erhalten Hacker und Exploit-Entwickler weniger Informationen, wodurch der Prozess des Reverse Engineerings wesentlich erschwert wird.
Typische Obfuskationstechniken
Neben dem Austauschen von Buchstaben bzw. Zahlen und dem Entfernen von Zeilennummern, gibt es auch noch eine Vielzahl anderer Tricks, die von den einzelnen Obfuskatoren eingesetzt werden, um Quellcode zu obfuskieren. Eine effektive Möglichkeit zur Verschleierung von Quellcode ist die Aneinanderreihung bedeutungsloser Zeichenketten. Bei diesem Obfuscate-Verfahren wird ein Symbol in der Klassendatei durch eine Zeichenkette ersetzt. Der Ersatz kann beispielsweise ein bestimmtes Schlüsselwort sein oder ein völlig bedeutungsloses Symbol, wie zum Beispiel „***“.
Eine andere Technik, die bei vielen Obfuskatoren implementiert wird, zielt auf bestimmte Decompiler ab, wie zum Beispiel JODE oder Mocha. Hier wird durch das sogenannte „Dependency Injection-Verfahren“ eine schlechte Anweisung absichtlich in den Code eingebaut. Beim normalen Einsatz macht der Code keinerlei Probleme. Möchte man den Code jedoch mit einem Decompiler analysieren, lässt die schlechte Anweisung den Decompiler abstürzen. Als Beispiel für eine solche schlechte Anweisung dient uns der folgende Java-Code:
Method void main(java.lang.String[])
0 new #5
3 invokespecial #15
6 return
Nach dem Obfuscate-Verfahren wird in diesem Code am Ende eine schlechte Anweisung „pop“ eingefügt:
Method void main(java.lang.String[])
0 new #5
3 invokespecial #15
6 return
7 pop
Die Funktion verfügt jetzt nach der Returnanweisung auch über eine Abhebungsanweisung. Eine Funktion kann nach ihrem Rücksprung jedoch nichts mehr bewirken, sodass die Abhebungsanweisung beim normalen Einsatz ignoriert wird. Möchte man den Code mit einem Decompiler inspizieren, bewirkt dies einen Absturz des Programms.
- Über den Autor
- Aktuelle Beiträge
Daniel Faust ist Redakteur im Content-Team der Biteno und betreut den Blog der Biteno GmbH.