Aus Gründen(TM) musste ich mir über Lizenzmanagement bei kommerzieller Software Gedanken machen. Wenn man einen Lizenzmanagement-Mechanismus entwickelt setzt man üblicherweise auf asymmetrische Signaturen, sprich: Ähnlich wie bei einer Email-Signatur kommt ein Kryptoverfahren zum Einsatz, das den privaten Schlüssel eines Lizenzgebers nutzt, um eine Lizenz “zu unterschreiben”. Das ist auch die richtige Idee und funktioniert ganz gut.
Allerdings wird oft vergessen, das man das ausgelieferte Software-Binary ja verändern kann. Ein Experiment in C:
#include <stdio.h>
enum License {
VALID = 1,
INVALID = 2,
};
int checklicense(void) {
return INVALID;
}
int main (int argc, char const* argv[]) {
if (checklicense() == VALID) {
printf("License is valid\n");
} else {
printf("License is invalid\n");
}
return 0;
}
Das ist natürlich stark vereinfacht, aber es geht ja darum, meinen Punkt zu veranschaulichen. Aus diesem Quellcode ist via
$ gcc -o main -O0 main.c
schnell ein Binary erzeugt. Via -O0
schalte ich die Optimierungen
des Compilers aus, damit das Kompilat simpel bleibt. Beim Starten gibt
das Programm
$ ./main
License is invalid
aus, denn die Funktion checklicense()
gibt ja den Wert INVALID
zurück. In einem realen Programm wäre dort die Lizenzprüfung
untergebracht — hier ist alles sehr vereinfacht.
So weit so gut; bis jemand ein Reverse Engineering-Werkzeug wie
Radare2 öffnet. Diese Werkzeuge sind ziemlich
gewöhnungsbedürftig, aber sehr mächtig. Ich öffne das Binary also gleich
mal im Schreibmodus (-w
), damit ich einzelne Bytes direkt in r2
patchen
kann:
$ r2 -Aw main
-A
analysiert das Binary direkt beim Öffnen, alternativ hätte ich
auch den Befehl aaaa
in der Konsole nutzen können. Da die Analyse
nun aber schon durch ist kann ich mittels
s main
pdf
direkt zur main
-Funktion springen und mir den aktuellen Frame als
Assembler-Code anzeigen lassen:
[0x00400430]> s main
[0x00400531]> pdf
| ; CODE (CALL) XREF from 0x00400531 (unk)
/ (fcn) sym.main 54
| 0x00400531 55 push rbp
| 0x00400532 4889e5 mov rbp, rsp
| 0x00400535 4883ec10 sub rsp, 0x10
| 0x00400539 897dfc mov [rbp-0x4], edi
| 0x0040053c 488975f0 mov [rbp-0x10], rsi
| 0x00400540 e8e1ffffff call sym.checklicense
| sym.checklicense(unk)
| 0x00400545 83f801 cmp eax, 0x1
| ,=< 0x00400548 750c jnz 0x400556
| | 0x0040054a bff4054000 mov edi, str.Licenseisvalid
| | 0x0040054f e8acfeffff call sym.imp.puts
| | sym.imp.puts()
| ,==< 0x00400554 eb0a jmp loc.00400560
| |`-> 0x00400556 bf05064000 mov edi, str.Licenseisinvalid
| | ; CODE (CALL) XREF from 0x00400400 (fcn.004003fc)
| | 0x0040055b e8a0feffff call sym.imp.puts
| | sym.imp.puts()
| | ; CODE (CALL) XREF from 0x00400554 (unk)
|- loc.00400560 7
| `--> 0x00400560 b800000000 mov eax, 0x0
| 0x00400565 c9 leave
\ 0x00400566 c3 ret
Nu ist eigentlich recht schnell zu sehen, das nach dem call sym.checklicense
der Rückgabewert der Funktion im eax
-Register
liegt. Die Anweisung
cmp eax, 0x1
vergleicht diesen Rückgabewert mit VALID
, also dem Wert 0x1
. Ich
kann nun direkt an diesen Offset im Binary springen und den Wert gegen
0x2
austauschen:
[0x00400531]> s 0x00400545
[0x00400545]> wx 83f802
[0x00400545]> pdf
| ; CODE (CALL) XREF from 0x00400531 (unk)
/ (fcn) sym.main 54
| 0x00400531 55 push rbp
| 0x00400532 4889e5 mov rbp, rsp
| 0x00400535 4883ec10 sub rsp, 0x10
| 0x00400539 897dfc mov [rbp-0x4], edi
| 0x0040053c 488975f0 mov [rbp-0x10], rsi
| 0x00400540 e8e1ffffff call sym.checklicense
| sym.checklicense(unk)
| 0x00400545 83f802 cmp eax, 0x2
| ,=< 0x00400548 750c jnz 0x400556
| | 0x0040054a bff4054000 mov edi, str.Licenseisvalid
| | 0x0040054f e8acfeffff call sym.imp.puts
| | sym.imp.puts()
| ,==< 0x00400554 eb0a jmp loc.00400560
| |`-> 0x00400556 bf05064000 mov edi, str.Licenseisinvalid
| | ; CODE (CALL) XREF from 0x00400400 (fcn.004003fc)
| | 0x0040055b e8a0feffff call sym.imp.puts
| | sym.imp.puts()
| | ; CODE (CALL) XREF from 0x00400554 (unk)
|- loc.00400560 7
| `--> 0x00400560 b800000000 mov eax, 0x0
| 0x00400565 c9 leave
\ 0x00400566 c3 ret
^D
Das Binary prüft nun also, ob die Lizenz ungültig ist, und springt dann in den Pfad für eine erfolgreiche Lizenzprüfung:
$ ./main
License is valid
Tjoa. Und schon ist die schöne Lizenzprüfung umgangen. Es kommt also nicht nur darauf an, das Verfahren zur Erzeugung einer Lizenzdatei gut zu durchdenken. Sondern auch die Prüfung auf Gültigkeit ist gar nicht so einfach, jedenfalls nicht, wenn man derartige Angriffe ausschließen will. Es hat schon einen Grund, warum Firmen wie WIBU Systems sich auf genau diese Problemstellungen spezialisiert haben.