"1001 Killer Internet Marketing Strategies" v1.3
[Reversing essay]
Published by Tsehp, July 2000.

Subject: Cracking
Target: 1001 Killer Internet Marketing Strategies v1.3
URL: http://www.killertactics.com
Author: BlackB
Date: 2000-07-25
Tools used: SoftICE, Borland C++ v5.02
Difficulty (scale 1-5): 2

Before starting!
This essay is for knowledge purposes only!!
Software developers spend much time in making their programs. They live from the money we give them!
Please buy good software!!
I. Introduction
This essay is made as a result of a mail I got from a person that asked me for help for this program. Just to show you that if you send a polite email with the best intentions, I do such things ;-)
However....I throw many of those requests away as most of them, in the end, come down to only one thing: a crack or a serial. And in that case your mail will be for maximum 5 seconds on my computer's harddisk.
This essay won't only show you where you can find a valid serial, but also will show you how to write a keygen for it.
II. About the protection
Some limitations, option to enter username and unlockcode.
III. Cracking it

Run the program :-) Before I started reversing it, I had read some of the articles, and I must admit that it's very interesting! The mind of a general internetsurfer is completely reversed, therefore it's also an excellent guide on how to protect yourself and others against the aggressive commercial advertisement on the net. Someone ever said: "To beat your enemy, you first have to know your enemy"....and sure this program helps.

Anyway, I would explain how to crack this program ;-) Click on the little key picture in the right. A window appears where you can see a: fixed serial, namebox, fixed publication title and registration code box. I suspect that the serial generated in the caption of the registration window is not really fixed, but differs from computer to computer.
Ok, now enter your name or nickname, enter a bogus registration code. Notice that the registration code should be 12 chars long, as that's the maximum allowed to type in the box.
In SoftICE set a breakpoint on hmemcpy: bpx hmemcpy. Now click the 'OK' button and SoftICE pops up. Press F12 (=execute until 'ret') until you come into the main code. It looks like this:

Start partial code

0137:0045ACDB  MOV       EAX,[EBP-24]
0137:0045ACDE  LEA       EDX,[EBP-20]
0137:0045ACE1  CALL      0045B850
0137:0045ACE6  LEA       EAX,[EBP-20]
0137:0045ACE9  MOV       EDX,0045B030
0137:0045ACEE  CALL      0040377C


End partial code

Start tracing the instructions with F10. There are much unimportant instructions that do basic tests, like checking if the username is minimum 3 chars long and that sort of stuff.
At a certain moment you come across these instructions:

Start partial code

0137:0045AE73  MOV       ECX,[EBP-10]
0137:0045AE76  MOV       EDX,[EBP-08]
0137:0045AE79  MOV       EAX,[EBP-04] (1.)

0137:0045AE7C  CALL      0045B998     (2.) 
0137:0045AE81  LEA       EDX,[EBP-20] 
0137:0045AE84  MOV       EAX,[EBP-1C]
0137:0045AE87  CALL      0045BA88     (3.)
0137:0045AE8C  MOV       EAX,[EBP-20]
0137:0045AE8F  MOV       EDX,[EBP-0C]
0137:0045AE92  CALL      00403884
0137:0045AE97  JNZ       0045AFB5

End partial code

(1.) These three MOV instructions load our username, fixed serial, and the string "1001 Killer Internet".
Therefore the next call is pretty suspicious, as it will probably process the loaded items. Therefore you can also know that all the other instructions before are not important as they never load so many important data before calling a procedure.

(2.) And as I predicted, this call does some 'magic' calculations ....let's take a look inside ;-) Trace into that call (F8) and trace further (F10) until you see this:

Start partial code

0137:0045B9F1  MOV       DL,[EBX+EAX-01]
0137:0045B9F5  MOV       EAX,[EBP-08]
0137:0045B9F8  XOR       ECX,ECX
0137:0045B9FA  MOV       CL,[EBX+EAX-01]            <- CL= char of string1
0137:0045B9FE  CMP       BYTE PTR [EBP+0C],00
0137:0045BA02  JZ        0045BA0E
0137:0045BA04  MOV       EAX,[EBP-0C]
0137:0045BA07  MOVZX     EAX,BYTE PTR [EBX+EAX-01]  <- EAX= char of serial
0137:0045BA0C  JMP       0045BA13
0137:0045BA0E  MOV       EAX,00000001
0137:0045BA13  MOV       ESI,[EBP+10]               <- ESI= string2
0137:0045BA16  MOVZX     ESI,BYTE PTR [EBX+ESI-01] 
0137:0045BA1B  MOV       [EBP-10],ESI               <- EBP-10= char of string2  
0137:0045BA1E  MOV       ESI,ECX                    <- ESI= char of string1
0137:0045BA20  XOR       ESI,EDX                    <- ESI= string1[ebx] XOR username[ebx] 
0137:0045BA22  XOR       ESI,[EBP-10]               <- ESI= ESI XOR string2[ebx]   
0137:0045BA25  XOR       ESI,EAX                    <- ESI= ESI XOR serial[ebx]
0137:0045BA27  TEST      ESI,ESI
0137:0045BA29  JNZ       0045BA30
0137:0045BA2B  MOV       ESI,000000FF
0137:0045BA30  LEA       EAX,[EBP-14]
0137:0045BA33  MOV       EDX,ESI
0137:0045BA35  CALL      00403710
0137:0045BA3A  MOV       EDX,[EBP-14]
0137:0045BA3D  MOV       EAX,EDI
0137:0045BA3F  CALL      0040377C
0137:0045BA44  INC       EBX
0137:0045BA45  CMP       EBX,0D                     <- Processed all 12 chars?
0137:0045BA48  JNZ       0045B9EC                   <- No: jump, Yes: don't jump.

End partial code


first of all let me explain my variables I declared myself:
username= BlackB
serial= 2393-1455123 (123 is added at the end btw)
string1= 1001 Killer
string2= 123456789012

All these strings are arrays of characters. That means that username[1] equals B and that serial[3] equals 9 and that string2[10] equals 0 and that string1[6] equals K.....you get the point?

In previous code snippet, EBX is the counter. It's used to check if the end of the 12 chars is reached AND used to select a certain character. If EBX==1 then serial[ebx] equals 2. If EBX==4 then username[ebx] equals c. Got it? :)
Now go back to the code snippet and try to understand what it does.

You did that? You still don't understand? Okay, let me translate this assembler snippet into some kind of English or pseudo programming language:
First assume the variables I declared above in yellow.

equal ebx to 1
declare a variable named 'result'
while ebx does not equal 13 repeat following instructions:
result= string1[ebx] XOR username[ebx]
result= result XOR string2[ebx]
result= result XOR serial[ebx]
increase ebx with 1

When everyting is executed, the variable 'result' will contain a 12-char long string that will be used in the next call to generate a valid registration code.

(3.) As I just mentioned, this call generates the real registration code.
Trace into that call (=F8). You should see this:

Start partial code

0137:0045BAEE  JMP       [EDX*4+0045BAF5]            <- EDX is counter now, startvalue is 1
                                                        This instruction jumps to the table
                                                        you can see below. 
0137:0045BB32  MOV       DWORD PTR [EBP-10],0000000E 
0137:0045BB39  JMP       0045BB93
0137:0045BB3B  MOV       DWORD PTR [EBP-10],00000009
0137:0045BB42  JMP       0045BB93
0137:0045BB44  MOV       DWORD PTR [EBP-10],0000000F
0137:0045BB4B  JMP       0045BB93
0137:0045BB4D  MOV       DWORD PTR [EBP-10],00000003
0137:0045BB54  JMP       0045BB93
0137:0045BB56  MOV       DWORD PTR [EBP-10],00000005
0137:0045BB5D  JMP       0045BB93
0137:0045BB5F  MOV       DWORD PTR [EBP-10],00000001
0137:0045BB66  JMP       0045BB93
0137:0045BB68  MOV       DWORD PTR [EBP-10],00000012
0137:0045BB6F  JMP       0045BB93
0137:0045BB71  MOV       DWORD PTR [EBP-10],0000000D
0137:0045BB78  JMP       0045BB93
0137:0045BB7A  MOV       DWORD PTR [EBP-10],00000008
0137:0045BB81  JMP       0045BB93
0137:0045BB83  MOV       DWORD PTR [EBP-10],0000000C
0137:0045BB8A  JMP       0045BB93
0137:0045BB8C  MOV       DWORD PTR [EBP-10],00000013
0137:0045BB93  XOR       EBX,EBX
0137:0045BB95  MOV       BL,AL         <- Put first char of variable 'result' into BL
0137:0045BB97  ADD       EBX,[EBP-10]  <- Add the table nr. to it
0137:0045BB9A  CMP       EBX,5A        <- From here to 0045BBB4 all calculations are made
                                          to convert the char in BL into a valid char for
                                          the registration code
0137:0045BB9D  JLE       0045BBA7
0137:0045BB9F  SUB       EBX,19
0137:0045BBA2  CMP       EBX,5A
0137:0045BBA5  JG        0045BB9F
0137:0045BBA7  CMP       EBX,41
0137:0045BBAA  JGE       0045BBB4
0137:0045BBAC  ADD       EBX,19
0137:0045BBAF  CMP       EBX,41
0137:0045BBB2  JL        0045BBAC
0137:0045BBB4  LEA       EAX,[EBP-18]
0137:0045BBB7  MOV       EDX,EBX
0137:0045BBB9  CALL      00403710
0137:0045BBBE  MOV       EDX,[EBP-18]
0137:0045BBC1  LEA       EAX,[EBP-14]
0137:0045BBC4  CALL      0040377C
0137:0045BBC9  INC       ESI
0137:0045BBCA  CMP       ESI,0D
0137:0045BBCD  JNZ       0045BAC2       <- If not all 12 chars processed: jump and repeat

End partial code

The only thing that may be confusing here is the table. Let me explain:

EDX is a counter that has startvalue 1. It's used to point to the first char of our string variable 'result'. Everytime the calculations for that first char are finished, EDX is increased with 1. So, in the next loop, EDX will be 2 and it will point to the second char of the string variable 'result'. Hope that's clear enough.

Everytime a loop is executed, a number is added to every char of our variable 'result'.
However, that number is not fixed. It's differs every time the loop is executed.
This is quite obvious as if EDX==2, it will execute: "jmp [2*4 + 0045BAF5]" and execute "0137:0045BB32 MOV DWORD PTR [EBP-10],0000000E", which moves the hex value '0E' into EBP-10.
Next loop, EDX will be 3, and so it will execute: "jmp [3*4 + 0045BAF5]" and that will jump to "0137:0045BB3B MOV DWORD PTR [EBP-10],00000009".

I wrote that table down on paper:

loop01 value: 6
loop02 value: E
loop03 value: 9
loop04 value: F
loop05 value: 3
loop06 value: 5
loop07 value: 1
loop08 value: 12
loop09 value: D
loop10 value: 8
loop11 value: C
loop12 value: 13

(note that all these values are HEX values!)

The rest of the instructions only make an upcase character. You only have to be able to translate it into a programming language to make your keygen. I won't explain how I translated it into C++ as I assume you can if you're a programmer. And if you're not programmer you can't make keygens.

Start keygen c++ code

#include <iostream.h>
#include <string.h> 
#include <iomanip.h>

char table[12]={0x6, 0xE, 0x9, 0xF, 0x3, 0x5, 0x1, 0x12, 0xD, 0x8, 0xC, 0x13};
char string1[12]={"1001 Killer "};
char string2[12]={"123456789012"};

char serial[12];
unsigned char result[13];
char username[12];
int i=1;

void readln(char *s)
 int i=0;
 char ch;
 cin >> resetiosflags(ios::skipws);
 while (cin >> ch, ch!='\n') s[i++] = ch;

void main(void)
 cout << "Keygen for \"1001 Killer Internet Marketing Tactics\" by BlackB [EVC] 2000" << endl;
 cout << "Enter username: ";
 while (strlen(username) < 3 || strlen(username) > 12)
   cout << "Username must be at least 3 characters and max. 12 characters long!" << endl;
   cout << "Re-enter username:";
 cout << "Enter serial (appears in left top corner of reg window):";
 cin >> serial;
 while (strlen(serial) != 9)
   cout << "Length of serial should be 9!" << endl;
   cout << "Re-enter serial:";
   cin >> serial;

 for (i;(12-strlen(username));i++)
   strcat(username, " ");  //Add spaces, as they are used to generate the unlock code.

 for (i=0;i<=11;i++)
   result[i]= string1[i]^username[i];
   result[i]= result[i]^string2[i];
   result[i]= result[i]^serial[i];

 for (i=0;i<=11;i++)
   if (result[i] <= 0x5A)
     while (result[i] < 0x41)
    } else               //else: if result[i] > 0x5A
        while (result[i] > 0x5A)

 cout << "Your registration code is: " << result << endl;
 cout << "Enjoy!" << endl;

End keygen c++ code

If you don't want to keygen this program, and just want to know where the serial is created: just follow this tutorial, don't mind if you don't understand the keygen algorithms, and find the real unlock code after the two loops in the two calls have completed....should be a one minute job ;)

Oh yea, before I forget: your registration information is stored in X:\WINDOWS\HMVIEWER.INI. Note that it's encrypted.
Removing the file will unregister you again ;-) I found out by using FileMon on the program......obviously :P

Finished again.....

IV. In the end

When you're done reversing this application, take your time to read the valuable information inside it. It's really worth the time.
Greets goto FishMAN. Hope you learned something with this tutorial.


The Blackbird

Essay written by The Blackbird 1999-2000
This essay can be freely distributed/ published/ printed etc... as long as no modifications are made.