Ou “Walkthrought: How to crack”. Cet article est un exemple détaillé de rétro-engineering d’une application client (un .exe de mon boulot) dans le but d’en tester la licence, pour prouver que celle-ci est déficiente et qu’une autre approche doit être envisagée.

Le rétro-engineering d’un logiciel 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. L’article vise donc à démontrer qu’on doit s’en remettre à la morale de l’utilisateur et non à des pseudo-sécurités codés dans l’application cliente.

L’environnement

Connaître les grands principes de (toute donnée entrante n’est pas fiable, toute donnée sortie n’est plus traçable, tout code/programme côté client peut être altéré, etc) permet de se prémunir de la grande majorité des failles applicatives. Mais si on ne visualise pas l’attaque qui peut exploiter ces failles, alors il peut être difficile de comprendre pourquoi ces principes doivent être respectés. Donc, voyons aujourd’hui une attaque par la pratique.

Objectif

A mon travail, nous avons une application cliente lourde (un exécutable) que l’on souhaite sécuriser, à l’image d’un logiciel ou d’un jeu qu’on achète avant de pouvoir le télécharger et le lancer. Le but est donc de s’assurer que celui qui lance ce programme a légitimement le droit de s’en servir. C’est une problématique très courante, que l’on peut retrouver dans les applications mobiles, les jeux PC, ou toute autre forme de programme qui doit être exécuté ailleurs que sur un serveur (sur lequel on aurait le contrôle). L’objectif est donc simple: quelqu’un sans licence peut-il se servir du logiciel?. Cela revient donc à se demander si le logiciel peut être piraté. Spoiler: oui, un programme côté client peut toujours être cracké.

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: 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, qui permet d’utiliser le programme sur la machine du client sans devoir ré-entrer la clef de libération.

On considère donc qu’un pirate n’a accès qu’aux mêmes informations que l’utilisateur: il n’a pas accès au système générant les clefs de libération, ni aux sources du projet. Eventuellement, on peut considérer qu’il a accès à une seule licence fonctionnelle (par exemple, via le poste d’un collègue). Cette condition ne sera toutefois pas nécessaire pour cracker le logiciel: elle ne fera que faciliter ce crackage. De plus, on considèrera que le pirate sait que la clef dépend du code unique fournit par l’utilisateur, qui varie d’une machine à l’autre, empêchant l’utilisateur de communiquer cette clef de libération à d’autres personnes qui voudraient utiliser SFT.

Méthode général

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. Ne se concentrer que sur une et une seule piste serait souvent trop vain (si, manque de bol, cette piste n’aboutit pas, alors on aura perdu du temps). Il faut donc trouver un maximum de pistes et d’idées, ce qui se fait souvent en creusant une piste jusqu’à ce qu’elle devienne plus compliquée qu’une autre. On creuse alors l’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. Les pistes ont donc tendance à se croiser, mais pour clarifier la lecture de l’article, je détaillerai à fond chacune d’elle avant de passer définitivement à la suivante. En pratique, sachez que vous ne procéderez pas ainsi: vous mettrez de côté une piste quand celle-ci sera devenue plus complexe qu’une autre, que vous privilégierez alors.

Attaques

Par la clef de libération

En lançant SFT, le logiciel fournit le code unique suivant: bm9rZXkKbm9rZXkKbm9rZXkKTm90IEF2YWlsYWJsZQpub2tleQpBUFBMRSAgLSA1NwpDMDJDUDFGRURDN0MKbm9rZXkKQURNSU5B. 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

On s’aperçoit qu’il s’agit d’un base64, auquel le code unique aurait été accolé. On retire donc le code unique:

U0ZUDQoyLjAuMA0KU2luZ2xlDQoyDQpBRE1JTkENCjANCjIwMTctMDEtMjMNCg0KMA0KQUFBQUIzTnphQzF5YzJFQUFBQ0FUWWdkZ2ovYnBaZDk0OGZWWVIydnF4bHVXb1l0SktoSGNobU5lM3FwSFo5NVI5ODczeWl0dldyZlJiN1RVUmpsR1piczZwZVNmb1RVUFB2VFZQVHRJYW81TCs4VEpFQXd0ak9JY0lUcnhaOGhIY05BMjVjZ3JlZGw2dHpLcDkrR0NzMk94T3BZS28xMEw1VTNtM3MrNlNBc2haTUlqWkNsTnY2bTVsakxEQjAaLck

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

SFT
2.0.0
Single
2
ADMINA
0
2017-01-23

0
AAAAB3NzaC1yc2EAAACATYgdgj/bpZd948fVYR2vqxluWoYtJKhHchmNe3qpHZ95R9873yitvWrfRb7TURjlGZbs6peSfoTUPPvTVPTtIao5L+8TJEAwtjOIcITrxZ8hHcNA25cgredl6tzKp9+GCs2OxOpYKo10L5U3m3s+6SAshZMIjZClNv6m5ljLDB0-

En comparant ces données à la licence (voir piste suivante), on s’aperçoit 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. On s’attardera 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. Celle-ci prend la forme d’un fichier texte, SFT.lic contenant entre autres le code unique de la machine, 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

En analysant un peu, on s’aperçoit 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 et que LicenseCode est le code unique de l’utilisateur.

En continuant la réflexion, on voit que le LicenseCode est un base64 que l’on peut aisément décoder.

nokey
nokey
nokey
Not Available
nokey
APPLE  - 57
C02CP1FEDC7C
nokey
ADMINA

On en déduit 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 non-spécifique, à laquelle on n’a pas demandé tous les arguments possibles).

Si on altère un élément de la licence (n’importe quelle ligne), le logiciel refuse de démarrer. On en déduit donc qu’une redondance doit exister dans la licence, certainement le Hash1. En cherchant les différents noms des champs de la license (LicenseType LicenseClass LicenseKey) 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), 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, on peut finir par découvrir que Hash1 est en fait un simple MD5 du LastUsed.

On connait 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 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, 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 on dispose correspond au code unique bm9rZXkKbm9rZXkKbm9rZXkKTm90IEF2YWlsYWJsZQpub2tleQpBUFBMRSAgLSA1NwpDMDJDUDFGRURDN0MKbm9rZXkKQURNSU5B: si tous les PC sur lesquels on veut faire tourner SFT renvoyaient ce code unique, alors on pourrait 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, on tombe sur un générateur dans lequel ce code peut être entré. On apprend 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. 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à.

La vue d’Alugen, 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. De plus, cela contraint l’utilisateur à faire tourner SFT dans une VM, ce qui n’est pas forcément à la portée d’un utilisateur lambda.

Emuler le serveur de licence

Les pistes précédentes nous 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 disponibles 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 retrouve 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. On pourrait é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, on a donc les codes sources qui vont avec. L’idée serait de s’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 nous apprend que SFT utilise deux DLL, ActiveLock3.dll et ALUGen.dll (on retrouve nos pistes précédentes). Il faudrait donc réussir à compiler ces DLL via les sources publiquement disponibles pour hacker notre logiciel. Ces sources sont en VB (mince…!).

Beaucoup de versions d’ActiveLock existent, et avant de plonger dans la création/modification d’un projet VB, il faudrait savoir quelle version nous 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 IDE n’a su prendre le projet en charge: j’ai donc vite abandonné cette piste.

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 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 version 2.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;
}
Le programme de test: il faut réussir à le lancer même sans connaître le code!

Les sources complètes du projet, incluant le .exe original compilé et sa version crackée, sont données à cette adresse: https://toile.reinom.com/wp-content/uploads/2017/05/Diassemble-try.zip. 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, attaquons ce logiciel en assembleur. En l’ouvrant dans OllyDbg et en déroulant l’exécution pas à pas (F8), on s’aperçoit 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é. On entre un code bidon, et on 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.

Normalement, vous devriez finir 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

Il ne 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à, vous avez déjà trouvé le code statique de l’application (82634), que vous pourriez 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 pour passer l’instruction de vérification.

Le crack du programme de test

Le programme se déroule très simplement, en respectant les commandes assembleur d’Intelx86. En effet, placez un point d’arrêt (F2) sur l’instruction 00401386 MOV DWORD PTR SS:[ESP+4], 47F024 et relancez l’exécution. Le programme s’arrêtera sur le point d’arrêt, et vous pourrez 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 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 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!) vous verrez que le JUMP sera 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, vous avez un code assembleur modifié, mais il faut encore pouvoir le sauvegarder en un .exe que vous lancerez alors normalement. Pour cela, sélectionnez la zone modifiée (disons 00401367..004013FC), et cliquez droit, Edit, Copy to executable. Une fenêtre avec le nouveau code exécutable apparait (vérifiez que votre modification est bien là). Cliquez droit, Save file, et sauvez votre crack. Lancez-le normalement, et admirez: vous pouvez répondre n’importe quoi comme code, il sera accepté.

Cracker SFT

Maintenant, il suffit d’appliquer le même procédé au logiciel que l’on souhaite cracker. Dans le cas de SFT, le crack 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.

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 aurait fallu comprendre chaque instruction, et déterminer si le JUMP devait être suivi ou non. Notez qu’il aurait peut-être alors été possible de ne faire qu’un seul JUMP, au début, qui renvoie à JMP SFTWIN_ori.00A62C2C. 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

Ce code maintenant modifié, il ne 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. On sauve le programme comme précedemment, et le tour est joué!

Bilan

L’assembleur permet donc de toujours contourner les restrictions que vous pouvez mettre sur un programme client. Il est donc totalement vain de vouloir les sécuriser. Cette démonstration a permi de mettre en lumière que la faille n’est pas corrigeable, peu importe la méthode utilisée: il me sera toujours possible de faire des JUMP non-conditionnels sur ma machine. 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.