Writing Basic Keygens for Fun and Profit
v1.0 - Biomechanica
TL;DR WARNING
Contrary to my preview tutorial, this one will not have pretty screenshots. Everything was done through a text-based debugger, I figured a screenshot of a black terminal window with white text does not really have any advantages over a grey quote box with white text. This means the tutorial looks like one big wall of text. For a general idea of the methods used, you can skip over the assembly code if you want. However, it helps to kinda read through this code as it'll be easier to see what made me draw certain conclusion and how everything comes together to form our program.
So, finally, here is the sequel to http://www.torrent-invites.com/tutor...un-profit.html :)
In this guide, I will try to broaden your knowledge of code analysis and reverse-engineering. Whereas the cracking tutorial, which was about binary patching, only required you to recognise important segments in disassembled code, this tutorial will require a deeper understanding of the code so you can mimick it's exact functionality. You will essentially be restoring the disassembled, compiler-generated and optimized 'mess' of assembly code back to higher-level code. I will try to make you more familiar with (static) code analysis and recognizing things that may be of help.
In this example, I'll be using code I wrote a while ago, when I first came up with the idea of writing a keygenning tutorial. I just compiled the code, without looking at it, so it was kinda black box-ish for me as well, making it more realistic. All in all, I'd say this is a realistic representation of a very basic application that requires a serial. Again, this is very basic: the algorithm is easy to understand without much advanced tricks and the binary itself is not obfuscated, crypted, packed or protected in any other way.
The analysis has been performed on a Linux system using GDB as a disassembler/debugger. The same techniques, however, apply to keygenning on Windows-based system as well. Contrary to the previous tutorial, this one focusses on code analysis. Though there are some minor debugger-related things, like using breakpoints to check how memory is manipulated throughout functions, this is all used to confirmed assumptions made based on the disassembled code. The techniques used in this tutorial apply to any disassembler on any platform and you can use any debugger to aid you.
Requirements:
- A basic understanding of assembly language.
Though you do not need to be fluent in assembly for this tutorial, but basic understanding of the most basic commands will most certainly be required to understand what is going on.- A basic understanding of a high(er)-level language.
You will have to rewrite the disassembled code to another language. In this tutorial, I will be using C, but you can pick whatever language you prefer. Kudo's to the first person rewriting the keygen in Brainfuck!- A basic understanding of how computers handle memory and how registers are used.
In the end, all a program does is manipulate and execute data in memory, registers hold information currently of importance: ranging from a simple value of a counter to the location in memory of a serial. Therefore, this understanding is required to fully comprehend what is going on in your program. Again, you do not need to know the more advanced stuff, but you should at least be aware of how registers are used in a program and exactly what the heap and the stack is and how they are used. It'll also come in very handy if you're able to read or recognise ASCII and know how to use hexadecimal numbers.- Terminology
I'm not going into basic terms such as 'packers', 'crypters', 'debuggers', 'disassembly', et cetera. These are easy to look up and would just make this guide unnecessarily long and complicated while a quick Google search could probably provide you with better information than I could ever give you without going into too much details.- A basic understanding of any disassembler or debugger you're using
I will be using GDB in this tutorial, which is a text-based debugger for Linux. You will be able to pick up some basic commands from this tutorial, but that's not what this tutorial focusses on. The techniques shown here are applicable on a wide variety of debuggers and disassemblers, so get familiar with whatever software you chose to use before diving into this kind of stuff.
Now, without further ado, let's get started :)
Looks good :) Now, let's run our program for the first time.biomechanica@biobox:~/Desktop$ file keygenme
keygenme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
Alright, well there's not much we can deduct from this, let's just fire up a debugger. Seeing as GDB uses the AT&T syntax by default, and I prefer the Intel-syntax, I'll change that settings first.biomechanica@biobox:~/Desktop$ ./keygenme
Biomechanica KeygenMe v1.0
For Torrent-Invites.com
Please enter your name: AAAAAAAAAA
Please enter your serial: BBBBBBBBBB
FAIL - Invalid serial DUURRR HURRR
Let's take at the code's entry point (main() function in C):biomechanica@biobox:~/Desktop$ gdb -q ./keygenme
Reading symbols from /home/entropy/Desktop/keygenme...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
Reading through the code, you should notice some points of interest.(gdb) disas main
Dump of assembler code for function main:
0x080485c4 <main+0>: push ebp
0x080485c5 <main+1>: mov ebp,esp
0x080485c7 <main+3>: and esp,0xfffffff0
0x080485ca <main+6>: sub esp,0x20
0x080485cd <main+9>: mov eax,0x8048910
0x080485d2 <main+14>: mov DWORD PTR [esp+0x8],0x804894a
0x080485da <main+22>: mov DWORD PTR [esp+0x4],0x804894f
0x080485e2 <main+30>: mov DWORD PTR [esp],eax
0x080485e5 <main+33>: call 0x80484b4 <printf@plt>
0x080485ea <main+38>: mov DWORD PTR [esp],0x32
0x080485f1 <main+45>: call 0x80484d4 <malloc@plt>
0x080485f6 <main+50>: mov DWORD PTR [esp+0x10],eax
0x080485fa <main+54>: mov DWORD PTR [esp],0x32
0x08048601 <main+61>: call 0x80484d4 <malloc@plt>
0x08048606 <main+66>: mov DWORD PTR [esp+0x14],eax
0x0804860a <main+70>: mov eax,0x8048954
0x0804860f <main+75>: mov DWORD PTR [esp],eax
0x08048612 <main+78>: call 0x80484b4 <printf@plt>
0x08048617 <main+83>: mov eax,ds:0x8049b68
0x0804861c <main+88>: mov DWORD PTR [esp+0x8],eax
0x08048620 <main+92>: mov DWORD PTR [esp+0x4],0x31
0x08048628 <main+100>: mov eax,DWORD PTR [esp+0x10]
0x0804862c <main+104>: mov DWORD PTR [esp],eax
0x0804862f <main+107>: call 0x8048474 <fgets@plt>
0x08048634 <main+112>: mov eax,0x8048971
0x08048639 <main+117>: mov DWORD PTR [esp],eax
0x0804863c <main+120>: call 0x80484b4 <printf@plt>
0x08048641 <main+125>: mov eax,ds:0x8049b68
0x08048646 <main+130>: mov DWORD PTR [esp+0x8],eax
0x0804864a <main+134>: mov DWORD PTR [esp+0x4],0x31
0x08048652 <main+142>: mov eax,DWORD PTR [esp+0x14]
0x08048656 <main+146>: mov DWORD PTR [esp],eax
0x08048659 <main+149>: call 0x8048474 <fgets@plt>
0x0804865e <main+154>: mov eax,DWORD PTR [esp+0x10]
0x08048662 <main+158>: mov DWORD PTR [esp],eax
0x08048665 <main+161>: call 0x80484a4 <strlen@plt>
0x0804866a <main+166>: cmp eax,0x9
0x0804866d <main+169>: jbe 0x8048680 <main+188>
0x0804866f <main+171>: mov eax,DWORD PTR [esp+0x14]
0x08048673 <main+175>: mov DWORD PTR [esp],eax
0x08048676 <main+178>: call 0x80484a4 <strlen@plt>
0x0804867b <main+183>: cmp eax,0x9
0x0804867e <main+186>: ja 0x8048693 <main+207>
0x08048680 <main+188>: mov DWORD PTR [esp],0x8048990
0x08048687 <main+195>: call 0x80484e4 <puts@plt>
0x0804868c <main+200>: mov eax,0xffffffff
0x08048691 <main+205>: jmp 0x80486f2 <main+302>
0x08048693 <main+207>: mov eax,DWORD PTR [esp+0x10]
0x08048697 <main+211>: mov DWORD PTR [esp],eax
0x0804869a <main+214>: call 0x80486f4 <getSerial>
0x0804869f <main+219>: mov DWORD PTR [esp+0x18],eax
0x080486a3 <main+223>: mov eax,DWORD PTR [esp+0x10]
0x080486a7 <main+227>: mov DWORD PTR [esp],eax
0x080486aa <main+230>: call 0x80484a4 <strlen@plt>
0x080486af <main+235>: sub eax,0x1
0x080486b2 <main+238>: mov DWORD PTR [esp+0x8],eax
0x080486b6 <main+242>: mov eax,DWORD PTR [esp+0x18]
0x080486ba <main+246>: mov DWORD PTR [esp+0x4],eax
0x080486be <main+250>: mov eax,DWORD PTR [esp+0x14]
0x080486c2 <main+254>: mov DWORD PTR [esp],eax
0x080486c5 <main+257>: call 0x8048454 <memcmp@plt>
0x080486ca <main+262>: test eax,eax
0x080486cc <main+264>: je 0x80486e1 <main+285>
0x080486ce <main+266>: mov DWORD PTR [esp],0x80489cc
0x080486d5 <main+273>: call 0x80484e4 <puts@plt>
0x080486da <main+278>: mov eax,0xffffffff
0x080486df <main+283>: jmp 0x80486f2 <main+302>
0x080486e1 <main+285>: mov DWORD PTR [esp],0x80489f4
0x080486e8 <main+292>: call 0x80484e4 <puts@plt>
0x080486ed <main+297>: mov eax,0x0
0x080486f2 <main+302>: leave
0x080486f3 <main+303>: ret
End of assembler dump.
These calls to fgets() are, judging by their location in the code, used to get the username and user serial. This is important because that means we can easily retrieve the addresses at which they're stored. This will come in handy later on, as it'll make it easier to identify function that modify or use those specific memory addresses.0x08048659 <main+149>: call 0x8048474 <fgets@plt>
0x08048659 <main+149>: call 0x8048474 <fgets@plt>
This looks suspiciously like some kind of string length check, meaning the length of our strings (presumably username and serial) are restricted. This is an important condition, what's the point of creating a valid serial if the username is too short to be accepted for instance?0x08048665 <main+161>: call 0x80484a4 <strlen@plt>
0x0804866a <main+166>: cmp eax,0x9
0x0804866d <main+169>: jbe 0x8048680 <main+188>
0x0804866f <main+171>: mov eax,DWORD PTR [esp+0x14]
0x08048673 <main+175>: mov DWORD PTR [esp],eax
0x08048676 <main+178>: call 0x80484a4 <strlen@plt>
0x0804867b <main+183>: cmp eax,0x9
0x0804867e <main+186>: ja 0x8048693 <main+207>
This sounds like a rather interesting function. Disassemblers like IDA will also pick up function names like these, though I'm not sure whether Olly can find them as well. We'll dive into this one later on.0x0804869a <main+214>: call 0x80486f4 <getSerial>
This code snippet is most likely used to check our serial.0x080486c5 <main+257>: call 0x8048454 <memcmp@plt>
0x080486ca <main+262>: test eax,eax
0x080486cc <main+264>: je 0x80486e1 <main+285>
Let's start by retrieving the addresses of the username. As you will probably know, a function's return value is stored in EAX. This means fgets() will store the address of the string it has stored in EAX after it has been called. I'll retrieve it by placing a breakpoint right after the first call to fgets():
0x41 = "A"; jackpot, we found the location of our username: 0x804a008. Let's do the same thing to retrieve the location of our serial.(gdb) b *0x08048634
Breakpoint 1 at 0x8048634
(gdb) r
Starting program: /home/biomechanica/Desktop/keygenme
Biomechanica KeygenMe v1.0
For Torrent-Invites.com
Please enter your name: AAAAAAAAAA
Breakpoint 1, 0x08048634 in main ()
(gdb) i r
eax 0x804a008 134520840
ecx 0x0 0
edx 0xf7fb8334 -134511820
ebx 0xf7fb6ff4 -134516748
esp 0xffffd430 0xffffd430
ebp 0xffffd458 0xffffd458
esi 0x0 0
edi 0x0 0
eip 0x8048634 0x8048634 <main+112>
eflags 0x246 [ PF ZF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x $eax
0x804a008: 0x41414141
0x42 = "B", so we now know the serial is stored at 0x804a040. To get an idea of what our serial will look like, let's try to fish it out. The memcmp() call near the end of main() would seem like a logical place to start, so I placed a breakpoint at 0x080486c5 <main+257>: call 0x8048454 <memcmp@plt>.Please enter your serial: BBBBBBBBBB
Breakpoint 2, 0x0804865e in main ()
(gdb) i r
eax 0x804a040 134520896
ecx 0x0 0
edx 0xf7fb8334 -134511820
ebx 0xf7fb6ff4 -134516748
esp 0xffffd430 0xffffd430
ebp 0xffffd458 0xffffd458
esi 0x0 0
edi 0x0 0
eip 0x804865e 0x804865e <main+154>
eflags 0x246 [ PF ZF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x/20x $eax
0x804a040: 0x42424242 0x42424242 0x000a4242 0x00000000
EAX will look familiar, we already know 0x804a040 holds the user submitted serial. The other registers aren't of much help though. Let's delve into memcmp to see how it works, we should be able to derive an address from there.Breakpoint 3, 0x080486c5 in main ()
(gdb) i r
eax 0x804a040 134520896
ecx 0x3 3
edx 0xfff8 65528
ebx 0xf7fb6ff4 -134516748
esp 0xffffd430 0xffffd430
ebp 0xffffd458 0xffffd458
esi 0x0 0
edi 0x0 0
eip 0x80486c5 0x80486c5 <main+257>
eflags 0x206 [ PF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
I pretty much stopped reading here. You can see it's doing some pointer arithmetics in which the stack pointer (ESP) plays an important role. Let's investigate what ESP holds.(gdb) disas memcmp
Dump of assembler code for function memcmp:
0xf7ee8f40 <memcmp+0>: push ebx
0xf7ee8f41 <memcmp+1>: mov eax,DWORD PTR [esp+0x8]
0xf7ee8f45 <memcmp+5>: mov edx,DWORD PTR [esp+0xc]
0xf7ee8f49 <memcmp+9>: mov ecx,DWORD PTR [esp+0x10]
0xf7ee8f4d <memcmp+13>: cmp ecx,0x1
[.. a lot more code here ..]
The first two blocks seem to hold pointers. Seeing as the first one points to our user submitted serial and the other one is placed relatively close to it in memory, we might well have hit the jackpot. Let's see what 0x0804a078 has in store for us.(gdb) x/20x $esp
0xffffd430: 0x0804a040 0x0804a078 0x0000000a 0xffffd458
0xffffd440: 0x0804a008 0x0804a040 0x0804a078 0xf7fb6ff4
0xffffd450: 0x08048860 0x00000000 0xffffd4d8 0xf7e8ac76
0xffffd460: 0x00000001 0xffffd504 0xffffd50c 0xf7fde9e0
0xffffd470: 0x08048510 0xffffffff 0xf7ffcff4 0x08048338
This looks like a classical NULL terminated string. I noticed all the values where in the 0x3*-range. As you might know, 0x30 to 0x39 is the ASCII range for numbers, with 0x30 being 0, 0x31 being 1, 0x32 being 2, etc.. 0x2d is a simple dash ("-"). This means the string here is a bunch of numbers, seperated by a couple of dashes. Sounds like a valid candidate for a serial, right? Let's check.(gdb) x/20x 0x0804a078
0x804a078: 0x36373435 0x30313839 0x3234322d 0x31363135
0x804a088: 0x3139302d 0x34373238 0x3732382d 0x39393131
0x804a098: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a0a8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a0b8: 0x00000000 0x00000000 0x00000000 0x00000000
This is where the proper serial is stored
So the individual bytes would be ...0x804a078: 0x36373435 0x30313839 0x3633322d 0x31323233
0x804a088: 0x3532392d 0x38363235 0x3733352d 0x33323132
Keep in mind, we're running on an Intel machine, so the byte order is little endian. If this sounds unfamiliar, read up on endianness here: Endianness - Wikipedia, the free encyclopedia36 37 34 35 30 31 38 39 36 33 32 2d 31 32 32 33
35 32 39 2d 38 36 32 35 37 33 35 2d 33 32 31 32
With the proper corrections, we get this:
Again, let's test our assumption.35 34 37 36 39 38 31 30 2d 32 33 36 33 32 32 31
2d 39 32 35 35 32 36 38 2d 35 33 37 32 31 32 33
... which becomes ...
35 34 37 36 39 38 31 30 2d 32 33 36 33 32 32 31 2d 39 32 35 35 32 36 38 2d 35 33 37 32 31 32 33
ASCII: 54769810-2363221-9255268-5372123
There you have it, we've found the valid serial. Of course, this is of little use as it only applies to the username you've entered. A keygen should be able to generate a valid serial for every valid username, so now we need to figure out how this serial has been formed.biomechanica@biobox:~/Desktop$ ./keygenme
Biomechanica KeygenMe v1.0
For Torrent-Invites.com
Please enter your name: AAAAAAAAAA
Please enter your serial: 54769810-2363221-9255268-5372123
WIN - Good boy, your serial is valid, now go write a keygen :)
We do however now have a list of important addresses:
- Username: 0x804a008
- User serial: 0x804a040
- Valid serial: 0x804a078
We also know a valid serial consists of 32 characters which are only numbers seperated by dashes in the following form: 11111111-2222222-3333333-4444444. All of this will come in handy later on.
Now, it's time to dive into the getSerial() function and get to the interesting part.
Now, you might be thinking "OMG WTF WER DOES I START?!!??!!1". I can't blame you, a long list of abstract commands such as these can be quite daunting, but try to break it up in smaller sections that are related. This makes it easier to analyse as you can add up all the smaller pieces to figure out how the algorithm itself works.(gdb) disas getSerial
Dump of assembler code for function getSerial:
0x080486f4 <getSerial+0>: push ebp
0x080486f5 <getSerial+1>: mov ebp,esp
0x080486f7 <getSerial+3>: push ebx
0x080486f8 <getSerial+4>: sub esp,0x24
0x080486fb <getSerial+7>: mov DWORD PTR [esp],0x64
0x08048702 <getSerial+14>: call 0x80484d4 <malloc@plt>
0x08048707 <getSerial+19>: mov DWORD PTR [ebp-0x18],eax
0x0804870a <getSerial+22>: mov DWORD PTR [esp],0x0
0x08048711 <getSerial+29>: call 0x80484c4 <time@plt>
0x08048716 <getSerial+34>: mov DWORD PTR [esp],eax
0x08048719 <getSerial+37>: call 0x8048444 <srand@plt>
0x0804871e <getSerial+42>: mov eax,DWORD PTR [ebp+0x8]
0x08048721 <getSerial+45>: mov DWORD PTR [esp],eax
0x08048724 <getSerial+48>: call 0x80484a4 <strlen@plt>
0x08048729 <getSerial+53>: sub eax,0x1
0x0804872c <getSerial+56>: mov DWORD PTR [ebp-0x14],eax
0x0804872f <getSerial+59>: cmp DWORD PTR [ebp-0x14],0x20
0x08048733 <getSerial+63>: jle 0x804873c <getSerial+72>
0x08048735 <getSerial+65>: mov DWORD PTR [ebp-0x14],0x20
0x0804873c <getSerial+72>: mov DWORD PTR [ebp-0xc],0x0
0x08048743 <getSerial+79>: jmp 0x80487a0 <getSerial+172>
0x08048745 <getSerial+81>: mov eax,DWORD PTR [ebp-0xc]
0x08048748 <getSerial+84>: add eax,DWORD PTR [ebp+0x8]
0x0804874b <getSerial+87>: movzx eax,BYTE PTR [eax]
0x0804874e <getSerial+90>: movsx eax,al
0x08048751 <getSerial+93>: mov ecx,eax
0x08048753 <getSerial+95>: xor ecx,DWORD PTR [ebp-0xc]
0x08048756 <getSerial+98>: mov edx,0x66666667
0x0804875b <getSerial+103>: mov eax,ecx
0x0804875d <getSerial+105>: imul edx
0x0804875f <getSerial+107>: sar edx,0x2
0x08048762 <getSerial+110>: mov eax,ecx
0x08048764 <getSerial+112>: sar eax,0x1f
0x08048767 <getSerial+115>: mov ebx,edx
0x08048769 <getSerial+117>: sub ebx,eax
0x0804876b <getSerial+119>: mov eax,ebx
0x0804876d <getSerial+121>: mov DWORD PTR [ebp-0x10],eax
0x08048770 <getSerial+124>: mov edx,DWORD PTR [ebp-0x10]
0x08048773 <getSerial+127>: mov eax,edx
0x08048775 <getSerial+129>: shl eax,0x2
0x08048778 <getSerial+132>: add eax,edx
0x0804877a <getSerial+134>: add eax,eax
0x0804877c <getSerial+136>: mov edx,ecx
0x0804877e <getSerial+138>: sub edx,eax
0x08048780 <getSerial+140>: mov eax,edx
0x08048782 <getSerial+142>: mov DWORD PTR [ebp-0x10],eax
0x08048785 <getSerial+145>: mov eax,DWORD PTR [ebp-0x10]
0x08048788 <getSerial+148>: movsx eax,al
0x0804878b <getSerial+151>: add eax,0x30
0x0804878e <getSerial+154>: mov DWORD PTR [ebp-0x10],eax
0x08048791 <getSerial+157>: mov eax,DWORD PTR [ebp-0xc]
0x08048794 <getSerial+160>: add eax,DWORD PTR [ebp-0x18]
0x08048797 <getSerial+163>: mov edx,DWORD PTR [ebp-0x10]
0x0804879a <getSerial+166>: mov BYTE PTR [eax],dl
0x0804879c <getSerial+168>: add DWORD PTR [ebp-0xc],0x1
0x080487a0 <getSerial+172>: mov eax,DWORD PTR [ebp-0xc]
0x080487a3 <getSerial+175>: cmp eax,DWORD PTR [ebp-0x14]
0x080487a6 <getSerial+178>: jl 0x8048745 <getSerial+81>
0x080487a8 <getSerial+180>: jmp 0x80487e4 <getSerial+240>
0x080487aa <getSerial+182>: mov eax,DWORD PTR [ebp-0xc]
0x080487ad <getSerial+185>: mov ebx,eax
0x080487af <getSerial+187>: add ebx,DWORD PTR [ebp-0x18]
0x080487b2 <getSerial+190>: call 0x80484f4 <rand@plt>
0x080487b7 <getSerial+195>: mov ecx,eax
0x080487b9 <getSerial+197>: mov edx,0x66666667
0x080487be <getSerial+202>: mov eax,ecx
0x080487c0 <getSerial+204>: imul edx
0x080487c2 <getSerial+206>: sar edx,0x2
0x080487c5 <getSerial+209>: mov eax,ecx
0x080487c7 <getSerial+211>: sar eax,0x1f
0x080487ca <getSerial+214>: sub edx,eax
0x080487cc <getSerial+216>: mov eax,edx
0x080487ce <getSerial+218>: shl eax,0x2
0x080487d1 <getSerial+221>: add eax,edx
0x080487d3 <getSerial+223>: add eax,eax
0x080487d5 <getSerial+225>: mov edx,ecx
0x080487d7 <getSerial+227>: sub edx,eax
0x080487d9 <getSerial+229>: mov eax,edx
0x080487db <getSerial+231>: add eax,0x30
0x080487de <getSerial+234>: mov BYTE PTR [ebx],al
0x080487e0 <getSerial+236>: add DWORD PTR [ebp-0xc],0x1
0x080487e4 <getSerial+240>: cmp DWORD PTR [ebp-0xc],0x1f
0x080487e8 <getSerial+244>: jle 0x80487aa <getSerial+182>
0x080487ea <getSerial+246>: mov eax,DWORD PTR [ebp-0x18]
0x080487ed <getSerial+249>: add eax,0x8
0x080487f0 <getSerial+252>: mov DWORD PTR [esp+0x8],0x1
0x080487f8 <getSerial+260>: mov DWORD PTR [esp+0x4],0x2d
0x08048800 <getSerial+268>: mov DWORD PTR [esp],eax
0x08048803 <getSerial+271>: call 0x8048484 <memset@plt>
0x08048808 <getSerial+276>: mov eax,DWORD PTR [ebp-0x18]
0x0804880b <getSerial+279>: add eax,0x10
0x0804880e <getSerial+282>: mov DWORD PTR [esp+0x8],0x1
0x08048816 <getSerial+290>: mov DWORD PTR [esp+0x4],0x2d
0x0804881e <getSerial+298>: mov DWORD PTR [esp],eax
0x08048821 <getSerial+301>: call 0x8048484 <memset@plt>
0x08048826 <getSerial+306>: mov eax,DWORD PTR [ebp-0x18]
0x08048829 <getSerial+309>: add eax,0x18
0x0804882c <getSerial+312>: mov DWORD PTR [esp+0x8],0x1
0x08048834 <getSerial+320>: mov DWORD PTR [esp+0x4],0x2d
0x0804883c <getSerial+328>: mov DWORD PTR [esp],eax
0x0804883f <getSerial+331>: call 0x8048484 <memset@plt>
0x08048844 <getSerial+336>: mov eax,DWORD PTR [ebp-0x18]
0x08048847 <getSerial+339>: add esp,0x24
0x0804884a <getSerial+342>: pop ebx
0x0804884b <getSerial+343>: pop ebp
0x0804884c <getSerial+344>: ret
Let's start by checking if this function does indeed return the valid serial. Again, functions will store their return value in EAX. If you look at the end of the function, you'll notice there is one last instruction which writes to EAX: 0x08048844 <getSerial+336>: mov eax,DWORD PTR [ebp-0x18]. I'll place a breakpoint at 0x08048844 and have a look to see what value ebp-0x18 holds.
0x0804a078 is the address that holds our proper serial :) GG, looks like we found the function that generates the serial. Now let's see how exactly the serial is generated.(gdb) x $ebp-0x18
0xffffd410: 0x0804a078
Quickly scanning through the code, I noticed the following:
A C coder should immediatley notice the classic time() and srand() functions, combined with the rand() function. Srand() sets its argument as the seed for randomly generated numbers which are rand()'s output. Generally, this is combined with time(0); note the instruction moving 0x0 onto the stack. This means there will be a line along the lines of srand(time(0)); in the original source. Also, it means there's probably some random-numbers used in the serial.0x0804870a <getSerial+22>: mov DWORD PTR [esp],0x0
0x08048711 <getSerial+29>: call 0x80484c4 <time@plt>
0x08048716 <getSerial+34>: mov DWORD PTR [esp],eax
0x08048719 <getSerial+37>: call 0x8048444 <srand@plt>
[.. snip ..]
0x080487b2 <getSerial+190>: call 0x80484f4 <rand@plt>
I also noticed this:
This means there's some code like int x = strlen(str)-1 in this code. Time to see exactly what 'str' will be.0x08048721 <getSerial+45>: mov DWORD PTR [esp],eax
0x08048724 <getSerial+48>: call 0x80484a4 <strlen@plt>
0x08048729 <getSerial+53>: sub eax,0x1
0x804a008 is the location of our username! EAX holds a function's first argument, that means the code takes the length of our username and subtracts one. This is often used in loops; time to look for evidence to support this.(gdb) b *0x08048721
Breakpoint 4 at 0x8048721
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/biomechanica/Desktop/keygenme
Biomechanica KeygenMe v1.0
For Torrent-Invites.com
Please enter your name: AAAAAAAAAA
Please enter your serial: BBBBBBBBBB
Breakpoint 4, 0x08048721 in getSerial ()
(gdb) i r
eax 0x804a008 134520840
ecx 0x6a28015e 1781006686
edx 0xf7fb7044 -134516668
ebx 0xf7fb6ff4 -134516748
esp 0xffffd400 0xffffd400
ebp 0xffffd428 0xffffd428
esi 0x0 0
edi 0x0 0
eip 0x8048721 0x8048721 <getSerial+45>
eflags 0x292 [ AF SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
remember, eax = strlen(username)-1, this is copied to [ebp-0x14]; let's call this strlen for now. Strlen is compared to 0x20 (32), if strlen is lower or equal to 0x20,0x0804872c <getSerial+56>: mov DWORD PTR [ebp-0x14],eax
0x0804872f <getSerial+59>: cmp DWORD PTR [ebp-0x14],0x20
0x08048733 <getSerial+63>: jle 0x804873c <getSerial+72>
0x08048735 <getSerial+65>: mov DWORD PTR [ebp-0x14],0x20
0x0804873c <getSerial+72>: mov DWORD PTR [ebp-0xc],0x0
0x08048743 <getSerial+79>: jmp 0x80487a0 <getSerial+172>
it jumps over the next instruction which moves 0x20 into [ebp-0x14] (strlen). So this code will look something like..
The jumps leads us here:int strlen = strlen(username);
if( strlen > 32 )
strlen = 32;
Hmm, [ebp-0xc], we've seen that address before at the start of our function: 0x0804870a <getSerial+22>: mov DWORD PTR [esp],0x00x080487a0 <getSerial+172>: mov eax,DWORD PTR [ebp-0xc]
0x080487a3 <getSerial+175>: cmp eax,DWORD PTR [ebp-0x14]
0x080487a6 <getSerial+178>: jl 0x8048745 <getSerial+81>
also, right before the check, it's value is increased by one: 0x0804879c <getSerial+168>: add DWORD PTR [ebp-0xc],0x1
Sounds suspiciously like a counter, doesn't it? Combine this with the strlen code, throw in the srand() and time() functions and we can start filling in part of our
serial generating function...
The jump if lower (jl) statement takes us to getSerial+81, which is at 0x08048745.function genSerial(char *username) {
char *serial;
int len, cnt;
srand(time(0));
len = strlen(username)-1;
if( len > 32 )
len = 32;
for( cnt = 0; cnt < len; ++cnt ) {
// do stuff here
}
// other code here
return serial;
}
That means this is where the loops starts, and our for() loop takes place between 0x08048733 and 0x080487a6. Let's check the code this loop holds:
The first part shouldn't be too hard to understand.0x08048745 <getSerial+81>: mov eax,DWORD PTR [ebp-0xc]
0x08048748 <getSerial+84>: add eax,DWORD PTR [ebp+0x8]
0x0804874b <getSerial+87>: movzx eax,BYTE PTR [eax]
0x0804874e <getSerial+90>: movsx eax,al
0x08048751 <getSerial+93>: mov ecx,eax
0x08048753 <getSerial+95>: xor ecx,DWORD PTR [ebp-0xc]
0x08048756 <getSerial+98>: mov edx,0x66666667
0x0804875b <getSerial+103>: mov eax,ecx
0x0804875d <getSerial+105>: imul edx
0x0804875f <getSerial+107>: sar edx,0x2
0x08048762 <getSerial+110>: mov eax,ecx
0x08048764 <getSerial+112>: sar eax,0x1f
0x08048767 <getSerial+115>: mov ebx,edx
0x08048769 <getSerial+117>: sub ebx,eax
0x0804876b <getSerial+119>: mov eax,ebx
0x0804876d <getSerial+121>: mov DWORD PTR [ebp-0x10],eax
0x08048770 <getSerial+124>: mov edx,DWORD PTR [ebp-0x10]
0x08048773 <getSerial+127>: mov eax,edx
0x08048775 <getSerial+129>: shl eax,0x2
0x08048778 <getSerial+132>: add eax,edx
0x0804877a <getSerial+134>: add eax,eax
0x0804877c <getSerial+136>: mov edx,ecx
0x0804877e <getSerial+138>: sub edx,eax
0x08048780 <getSerial+140>: mov eax,edx
0x08048782 <getSerial+142>: mov DWORD PTR [ebp-0x10],eax
0x08048785 <getSerial+145>: mov eax,DWORD PTR [ebp-0x10]
0x08048788 <getSerial+148>: movsx eax,al
0x0804878b <getSerial+151>: add eax,0x30
0x0804878e <getSerial+154>: mov DWORD PTR [ebp-0x10],eax
0x08048791 <getSerial+157>: mov eax,DWORD PTR [ebp-0xc]
0x08048794 <getSerial+160>: add eax,DWORD PTR [ebp-0x18]
0x08048797 <getSerial+163>: mov edx,DWORD PTR [ebp-0x10]
0x0804879a <getSerial+166>: mov BYTE PTR [eax],dl
0x0804879c <getSerial+168>: add DWORD PTR [ebp-0xc],0x1
0x080487a0 <getSerial+172>: mov eax,DWORD PTR [ebp-0xc]
DWORD PTR [ebp-0xc] = counter (as seen before), now let's see what value is held at DWORD PTR [ebp+0x8]:0x08048745 <getSerial+81>: mov eax,DWORD PTR [ebp-0xc]
0x08048748 <getSerial+84>: add eax,DWORD PTR [ebp+0x8]
0x0804874b <getSerial+87>: movzx eax,BYTE PTR [eax]
0x0804874e <getSerial+90>: movsx eax,al
0x08048751 <getSerial+93>: mov ecx,eax
0x08048753 <getSerial+95>: xor ecx,DWORD PTR [ebp-0xc]
So, DWORD PTR [ebp+0x8] holds the location of our username. This means the code would look something like this in C:Breakpoint 1, 0x08048748 in getSerial ()
(gdb) i r
eax 0x0 0
ecx 0x5 5
edx 0xffe0 65504
ebx 0xf7fb6ff4 -134516748
esp 0xffffd400 0xffffd400
ebp 0xffffd428 0xffffd428
esi 0x0 0
edi 0x0 0
eip 0x8048748 0x8048748 <getSerial+84>
eflags 0x297 [ CF PF AF SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x $ebp+0x8
0xffffd430: 0x0804a008
We are now left with this code:char a = username+counter; // 0x08048745 to 0x08048751 (this is essentially the same as username[counter]!)
a ^= counter; // 0x08048753
Now, I won't lie. When I first saw this, I was all 'LOLWUT, WTF'. The first part appeared to be made up of some random maths and quite frankly I couldn't be arsed to check what value each register held at a certain time. The hardcoded values like 0x66666667 and the numerous shifts and mov's didn't help either. However, I couldn't help but notice this line: 0x0804878b <getSerial+151>: add eax,0x30.0x08048756 <getSerial+98>: mov edx,0x66666667
0x0804875b <getSerial+103>: mov eax,ecx
0x0804875d <getSerial+105>: imul edx
0x0804875f <getSerial+107>: sar edx,0x2
0x08048762 <getSerial+110>: mov eax,ecx
0x08048764 <getSerial+112>: sar eax,0x1f
0x08048767 <getSerial+115>: mov ebx,edx
0x08048769 <getSerial+117>: sub ebx,eax
0x0804876b <getSerial+119>: mov eax,ebx
0x0804876d <getSerial+121>: mov DWORD PTR [ebp-0x10],eax
0x08048770 <getSerial+124>: mov edx,DWORD PTR [ebp-0x10]
0x08048773 <getSerial+127>: mov eax,edx
0x08048775 <getSerial+129>: shl eax,0x2
0x08048778 <getSerial+132>: add eax,edx
0x0804877a <getSerial+134>: add eax,eax
0x0804877c <getSerial+136>: mov edx,ecx
0x0804877e <getSerial+138>: sub edx,eax
0x08048780 <getSerial+140>: mov eax,edx
0x08048782 <getSerial+142>: mov DWORD PTR [ebp-0x10],eax
0x08048785 <getSerial+145>: mov eax,DWORD PTR [ebp-0x10]
0x08048788 <getSerial+148>: movsx eax,al
0x0804878b <getSerial+151>: add eax,0x30
0x0804878e <getSerial+154>: mov DWORD PTR [ebp-0x10],eax
0x08048791 <getSerial+157>: mov eax,DWORD PTR [ebp-0xc]
0x08048794 <getSerial+160>: add eax,DWORD PTR [ebp-0x18]
0x08048797 <getSerial+163>: mov edx,DWORD PTR [ebp-0x10]
0x0804879a <getSerial+166>: mov BYTE PTR [eax],dl
0x0804879c <getSerial+168>: add DWORD PTR [ebp-0xc],0x1
0x080487a0 <getSerial+172>: mov eax,DWORD PTR [ebp-0xc]
0x30 huh? Sounds familiar? Remember our serial was completely made up of numbers, and numbers in ASCII are in the 0x30 - 0x39 range? Here, we see 0x30 being added to the result of whatever happens between 0x08048756 and 0x08048788. So, in order to get a valid ASCII number, EAX will have to hold a value of 0x0 to 0x9. The easiest way to sure you get a value between 0 and 9 is by dividing your result by 10 (0xa). This will leave a remainder between 0 and 9 :) So, could all of this code really be the assembly representation of the %-operator in C? Let's find out:
As you can see, EAX holds 0x41 (decimal: 65) at the start of this snippet of code. This makes sense, as it's the first character of our string which means the counter is set at 0: N xor 0 = N. At the end, it holds 0x5 (decimal: well... 5). 0x30 Will then be added to the value in EAX, meaning it would hold 0x35. OH SNAP SON; this is also the first character of our serial! NO WAI, IT MUST BE HAX!!(gdb) b *0x08048756
Breakpoint 14 at 0x8048756
(gdb) b *0x08048788
Breakpoint 15 at 0x8048788
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/biomechanica/Desktop/keygenme
Biomechanica KeygenMe v1.0
For Torrent-Invites.com
Please enter your name: AAAAAAAAAA
Please enter your serial: BBBBBBBBBB
Breakpoint 14, 0x08048756 in getSerial ()
(gdb) i r
eax 0x41 65
ecx 0x41 65
edx 0xfff8 65528
ebx 0xf7fb6ff4 -134516748
esp 0xffffd400 0xffffd400
ebp 0xffffd428 0xffffd428
esi 0x0 0
edi 0x0 0
eip 0x8048756 0x8048756 <getSerial+98>
eflags 0x206 [ PF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) c
Continuing.
Breakpoint 15, 0x08048788 in getSerial ()
(gdb) i r
eax 0x5 5
ecx 0x41 65
edx 0x5 5
ebx 0x6 6
esp 0xffffd400 0xffffd400
ebp 0xffffd428 0xffffd428
esi 0x0 0
edi 0x0 0
eip 0x8048788 0x8048788 <getSerial+148>
eflags 0x216 [ PF AF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
If you aren't sure yet, you can let the code continue a few times and you'll see that it indeed divides username[counter] by 10 and saves the remainder in EAX: our assumption was correct. We are now left with this:
The following code just looks like some pointer arithmetics, it's probably used to save the result (0x35) of the last code snippet, let's check.0x0804878e <getSerial+154>: mov DWORD PTR [ebp-0x10],eax
0x08048791 <getSerial+157>: mov eax,DWORD PTR [ebp-0xc]
0x08048794 <getSerial+160>: add eax,DWORD PTR [ebp-0x18]
0x08048797 <getSerial+163>: mov edx,DWORD PTR [ebp-0x10]
0x0804879a <getSerial+166>: mov BYTE PTR [eax],dl
0x0804879c <getSerial+168>: add DWORD PTR [ebp-0xc],0x1
0x080487a0 <getSerial+172>: mov eax,DWORD PTR [ebp-0xc]
0x35 = 5 = the first key of our valid serial! Also, 0x804a078 is the location we determined the proper serial would be located. I'd say it's safe to assume we've analysed this loop well enough to rewrite it's functionality in C.(gdb) b *0x080487a0
Breakpoint 16 at 0x80487a0
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) n
Program not restarted.
(gdb) c
Continuing.
Breakpoint 16, 0x080487a0 in getSerial ()
(gdb) i r
eax 0x804a078 134520952
ecx 0x41 65
edx 0x35 53
ebx 0x6 6
esp 0xffffd400 0xffffd400
ebp 0xffffd428 0xffffd428
esi 0x0 0
edi 0x0 0
eip 0x80487a0 0x80487a0 <getSerial+172>
eflags 0x202 [ IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x $eax
0x804a078: 0x00000035
First, the username's character currently pointed to by the counter is saved in EAX and than XOR'ed with the counter. In C, this would look something like this: int a = username[n] ^ n
After that, it's divided by 10 and the remainders gets 0x30 added to it.
a %= 10;
a += 0x30;
Now, the result is saved in our serial.
serial[n] = a;
So, let's see how all this works out in our loop:
Ok, that makes sense :) However, as you can see the valid serial is 32 bytes long (32 ASCII characters) while this loop will only generate a string as long as the username! Let's have a look at the next snippet of code to see how the rest of the serial is formed. We still have to plow through this code:function genSerial(char *username) {
char *serial;
int len, cnt, a;
srand(time(0));
len = strlen(username)-1;
if( len > 32 )
len = 32;
for( cnt = 0; cnt < len; ++cnt ) {
a = username[cnt] ^ cnt;
a %= 10;
a += 0x30;
serial[cnt] = a;
}
// other code here
return serial;
}
again, we see a familiar structure:0x080487a8 <getSerial+180>: jmp 0x80487e4 <getSerial+240>
0x080487aa <getSerial+182>: mov eax,DWORD PTR [ebp-0xc]
0x080487ad <getSerial+185>: mov ebx,eax
0x080487af <getSerial+187>: add ebx,DWORD PTR [ebp-0x18]
0x080487b2 <getSerial+190>: call 0x80484f4 <rand@plt>
0x080487b7 <getSerial+195>: mov ecx,eax
0x080487b9 <getSerial+197>: mov edx,0x66666667
0x080487be <getSerial+202>: mov eax,ecx
0x080487c0 <getSerial+204>: imul edx
0x080487c2 <getSerial+206>: sar edx,0x2
0x080487c5 <getSerial+209>: mov eax,ecx
0x080487c7 <getSerial+211>: sar eax,0x1f
0x080487ca <getSerial+214>: sub edx,eax
0x080487cc <getSerial+216>: mov eax,edx
0x080487ce <getSerial+218>: shl eax,0x2
0x080487d1 <getSerial+221>: add eax,edx
0x080487d3 <getSerial+223>: add eax,eax
0x080487d5 <getSerial+225>: mov edx,ecx
0x080487d7 <getSerial+227>: sub edx,eax
0x080487d9 <getSerial+229>: mov eax,edx
0x080487db <getSerial+231>: add eax,0x30
0x080487de <getSerial+234>: mov BYTE PTR [ebx],al
0x080487e0 <getSerial+236>: add DWORD PTR [ebp-0xc],0x1
0x080487e4 <getSerial+240>: cmp DWORD PTR [ebp-0xc],0x1f
0x080487e8 <getSerial+244>: jle 0x80487aa <getSerial+182>
0x080487ea <getSerial+246>: mov eax,DWORD PTR [ebp-0x18]
0x080487ed <getSerial+249>: add eax,0x8
0x080487f0 <getSerial+252>: mov DWORD PTR [esp+0x8],0x1
0x080487f8 <getSerial+260>: mov DWORD PTR [esp+0x4],0x2d
0x08048800 <getSerial+268>: mov DWORD PTR [esp],eax
0x08048803 <getSerial+271>: call 0x8048484 <memset@plt>
0x08048808 <getSerial+276>: mov eax,DWORD PTR [ebp-0x18]
0x0804880b <getSerial+279>: add eax,0x10
0x0804880e <getSerial+282>: mov DWORD PTR [esp+0x8],0x1
0x08048816 <getSerial+290>: mov DWORD PTR [esp+0x4],0x2d
0x0804881e <getSerial+298>: mov DWORD PTR [esp],eax
0x08048821 <getSerial+301>: call 0x8048484 <memset@plt>
0x08048826 <getSerial+306>: mov eax,DWORD PTR [ebp-0x18]
0x08048829 <getSerial+309>: add eax,0x18
0x0804882c <getSerial+312>: mov DWORD PTR [esp+0x8],0x1
0x08048834 <getSerial+320>: mov DWORD PTR [esp+0x4],0x2d
0x0804883c <getSerial+328>: mov DWORD PTR [esp],eax
0x0804883f <getSerial+331>: call 0x8048484 <memset@plt>
0x08048844 <getSerial+336>: mov eax,DWORD PTR [ebp-0x18]
0x08048847 <getSerial+339>: add esp,0x24
0x0804884a <getSerial+342>: pop ebx
0x0804884b <getSerial+343>: pop ebp
0x0804884c <getSerial+344>: ret
We see 1 being added to a value, which is than compared to 0x1f (31 decimal). If the result is lower or equal, it jumps back to an early piece of code: looks0x080487a8 <getSerial+180>: jmp 0x80487e4 <getSerial+240>
0x080487aa <getSerial+182>: mov eax,DWORD PTR [ebp-0xc]
[.. snip ..]
0x080487e0 <getSerial+236>: add DWORD PTR [ebp-0xc],0x1
0x080487e4 <getSerial+240>: cmp DWORD PTR [ebp-0xc],0x1f
0x080487e8 <getSerial+244>: jle 0x80487aa <getSerial+182>
like another loop! Also, remember our valid serial holds 32 characters? Let's keep this in mind and explore our code further.
In the earlier code we saw this snippet: 0x0804873c <getSerial+72>: mov DWORD PTR [ebp-0xc],0x0
This set the value of our counter, located at [ebp-0xc] to 0. However, in this piece of code we see the same address being used without it being set to 0. This means,
the counter will have the same value it got in the previous loop, which is strlen(username)-1. As you can see, this loop will fill in the other characters so the serial
will be 32 bytes long (serial[0] to serial[31])!
We can conclude the loop must have looked something like this:
We can see there's a bit of code left that needs to be analysed. But, we also haven't explained the dashes in the valid serial! Looks like our analysis is coming to an end! Here's what we still need to go through:for(; cnt < 32; ++cnt ) {
a = rand();
a %= 10;
a += 0x30;
serial[cnt] = a;
}
The rest of the code looks like your standard function epilogue, no need to worry about that.0x080487ea <getSerial+246>: mov eax,DWORD PTR [ebp-0x18]
0x080487ed <getSerial+249>: add eax,0x8
0x080487f0 <getSerial+252>: mov DWORD PTR [esp+0x8],0x1
0x080487f8 <getSerial+260>: mov DWORD PTR [esp+0x4],0x2d
0x08048800 <getSerial+268>: mov DWORD PTR [esp],eax
0x08048803 <getSerial+271>: call 0x8048484 <memset@plt>
0x08048808 <getSerial+276>: mov eax,DWORD PTR [ebp-0x18]
0x0804880b <getSerial+279>: add eax,0x10
0x0804880e <getSerial+282>: mov DWORD PTR [esp+0x8],0x1
0x08048816 <getSerial+290>: mov DWORD PTR [esp+0x4],0x2d
0x0804881e <getSerial+298>: mov DWORD PTR [esp],eax
0x08048821 <getSerial+301>: call 0x8048484 <memset@plt>
0x08048826 <getSerial+306>: mov eax,DWORD PTR [ebp-0x18]
0x08048829 <getSerial+309>: add eax,0x18
0x0804882c <getSerial+312>: mov DWORD PTR [esp+0x8],0x1
0x08048834 <getSerial+320>: mov DWORD PTR [esp+0x4],0x2d
0x0804883c <getSerial+328>: mov DWORD PTR [esp],eax
0x0804883f <getSerial+331>: call 0x8048484 <memset@plt>
You should notice a few things here:
First, the code calls memset, which means there's some memory values being changed. Furthermore, we see the value 0x2d, which either means the decimal value 45 or.. a
dash in ASCII! Looks like we found the code that sets our dashes :)
The code is fairly self explanatory. A pointer to the address of our serial, located at [ebp-0x18] as we previously found out, is saved in eax. eax is then incremented
and the value it currently points to is set to 0x2d using memset. We can see the 0x8'th, 0x10'th and 0x18'th values being changed, that's the 8'th, 16'th and 24'th values, which would means our serial looks something like this: XXXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX
Compare this to our previously fished serial:
54769810-2363221-9255268-5372123
XXXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX
It's a match! So, it's time to conclude our function:
There you have it! In the end, this is the full source code to my keygen:function genSerial(char *username) {
char *serial;
int len, cnt, a;
srand(time(0));
len = strlen(username)-1;
if( len > 32 )
len = 32;
for( cnt = 0; cnt < len; ++cnt ) {
a = username[cnt] ^ cnt;
a %= 10;
a += 0x30;
serial[cnt] = a;
}
while( cnt < 32 ) {
a = rand();
a %= 10;
a += 0x30;
serial[cnt] = a;
++cnt;
}
memset(serial+8, 0x2d, 1); // memset requires an integer so we can't use the string "-"!
memset(serial+16, 0x2d, 1);
memset(serial+24, 0x2d, 1);
return serial;
}
You may have noticed I changed one small detail in this code; I didn't subtract 1 from strlen(username). The reason for this is simple; I did have that line in the#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char **argv) {
printf("Keygen for Biomechanica's keygenme v1.0\n");
printf("Biomechanica 2010\n\n");
srand(time(0));
if( argc != 2 ) {
printf("Usage: %s <username>\n", argv[0]);
return -1;
}
char *serial = malloc(100), *username = malloc(100);
int len, cnt, a;
username = argv[1];
len = strlen(username);
if( len < 10 ) {
printf("Username must be atleast 10 characters!\n");
return -1;
}
if( len > 32 )
len = 32;
for( cnt = 0; cnt < len; ++cnt ) {
a = (int)username[cnt] ^ cnt;
a %= 10;
a += 0x30;
serial[cnt] = a;
}
for( ; cnt < 32; ++cnt ) {
a = rand();
a %= 10;
a += 0x30;
serial[cnt] = a;
}
memset(serial+8, 0x2d, 1);
memset(serial+16, 0x2d, 1);
memset(serial+24, 0x2d, 1);
printf("Username: %s\n", username);
printf("Serial: %s\n\n", serial);
return 0;
}
reversed code because I tried mimicking it's functionality as well as I could, but we don't need it here. This is because of the way the program recieves it's input. Remember those calls to fgets()? This means the keygenme binary uses fgets() to get it's input. However the user will terminate the input with a press of the return key. The return key essentially adds a newline to the string, which fgets() retains. This means a string like "AAAA" would be saved in memory as 414141410a00 instead of 4141414100 as you would expect. By subtracting 1 from the string's length, the loop stops before reaching the newline. The keygen code uses a command line argument (argv[1]) as it's source of input, in which case the string is NULL terminated. If we also subtract 1 from the input string's length, the keygen would never generate a valid serial as the correct value for the last character of the string would never be calculated.
Now, let's give it a try :)
Looks like we did it! w00t \o/$ ./keygenme
Biomechanica KeygenMe v1.0
For Torrent-Invites.com
Please enter your name: Biomechanica
Please enter your serial: Idunnololsumserialorsumthinlolol
FAIL - Invalid serial DUURRR HURRR
$ ./keygen
Keygen for Biomechanica keygenme v1.0
Biomechanica 2010
Usage: ./keygen <username>
$ ./keygen Biomechanica
Keygen for Biomechanica keygenme v1.0
Biomechanica 2010
Username: Biomechanica
Serial: 64907202-6563232-1820118-3609114
$ ./keygenme
Biomechanica KeygenMe v1.0
For Torrent-Invites.com
Please enter your name: Biomechanica
Please enter your serial: 64907202-6563232-1820118-3609114
WIN - Good boy, your serial is valid, now go write a keygen :)
Here's the Keygenme's source code:
I'm pretty sure it'll compile on Windows just fine as well. You see we did a pretty good job on the keygen's code as it's nearly exactly the same, though this also has to do with the fact I coded both pieces of code myself so they both reflect my personal coding style. Either way, it's not the cleanest code ever as it was coded in a hurry, but it does show a basic method of generating serials. Sadly, the identation is lost when posting it on here (crappy quote boxes instead of proper code boxes are to blame!), but I'm sure it's understandable regardless./*
*
* keygenme.c
* Biomechanica Keygenme v1.0 - 2010
* Just a simple Linux keygenme, perfect for beginners and tutorials.
* Shoutout to torrent-invites.com! I assume there's no need for any
* comments as the code is fairly straightforward :)
*
* ~ Biomechanica
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define BLD "\033[1m" // Bold text
#define RST "\033[0m" // Reset text style
#define BUFSIZE 50
char *getSerial(char *str);
int main(int argc, char **argv) {
printf("\n%sBiomechanica KeygenMe v1.0%s\n"
"For Torrent-Invites.com\n\n", BLD, RST);
char *usrName, *usrSerial, *ctlSerial;
int len;
usrName = malloc(BUFSIZE);
usrSerial = malloc(BUFSIZE);
printf("Please enter your name: ");
fgets(usrName, BUFSIZE-1, stdin);
printf("Please enter your serial: ");
fgets(usrSerial, BUFSIZE-1, stdin);
if( strlen(usrName) < 10 || strlen(usrSerial) < 10 ) {
printf("Please check the length of your username and/or serial.\n\n");
return -1;
}
ctlSerial = getSerial(usrName);
if( memcmp(usrSerial, ctlSerial, strlen(usrName)-1 ) ) {
printf("\n\tFAIL - Invalid serial DUURRR HURRR\n\n");
return -1;
} else {
printf("\n\tWIN - Good boy, your serial is valid, now go write a keygen :)\n\n");
return 0;
}
}
char *getSerial(char *str) {
char *ret = malloc(100);
int len, tmp, n;
srand(time(0));
len = strlen(str)-1;
if( len > 32 )
len = 32;
for( n = 0; n < len; ++n ) {
tmp = ((int)str[n] ^ n) % 10;
tmp = (char)tmp + 0x30;
ret[n] = tmp;
}
while( n < 32 ) {
ret[n] = rand() % 10 + 0x30;
++n;
}
memset(ret+8, 0x2d, 1);
memset(ret+16, 0x2d, 1);
memset(ret+24, 0x2d, 1);
// printf("getSerial - ret = %s\n", ret); // DEBUG
return ret;
}
You can view the sources with proper identation and syntax highlighting here:
- Keygenme.c: C | /* * * keygenme.c * Biom - Biomechanica@TI - W0djXqwM - Pastebin.com
- Keygen.c: C | #include <stdio.h> #include < - Biomechanica@TI - tKgVW284 - Pastebin.com
If you feel I missed something important or if you find a mistake, please let me know. I'll fix it and give you credit of course.









LinkBack URL
About LinkBacks
Reply With Quote






