Cracking a binary
Piracy is bad, but when you’re a broke student and you find a cracked launcher for a game you want to play, you don’t ask questions. You simply replace the launcher with the “cracked” copy and start playing. Not me—I would never do it—but I know people who have.
It’s wrong, very wrong, but at the same time, it’s fascinating how a single file you can’t even read can bypass the security put in place by a multi-million-dollar game project.
There have been several methods used to lock games down—and just as many tricks to break them open. I’ll dive into some of the techniques used in the early 2000s to crack PC games, online Flash games, and mock license servers.
Let’s start with a toy problem, pretty basic piece of code with some intentional inclusions.
There are two global variables msga
and msgb
.
Two user defined routines allow
and deny
.
One conditional call to an external program using execvp
.
The idea here is to examine the executable this program creates, find out where the code lands in the executable and what the compiler adds on top of it, and then modify the binary directly and change its behaviour.
#include <stdio.h>
#include <unistd.h>
char *msga = "Allow";
char *msgb = "Deny";
void allow() {
printf("%s\n", msga);
}
void deny() {
printf("%s\n", msgb);
}
int main(int argc, char **argv) {
deny();
int runExternal = 0;
if (runExternal) {
char* lsargs[] = {"ls", "-l", NULL};
execvp("ls", lsargs);
}
}
compile the code with debug symbol and then use objdump
to get the disassembled output of the code.
gcc -g examinebin.c -o examinebin
objdump -d examinebin
We are interested in Disassembly of section .text
in the objdump output and looking for allow
, deny
and cmp
and jmp
instruction in main
Once the exploration is done it is time to change the behaviour of this code:
- call allow instead of deny.
- change
runExternal
flag value to non-zero. - change
"-l"
to"-a" in
lsargs`
Replace deny
The hexadecimal code calling deny
from objdump
output
0000000000001189 <allow>:
00000000000011a3 <deny>:
00000000000011bd <main>:
11e4: e8 ba ff ff ff call 11a3 <deny>
from the call reference we know that opcode e8
takes the operand ba ff ff ff
(0xffffffba) which is basically the offset from next instruction 0x11e9
. So, it should point to (0x11a3)
offset = hex(0xffffffba - 0x100000000) # getting the negative value
deny_addr = hex(0x11e9 + int(offset, 16))
print(deny_addr)
To call allow
instead we will have to change (0xffffffba) to something that gives (0x1189) instead.
allow_addr = hex(0x1189)
offset = hex(int(hex(int(allow_addr, 16) - 0x11e9), 16) + 0x100000000)
print(offset)
# 0xffffffa0 -> a0 ff ff ff
So all we need to do is change ba
to a0
in the binary
Change runExternal
This is quite simple, all we need to do is locate the mov
instruction that is putting the value in the flag.
11e9: c7 45 dc 00 00 00 00 movl $0x0,-0x24(%rbp)
and change the value to any non-zero one. ref
00 00 00 00 -> 01 00 00 00
Change command flag "-l"
We basically want to change the arguments going into execvp
function call.
In the assembly we can see the location where the call
to execvp has been made and there should be
push
or lea
instruction before that to add the argument into the stack.
Since these values are hardcoded in the binary all we need to do is get the location of -l
and change it to -a
.
11f6: 48 8d 05 12 0e 00 00 lea 0xe12(%rip),%rax # 200f <_IO_stdin_used+0xf>
11fd: 48 89 45 e0 mov %rax,-0x20(%rbp)
1201: 48 8d 05 0a 0e 00 00 lea 0xe0a(%rip),%rax # 2012 <_IO_stdin_used+0x12>
1208: 48 89 45 e8 mov %rax,-0x18(%rbp)
121b: 48 8d 05 ed 0d 00 00 lea 0xded(%rip),%rax # 200f <_IO_stdin_used+0xf>
1222: 48 89 c7 mov %rax,%rdi
1225: e8 66 fe ff ff call 1090 <execvp@plt>
lea instruction is basically calculating the effective address which is offset to the next instruction pointer. So we have three addresses, which can be calculated or seen in the objdump output as well.
print(hex(0xe12 + 0x11fd)) # 0x200f
print(hex(0xe0a + 0x1208)) # 0x2012
print(hex(0xded + 0x1222)) # 0x200f
from the xxd
output we can clearly see that our strings are really there.
00002000: 0100 0200 416c 6c6f 7700 4465 6e79 006c ....Allow.Deny.l
00002010: 7300 2d6c 0000 0000 011b 033b 4400 0000 s.-l.......;D...
00002020: 0700 0000 08f0 ffff 7800 0000 48f0 ffff ........x...H...
Changing the fourth byte from the right 00002010:
6c -> 61 will make l->a
.
Changes
Summary of the changes to modify the xxd
output
xxd examinebin > examinebin.txt
Changes for allow
000011e0: 0000 0000 e8(ba) ffff ffc7 45dc 0000 0000
000011e0: 0000 0000 e8(a0) ffff ffc7 45dc 0000 0000
Changes for runExternal
flag
000011e0: 0000 0000 e8ba ffff ffc7 45dc (00)00 0000
000011e0: 0000 0000 e8ba ffff ffc7 45dc (01)00 0000
Changes for l -> a
00002010: 7300 2d(6c) 0000 0000 011b 033b 5400 0000
00002010: 7300 2d(61) 0000 0000 011b 033b 5400 0000
final diff
< 000011e0: 0000 0000 e8ba ffff ffc7 45dc 0000 0000
---
> 000011e0: 0000 0000 e8a0 ffff ffc7 45dc 0100 0000
< 00002010: 7300 2d6c 0000 0000 011b 033b 4400 0000
---
> 00002010: 7300 2d61 0000 0000 011b 033b 4400 0000
Now that we have the modified text file we can create an executable using xxd
xxd -r examinebin-mod.txt > examinebin-mod.out
chmod +x examinebin-mod.out
./examinebin-mod.out
Running the modified binary will print Allow
and run the ls
with -a
options.
Some handy command line tools
- file
utility that gives the file name, file type and other format related information.
- sum
Get the checksum and number of blocks in the file. Once we do some reverese engineering this output will tell us that the new executable is not genuine.
- ldd
Gives the list of shared objects required by the executable
- strings
Displays all printable characters and strings in the file. Works on any file in fact not just executable
- nm
Lists all the symbols present in the executable file address map.
- xxd or hexdump
These are plain read-write tools to deal with binary files and not just executables. Reading part creates a text file showing hexadecimal values at each byte and if possible there is a printable version side by side. Any changes to this output text file can be fed back to the tool, which can then create a binary file.
- objdump
using the -d option you can get the detailed version of each section and segment of your executable along with the interpreted assembly instruction.
SRE Framework
https://github.com/NationalSecurityAgency/ghidra
Debugger
https://github.com/x64dbg/x64dbg
A real binary
This was all fun and a bit too easy. When the code is this simple and written by yourself it is not too difficult to crack. For the next phase of this experiment I’ll try to see what I can do with a real game binary https://archive.org/details/Wolfenstein3d