Académique Documents
Professionnel Documents
Culture Documents
https://f95zone.to/threads/beginners-guide-to-cracking.59711/
Disclaimer:
Avertissement: Je ne suis en aucun cas un expert sur ce sujet, mais j'ai réussi à résoudre certains problèmes
de la vie réelle et j'ai donc quelques compétences. De plus, ce guide ne traite que des applications
Windows. J'imagine que les principes généraux s'appliquent à d'autres systèmes d'exploitation, mais je n'en
ai aucune expérience. Ce guide s'adresse principalement aux personnes qui aimeraient essayer de cracker
quelque chose et qui ont des connaissances techniques, mais qui ne savent pas par où commencer. J'ai
organisé le guide en sections. J'ai eu du mal à trouver un ordre logique dans lequel tout couvrir, donc ça
pourrait être un peu un vidage d'informations, désolé pour ça. Étant donné que les sections ne suivent pas
un ordre strict, vous pouvez ignorer celles qui ne semblent pas intéressantes.
Compétences préalables
1. Connaissance générale du fonctionnement d'un ordinateur au niveau inférieur
En fin de compte, le point n°3 est le plus important. J'apprends généralement beaucoup sur le
fonctionnement des ordinateurs en essayant de déchiffrer quelque chose. Vous apprenez le plus en faisant,
alors ne vous découragez pas trop si vous ne comprenez pas encore quelque chose. Cela dit, si vous n'avez
pas les points 1 ou 2, la courbe d'apprentissage initiale sera ridiculement raide. Dans ce cas, vous feriez
probablement mieux de faire des projets axés sur l'apprentissage de ces compétences générales avant de
revenir au sujet du cracking. Savoir programmer est important car de nombreuses méthodes vous obligent
à écrire et compiler un programme. Un langage comme C++ est le plus utile pour le travail de crackage
"réel", mais un langage de haut niveau tel que Python est également très utile pour automatiser toutes les
tâches diverses qui se présentent.
Useful tools
---------------
Notepad++ (https://notepad-plus-plus.org/)
(plain-text editor)
Use:
- Taking notes
- General purpose text editing
- Viewing files that are mostly text
Notes:
- There is a button for "show all characters". This will show whitespace characters and line-endings. Use it
whenever you want to be sure that you are seeing the full file.
- Check the "Encoding" menu. Some mostly-text files won't display properly if you use the wrong
encoding.
HxD (https://mh-nexus.de/en/hxd/)
(hex-editor)
Use:
- View binary data of any file
- Check for differences between two files or just check if two files are identical
- Can manually edit files in a pinch
Notes:
- Has side-tab that shows ascii encoding of data. Lets you scan through files for readable strings, which are
more common than you might think.
Fiddler 4 (https://www.telerik.com/fiddler)
(Gives information on web traffic)
Use:
- Lets you see which web requests are being made on your machine
- Also lets you decode requests or setup automated responses
Notes:
If you really want to get into the network side of things there's also WireShark which does similar things
but is more complex
ILSpy (https://github.com/icsharpcode/ILSpy)
(Decompiler for managed code)
Use:
- Let's you decompile managed code into a (from my experience) quite readable result
Notes:
Unity applications use C# which compiles to managed code.
Other than that most applications use unmanaged code though so you might not get much use out of this
one.
x64dbg (https://x64dbg.com/)
(General purpose debugger)
Use:
- Let's you see which assembly code a program is running and step through the code
- Also lets you see which modules are loaded, what the memory space looks like, and which threads are
running
Notes:
Some applications can only be latched onto if you are running the debugger in admin mode.
Many applications you will encounter (especially dedicated DRM systems) will check for a debugger and
shutdown if one is found. Of course, checking for a debugger still requires code to be executed, so if you
are stepping through the program line-by-line you can probably do something to prevent the debugger
check from triggering. But there is a LOT of assembly code so it most cases you want to run until a
breakpoint or until a function returns and in those situations you often find that a debugger check got
triggered somewhere and shut everything down.
1. You can latch onto an application after it is running, so debugger-checks in the start-up phase are
bypassed
2. The process is paused when the debugger latches on, so you get a snapshot of the program at that
time which you can analyze for as long as you wish. (and you can use hooks to pause the program
and latch on during a moment of interest. More on hooks later)
3. Debugger checks don't happen constantly. You might be able to step through quite a bit of code
before encountering one.
4. If the program uses the IsDebuggerPresent windows function, then you may be able to hook it and
disable the checks that way.
Warning:
I only started using a debugger in my workflow fairly recently and found it incredibly helpful. However
my advice regarding it's use and assembly language in general might not be optimal since I'm still learning.
Notes:
I've had one instance where a process couldn't be killed with Task Manager but Process Explorer could.
Something to keep in mind
Process Monitor (https://docs.microsoft.com/en-us/sysinternals/downloads/procmon)
Use:
- Let's you view file-reads, file-write, registry access, dll loads, and a few other event types
- For each event, also gives you the stack-trace of the program at that time
Notes:
Together this can tell you a lot about what the program is doing.
Example 1:
You have an archive file of an unknown format and you see the program do something like:
- Read a bit in section A
- Suddenly do a far jump and read a block of data in section B
Then section A is likely an index of some sort and section B holds the raw file data.
Try extracting the data block that got read from section B and see if it's readable.
If not there is probably a second decryption or decompression step applied.
Example 2:
You see the program read through an entire file in equal-sized chunks.
The program is probably performing some sort of integrity check on the file.
Example 3:
You see the program make a failed registry access under a program-specific name
If it's a registry "folder", try creating it and seeing how the program responds.
If you're lucky the program may be trying to read a "IsVerified" value which you can just set to 1 (true)
Admittedly, this DRM setup would be pretty dumb, but there are a lot of dumb DRM setups out there. It's
worth trying the simple stuff.
Warning:
Similar to using the debugger, you have a problem with information overload here.
Programs will trigger a ton of events and most of them are not noteworthy.
The first time I used Process Monitor I saw it making an access to a cryptically named registry key and
thought "Aha! That's where the encryption key is stored!". But no, it was just one of the registries that gets
accessed during normal execution of windows programs.
It might be worthwhile looking at a simple program like say notepad just to get an idea of what the
baseline level of event spam is.
Les détails techniques ----------------------------- Maintenant, vous vous demandez peut-être: OK c'est gentil et tout,
mais comment puis-je faire cela? Toutes ces informations sont disponibles en ligne afin que vous puissiez
simplement Google, mais depuis que j'essaie d'être utile ici, je vais également passer sur certaines des méthodes
exactes. Quand j'ai appris pour la première fois à être un talonneur, j'ai utilisé une bibliothèque C # appelée
EasyHook. Cela fonctionne, en quelque sorte, et vous pouvez vous donner un coup si vous aimez vraiment C #, mais
je vous conseillerais d'utiliser C ++ à la place. Raisons d'utiliser C ++ au lieu de C #: 1. C # est du code géré afin que
vous injectionsiez du code géré dans une application non gérée. Cela fonctionne dans la plupart des cas, mais je
pense que cela peut causer un étrange comportement en fonction de ce que vous accrochez. J'ai eu un problème
avec des crochets échouant qui s'est éloigné une fois que je suis passé à C ++ En tant que 2. Avec C ++, il est
beaucoup plus facile de décrire avec précision les signatures de fonction. Par exemple, si vous regardez la fonction
de l'API Windows GetProcAddress, vous verrez la signature est la suivante: FARMROC GETPRODDRESS (HMODULE,
LPCSTR) En tant que Maintenant, vous vous demandez peut-être: WTF est une hmodule, qu'est-ce qu'un LPCSTR?
Qu'est-ce qui est lointain? Si vous continuez à creuser un peu, vous trouvez que: FarProc peut être traité comme
vide * Hmodule peut être traité comme vide * Lpcstr est char * est char * En tant que Maintenant, vous devez
trouver tous les équivalents C # pour construire la définition de la signature et si vous faites des erreurs, cela ne
fonctionnera pas. Avec C ++, vous venez d'importer une fenêtre.h et utilisez directement la merde LPCSTR. Dans
Visual Studio, vous pouvez également survoler le type pour voir ce que le type sous-jacent est. Et si vous utilisez le
type de manière incorrecte, vous obtiendrez probablement une erreur de compilateur. Quoi qu'il en soit, vous
pouvez être sûr que votre signature de fonction est exacte à la documentation. Alors ya, épargnez-vous une
douleur et utilisez c ++ Quoi qu'il en soit, disons que vous souhaitez accrocher une application et votre journal
lorsqu'il écrit dans un fichier. Démarrez Visual Studio et créez un nouveau projet C ++ Console Console, permet de
l'appeler "injecteur". Ajoutez un nouveau projet à votre solution, cette fois une DLL C ++, appelez-la "in3jectiondll".
Si "l'injecteur" n'est pas en gras, cliquez dessus avec le bouton droit de la souris et définissez-le comme "Projet de
démarrage". Aussi cliquez avec le bouton droit de la souris sur le bouton droit de la souris sur les dépendances de
la construction> Dépendances du projet et définissez In3jectionDLL comme une dépendance. Maintenant, lorsque
vous appuyez sur le bouton de démarrage, les deux projets construiront (si nécessaire) et «injecteur» seront
exécutés. Comme j'écris cela, je pense que je vais inclure ce programme de démonstration avec le message au lieu
d'écrire tout ici. Je vais simplement passer sur les points de haut niveau ici. Écrivez le code pour faire l'injection DLL
vous-même. Au début, j'ai cherché une bibliothèque pour cela, mais c'est en fait facile comme merde. (seulement
comme 30 lignes de code) Les étapes sont: - Obtenez une poignée à votre processus de victime avec les
autorisations nécessaires (peut avoir besoin d'exécuter en mode administrateur en fonction de ce que vous ciblez)
- Obtenir l'adresse de LoadLibrarya - allouer la mémoire dans le processus de victime et copier le chemin de votre
dll d'injection - Créez un fil distant qui appelle LoadLibrarya avec le chemin de votre DLL d'injection comme
argument (Étant donné que l'argument est en réalité un pointeur, nous avons dû charger les données de chaîne
dans le processus de victime en premier) - Attendez que le fil distant finisse - Fermez la poignée de processus et la
mémoire allouée librement (ou non, l'injection DLL est effectuée à ce stade) Si vous utilisez la méthode d'injection
n ° 1, vous ajoutez les étapes d'emballage: - lancer EXE dans l'état suspendu (nécessite le chemin de fichier),
renvoyer une poignée - Autres étapes - libérer EXE de l'état suspendu Si vous utilisez la méthode d'injection n ° 2,
vous ajoutez les étapes d'emballage: - Obtenez la poignée pour exécuter le processus (nécessite le PID), retourner
une poignée - Autres étapes Et c'est tout l'injecteur doit faire. Vous pouvez copier-coller le code de l'injecteur pour
chaque projet de craquage que vous effectuez ou configurez quelque chose de fantaisie qui réutilise le même
projet pour tout. Pour l'injectiondll, le point d'entrée est la fonction appelée DLLMAIN. Pour commencer, écrivez
simplement un message dans un fichier et confirmez que le code DLLMain est appelé. Pour effectuer l'accrochage,
j'utilise une bibliothèque appelée Minhook (https://www.codeproject.com/articles/44326/minhook-the-
minimalistic-x-api-hooking-libra) Il y a d'autres bibliothèques d'accrochage, mais je vais juste couvrir Minhook ici.
Installez Minhook dans votre projet DLL et appelez mh_initialize () pour vous assurer que cela fonctionne. Le
processus général de création d'un crochet est: - Créer un TYPEDEF pour la poignée de la fonction. - Créer une
variable globale pour contenir le pointeur de fonction d'origine - Créer une fonction avec la même signature qui
sera appelée à la place - appelez mh_createhook pour faire le crochet - appelez mh_enablehook pour l'activer
(n'oubliez pas cette étape) Dans notre exemple, le code est:
C:
typedef BOOL (__stdcall* WriteFileDef)(HANDLE, LPCVOID,DWORD,LPDWORD,LPOVERLAPPPED);
WriteFileDef WriteFileOriginal = NULL;
void hookWriteFile(){
if(MH_CreateHook(&WriteFIle, &WriteFileDetour, &WriteFileOriginal) == MH_OK){
// Created successfully
if(MH_EnableHook(&WriteFile) == MH_OK){
// Success!
}
else{
// TODO: Print error message
}
}
else{
// TODO: Print error message
}
}
Ensuite, pour appliquer le hook, vous appelez simplement hookWriteFile() dans DllMain après avoir appelé
MH_Initialize()
C:
void* hookAddr = (void*)GetProcAddress(GetModuleHandleA("external.dll"), "ReadFile");
MH_CreateHook(hookAddr, &WriteFileDetour, &WriteFileOriginal);
MH_EnableHook(hookAddr);
Note that for this to work, "external.dll" needs to be loaded by the process.
Que faire si ma dll cible n'est pas chargée ? ---What if my target dll is not loaded?--
Deux options: Chargez-le vous-même en appelant LoadLibrary() Lorsque le processus hôte essaiera de le
charger à nouveau plus tard, il renverra simplement un descripteur au processus déjà chargé. Accrochez
LoadLibrary et attendez que le processus hôte charge la dll cible. Chargez-le, puis installez vos crochets
avant de revenir.
Piège : caractères larges vs caractères étroits -------------------------------------------------- --------
Comme vous le savez peut-être, en C++, il existe des caractères "normaux" (char) et des caractères larges
(wchar_t) char est de 1 octet (et s'habitue souvent à ne représenter que des données binaires non
textuelles) tandis que wchar_t est de deux octets. Dans le contexte du texte, char est utilisé pour les
caractères ascii tandis que wchar_t est pour l'unicode et d'autres jeux de caractères plus larges. Si vous
avez un fichier avec un caractère japonais dans le nom, vous souhaitez utiliser une chaîne de caractères
large pour représenter le chemin. Quoi qu'il en soit, en gros, tous les appels d'API Windows qui
impliquent du texte ont une version large et "étroite". La version large a un suffixe W tandis que la
version étroite/ascii a un suffixe A. La version par défaut de tous les appels d'API est la version large. Par
exemple, si vous voyez LoadLibrary("x.dll"), il s'agit en fait de LoadLibraryW("x.dll"). LoadLibrary est une
macro qui se développe en LoadLibraryW Un appel à LoadLibraryA et un appel équivalent à LoadLibraryW
se résoudront à une fonction commune sur toute la ligne, mais je préfère ne pas accrocher des trucs de
très bas niveau. Si vous souhaitez surveiller chaque bibliothèque chargée, connectez simplement
LoadLibraryA et LoadLibraryW.
La même idée s'applique aux autres fonctions de l'API.
Piège : convention d'appel incorrecte ----------------------------------------------
Si vous vous trompez sur la convention d'appel d'une fonction, vous obtiendrez un message d'erreur très
utile qui dit quelque chose sur le registre ESP et suggère que vous avez foutu la convention d'appel. Il
existe quelques conventions d'appel, mais les deux que je rencontre constamment sont __stdcall et
__cdecl. Une définition de fonction qui spécifie la convention d'appel ressemble à :
Si vous connectez une API documentée, elle vous indiquera quelle convention d'appel utiliser, souvent
spécifiée via une macro.
Par exemple, l'API Windows a la macro WINAPI qui se développe en __stdcall Utiliser des macros pour
écrire des hooks plus rapidement --------------------------------------------
Vous remarquerez peut-être qu'il y a beaucoup de répétitions lors de l'écriture d'un crochet. Vous devez
écrire deux fois la définition de la fonction et créer plusieurs types et objets portant le même nom. Eh
bien, vous pouvez configurer des macros pour accélérer le processus. J'inclurai les macros que j'utilise
actuellement dans le projet de démonstration. En bref, ils vous permettent d'écrire un crochet comme
celui-ci :
C:
HOOK_DEF(LoadLibraryW, __stdcall, HMODULE, LPCWSTR moduleName) {
// Do something
return fp_LoadLibraryW(moduleName)
}
CREATE_HOOK_SIMPLE(LoadLibraryW)
When hooking a documented API you usually know all three but sometimes you aren't so lucky.
Let's go through them in order:
Case 1: I don't know anything
--------------------------------------
At the very minimum you need to know what your looking for.
Usually this goes something like "I think there is a function that does X, I want to hook it"
Let's say you are trying to remove a DRM check. You speculate that the program has a function like
IsUserAuthorized() which just returns a boolean value. You want to hook this function and have it always
return true.
To make this more challenging, lets also say that the program uses aggressive debugger checking, so
stepping through the assembly code for any extended length of time is not an option.
The first step is to analyze where this function call is taking place. Look at the events triggered by process
monitor and see if you can determine a region where the DRM check is likely happening. Lets say you
determine that while the DRM check is happening a public API function X is being called.
Hook function X and make the thread sleep for, say, 15 seconds, then return the proper value. This way
you are ensuring that the IsUserAuthorized() function is running for at least 15 seconds. Launch the
program (with the hook) and within that 15 second window, latch on with the debugger.
The program is now paused within the DRM check and you can analyze to your hearts content.
(Note: There are usually a lot of threads running. Make sure you are looking at the correct one)
Look at the stack trace and hop around looking at the functions that are currently active. IsUserAuthorized
is probably somewhere near the top (assuming it exists).
This step is obviously easier the more you know about assembly.
I'm not great at assembly myself so I don't want to give too much advice on it here, but two keys things to
note are:
1. Arguments to a function get added by pushing them to the stack. If you see X "push" commands before
a function call, the function probably takes X arguments.
2. Function definitions usually have some "blank" space before them in the form of "INT3" instructions
Once you find a candidate function, note down it's address. (that is, the address of the first instruction in
the function) If the function is within the memory space of a module, write down the base address of the
module.
You can also use the raw address directly, but it is less reliable because modules are not guaranteed to be
loaded in the same place.
Note of caution:
As far as my understanding goes, not all assembly level functions can be hooked seamlessly.
"Proper" functions follow some rules like not modifying the registers. At the assembly language level
anything goes. You could have an assembly-level function that modifies registers and external code that
depends on the registers being modified. In that case adding a "proper" function wrapper breaks the code
since the wrapper function will restore registers to their original value preventing the internal function
from having the same effect.
Side note:
The code for calculating a raw address looks like:
void* address = (DWORD)GetModuleHandleA("baseModule") + (DWORD)0x187a0
There are probably a few ways to format it, but that's the method that's worked for me.
You can experimentally figure out how many arguments a function takes.
This may be wrong, but I think when dealing with 32bit applications, every "type" can just be seen as a 32
bit value.
So incrementally try:
UINT32 __stdcall funName()
UINT32 __stdcall funName(UINT32 arg0)
UINT32 __stdcall funName(UINT32 arg0,UINT32 arg1)
etc.
In each case, just call the original function, return the value, and do nothing else.
From my experience:
- If you get the number of arguments wrong, the program will just crash without any error message or a
very generic one.
- If you have the number of arguments right but the calling convention wrong, you will get an error
message telling you you're using the wrong calling convention
- If you have the function signature right and you're not hooking a weird assembly-level function, then the
program will run like normal!
So, add arguments until you get a calling convention error, then cycle calling conventions until the errors
stop.
If you reach 10+ arguments, check your assumptions. It's very unlikely for a function to have that many
arguments.
Case 3: I know the address and signature but not what it does
------------------------------------------------------------------------------
Start analyzing the function by printing the arguments and the returned value (from the original function)
as hex values.
If the value is relatively low, it's probably a raw value. If it's high it's probably a pointer of some sort.
To figure out pointers, latch on with the debugger and check where in memory the potential pointer is
pointing. If it points to a section of 0 bytes, it's probably not a pointer. Similarly, if it points to the middle
of a section that looks completely random, then it's probably not a raw pointer either.
If you're lucky, you can recognize some data in the area the pointer is pointing to.
You should also try stepping through the function with the debugger if you're able to. Make note of values
that come up and see where they re-appear.
Primer on XOR
-------------------
If you aren't familiar with the XOR operation (also written: ^), the key properties are:
1. It's a bitwise operation, meaning the inputs and outputs are the same length
2. A ^ B = B ^ A
3. A ^ (B ^ C) = (A ^ B) ^ C
4. A ^ A = 0 (ie, a sequence of 0 bits as long as A)
5. A ^ 0 = A
Security systems love xor-ing shit so it's a good operation to be familiar with.
1. XORing the plaintext to the ciphertext results in a repeating sequence (or one with a clear pattern)
(VERY WEAK)
2. XORing the plaintext to the ciphertext results in a non-repeating random sequence
2a. ...but this sequence is the same for any plain-cipher pair
2b. ...and this sequence is unique for any plain-cipher pair
3. Blocks of plaintext data (often 16 bytes) that are equal get mapped to equal ciphertext blocks
The smaller the blocksize is, the more relevant this property is.
4. Equal plaintext files get equal ciphertext files
5. Plaintext results in a ciphertext of equal size (pretty common)
Assuming you get your hands on some plaintext-ciphertext pairings, most of these properties are pretty
easy to check so I'd recommend doing so.
If your properties are:
XORing results in no usefull data (2b)
Equal blocks don't necessarily map the same (not 3)
Equal files map the same (4) (hard to check though)
AES Level 1:
----------------
Let's just abstract away the details of how a block gets processed and view it as two functions:
encrypt(key,plain-block) = cipher-block
decrypt(key,cipher-block) = plain-block
(where decrypt(key,encrypt(key,x)) = encrypt(key,decrypt(key,x)) = x)
It's important to remember that encrypt/decrypt only works on binary sequences that are exactly 128 bits
long (the block size)
The main two modes of AES, ECB & CBC, that I will go over take the general approach of breaking the
input up into block-sized chunks and running encrypt/decrypt on each chunk.
But what if the input sequence is not divisible by the block size? Then you have some 1-127 bits leftover
that still need to be dealt with.
2. Use a different encryption scheme for the last part that does not have the block-size restriction
There are a lot of encryption schemes out there that don't have the block-size restriction, including
some variants of AES.
Often encryption schemes aren't too complicated when run on very short sequences of data. The
whole system may simplify down to XORing the remaining X bits with the first X bits of a static
sequence.
Right, so assuming the data has been adjusted so it can be represented as a sequence of blocks, how do we
encrypt it?
(For the following sections plainX means "Xth block of plaintext" and cipherX means "Xth block of
ciphertext")
ECB Mode
--------------
For AES using ECB mode:
cipher1 = encrypt(key,plain1)
cipher2 = encrypt(key,plain2)
...
cipherN = encrypt(key,plainN)
This has the weakness that identical blocks will always get encrypted the same way. If you have access to
some plain-cipher pairs then you may be able to partially decrypt a different file by seeing which cipher
blocks match ones from your known set.
You also know that if two cipher files have equal cipher blocks in a location, then the plain-text files are
identical for those blocks.
That said, if your goal is to decrypt the whole file, ECB mode doesn't really help you. You're still gonna
need the key.
For practical purposes ECB mode is useful since for one-block inputs it is identical to the underlying
encrypt/decrypt functions upon which the other AES modes are built.
CBC Mode
--------------
Unlike ECB, AES in CBC mode requires an additional parameter called an IV (initialization vector).
The IV is always 1 block long regardless of the key size.
Since every block (expect for the first one) uses the output of the previous block, cipherN depends on
every plain-text block that came before it as well as the IV. This means that two inputs will only get
identical outputs for as long as they are identical, after the first deviation they will look completely
different (doesn't matter if they re-converge later).
However from a cracking perspective, this scheme isn't really harder to deal with than ECB.
The decryption pattern looks like this:
plain1 = decrypt(key,cipher1) ^ IV
plain2 = decrypt(key,cipher2) ^ cipher1
plain3 = decrypt(key,cipher3) ^ cipher2
...
plainN = decrypt(key,cipherN) ^ cipherN-1
Since you have access to all the cipher blocks, the chained nature of the algorithm doesn't really
complicate your computations.
A happy little fact of life is that if you use the wrong IV only the first block will be incorrect. This means
you can hunt for the key before worrying about the IV.
Once you have the key you can determine the IV as long as you know the first plain-text block of any file.
Many file types start with a consistent header that takes up the first block, so in many cases you can deduce
the first block by just looking at the extension.
The result you get from your incorrect decryption of cipher1 is:
decrypt(key,cipher1) ^ wrongIV
Simple.
It's for this reason that the IV is not considered to be a second "key", just a parameter.
AES Level 2:
---------------
So how does the encrypt/decrypt function actually work?
Code:
block = block ^ keys[0]
for i in [1,2,...X-1]:
// Round i
block = subBytes(block)
block = shiftRows(block)
block = mixColumns(block)
block = block ^ keys[i]
return block
Code:
block = block ^ keys[X]
for i in [X-1,...2,1]:
// Round i
block = inv_shiftRows(block)
block = inv_subBytes(block)
block = block ^ keys[i]
block = inv_mixColumns(block)
return block
Note on possible confusion:
At first glance it might look like decryption is not symmetric since the looped code does not read:
Code:
block = block ^ keys[i]
block = inv_mixColumns(block)
block = inv_shiftRows(block)
block = inv_subBytes(block)
but that is just because its still formatted as loop + extra. If you read the operations for encryption
backwards you'll see it all matches up.
I won't go into more detail than that on the sub-ops (that would be level 3). Google it if you wish to know
more.
Now you might be wondering: That's great and all, but how does this affect my cracking?
Two ways:
1. If your digging through code, it's good to have a general idea of what the AES algorithm looks like so
you can recognize it when you encounter it
2. When an AES encryption is taking place, you should be able to find the expanded key in memory.
Knowing how to reliably get the base key from the expanded key requires you to understand this.
So, you've found an expanded key in memory, how do you get the base key?
For a normal expanded key, the base-key is just the first X bits where X is the length of the base key.
When decryption is performed, the expanded key may be loaded in reverse block order, so you need to
grab the last blocks, not the first.
This is all super simple UNLESS the algorithm implements the "equivalent inverse cipher" variant.
// Round X
block = inv_subBytes(block)
block = inv_shiftRows(block)
block = block ^ keys[X]
Almost the exact same as the encryption function! Neat!
However to make this work, some adjustments need to be made to the expanded key.
1. The keys are accessed 0->X, so the order of keys needs to be reversed to compensate.
2. In the repeated round, the XOR operation is moved after the mix-columns step.
Luckily this sub-op has the property that:
inv_mixColumns(x ^ y) = inv_mixColumns(x) ^ inv_mixColumns(y)
So if we pre-apply the inv_mixColumns sub-op to all key values other than the first and last we'll get the
same result.
Note:
If you're dealing with a 128bit key, it's just the last block in the expanded key. Don't even need a program
for that one.
This process can be further complicated if the algorithm uses a different representation for the 4x4 abstract
matrix.
I think it's most common that for a block with the bytes x0, x1, x2, ... the 4x4 matrix is:
x0 x1 x2 x3
x4 ...
x8 ...
x12 ..
A simple algorithm you could use to assign a "randomness" score to a sequence is:
Code:
// Initialize with 0's
byte histogram[256];
score = 0
for count in histogram:
score += (count/len(data) - 1/256)^2
return score
This will result in a score of 0 if the probability distribution if perfectly flat and higher scores the more it
deviates from the norm.
Note:
You can only get a good estimate of the probability distribution for large files. Determining if a short
sequence of binary data is encrypted is very difficult.
It's pretty hard to have a DRM system that does not require an internet connection, so projects under this
category are more likely to deal with extracting encrypted assets than removing DRM.
Assuming you have all the files that were present during the game's installation, this type of project is
always theoretically crackable. All the information is there, you just need to deal with obfuscation.
Tier 0.5: Games that send web-requests, but the web-requests don't contain vital* data
This could be because the DRM system is kind of dumb, or because the game has optional online features
(most games that use the steam API don't really *require* it)
If the remote server does not hold data that you need, then you can cut it out of the equation. Either by
having the game contact a fake server you control, tricking the game into thinking an incorrect server
response is valid, or bypassing the web-request code entirely.
Although on a fundamental level tier 0.5 is very similar to tier 0, unlike tier 0, tier 0.5 apps usually won't
run on their own without valid authorization. So unless you own the app, you have a lot less information to
work with.
Tier 1: Games that use online verification upon the initial installation (and the server sends vital data)
2. Get the server to accept a set of fake credentials. Preferably ones that can be randomly generated. (the
keygen approach)
This approach only works if the people in charge of the security system fucked up somewhere. Can work,
but it's not reliable.
3. Pass the authentication using valid credentials and then replicate the game's functions such that it can be
run independently.
Depending on how interwoven the DRM system is with the game's code, the "replicate" step could be quite
difficult, but in most cases the DRM system is just a light layer on top.
With this approach you normally need to decrypt the game's files and sometimes replace the .exe used to
launch the project.
For a lot of game engines, the .exe is fairly interchangeable so it's just a case of decrypting the files.
This approach is the most reliable and should always be theoretically possible.
But it does require you to possess valid credentials, which normally means buying the game.
Tier 2: Games that send continuous web-requests that contain vital data.
Multiplayer games fall under this category. If a game uses multiplayer interactions as a primary source of
content it would take an enormous amount of effort to crack since you'd have to replicate all
undocumented server functions and host your own servers for people to use. For most intents and purposes
I'd consider these to be uncrackable.
If a game is single-player but is hosted online, the difficulty depends on how complex the game is.
For example, if it's just a collection of static web pages, then iterating over all of them and storing the data
offline should be feasible. Same deal if the webpages just run client-side scripts. If the game relies on a lot
of server-side scripts then you have a problem. You can only observe the inputs and outputs from the
server and thus can only guess at what the inner mechanics are.
*By "vital data" I mean data that is necessary for the game experience and can't be easily
guessed/replicated
This could be an encryption key or just part of the game's content, like chapters or CG images.
For most situations I would highly recommend the second approach because it is normally way easier than
reverse engineering the underlying logic. In the case of decryption, you don't need to know what the
encryption method is or what the keys are.
Miscellaneous Stuff
------------------------
Example 1:
In a real recent example a program was contacting a DRM server to verify ownership. The server URL was
stored in the exe. Using a hex editor I was able to change it to instead contact a localhost server I had
control over. A quick, dirty, and simple way to perform request redirection.
Example 2:
Let's say you found a string in the exe/dll that looks like it could be a decryption key.
You could try changing it and seeing if the encryption breaks.
If the program breaks it could be for a lot of reasons, but if it doesn't immediately break you can rule that
string out as an encryption key. (unless the key is present in multiple locations)
Note:
Programs often perform some sort of integrity check of used files which makes manual edits more
difficult.
Which brings me to the next point
Getting started
-------------------
1. Original Work
---------------------
1a) The illegal way
Look through the cracking forum for a post that interests you and has the uncracked files uploaded. (or buy
the game yourself)
Download the files and start seeing what you can figure out!
The advantage of doing completely original work is that it's the real deal. You won't find any solutions
online that tell you how to solve this specific case. It's just you and your wits against the program. It's very
satisfying if you're able to make progress knowing that you've broken new ground so to speak.
The disadvantage of doing original work as a beginner is that you have no idea how difficult it is going to
be. You could get lucky and get something simple, or you could stumble across something completely
outside of your skill range.
Even if the project is outside of your skill level, you should still be able to make some progress and at least
learn more about how the system works and learn new skills in the process. But it is more fun when you
can make some major strides it breaking the security.
The obvious advantage of this is that you'll get manageable tasks to deal with and have some support if you
need it.
The drawback is that it's less satisfying to defeat something when you had outside help and know it doesn't
contribute anything to our lovely community. Also since it's already cracked you might stumble across an
exact solution that spoils the problem.
If you're interested I can dig up some project files from my PC and upload them.
Alternatively, some projects you could try which are currently publicly available:
For the other DLSite DRM systems, some are easy to crack, some are hard. I think the serial-code one may
even be uncracked at this point? Not sure.
Conclusion
--------------
That's all I have to say on this topic.
Reading this guide may be like cheating in some ways. I had to figure this stuff out the hard way and here I
am just giving you the answers. That said, most of this information is scattered on the web. I just collected
it in one place.
I've found one difficulty with cracking is that while there is a lot of information on security systems out
there, little of it is written for the perspective of an attacker, so hopefully this guide helps.
Let me know what you think in the comments and whether there's an area you'd like extra information on.
Attachments
InjectionExample.zip
178 KB
erri120
Newbie