Crackage d'un logiciel (récit)

"Cracker" un logiciel (rétro-engineering) n'est pas nécessairement illégal. En revanche, l'usage d'un logiciel sous licence sans disposer légalement de cette licence n'est pas légal. Dans le cadre de cet article, c'est mon employeur (l'ayant-droit) qui m'a demandé de tester si un de ses logiciels était crackable (spoiler: oui!).

Objectif

Mon employeur me donne un application ainsi qu'une licence valide (sous forme d'un fichier texte), et il me demande: quelqu'un sans licence peut-il se servir du logiciel?. Si oui, alors l'impact est évident: perte de chiffre d'affaire! Par principe, ai-je répondu, une application "client-lourd" (un exécutable sur le PC de "l'acheteur") est forcément piratable. Mais l'employeur me demande alors une preuve (parce que transformer l'application client lourd en site web, ça coûte cher, et il faut donc être sûr que ce sera utile).

Il ne suffit pas d'énoncer des principes de sécurité à votre employeur: vous devez prouvez que les failles sont exploitables (= créer un "exploit"), et qu'elles ont un impact sur le business de votre employeur (perte de chiffre d'affaire, de propriété intellectuelle, d'image de marque, etc).
Sans ces deux critère, la faille sera ignorée (à raison!).

Moyens et contexte

L'application se présente comme un fichier exécutable SFT.exe (pour "SoFT", ce nom n'est pas celui de l'appli originale évidemment!) que l'on lance pour démarrer l'application. Celle-ci va alors fournir à l'utilisateur un code unique (exemple tronqué: bm9rZX...RURDN0MKbm9rZXkKQURNSU5B), qu'il doit envoyer (par mail) à l'entreprise. Celle-ci génèrera alors une "clef de libération" (exemple: U0ZUDQoyLjAuMA0KU2luZ...ZXkKQURNSU5B), qu'elle retourne à l'utilisateur. Une fois la clef de libération entrée dans le logiciel, ce dernier génère un fichier de licence SFT.lic (celui que l'employeur m'a fournit initialement), qui permet d'utiliser le programme sur la machine du client sans devoir ré-entrer la clef de libération.

Pour ce crackage, on considère que le pirate n'a accès qu'aux mêmes informations qu'un utilisateur lambda: il n'a pas accès au système générant les clefs de libération, ni aux sources du projet. Il a accès à l'exécutable compilé (le .exe) et à une seule licence fonctionnelle (raison pour laquelle l'employeur m'en a fourni une).

Méthode générale

Dans une recherche scientifique, il est d'usage de lancer différentes pistes pour résoudre un problème, et de laisser tomber certaines d'entre elles au fil de la recherche, pour ne se concentrer au final que sur la piste la plus prometteuse. Cette même approche doit être mise en pratique quand il s'agit d'éprouver la sécurité d'un système en général. Il faut donc trouver un maximum de pistes et d'idées, et creuser la piste la plus simple en premier, jusqu'à ce qu'elle devienne plus compliquée qu'une autre. On creusera alors cette autre piste, jusqu'à en découvrir de nouvelles (plus simples) ou jusqu'à ce que cette autre piste redevienne plus complexe que la première, à laquelle on retourne alors (etc).

Ne vous focalisez pas sur une seule façon d'attaquer (cracker) un système: soyez inventif, et trouvez autant de pistes que possibles, puis exploitez-les un peu toutes (jusqu'à ce que l'une d'elles marche).

Les pistes ont donc tendance à se croiser lorsque vous crackerez réellement un logiciel, mais pour clarifier la lecture de l'article, je détaillerai à fond chacune d'elle avant de passer définitivement à la suivante.

Attaques

Par la clef de libération

Ici, je cherche à casser le code de libération, pour être capable d'en générer un à partir de la clef unique fournie par le logiciel. Si j'y parviens, je pourrai alors utiliser ce code de libération comme si l'entreprise (mon employeur) me l'avait fourni, ce qui me permettrai (en tant que pirate) d'utiliser le logiciel sans avoir réellement payé la licence auprès de l'entreprise.

En lançant SFT, le logiciel me fournit le code unique suivant:

bm9rZXkKbm9rZXkKbm9rZXkKTm90IEF2YWlsYWJsZQpub2tleQpBUFBMRSAgLSA1NwpDMDJDUDFGRURDN0MKbm9rZXkKQURNSU5B
Le code unique (connu du pirate)

Ce code n'est donc pas confidentiel: n'importe quel utilisateur, pirate ou non, peut l'obtenir sans soucis. Ayant une licence valide, je connais également la clef de libération fournie par l'entreprise associée à ce code unique:

U0ZUDQoyLjAuMA0KU2luZ2xlDQoyDQpBRE1JTkENCjANCjIwMTctMDEtMjMNCg0KMA0KQUFBQUIzTnphQzF5YzJFQUFBQ0FUWWdkZ2ovYnBaZDk0OGZWWVIydnF4bHVXb1l0SktoSGNobU5lM3FwSFo5NVI5ODczeWl0dldyZlJiN1RVUmpsR1piczZwZVNmb1RVUFB2VFZQVHRJYW81TCs4VEpFQXd0ak9JY0lUcnhaOGhIY05BMjVjZ3JlZGw2dHpLcDkrR0NzMk94T3BZS28xMEw1VTNtM3MrNlNBc2haTUlqWkNsTnY2bTVsakxEQjAaLckbm9rZXkKbm9rZXkKbm9rZXkKTm90IEF2YWlsYWJsZQpub2tleQpBUFBMRSAgLSA1NwpDMDJDUDFGRURDN0MKbm9rZXkKQURNSU5B
Le code de libération de la licence fonctionnelle (= ce que le pirate veut cracker)

Je m'aperçois qu'il s'agit d'un base64, auquel le code unique aurait été accolé. Je retire donc le code unique:

U0ZUDQoyLjAuMA0KU2luZ2xlDQoyDQpBRE1JTkENCjANCjIwMTctMDEtMjMNCg0KMA0KQUFBQUIzTnphQzF5YzJFQUFBQ0FUWWdkZ2ovYnBaZDk0OGZWWVIydnF4bHVXb1l0SktoSGNobU5lM3FwSFo5NVI5ODczeWl0dldyZlJiN1RVUmpsR1piczZwZVNmb1RVUFB2VFZQVHRJYW81TCs4VEpFQXd0ak9JY0lUcnhaOGhIY05BMjVjZ3JlZGw2dHpLcDkrR0NzMk94T3BZS28xMEw1VTNtM3MrNlNBc2haTUlqWkNsTnY2bTVsakxEQjAaLck
Le code de libération - le code unique

Un caractère est alors en trop (ou manquant). Je le retire, et je décode:

SFT 2.0.0 Single 2 ADMINA 0 2017-01-23 0 AAAAB3NzaC1yc2EAAACATYgdgj/bpZd948fVYR2vqxluWoYtJKhHchmNe3qpHZ95R9873yitvWrfRb7TURjlGZbs6peSfoTUPPvTVPTtIao5L+8TJEAwtjOIcITrxZ8hHcNA25cgredl6tzKp9+GCs2OxOpYKo10L5U3m3s+6SAshZMIjZClNv6m5ljLDB0-
Base64 décodé

En comparant ces données à la licence (voir la piste suivante), je m'aperçois qu'il s'agit quasiment des mêmes informations, dans un ordre spécifique. Donc, casser la clef de libération revient ni plus ni moins à casser le fichier de licence. Je m'attarderai donc sur ce fichier plutôt que sur cette clef. Cette première piste se termine ici (et enchaîne en fait sur la suivante).

Par la licence

S'attaquer à la licence consiste à forger un fichier de licence sans passer par le serveur de licences de l'entreprise. Cette licence prend la forme d'un fichier texte, SFT.lic contenant entre autres le code unique donné par le logiciel pour ce PC, et les informations dérivées de la clef de libération décodée dans la piste précédente.

[SFT] ProductVersion=2.0.0 LicenseType=2 LicenseClass=Single Licensee=ADMINA RegisteredLevel=0 MaxCount=0 LicenseKey=AAAAB3NzaC1yc2EAAACATYgd... RegisteredDate=2017-01-23 LastUsed=09-May-2017 Hash1=8f4e42209...015 LicenseCode=bm9rZXkKbm9rZXkKbm9rZXkKTm90IEF2YWlsYWJsZQpub2tleQpBUFBMRSAgLSA1NwpDMDJDUDFGRURDN0MKbm9rZXkKQURNSU5B
Le fichier de licence (tronqué)

En analysant un peu, je m'aperçois que Licensee est le nom du compte utilisateur, que LicenseKey est une clef RSA (une simple recherche DuckDuckGo sur AAAAB3NzaC1yc2E nous le dit ), que Hash1 est certainement un hash (vérifiant l'intégrité des données?) et que LicenseCode est le code unique donné par l'utilisateur.

En continuant ma réflexion, je vois que le LicenseCode est un base64 que je peux aisément décoder là aussi:

nokey nokey nokey Not Available nokey APPLE - 57 C02CP1FEDC7C nokey ADMINA
Le LicenseCode dé-base64-ifié

J'en déduis alors que ce code unique est un "fingerprint" de la machine, incluant le nom du poste (APPLE), le nom d'utilisateur (ADMINA) et ce qui semble être un code unique à la machine (C02CP1FEDC7C). Les "nokey" et le "Not Available" semblent montrer qu'il s'agit d'un système de licence standard qui n'aurait pas été exploité à fond; c'est à dire que le fingerprint doit être généré via une méthode standard, à laquelle on n'a pas demandé de prendre en compte tous les paramètres possibles (ie: ignorer le numéro de série de la carte mère par exemple).

Si j'altère un élément de la licence (n'importe quelle ligne), le logiciel refuse de démarrer. J'en déduis donc qu'il existe une redondance entre cette clef unique et le contenu de la licence, certainement via le Hash1 qui doit être une "signature" de la licence (à minima, de la clef unique). En cherchant les différents noms des champs de la license (LicenseType LicenseClass LicenseKey) sur le net, on s'aperçoit que les résultats pointent sur ActiveLock, un système standard de licence. Bonne pioche: cela colle avec l'impression précédente! Commence alors une grosse phase de fouilles, qui a consisté à comprendre ce qu'est ActiveLock (un jeu de DLL en charge de générer un code unique par machine, et de vérifier la validité de la licence fournie par l'utilisateur), et son fonctionnement (une clef publique, VCode, est stockée dans le logiciel diffusé et le serveur de licences contient une clef privée, servant à générer la LicenseKey que la DLL validera côté client). Dans cette phase, je finis par découvrir que Hash1 est en fait un simple MD5 du LastUsed.

Je connais donc tous les champs du fichier de licence, mis à part la LicenceKey. Or, la présence d'un algo comme RSA dans cette valeur stoppe cette piste, car casser des clefs RSA assez longues (≥2048 bits) est juste impossible (sinon, il y aurait bien plus de failles que le simple crackage d'un logiciel d'entreprise). La seule solution serait de connaître les clefs privées et publiques pour espérer générer ces LicenceKey RSA. Ne les connaissant pas, et n'ayant pas envie de pirater directement le serveur de licences, la piste est donc avortée.

Par le code unique

Puisque la clef de libération dépend du code unique envoyé par l'utilisateur, une autre piste consisterait à casser la génération de ce code unique, pour faire en sorte que celui-ci prenne une valeur connue. Par exemple, la licence valide dont je dispose correspond au code unique bm9rZXkKbm9rZXkKbm9rZXkKTm90IEF2YWlsYWJsZQpub2tleQpBUFBMRSAgLSA1NwpDMDJDUDFGRURDN0MKbm9rZXkKQURNSU5B: si tous les PC sur lesquels je veux faire tourner SFT renvoyaient ce code unique, alors je pourrai faire tourner SFT dessus via cette même licence!

Comme vu dans la piste précédente (c'est aussi pour cela que les pistes avancent en parallèle), ce code unique est un base64 d'une liste de valeurs. En allant voir du côté de ActiveLock, je tombe sur un générateur dans lequel ce code peut être entré. J'apprends alors que les nokey correspondent à des champs comme l'IP, le nom du compte utilisateur, le Firmware du disque dur, l'adresse MAC, la version du BIOS ou des données de la carte mère (je n'étais pas loin avec ma première impression!). Il semble donc impossible de modifier ces données dans Windows de sorte que SFT propose toujours le même code unique. La piste s'arrête là.

Screenshot d'alugen
La vue d'Alugen, le générateur de clef unique, disponible en open-source

Par la virtualisation

Puisque le code unique dépend de la machine, il est théoriquement possible de créer une machine virtuelle identique à celle où se trouve la licence, et de faire tourner SFT dessus. Néanmoins, cela implique de créer une VM strictement identique à la machine originale, hardware inclus (d'après la piste précédente). Cela semble donc possible, mais très difficile à maintenir dans le temps (et pas à la portée d'un utilisateur lambda).

Le pirate pourrait aussi créer une VM, lancer le logiciel dessus pour en sortir un code unique propre à la VM, envoyer ce code à l'entreprise pour acheter la licence (qui tournera sur la VM), et cloner cette VM. Mais le risque que l'entreprise voit qu'il s'agit d'une VM en observant le contenu du code unique est élevé, donc, cette attaque ne sera pas prise en compte.

Emuler le serveur de licence

Les pistes précédentes m'ont appris qu'Alugen est utilisé pour générer les clefs de libération. Or, ce logiciel est disponible gratuitement et publiquement. Une bonne idée serait donc de faire tourner Alugen sur son poste (et non plus sur le serveur de licences, donc disponible aux pirates) pour générer nos propres clefs. Ce faisant, on s'aperçoit que la clef privée du RSA dépend non seulement des informations du logiciel (que l'on retrouvent dans le fichier de licence), mais aussi de données apparemment aléatoires: il sera impossible d'utiliser Alugen pour générer des clefs de libération pirates, puisque la clef privée ne dépend pas que des données dont le pirate dispose. La piste aura vite avortée! En revanche, notez que des algos maisons auraient probablement pu être cassés, et que cette piste aurait alors été prometteuse. J'aurai pu également tenter de casser la source d'aléatoire d'Alugen, mais ce serait une piste trop compliquée.

Par les sources

ActiveLock est open-source, j'ai donc accès aux codes sources qui vont avec. L'idée serait de m'en servir pour créer un faux système de validation, et faire croire à SFT que la licence fournie par l'utilisateur est valide, même si celle-ci est fausse. Un petit tour via ProcessMonitor m'apprend que SFT utilise deux DLL, ActiveLock3.dll et ALUGen.dll (on retrouve mes pistes précédentes). Il faudrait donc réussir à compiler ces DLL via les sources publiquement disponibles pour hacker le logiciel. Ces sources sont en VB (mince, j'aime pas le VB…!).

Beaucoup de versions d'ActiveLock existent, et avant de plonger dans la modification d'un projet VB, il faudrait savoir quelle version me sera utile. Il faut donc les tester une à une. Manque de chance: aucune des DLL pré-compilées proposées directement sur le site d'ActiveLock n'a marché ("Class does not support Automation")! Persister dans cette voie en tentant de recompiler une DLL activelock qui serait "hackée" devient donc risqué, car la DLL compilée aura peu de chances de marcher. J'ai toutefois poussé encore un peu la piste, mais comme ActiveLock3 est écrit en VB6, aucun de mes IDE habituels n'a su prendre le projet en charge: j'ai donc vite abandonné cette piste.

Le découragement peut vite survenir quand vous essayez de construire un "exploit" pour une faille de sécurité; mais persistez si vous voulez être le premier: c'est parce que les autres pirates ont abandonné avant de trouver que personne n'avait su, jusqu'ici, cracker le logiciel en question!

Par l'assembleur

La dernière solution possible consiste à passer par l'assembleur. De manière amusante, c'est la dernière piste à laquelle recourir, mais c'est aussi la plus générique et en cela la plus puissante. En effet, l'assembleur permet de tout faire, mais cela nécessite souvent des jours de travail. Mieux vaut donc épuiser les pistes simples précédentes (en quelques heures souvent) avant de plonger là-dedans. Je n'en ai jamais fait (en cas pratique comme cela), et ce fût donc très instructif.

Outils pour l'assembleur

D'abord, il faut les outils pour faire de l'assembleur. Je suis parti sur OllyDbg: j'en avais déjà entendu parler, et c'est ce logiciel qui est de nouveau ressorti de mes recherches. Une fois installé (j'ai pris la v2.0, soyons "modernes"!), il faut en comprendre le fonctionnement. Pour cela, j'ai décidé de créer d'abord un petit exécutable perso, avec un verrou très simple, et je me suis fixé l'objectif de le cracker en assembleur, comme l'explique très bien ce tutoriel anglais sur OllyDbg .

Programme de test

Le petit programme de test à cracker est donné ci-dessous. Il s'agit simplement d'un programme en console demandant un nombre pour pouvoir continuer son exécution (très similaire donc à un verrou par licence). Les sources seraient faciles à cracker, mais pour le code en assembleur, c'est autre chose…

// #include "stdafx.h" #include <iostream> #include <sstream> using namespace std; void execute() { string numbers; int hold; for(;;) { cout < < "Please enter the code: \n"; getline(cin, numbers); if(numbers != "82634") { cout << "\nTry again.\n"; } else { cout << "Code accepted"; break; } } cin >> hold; } int main(int argc, char* argv[]) { execute(); return 0; }
Mon programme de test: il faut réussir à le lancer même sans connaître le code (qui n'est vraiment pas très bien caché dans ces sources!)

Les sources complètes du projet, incluant le .exe original compilé et sa version crackée, vous sont disponibles ici. L'IDE utilisé est Code::Blocks, mais vous pouvez utiliser Netbeans ou tout autre IDE: le projet tient en un fichier .cpp qu'il vous sera facile d'importer.

Attaque du programme de test

Maintenant, j'attaque mon petit logiciel de test via l'assembleur! En l'ouvrant dans OllyDbg et en déroulant l'exécution pas à pas (F8), je m'aperçois que le programme bloque une première fois sur l'instruction 00401290 E8 6BFDFFFF CALL Diassemble_try_-_Copie.00401000. Celle-ci attend que l'utilisateur entre le code demandé. J'entre un code bidon, et je déroule à nouveau l'exécution pas à pas… Ah mince, OllyDbg ne bloque nulle part! Il faut donc relancer le programme, et faire défiler le code assembleur plus en détail, instruction par instruction, en retrant dans les procédures CALL (via F7). Notez qu'un exécutable peut faire appel à des DLL ou à d'autres modules. Regardez la barre de titre d'OllyDbg et la fenêtre "Executable Modules" pour savoir où vous vous situez. N'hésitez pas à jouer un peu avec le logiciel pour vous en forger votre propre compréhension.

J'ai alors fini par trouver l'instruction qui demande le code à l'utilisateur, suivit de celle qui le vérifie:

CPU Disasm Address Hex dump Command Comments 00401386 |> /C74424 04 24F /MOV DWORD PTR SS:[LOCAL.29], OFFSET Diassemble_try_-_Copie.0047F024 ; ASCII "Please enter the code:" 0040138E |. |C70424 409948 |MOV DWORD PTR SS:[LOCAL.30], OFFSET Diassemble_try_-_Copie.00489940 ; ASCII "lTH" 00401395 |. |C745 A8 01000 |MOV DWORD PTR SS:[LOCAL.22], 1 0040139C |. |E8 8F8F0700 |CALL Diassemble_try_-_Copie.0047A330 004013A1 |. |8D45 E4 |LEA EAX, [LOCAL.7] 004013A4 |. |894424 04 |MOV DWORD PTR SS:[LOCAL.29], EAX 004013A8 |. |C70424 009A48 |MOV DWORD PTR SS:[LOCAL.30], OFFSET Diassemble_try_-_Copie.00489A00 ; ASCII ",TH" 004013AF |. |E8 CC790700 |CALL Diassemble_try_-_Copie.00478D80 004013B4 |. |C74424 04 3DF |MOV DWORD PTR SS:[LOCAL.29], OFFSET Diassemble_try_-_Copie.0047F03D ; ASCII "82634" 004013BC |. |8D45 E4 |LEA EAX, [LOCAL.7] 004013BF |. |890424 |MOV DWORD PTR SS:[LOCAL.30], EAX 004013C2 |. |E8 A9960700 |CALL Diassemble_try_-_Copie.0047AA70 004013C7 |. |84C0 |TEST AL, AL 004013C9 |. |74 16 |JE SHORT Diassemble_try_-_Copie.004013E1 004013CB |. |C74424 04 43F |MOV DWORD PTR SS:[LOCAL.29], OFFSET Diassemble_try_-_Copie.0047F043 ; ASCII 0A,"Try again." 004013D3 |. |C70424 409948 |MOV DWORD PTR SS:[LOCAL.30], OFFSET Diassemble_try_-_Copie.00489940 ; ASCII "lTH" 004013DA |. |E8 518F0700 |CALL Diassemble_try_-_Copie.0047A330 004013DF |.^\EB A5 \JMP SHORT Diassemble_try_-_Copie.00401386 004013E1 |> C74424 04 50F MOV DWORD PTR SS:[LOCAL.29], OFFSET Diassemble_try_-_Copie.0047F050 ; ASCII "Code accepted" 004013E9 |. C70424 409948 MOV DWORD PTR SS:[LOCAL.30], OFFSET Diassemble_try_-_Copie.00489940 ; ASCII "lTH" 004013F0 |. C745 A8 01000 MOV DWORD PTR SS:[LOCAL.22], 1
Les instructions intéressantes de mon programme de test

Il ne me reste plus qu'à déterminer comment se déroule l'exécution du programme, et comment passer dans "Code accepted" plutôt que dans "Try again". Notez qu'à ce niveau-là, j'ai déjà trouvé le code statique de mon application de test (82634), que je pourrai directement utiliser dans cette même application. Mais SFT n'a pas de code statique: cette méthode ne marcherait pas, et il faut donc comprendre comment marche l'assembleur et passer l'instruction de vérification. Procéder de même dans SFT devrait alors être possible.

Le crack du programme de test

Le programme se déroule très simplement, en respectant les commandes assembleur d'Intelx86. Je place alors un point d'arrêt (F2) sur l'instruction 00401386 MOV DWORD PTR SS:[ESP+4], 47F024 et je relance l'exécution. Le programme s'arrête maintenant sur le point d'arrêt, et je peux dérouler l'exécution instruction par instruction, via F8. L'utilisateur rentre le code lors de l'instruction CALL 00478D80. Puis, la constante 82634 (le secret statique de ce test) est comparée à ce code dans TEST AL, AL, et suivant le résultat de la comparaison, l'instruction JE SHORT 004013E1 branchera l'exécution ou non.
Sommairement, si la comparaison a échoué, le JUMP (ou GO TO) n'est pas suivi, et l'exécution continue à la ligne suivante du programme, MOV DWORD PTR SS:[ESP+4], 47F043 ("Try Again"). Si la comparaison a réussie, le JUMP est suivi et le programme poursuit son exécution à l'emplacement 004013E1 MOV DWORD PTR SS:[ESP+4], 47F050 ("Code accepted"). Il faudrait donc que ce JUMP soit toujours suivi, quelque soit le code entré. Cela peut se faire grâce à l'instruction JMP, "Unconditional Jump" . Le GO TO sera donc toujours suivi, à l'image d'un if (true) qui sera toujours exécuté. Il me suffit donc de double-cliquer sur l'instruction JE SHORT 004013E1 et de la remplacer par un JMP SHORT 004013E1, et le tour est joué! En redéroulant l'exécution instruction par instruction (même pas besoin de relancer le programme) je vois alors que le JUMP est suivi quelque soit le code entré, et le programme est déverrouillé!

Enregistrer le crack

Dernière étape, l'enregistrement du crack. Pour l'instant, j'ai un code assembleur (de test) modifié, mais il faut encore pouvoir le sauvegarder en un .exe que l'utilisateur lancerait normalement. Pour cela, je sélectionne la zone modifiée (disons 00401367..004013FC), puis clic droit, Edit, Copy to executable. Une fenêtre avec le nouveau code exécutable apparait (je vérifie au passage que ma modification est bien là). Je clique droit à nouveau, Save file, et je sauve mon crack. Je le lance ensuite, comme un utilisateur lambda, et j'admire: je peux répondre n'importe quoi comme code, il sera accepté!

Cracker SFT

Maintenant, il me suffit d'appliquer le même procédé au logiciel que je souhaite cracker. Dans le cas de SFT, le crack m'a pris un peu plus de temps, car grossièrement, chaque ligne du fichier de licence est lue et vérifiée avant de passer à la suivante, jusqu'à l'appel à la DLL de vérification. Mais le principe rest le même:

CPU Disasm Address Hex dump Command Comments 00A62162 |. FF91 A0000000 CALL NEAR DWORD PTR DS:[ECX+0A0] 00A62168 |. DBE2 FCLEX 00A6216A |. 85C0 TEST EAX, EAX 00A6216C |. 7D 14 JGE SHORT SFTWIN_ori.00A62182 ... 00A6218D |. 85C0 TEST EAX, EAX 00A6218F |. 0F84 5F010000 JE SFTWIN_ori.00A622F4 ... 00A62308 |. 85C0 TEST EAX, EAX 00A6230A |. 7D 11 JGE SHORT SFTWIN_ori.00A6231D ... 00A6235B |. 85C0 TEST EAX, EAX 00A6235D |. 7D 11 JGE SHORT SFTWIN_ori.00A62370 ... 00A623AA |. 85C0 TEST EAX, EAX 00A623AC |. 7D 14 JGE SHORT SFTWIN_ori.00A623C2 ... 00A623FE |. 85C0 TEST EAX, EAX 00A62400 |. 7D 14 JGE SHORT SFTWIN_ori.00A62416 ... 00A62437 |. 85C0 TEST EAX, EAX 00A62439 |. 75 0C JNE SHORT SFTWIN_ori.00A62447 ... 00A6245B |. 85C0 TEST EAX, EAX 00A6245D |. 7D 14 JGE SHORT SFTWIN_ori.00A62473 ... 00A62493 |. 85C0 TEST EAX, EAX 00A62495 |. 7D 11 JGE SHORT SFTWIN_ori.00A624A8 ... 00A624C8 |. 85C0 TEST EAX, EAX 00A624CA |. 7D 11 JGE SHORT SFTWIN_ori.00A624DD ... 00A624FD |. 85C0 TEST EAX, EAX 00A624FF |. 7D 11 JGE SHORT SFTWIN_ori.00A62512 ... 00A62533 |. 66:85F6 TEST SI, SI 00A62536 |. BA 6CA94200 MOV EDX, SFTWIN_ori.0042A96C ; UNICODE "Networked" 00A6253B |. 75 05 JNE SHORT SFTWIN_ori.00A62542 ... 00A62561 |. 85C0 TEST EAX, EAX 00A62563 |. 7D 11 JGE SHORT SFTWIN_ori.00A62576 ... 00A62671 |. 66:85DB TEST BX, BX 00A62674 |. 74 0A JE SHORT SFTWIN_ori.00A62680 ... 00A626EB |. 66:85DB TEST BX, BX 00A626EE |. 74 07 JE SHORT SFTWIN_ori.00A626F7 ... 00A62762 |. 66:85F6 TEST SI, SI 00A62765 |. 74 10 JE SHORT SFTWIN_ori.00A62777 ... 00A62789 |. 68 7F2CA600 PUSH SFTWIN_ori.00A62C7F 00A6278E \. E9 99040000 JMP SFTWIN_ori.00A62C2C
Le code exécutable original: les JUMP sont conditionnels et nécessitent une licence valide

Ayant une licence valide, j'ai pu utiliser OllyDbg sur une machine dans laquelle l'exécution débouchait sur une licence acceptée. Cela m'a permis de simplement noter la liste des JUMP qui sont suivis (c'est à dire ceux où l'exécution du programme saute jusqu'à l'instruction spécifiée par le JUMP) et ceux qui ne le sont pas (l'exécution du programme se poursuit avec l'instruction juste derrière le JUMP non-suivi). Il m'a donc suffit de remplacer les "JUMP suivis" par des JMP (dits aussi "Always Jump") et les "JUMP non-suivis" par un NOP (signifiant "No Operation", le programme continuera donc avec la prochaine instruction). Sans une version fonctionnelle, il m'aurait fallu comprendre chaque instruction, et déterminer si le JUMP devait être suivi ou non. Le remplacement final donne le code assembleur ci-dessous.

CPU Disasm Address Hex dump Command Comments 00A62162 |. FF91 A0000000 CALL NEAR DWORD PTR DS:[ECX+0A0] 00A62168 |. DBE2 FCLEX 00A6216A |. 85C0 TEST EAX, EAX 00A6216C \. EB 14 JMP SHORT SFTWIN.00A62182 ... 00A6218D |. 85C0 TEST EAX, EAX 00A6218F \. E9 60010000 JMP SFTWIN.00A622F4 ... 00A62308 |. 85C0 TEST EAX, EAX 00A6230A \. EB 11 JMP SHORT SFTWIN.00A6231D ... 00A6235B |. 85C0 TEST EAX, EAX 00A6235D \. EB 11 JMP SHORT SFTWIN.00A62370 ... 00A623AA |. 85C0 TEST EAX, EAX 00A623AC \. EB 14 JMP SHORT SFTWIN.00A623C2 ... 00A623FE |. 85C0 TEST EAX, EAX 00A62400 \. EB 14 JMP SHORT SFTWIN.00A62416 ... 00A62437 |. 85C0 TEST EAX, EAX 00A62439 |. 90 NOP 00A6243A |. 90 NOP ... 00A6245B |. 85C0 TEST EAX, EAX 00A6245D \. EB 14 JMP SHORT SFTWIN.00A62473 ... 00A62493 |. 85C0 TEST EAX, EAX 00A62495 \. EB 11 JMP SHORT SFTWIN.00A624A8 ... 00A624C8 |. 85C0 TEST EAX, EAX 00A624CA \. EB 11 JMP SHORT SFTWIN.00A624DD ... 00A624FD |. 85C0 TEST EAX, EAX 00A624FF \. EB 11 JMP SHORT SFTWIN.00A62512 ... 00A62533 |. 66:85F6 TEST SI, SI 00A62536 |. BA 6CA94200 MOV EDX, SFTWIN.0042A96C ; UNICODE "Networked" 00A6253B |. 90 NOP 00A6253C |. 90 NOP ... 00A62561 |. 85C0 TEST EAX, EAX 00A62563 \. EB 11 JMP SHORT SFTWIN.00A62576 ... 00A62671 |. 66:85DB TEST BX, BX 00A62674 \. EB 0A JMP SHORT SFTWIN.00A62680 ... 00A626EB |. 66:85DB TEST BX, BX 00A626EE \. EB 07 JMP SHORT SFTWIN.00A626F7 ... 00A62762 |. 66:85F6 TEST SI, SI 00A62765 |. 90 NOP 00A62766 |. 90 NOP ... 00A62789 |. 68 7F2CA600 PUSH SFTWIN.00A62C7F 00A6278E \. E9 99040000 JMP SFTWIN.00A62C2C
Le code assembleur cracké: JMP et NOP remplacent les vrais tests

Notez qu'il aurait peut-être été possible de ne faire qu'un seul JUMP, au début, qui renvoie directement à JMP SFTWIN_ori.00A62C2C. Mais je ne l'avais pas vu…

Ce code maintenant modifié, il ne me reste plus qu'à relancer SFT dans OllyDbg. L'exécution du programme suit alors le même chemin qu'une licence valide, et le programme se lance! En pratique, il requiert toujours un fichier de licence, mais ce dernier peut contenir n'importe quoi: les JUMP ne sont plus conditionnels, mais s'assimilent à des if (true) ou if (false), qui accepteront n'importe quelles données de licence. Je sauve le programme comme précedemment, et le tour est joué!

J'ai montré le résultat final à mon employeur: il suffisait de lancer SFT-cracked.exe pour ouvrir le logiciel sans aucune licence valide. Cette simplicité d'utilisation l'a bluffé!

Bilan

L'assembleur permet donc de contourner toutes les restrictions que vous pouvez mettre sur un programme client. Il est donc totalement vain de vouloir les sécuriser (cela reviendrait à "sécuriser le javascript dans mon jeu web" pour reprendre une question que j'ai déjà entendue).

Cette démonstration m'a permis d'appuyer le principe ci-dessus, et la solution retenue a donc été de laisser tomber la "sécurisation" du client, et de considérer, en quelque sorte, que le client est "open-source" et ne doit contenir aucune logique métier, ni aucune donnée sensible. La valeur ajoutée du logiciel doit se trouver sur le serveur, dans lequel les codes métiers se trouvent (données de la BDD, génération de graphs, tableaux croisés, aide à la décision, etc). Le client n'embarque finalement rien de critique, et l'application lourde deviendra un site web plus classique, où l'utilisateur n'aura accès qu'à certaines données associées à l'entreprise cliente à laquelle il appartient.