Ok, prepare yourself for a long journey - here we go...
After downloading our target from the above URL, I installed it immediately and opened the
urlmaster.exe with Hex-Workshop (MY favourite Hex Editor ;-)) to catch some additional info
about the file. First thing which hit my eyes was that "MSVBVM60.DLL" String at file offset
0x248. "Hmm, another damn Visual Basic program.." I thought by myself and closed Hex
Workshop. Double-Click on urlmaster.exe, a short time of waiting and a Registration-dialog
popped up and told me that my "evaluation period has expired" (?) - it asked me to register
the program. To manage this it's necessary to provide the program with some data:
Username and Regcode... nothing special.
- I entered Username "Freakenstein" and Regcode "305419896" (==12345678H)
- then pressed Ctrl-D to popup SICE and entered "bpx hmemcpy" to set a breakpoint on the
hmemcpy function.
- pressed F5 -> exit SICE and clicked the "Register" Button in the Registration Dialog.
- WHACK! SICE popped up and I found myself in the kernel at the beginning of hmemcpy().
Read Razzia's tutorial "How to crack every VB program" to find out more about this
function.
- traced through the code a few lines by pressing F10 until I reached the following code
snippet: push ecx
shr ecx,02
repz movsd ; copy text (DWORD Blocks) from control to es:di
pop ecx
and ecx,03
repz movsb ; if text dosn't fit DWORD border, copy the rest
- I adjusted the SICE-data window by entering "db es:di" BEFORE the "repz movsd" to have
a look at the text which is going to be copied...
- F10 until repz movsd has been executed, a look at the data-window let me see:
"Freakenstein" - 12 bytes in length. Ok, 12 MOD 4=0 - so the following repz movsb won't
copy any more data, and that's correct of course :)
- So, no need to be interrupted by that bpx hmemcpy anymore. "bc *" cleared it (and all
the other bpx's which were maybe set) away.
- from here I traced through the code by pressing F10 and F12 until I finally left the
kernel and the following MSVBVM60 Module and landed directly in the code section of
another Module called "SOFTGUARD6!"
- Softguard ??? Hmm, never heard about that -> F5, exit SICE. A MessageBox appeared "The
Registrationcode you've entered is not correct for Freakenstein. Please try again." Of
course I was gonna try it again. Hehe. But before that I did a little search using
Altavista and finally found the Homepage of Sofware.pair.com - the home of Softguard.
Read the info I got from there at the top of this tutorial.
- Went offline and repeated the above steps until I found myself in the SOFTGUARD6
Module again. After reading all the stuff about encryption and time dependent codes
at the Softguard manufacturer site I prepared myself for a long and hard fight...
- I came from : CS:11027B79 CALL [ECX+000000A0] ; Get Username
actual code position: CS:11027B7F CMP EAX,EDI
.
- looked some lines below and saw CS:11027BB4 CALL [ECX+000000A0] ; Get Regcode
.
I suspected "Get Regcode" for the last call, checked it and was right :-)
- A few F10's later I found the following lines of code:
CS:11027BD2 mov ecx, [EBP-2C] ; address of my Regcode
CS:11027BD5 mov edi, [MSVBVM60!__vbaLenBstr] ; vb-lstrlen function
CS:11027BDB push ecx
CS:11027BDC call edi ; Returns length of Regcode -> eax
.
CS:11027BE8 push edx
CS:11027BE9 neg ebx
CS:11027BEB call edi ; Returns length of Username -> eax
.
Note that both strings, Username and Regcode, have already been converted to UNICODE
( see MultiByteToWideChar()- function) at this point of time. So, don't expect
"Freakenstein" but:
"F",0,"r",0,"e",0,"a",0,"k",0,"e",0,"n",0,"s",0,"t",0,"e",0,"i",0,"n",0
- Ok, after I knew that the program has determined both String-lengths, I started tracing
(F10) trough the code again... hundreds of lines later after I traced through the kernel,
MSVBVM60 Module, kernel, MSVBVM60... I finally reached SOFTGUARD6 module again:
I came from: CS:11011253 call [ecx+000002B0] ; Registration Dialog/Length check
and landed here: CS:11011259 test eax, eax ; Actual position
.
It seemed to me that those "call [exx+00000xxx]" lines are worth concentrating on.
- I kept that in mind and just found another one of those calls after some F10's:
CS:110114D9 call [ecx+00000874]
.
- This time I used F8 to single step into that call so that I could see what's going on
there. F8 again and the following jmp lead me directly to these lines of code:
CS:11019540 push ebp
CS:11019541 mov ebp,esp
CS:11019543 sub esp,08
.
.
- Some F10's/F8's later(I traced *every* call, except the __vba function calls, I found)
I reached this (most important) call: CS:11019AC7 CALL [EDX+00000844]
- F8 to step in and F8 once again to execute the following jmp, brought me to a routine at
CS:1100DCB0 which is our 'MAIN' KEYCHECK routine es I figured out later...
At this point of time we're DEEP in the body of that Softguard - Too late to stop!
But don't worry. The fun-part just begun...
* IMPORTANT NOTE: Please take this 'tracing through the code' serious! You should know
what's going on and keep a sharp look at the register-changes for the WHOLE time you
work on that program. Some additional looking at the memory beeing accessed should be
obvious. Every miss of an important value, a quickly traced 'call ...' because you're
maybe bored can lead you directly to a GAME OVER - This means that nice "Registration
code is not correct"- Message Box. So, pay attention! ;-) *
- Ok, let's go on.
Remember our actual position ? No ? It's CS:1100DCB0. I continued tracing from here and
found the following important (and of course MAGIC :-)) calls:
1. CS:1100DD94 CALL [EDX+000008A0] ; Removes all Chars <20h or >7Eh from Username
; and set new Namelength if necessary
2. CS:1100DDD1 CALL [EDX+000008A4] ; 'Scramble' Username, here: Freakenstein->nFireetaske
; I'll explain the "how to" later...
3. CS:1100DE08 CALL 110247F0h ; Build decimal codes from Chars of Scrambled Username
; and XOR every Char of a constant Key_1 (34 bytes long)
; with one char from these codes. Result: Key_2 (34b.)
; If decimal-code length is <34 reset offset to 0 again...
4. CS:1100DF30 CALL [EDX+00000898] ; Reverse byte order of Key_2
5. CS:1100E04F -> Begin of loop which builds decimal codes from chars of Key_2 and stores
them in a buffer [B]
- Explanation of how to build these "decimal code" Strings follows later!
- Well, that was a far step in the right direction :-) What now follows is the generation
of the FINAL KEY -> Key_F which will be compared later to the Regcode I entered
in the Registration Dialog. The Key-generation works as follows:
URLmaster v3.1 uses an ASCII-MASK to generate Key_F. After the loop at CS:1100E04F
I landed into another loop at CS:1100E196 which generates Key_F by using the Chars
from Buffer [B] and this mysterious ASCII-MASK. Here's the MASK and a short ex-
planation of the Chars it consists of:
UR###-#^#-###^^
This Mask represents the format a valid Regcode MUST have. "U", "R" and "-" are constant.
The key must contain them in same format as they appear in the MASK. Each "#" represents
a decimal number between 0 and 9 (30h-39h) and every "^" represents an UPPERCASE letter
ranging from "A" to "Z".
You should be able to find that out by yourself while tracing through that loop at
CS:1100E196. Hint: have a look at the memory location
eax points to, after __vbaStrCat or __vbaStrVarMove function-calls...
Generation of the "#"'s (the numbers)
Well, that's pretty easy! The routine takes these values directly from Buffer [B]:
01."#" ==> 01.Char from [B]
02."#" ==> 02.Char from [B]
03."#" ==> 03.Char from [B]
04."#" ==> 04.Char from [B]
05."#" ==> 07.Char from [B]
06."#" ==> 08.Char from [B]
07."#" ==> 09.Char from [B]
08."#" ==> 10.Char from [B]
Generation of the "^"'s (the letters)
01."^" ==> 05. and 06. Char from [B] ==> swap order (e.g. "49" -> "94") ==> ASCII to REAL8
==> Check range of REAL8 value and adjust it if necessary ==> REAL8 to INT ==> INT
to ASCII (THE letter)==> uppercase letter
02."^" ==> 11. and 12. Char from [B] =>... see 01.
03."^" ==> 13. and 14. Char from [B] =>... see 01.
That's all. Key_F has been generated. Name: Freakenstein - Regcode: UR851-2T4-641UY
But I'm not finished yet. There're some important questions left I think ;-)
- That range-check/Letter calculation routine which contains all that floating point stuff is
located at CS:1101D804. It uses rtcR8ValFromBstr function to convert the two-digit
decimal ASCII-String to a REAL8 floating point value X. I examined that routine, and here's
a summary of what it does (in same order as it appears in the routine):
X = our REAL8 value
IF X >=65 AND <=90 => DONE!
IF X >=97 AND <=99 => DONE!
IF X >=00 AND <=22 => ADD X,100 =>DONE!
IF X >=91 AND <=96 => SUB X,6 =>DONE!
IF X >=23 AND <=44 => ADD X,42 =>DONE!
IF X >=45 AND <=64 => ADD X,52 =>DONE!
Believe it or not (better check by yourself - Hehe) - this routine needs about 160(!) lines
of code to do these few checks and a few additional error-checks (Invalid Operation, Zero
Divide and Overflow). You see that the whole range of a possible value from a 2-digit decimal
number is checked. (0-99). Before exiting that routine the final value (REAL8) will be
converted to INT using the __vbaFpI4 function and then to a Char (our Letter) by using
the rtcVarBstrFromAnsi function. This letter will be always changed to UPPERCASE ("a" -> "A")
If you want to rip this HUGE check routine for your keygen, use IDA Pro to disassemble
the file 'softguard.ocx'. You'll find it in your WINDOWS\SYSTEM directory. For the number-
conversions you can use the following functions from MSVCRT.LIB:
atof() -> Decimal String to float
_gcvt() -> Float to Decimal String
atoi() -> Decimal String to int
Once examined that damn routine I reduced these 160 lines to a selfmade routine which doesn't
use any floating point operations and which only consists of a few lines. Decide by yourself
which routine you want to use. Here comes mine:
.DATA
add_tab db 23 dup(100) ; add 100 to values from 00 - 22
db 22 dup(42) ; add 42 to values from 23 - 44
db 20 dup(52) ; add 52 to values from 45 - 64
db 26 dup(0) ; add 0 to values from 65 - 90
db 6 dup(-6) ; add -6 to values from 91 - 96
db 3 dup(0) ; add 0 to values from 97 - 99
to_int db 0,0,0 ; buffer holds 2 digit Decimal-value for Letter-calc., ends with NULL
.CODE
CalcLetter PROC
call atoi, offset to_int ; decimal-string to int
add esp, 4 ; correct stack
lea esi, add_tab
mov bl, byte ptr[esi+eax] ; get value to add
add al, bl ; adjust
and eax, 0DFh ; to UPPERCASE
RET
CalcLetter ENDP
- Ok. Now the final "secret" behind that key-calculation: The Username-'Scrambling'.
Do you remember ? "Freakenstein" becomes "nFireetaske" - How ? Simple:
1. Get Namelength
2. Eliminate Char at position Namelength/2+1 -> Zero it!
You've just divided the Name into 2 parts: "Freake" and "stein"
3. Begin from Start of "Freake" and from the End of "stein"
4. Save "n" in destination buffer and decrease actual string-offset
5. Save "F" in destination buffer and increase actual string-offset
6. increase offset to destination buffer by 2
7. loop until (the eliminated) 0-byte is reached and save last value
which is non-zero (here: "e") at actual position of destination buffer.
Here's my solution for this:
ScrambleUserName PROC
lea esi, userinput ; buffer which holds username
mov edi, esi
lea edx, scr_buff ; destination buffer
mov ecx, nlen
add edi, ecx
dec edi
shr ecx, 1 ; length/2
and byte ptr[esi+ecx], 0 ; eliminate Char at Length/2+1
@@loop: mov al, [esi]
mov ah, [edi]
test ah, ah
je @@out ; exit loop if NULL byte has been reached
mov [edx], ah
mov [edx+1], al
add edx, 2
inc esi
dec edi
jmp @@loop
@@out: mov [edx], al ; store last non-zero value
and byte ptr[edx+1], 0 ; set byte after scrambled name to NULL
RET
ScrambleUserName ENDP
Other questions left ? Here're the answers:
- The konstant Key which the author uses for encryption is this:
db "^iAi>?FgxmU8r@obiwankenobiQGOGqeMe" - length: 34 Bytes
It can be easily found in memory while tracing through the code. Greets
to all Star Wars fans - have you noticed "obiwankenobi" ? :-)
- That "building of decimal codes" I told you of earlier, works as follows:
Get value (e.g. 41h for "A") => 41h (65 Dec.) => convert to String "65" (36h,35h)
That's all I think. I hope I could give you an answer to most of your questions.
If you're not satisfied yet -> Try to find the answer by yourself. Make a Keygen!
As you've seen it's not too hard.
Seems that we're done :-) Here is a little Step by Step Summary for a Keygen:
1. Check length of Username - A length with less than 2 chars won't work
2. Remove all Chars <20h or >7Eh from Username and adjust namelength if necessary
Example: "ABC§DE" - length 6, becomes: "ABCDE" - length 5 ("§"=A7h)
3. Scramble Username
4. Build Decimal-codes of scrambled name e.g. "ABCs" --> "656667115"
5. XOR the konstant key "^iAi>?FgxmU8r@obiwankenobiQGOGqeMe" with that Decimal-codes
Byte by Byte. 34 times. If Decimal Code is <34 and you reached its end just begin
from the start again... :-)
6. Reverse byte order from Key_2 you got as result in Step 5. That means save that key
from End to Start while increasing pointer to destination buffer.
7. Build Decimal-codes of that reversed Key_2
8. Generate valid key which has this format: UR###-#^#-###^^. Read above to find
out how to...
9. Keyoutput.
A. Have fun :-)
Furthermore don't call me stupid. I know that you can combine Steps 5 and 6 by storing
the results of the XOR operations in reversed order -from End to Start-, but I just
want to show you the way SoftGuard does this ;-)
| Final Notes |