Join the discord

crackmes.de : MaxXor's KeygenMe V6

27 Dec, 2013 11:13
Today i'll take a look at this crackme (keygenme to be precise) from MaxXor:


The information about it says that it is written in C, patches are forbidden, the only acceptable solution is keygen and the crackme is graded as level 2 (needs a little brain (or luck)). Good.

First of all, I'll make a small observation.
The file size is quite small, only 6.5KB, so it's most probably highly optimized (no debug information) or written with inline assembly routines, which should make my job easier.

But lets take a closer look, by loading it into PE Explorer.

There's some debug data in there at offset 0x678 and it's 0x6F bytes long.


This is the PDB file information data, added here by the linker.
From the string, we can see it's compiled from Visual Studio 2010, and where it was physically located on the author's PC.
Also, something interesting is the DWORD before that string. It contains a counter, that will increment every time the executable is recreated by the linker.
If that value is legit, this means the author had to compile it only once, so kudos for him, if he wrote the whole crackme in one shot. ;)

Anyways, let's move on to the more important things.

I've loaded the EXE in IDA to get a better look of the structure, and it turns out to be pretty simply written.
There's a dialog process with few cases - create the GUI, some WM_COMMAND handling for the Register, About and Exit button and so on. Good.

The check routine is written inside the dialog process, starting at 00401426 (I've split the code for clarity).

First there is the length validation:
asm / disassembly00401426   . FF35 18204000  PUSH DWORD PTR DS:[402018]                 ; /hWnd = 0001042E ('Username... (Example: 1234)',class='Edit',...)
0040142C   . FF15 60104000  CALL DWORD PTR DS:[<&USER32.GetWindowTex>  ; \GetWindowTextLengthA
00401432   . 83F8 04        CMP EAX,4
00401435   . 0F85 0F030000  JNZ KeygenMe.0040174A
0040143B   . FF35 20204000  PUSH DWORD PTR DS:[402020]                 ; /hWnd = 00010430 ('Serial...',class='Edit',...)
00401441   . FF15 60104000  CALL DWORD PTR DS:[<&USER32.GetWindowTex>  ; \GetWindowTextLengthA
00401447   . 83F8 14        CMP EAX,14
0040144A   . 0F85 FA020000  JNZ KeygenMe.0040174A
Username should be 4 characters long, and Password should be 0x14 (20 chars long). In case they aren't, it jumps to 0040174A where nothing will happen.

Then a string is moved to a buffer pointed by EBP-50:
asm / disassembly00401450   . 6A 09          PUSH 9
00401452   . 59             POP ECX
00401453   . BE 30124000    MOV ESI,KeygenMe.00401230                              ;  ASCII "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
00401458   . 8D7D B0        LEA EDI,DWORD PTR SS:[EBP-50]
0040145B   . F3:A5          REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
0040145D   . A4             MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
0040145E   . 8365 A4 00     AND DWORD PTR SS:[EBP-5C],0
That string contains the character set used in a valid serial.

After that, the user-typed Name and Serial are stored in two buffers - EBP-64 (username) and EBP-28 (serial), that were first zeroed by the memset functions:
asm / disassembly00401462   . 6A 05          PUSH 5
00401464   . 6A 00          PUSH 0
00401466   . 8D45 9C        LEA EAX,DWORD PTR SS:[EBP-64]
00401469   . 50             PUSH EAX
0040146A   . FF15 0C204000  CALL DWORD PTR DS:[40200C]	                   ;  ntdll.memset
00401470   . 83C4 0C        ADD ESP,0C
00401473   . 6A 15          PUSH 15
00401475   . 6A 00          PUSH 0
00401477   . 8D45 D8        LEA EAX,DWORD PTR SS:[EBP-28]
0040147A   . 50             PUSH EAX
0040147B   . FF15 0C204000  CALL DWORD PTR DS:[40200C]                     ;  ntdll.memset
00401481   . 83C4 0C        ADD ESP,0C
00401484   . 6A 05          PUSH 5                                         ; /Count = 5
00401486   . 8D45 9C        LEA EAX,DWORD PTR SS:[EBP-64]                  ; |
00401489   . 50             PUSH EAX                                       ; |Buffer
0040148A   . 6A 66          PUSH 66                                        ; |ControlID = 66 (102.)
0040148C   . FF35 24204000  PUSH DWORD PTR DS:[402024]                     ; |hWnd = 0003042A ('KeygenMe V6 - Trial',class='keygenme.v6')
00401492   . FF15 30104000  CALL DWORD PTR DS:[<&USER32.GetDlgItemTextA>]  ; \GetDlgItemTextA
00401498   . 6A 15          PUSH 15                                        ; /Count = 15 (21.)
0040149A   . 8D45 D8        LEA EAX,DWORD PTR SS:[EBP-28]                  ; |
0040149D   . 50             PUSH EAX                                       ; |Buffer
0040149E   . 6A 67          PUSH 67                                        ; |ControlID = 67 (103.)
004014A0   . FF35 24204000  PUSH DWORD PTR DS:[402024]                     ; |hWnd = 0003042A ('KeygenMe V6 - Trial',class='keygenme.v6')
004014A6   . FF15 30104000  CALL DWORD PTR DS:[<&USER32.GetDlgItemTextA>]  ; \GetDlgItemTextA

This small loop will then validate the Username:
asm / disassembly004014AC   . C645 A1 00     MOV BYTE PTR SS:[EBP-5F],0
004014B0   . C645 ED 00     MOV BYTE PTR SS:[EBP-13],0
004014B4   . 8365 98 00     AND DWORD PTR SS:[EBP-68],0
004014B8   . EB 07          JMP SHORT KeygenMe.004014C1
004014BA   > 8B45 98        MOV EAX,DWORD PTR SS:[EBP-68]              ; loop begin
004014BD   . 40             INC EAX
004014BE   . 8945 98        MOV DWORD PTR SS:[EBP-68],EAX
004014C1   > 837D 98 04     CMP DWORD PTR SS:[EBP-68],4
004014C5   . 73 35          JNB SHORT KeygenMe.004014FC
004014C7   . 8B45 98        MOV EAX,DWORD PTR SS:[EBP-68]
004014CA   . 0FBE4405 9C    MOVSX EAX,BYTE PTR SS:[EBP+EAX-64]
004014CF   . 50             PUSH EAX
004014D0   . FF15 10204000  CALL DWORD PTR DS:[402010]                 ;  ntdll.isdigit
004014D6   . 59             POP ECX
004014D7   . 85C0           TEST EAX,EAX
004014D9   . 75 1F          JNZ SHORT KeygenMe.004014FA
004014DB   . 6A 30          PUSH 30                                    ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
004014DD   . 68 24124000    PUSH KeygenMe.00401224                     ; |Title = "KeygenMe V6"
004014E2   . 68 00124000    PUSH KeygenMe.00401200                     ; |Text = "Please enter a number as username!"
004014E7   . FF35 24204000  PUSH DWORD PTR DS:[402024]                 ; |hOwner = 0003042A ('KeygenMe V6 - Trial',class='keygenme.v6')
004014ED   . FF15 5C104000  CALL DWORD PTR DS:[<&USER32.MessageBoxA>]  ; \MessageBoxA
004014F3   . 33C0           XOR EAX,EAX
004014F5   . E9 AB050000    JMP KeygenMe.00401AA5
004014FA   >^EB BE          JMP SHORT KeygenMe.004014BA                ; loop end
004014FC   > 8D45 9C        LEA EAX,DWORD PTR SS:[EBP-64]
Using the isdigit function, every character form the username is checked. In case the username doesn't contain numbers only, a "Please enter a number as username!" message will pop out.
If everything is fine, the jump at 004014C5 (JNB SHORT KeygenMe.004014FC) will be taken, so the correct code flow continues from 004014FC.

From there, we have this code:
asm / disassembly004014FC   > 8D45 9C        LEA EAX,DWORD PTR SS:[EBP-64]
004014FF   . 50             PUSH EAX
00401500   . FF15 14204000  CALL DWORD PTR DS:[402014]           ;  ntdll.atoi
00401506   . 59             POP ECX
00401507   . 8945 A8        MOV DWORD PTR SS:[EBP-58],EAX
0040150A   . 8365 94 00     AND DWORD PTR SS:[EBP-6C],0
0040150E   . EB 07          JMP SHORT KeygenMe.00401517
00401510   > 8B45 94        MOV EAX,DWORD PTR SS:[EBP-6C]        ; loop begin
00401513   . 40             INC EAX
00401514   . 8945 94        MOV DWORD PTR SS:[EBP-6C],EAX
00401517   > 837D 94 04     CMP DWORD PTR SS:[EBP-6C],4
0040151B   . 7D 1D          JGE SHORT KeygenMe.0040153A
0040151D   . 8B45 94        MOV EAX,DWORD PTR SS:[EBP-6C]
00401520   . 8D0CC5 1000000>LEA ECX,DWORD PTR DS:[EAX*8+10]
00401527   . 8B45 A8        MOV EAX,DWORD PTR SS:[EBP-58]
0040152A   . 99             CDQ
0040152B   . F7F9           IDIV ECX
0040152D   . 0FAF45 A8      IMUL EAX,DWORD PTR SS:[EBP-58]
00401531   . 8B4D 94        MOV ECX,DWORD PTR SS:[EBP-6C]
00401534   . 89448D F0      MOV DWORD PTR SS:[EBP+ECX*4-10],EAX
00401538   .^EB D6          JMP SHORT KeygenMe.00401510          ; loop end
0040153A   > 8365 90 00     AND DWORD PTR SS:[EBP-70],0
The username (all numeric) is converted from string to integer by the atoi function.
A loop between 00401510 and 00401538 will fill an array of integers with some calculations made by IDIV and IMUL.

The array has four elements, which are set like so:
C / pseudo codeint username = 1337;
unsigned int t[4] = {0};
for (i = 0; i < 4; i++) {
   t[i] = (username/(i*8+0x10))*username;
}


The code from this point and below is the keygen routine and it's doing both calculations and verifications. In other words, the code will verify every calculated number against the entered user, instead of just calculating the whole correct serial and comparing it to the user's serial.

The whole routine code is quite long and I'll again split it in parts (in matter of fact it's actually two almost identical pieces of code, but we'll get to that later):
asm / disassembly0040153E   . EB 07          JMP SHORT KeygenMe.00401547
00401540   > 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
00401543   . 40             INC EAX
00401544   . 8945 90        MOV DWORD PTR SS:[EBP-70],EAX
00401547   > 837D 90 14     CMP DWORD PTR SS:[EBP-70],14
0040154B   . 0F83 C5010000  JNB KeygenMe.00401716

From the code above, i can see that EBP-70 is the loop iterator that is INCed by 1 on every run, and when the iterator hits 0x14 bytes, the code flow jumps to the loop exit at 00401716.

Something worth mentioning here is the way that some simple calculations are made:
asm / disassembly00401540   > 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
00401543   . 40             INC EAX
00401544   . 8945 90        MOV DWORD PTR SS:[EBP-70],EAX
;...
004015B8   . 8B45 88        MOV EAX,DWORD PTR SS:[EBP-78]
004015BB   . 40             INC EAX
004015BC   . 8945 88        MOV DWORD PTR SS:[EBP-78],EAX
;...
00401608   . 8B45 8C        MOV EAX,DWORD PTR SS:[EBP-74]
0040160B   . 83E8 0A        SUB EAX,0A
0040160E   . 8945 8C        MOV DWORD PTR SS:[EBP-74],EAX
I don't know if that's intentional, to stretch out the code or just the compiler went "yeah, whatever" and did his job that way.

And another thing is this one:
asm / disassembly004015ED   . 40             INC EAX
004015EE   . 40             INC EAX
004015EF   . 40             INC EAX
004015F0   . 48             DEC EAX
004015F1   . 48             DEC EAX
004015F2   . 48             DEC EAX

;"The code! It does nothing!" - Me
While I'm not sure about the previous code, I'm pretty sure this one was added intentionally, maybe to mislead the reverser. :)

Moving on.
asm / disassembly00401551   . 837D 90 00     CMP DWORD PTR SS:[EBP-70],0
00401555   . 74 3B          JE SHORT KeygenMe.00401592
00401557   . 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
0040155A   . 99             CDQ
0040155B   . 6A 05          PUSH 5
0040155D   . 59             POP ECX
0040155E   . F7F9           IDIV ECX
00401560   . 85D2           TEST EDX,EDX
00401562   . 75 2E          JNZ SHORT KeygenMe.00401592
00401564   . 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
00401567   . 0FBE4405 D8    MOVSX EAX,BYTE PTR SS:[EBP+EAX-28]
0040156C   . 83E8 2D        SUB EAX,2D
0040156F   . F7D8           NEG EAX
00401571   . 1BC0           SBB EAX,EAX
00401573   . 40             INC EAX
00401574   . 8845 AF        MOV BYTE PTR SS:[EBP-51],AL
00401577   . 0FB645 AF      MOVZX EAX,BYTE PTR SS:[EBP-51]
0040157B   . 85C0           TEST EAX,EAX
0040157D   . 75 07          JNZ SHORT KeygenMe.00401586
0040157F   . E9 92010000    JMP KeygenMe.00401716
00401584   . EB 07          JMP SHORT KeygenMe.0040158D
00401586   > 8B45 A4        MOV EAX,DWORD PTR SS:[EBP-5C]
00401589   . 40             INC EAX
0040158A   . 8945 A4        MOV DWORD PTR SS:[EBP-5C],EAX
0040158D   > E9 7F010000    JMP KeygenMe.00401711
00401592   > 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
At the beginning 00401551 address, the iterator is compared to 0, and if there's a match, the code flow jumps to 00401592.
So, at the first run, the loop will skip the code inside, and on every next run it will execute it.

Between 00401557 and 00401562, the iterator is modulo divided by 5, and if the condition is true, the code between 00401564 and 0040157D will execute.

Something obvious is that SUB EAX,2D. 0x2D is the ASCII character "-", and the SUB;NEG;SBB;INC is a fancy way to "compare" it with the value [EBP+EAX-28] holds.

I already know that EBP-28 is the user's entered serial number, and EAX (EBP-70) is the loop iterator, so what this code does is to check that every 5th character of the correct serial number is "-".

If we apply that logic against a string of "*"'s, we'll get this:
*****-****-****-****
Good.

I can now enter some username - 1337 and some faulty serial - 11111-2222-3333-4444 to start debugging from here.

Here's what it seems to be a "is signed" check, but because EBP-70 is the iterator that is limited to 0x14 only, the DEC;OR;INC instructions should never be executed.
asm / disassembly00401592   > 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
00401595   . 25 01000080    AND EAX,80000001
0040159A   . 79 05          JNS SHORT KeygenMe.004015A1
0040159C   . 48             DEC EAX
0040159D   . 83C8 FE        OR EAX,FFFFFFFE
004015A0   . 40             INC EAX
004015A1   > 85C0           TEST EAX,EAX
004015A3   . 0F85 B8000000  JNZ KeygenMe.00401661
004015A9   . 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
;...
The author maybe added (unsigned int) before the JNS?

Anyway, the second TEST;JNZ is important.
Because EAX is first ANDed with 0x80000001, then TESTed, this code might be represented as:
C / pseudo codeif ((EBP-70 % 2) == 0) {
   // continue to 004015A9
} else {
   // jump to 00401661
}
So, it will execute different code every time the loop runs.

Moving on:
asm / disassembly004015A9   . 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
004015AC   . 99             CDQ
004015AD   . 83E2 03        AND EDX,3
004015B0   . 03C2           ADD EAX,EDX
004015B2   . C1F8 02        SAR EAX,2
004015B5   . 8945 88        MOV DWORD PTR SS:[EBP-78],EAX
004015B8   . 8B45 88        MOV EAX,DWORD PTR SS:[EBP-78]
004015BB   . 40             INC EAX
004015BC   . 8945 88        MOV DWORD PTR SS:[EBP-78],EAX
004015BF   . 837D 88 03     CMP DWORD PTR SS:[EBP-78],3
004015C3   . 7E 09          JLE SHORT KeygenMe.004015CE
004015C5   . 8B45 88        MOV EAX,DWORD PTR SS:[EBP-78]
004015C8   . 83E8 04        SUB EAX,4
004015CB   . 8945 88        MOV DWORD PTR SS:[EBP-78],EAX
004015CE   > 8B45 88        MOV EAX,DWORD PTR SS:[EBP-78]
004015D1   . 8B4485 F0      MOV EAX,DWORD PTR SS:[EBP+EAX*4-10]
004015D5   . 8945 8C        MOV DWORD PTR SS:[EBP-74],EAX
004015D8   . 8B4D 90        MOV ECX,DWORD PTR SS:[EBP-70]
004015DB   . 41             INC ECX
004015DC   . 8B45 8C        MOV EAX,DWORD PTR SS:[EBP-74]
004015DF   . 99             CDQ
004015E0   . F7F9           IDIV ECX
004015E2   . 8955 8C        MOV DWORD PTR SS:[EBP-74],EDX
004015E5   . 8B45 8C        MOV EAX,DWORD PTR SS:[EBP-74]
004015E8   . D1E0           SHL EAX,1
004015EA   . 8945 8C        MOV DWORD PTR SS:[EBP-74],EAX
See how stretchy the code is?

If we just simplify it (in pseudo assembler, that is), we'll get this:
asm / disassembly004015A9   . MOV EAX,iterator
004015B0   . ADD EAX,3
004015B2   . SAR EAX,2
004015B5   . MOV DWORD PTR SS:[EBP-78],EAX+1
004015BF   . CMP DWORD PTR SS:[EBP-78],3
004015C3   . JLE SHORT KeygenMe.004015CE
004015C5   . SUB DWORD PTR SS:[EBP-78],4
004015CE   > MOV EAX,DWORD PTR SS:[EBP-78]
004015D1   . MOV EAX,DWORD PTR SS:[EBP+EAX*4-10]
004015E0   . IDIV iterator+1
004015EA   . MOV DWORD PTR SS:[EBP-74],EDX<<1

or converted into pseudo code:
C / pseudo codei = EBP-70;
a = EBP-78;
b = EBP-74;

a = ((i+3)>>2)+1;
if (a > 3) {
   a -= 4;
}
b = ((EBP-10[a]) % (i + 1)) << 1;
Simple.

Finally we got this:
asm / disassembly;004015ED   . 40             INC EAX
;004015EE   . 40             INC EAX
;004015EF   . 40             INC EAX
;004015F0   . 48             DEC EAX
;004015F1   . 48             DEC EAX
;004015F2   . 48             DEC EAX
004015F3   . 837D 8C 09     CMP DWORD PTR SS:[EBP-74],9
004015F7   . 7F 09          JG SHORT KeygenMe.00401602
004015F9   . 8B45 8C        MOV EAX,DWORD PTR SS:[EBP-74]
004015FC   . 83C0 04        ADD EAX,4
004015FF   . 8945 8C        MOV DWORD PTR SS:[EBP-74],EAX
00401602   > 837D 8C 24     CMP DWORD PTR SS:[EBP-74],24
00401606   . 7C 09          JL SHORT KeygenMe.00401611
00401608   . 8B45 8C        MOV EAX,DWORD PTR SS:[EBP-74]
0040160B   . 83E8 0A        SUB EAX,0A
0040160E   . 8945 8C        MOV DWORD PTR SS:[EBP-74],EAX
00401611   > 837D 8C 00     CMP DWORD PTR SS:[EBP-74],0
00401615   . 7C 06          JL SHORT KeygenMe.0040161D
00401617   . 837D 8C 23     CMP DWORD PTR SS:[EBP-74],23
0040161B   . 7E 09          JLE SHORT KeygenMe.00401626
0040161D   > 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
00401620   . 83C0 07        ADD EAX,7
00401623   . 8945 8C        MOV DWORD PTR SS:[EBP-74],EAX
;00401626   > 40             INC EAX
;00401627   . 40             INC EAX
;00401628   . 40             INC EAX
;00401629   . 48             DEC EAX
;0040162A   . 48             DEC EAX
;0040162B   . 48             DEC EAX
0040162C   . 8B45 90        MOV EAX,DWORD PTR SS:[EBP-70]
0040162F   . 0FBE4405 D8    MOVSX EAX,BYTE PTR SS:[EBP+EAX-28]
00401634   . 8B4D 8C        MOV ECX,DWORD PTR SS:[EBP-74]
00401637   . 0FBE4C0D B0    MOVSX ECX,BYTE PTR SS:[EBP+ECX-50]
0040163C   . 2BC1           SUB EAX,ECX
0040163E   . F7D8           NEG EAX
00401640   . 1BC0           SBB EAX,EAX
00401642   . 40             INC EAX
00401643   . 8845 AF        MOV BYTE PTR SS:[EBP-51],AL
00401646   . 0FB645 AF      MOVZX EAX,BYTE PTR SS:[EBP-51]
0040164A   . 85C0           TEST EAX,EAX
0040164C   . 75 07          JNZ SHORT KeygenMe.00401655
See the INC EAX/DEC EAX that I mentioned before. Yeah, anyway.

The code here is pretty straight forward, and the INC/DEC's are just helping me to split it in two parts. :)

First part between 004015F3 and 00401623:
C / pseudo codeif (EBP-74 <= 9) {
   EBP-74 += 4;
}
if (EBP-74 >= 0x24) {
   EBP-74 -= 0x0A;
}
if (EBP-74 < 0 || EBP-74 > 0x23) {
   EBP-74 = i+7;
}

and the second part between 0040162C and 0040164C (again using that fancy SUB;NEG;SBB;INC check):
C / pseudo codeuser_serial = "11111-2222-3333-4444"
charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

if (user_serial[i] != charset[EBP-74]) {
   // whoops, too bad! try again.
} else {
   // continue with the next calculation
}

Now I mention that the calculation code is repeated twice, and that can be easily observed in IDA's graph view:


However, there is a slight difference on that EBP-74 += 4; code, and it's depending on the result of i%2.
Long story short, the rule is:
-case 1- if i%2 (i = 0,2,4,6,8,10...) equals 0, add 4 to EBP-74
-case 2- if i%2 (i = 1,3,5,7,9...) is not equal to 0, add 3 to EBP-74

So, let's write some test code!
Cchar username[] = "1337";					// I'll use that name
char serial[0x15] = "";						// Here I'll store the valid serial
char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";	// Characters used to create a valid serial
int name_int, i, i_name_sum, i_serial;				// Some integers that might be handy
unsigned int name_sums[4] = {0};				// The four elements array used for the username

Good. Now fill the name_sums array...
Cint name_int = atoi(username);

for (i = 0; i < 4; i++) {
   name_sums[i] = (name_int/(i*8+0x10))*name_int;
}

... and init the generator loop:
Cfor (i = 0; i < 0x14; i++) {
   // the following code goes here
}

First I'll add the dash ('-') character routine:
Cif (i > 0 && ((i%5)==0)) { // rule is: i bigger than 0 and i modulo of 5 equal to 0
   serial[i] = '-';        // set a '-' char
   continue;               // move to next iteration, and skip the code below
}

The code that calculates which item from the name_sums array will be used:
Ci_name_sum = (i >> 2) + 1;
if (i_name_sum > 3) {		// if i_name_sum is bigger than 3 (eg. it's 4)
   i_name_sum -=4;		// subtract 4 from it, so it always point somewhere inside name_sums array
}
i_serial = (name_sums[i_name_sum] % (i + 1)) << 1;

Now the character "limit" validators:
Cif (i_serial <= 9) {
   if ((i % 2) == 0) {
      i_serial += 4;		// if i = 0,2,4,6,8... (eg. even number), add 4
   } else {
      i_serial += 3;		// if i = 1,3,5,7,9... (eg. odd number), add 3
   }
}

if (i_serial >= 0x24) { 	// If i_serial exceeds or equals the charset string length
   i_serial -= 0xA;		// subtract 0x0A
}

if (i_serial < 0 || i_serial > 0x23) {	// if i_serial is lesser than 0 and bigger than 0x23
   i_serial = i+7;			// replace it with i+7
}

Finally the i_serial is used as pointer to the correct character, so we just add:
Cserial[i]=*(char*)&charset[i_serial];

There. The code is ready, lets test it.

For username "1337" I got "45898-45A5-IAE6-KAAU", so:


Good. Full keygen code with some bells and whistles added can be downloaded from here: http://nullsecurity.org/download/0e7bc578ebae69d632f0d4842cf13bfb

Comments

* You have an opinion? Let us all hear it!

Guest 16 Feb, 2016 07:18
Very nice explanation. Congratulations XpoZed! /aldeid
© nullsecurity.org 2011-2024 | legal | terms & rules | contacts