The content on the following pages describes how various software protections can be defeated by using very simple techniques - we try to the stick to the simplest ones. In no way should you use these cracks for any illegal purposes and we definitely do not support or condone any illicit behaviour from our readers. All the cracking procedures describe a way of attacking various software packages and we encourage the creators to improve the quality of their software by eliminating the flaws that we point out in the pages that follow from this name-space. Similarly, we would also like to point out to the public how the software is protected, as a risk management, in many cases where the software being attacked is able to transmit or hide data from the intended audience with or without their consent.
The following programs have been reverse-engineered using:
Wizardry and Steamworks does not distribute any pirated software. We are not a warez site. By following the instructions you will be able to break this software yourself and gain your own experience in the process.
For exercise' sake, the software in this section was cracked without using a debugger (except in one or two cases, using gdb
, after the crack, for clarifications) and the operations have been restricted to flow-control manipulation and code-elimination - our signature move.
We use a brute-approach to cracking programs, based on intuition and manipulation of flow control. Disassembling any piece of software yields a series of data movements between memory addresses, calls to functions that do more of that and a bunch of jumps (or goto
s) between them.
Every program consists of a series of a top-down flow of sequentially executed commands. If the program is event-driven, or threaded, if we were to coalesce all the threads into one single top-down flow, every command will be executed one after the other with a difficult, yet consistent trend of being sequential. The difficulty is that we may not be able to determine at what point a command will be executed but we will be able to observe the flow of execution. That is, unless all operations are atomic, we know the flow and can observe the decisions but we cannot tell when they will be executed.
On a high-level, if
-branches can be observed as a disposition of read
, compare
and jump
commands. The flowchart illustrates the typical pattern of decision making in the cracks bellow and the C
and ASM
counterparts illustrate the typical case in programming.
int reg = isRegistered(); // read if(reg != 1) { // compare return; // jump } start(); // continue ...
xor cl, cl ; read (exchange) cmp rax, rbx ; compare jle 0x10001FF07 ; jump mov cl, 0x1 ; continue ...
Manipulating if
-branches consists in negating either of the branches in order to favour in outcome. Better illustrated, let's take the code from the flow-chart above:
int reg = isRegistered(); // read if(reg != 1) { // compare return; // jump } start(); // continue ...
If reg
after the call to isRegistered()
(read) does not hold the value 1
(compare) then the function that the program is in returns (jump) and the outcome is that the program will not reach start()
(continue) - and we definitely do want to get the program to start.
In order to do that, we manipulate the outcome of the if
clause, barbarically, by just getting rid of jump:
int reg = isRegistered(); // read if(reg != 1) { // compare } start(); // continue ...
Now the if
-clause becomes superfluous. It does not matter whether reg
holds the value 1
or not because the program will never return:
if(reg != 1) { // compare }
There are many ways to eliminate the jump in assembler. The most obvious, given the top-down flow of execution, is to get rid of the jump (jle
):
xor cl, cl ; read (exchange) cmp rax, rbx ; compare jle 0x10001FF07 ; jump mov cl, 0x1 ; continue ...
by substituting the jump using some NOP
s (no operation):
xor cl, cl ; read (exchange) cmp rax, rbx ; compare nop ; no more jumping nop ; mov cl, 0x1 ; continue ...
Thus the comparison:
cmp rax, rbx ; compare
is not even checked. We can NOP
the CMP
as well, if we want to be tidy, but it is not needed.
Very often we want to short-circuit functions, namely functions that are labeled needsRegistration
, hasToBeNaggedAboutIt
, and as in Jitouch v2, functions named popTheJollyRoger
(it turned out to be a red herring). Functions can also be easily be short-circuited by understanding the basic flow. Code is organised in blocks that are called in succession and after they have executed, they return. From the call-stack point of view, this is rather dumb operation: pop the frame, execute the code and return to the callsite.
needsLicenseReminder() { // enter ... // execute return; // return }
_needsLicenseReminder: ; enter push rbp rbp, rsp r15 r14 rbx ... ; execute pop rbx pop r14 pop r15 pop rbp ret ; return
Since we are very important people and do not need a license, we can short-circuit this function such that it does not do anything. We could even NOP
out the call to it, but let us be tidy and have an overview of what we are doing:
_needsLicenseReminder: ; enter push rbp mov rbp, rsp push r15 push r14 push rbx jmp 0x2129 ; jump ... 0x2129 pop rbx pop r14 pop r15 pop rbp ret ; return
The result of adding a jmp
to 0x2129
is that the function _needsLicenseReminder
will be called and then it will return thereby doing absolutely nothing.
The number of operations and rules you will need to learn is very small, in fact, there are only 4
things you need to know. Most of the time you can guess what the program does by looking at XREF
s.
j
. That is the case of jmp
, jnle
, jle
, etc… jmp
being the main player which is an unconditional jump. jnle
, jle
and the rest of the family are translated to "jump to address if not less equal", "jump if less equal" and so on… However, the technique described here does not require you to frown very hard trying to remember what je
meant. It is ridiculously translated to: "if some crap happens, then jump somewhere".ret
. Very boilerplate. Some functions implement a stack guard, that sort of slings out of the function, then returns back, performing checksums and whatever other rubbish. You can generally ignore this, mind the previous rule, and jump to the ret
instead of the garbage checks.NOP
means no-operation. Remember that programs execute commands sequentially and that a series of nop
s one after the other means perpetually doing nothing, up to the first operation that is not a nop
.
As you may have noticed, NOP
can in fact be emulated by a jump forward by one operation. That is, both the left-side and the right-side of the following boxes are equivalent:
0x0001 nop ; do nothing 0x0002 mov rdi, r14 ; move
0x0001 jmp 0x0002 ; jump forward 0x0002 mov rdi, r14 ; move
More precisely, we can say that a NOP
is semantically equivalent to a JMP
by one operation forward. This is needed, in cases where, say, you are too lazy to write a NOP
and need to use a JMP
. The converse is also true.
The illustriously wise frequently take some post-condition and slide it via NOP
s to some check. Something along these lines:
int reg = getRegistered(); if(reg == 1) { reg = 1; // very clever! printf("You are now registered!"); return; } printf("Demo will expire in 7 days!"); setTimer(); return;
mov rbx, 0x1 ; reg = 1 nop ; fuuuuuuuuuu.. nop ; ...uuuuuuuuuuu... nop ; ...uuuuuuuuuuu... ... ... ... ... ... nop ; uuuuuuuuuuuuuuuu! ret
which essentially moves the assignment, by setting reg
to 1
out of the way of any other checks. It does that by sliding on the nop
sledge, ignoring every operation till the function returns. We could have found a better example and you can also tell that the bunch of NOP
s could be replaced by a single JMP
. However, there are cases, where you would want to slide reg = 1
to some if
-clause that tests whether reg
is equal to 1
later on, in the function. In illustrious wise terms, this is called sliding a post-condition to a check such that the post-condition will not be invalidated during the slide (by the other crap in-between, yo!).
bsdiff
can be used to create and apply binary patches. Most of the cracks presented here are for OSX and you will need either MacPorts or Homebrew to apply the patches. In case you have access and assuming you have Homebrew install, issue in a terminal:
brew install bsdiff
in case you have MacPorts, issue:
port install bsdiff
in order to install bsdiff
.
Patches are named in this namespace conventionally and you will need to copy & paste the gibberish text in files before applying them. For example, the patch for filename
would be pasted in a file called:
filename.bsdiff.uue
the uue
extension indicating an universal encoded file (using uuencode
), and:
filename.bsdiff
to indicate a patch file.
In order to patch filename
, you will first have to decode filename.bsdiff.uue
in order to obtain the filename.bsdiff
. This can be performed in a terminal by issuing:
uudecode -o filename.bsdiff < filename.bsdiff.uue
at which point you will have obtained the filename.bsdiff
file.
The next step is to apply the patch. This can be done by moving filename.bsdiff
to where filename
is to be found (usually indicated in the patches section of every crack) and then issuing:
bspatch filename filename filename.bsdiff
which will apply the patch filename.bsdiff
to filename
.
Remember that if you patched a binary with bsdiff
, the binary may not be executable after the patch. So you need to set the executable bit on the file by running:
chmod +x filename
in case the executable will not load.