Table of Contents

Disclaimer and Purpose of the Documents

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.

About

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. :-)

Theory

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 gotos) 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.

if

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.

Flow


\begin{tikzpicture}[scale=2, node distance = 2cm, auto]
    % Place nodes
    \node [block] (read) {\texttt{read}};
    \node [decision, below of=read] (compare) {\texttt{compare}};
    \node [block, right of=compare, node distance=2.5cm] (jump) {\texttt{jump}};
    \node [block, below of=compare, node distance=2.5cm] (continue) {\texttt{continue}};
    % Draw edges
    \path [line] (read) -- (compare);
    \path [line] (compare) -- (jump);
    \path [line] (compare) -- (continue);
\end{tikzpicture}

C

int reg = isRegistered(); // read
if(reg != 1) { // compare
  return; // jump
}
start(); // continue
...

ASM

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 NOPs (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.

Flow


\begin{tikzpicture}[scale=2, node distance = 2cm, auto]
    % Place nodes
    \node [block] (enter) {\texttt{enter}};
    \node [block, below of=enter, node distance=2.5cm] (execute) {\texttt{execute}};
    \node [block, below of=execute, node distance=2.5cm] (return) {\texttt{return}};
    % Draw edges
    \path [line] (enter) -- (execute);
    \path [line] (execute) -- (return);
\end{tikzpicture}

C

needsLicenseReminder() { // enter
  ... // execute
  return; // return
}

ASM

_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.

Stuff You Will Need To Know

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 XREFs.

Flow Equivalence

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.

NOP Sledges

The illustriously wise frequently take some post-condition and slide it via NOPs 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 NOPs 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!).

Applying Binary Patches

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.

Two Types of People: Some Have Seen Assembler, Some Did Not

Index