Welcome once again. It seems for some reason that a lot of very capable reversers struggle with Visual Basic applications, in fact the difficulty most people have with VB is following the quagmire that is the runtime dll. News Poster Pro provides an example of how to systematically reverse engineer a VB protection. However a small theoretical digression, with VB5 we already know that our beloved W32Dasm will for the most part be ineffective, so lets use other tools, like RegMon & FileMon for clues.
Launch News Poster Pro and make a note of your User Key (Register Menu), mine is USJXRK, now lets make our way to enter the registration key we obtained after paying our $199. With SoftICE our best option we must turn our mind to breakpoints, you should find that our nag box is easily located with a >bpx RtcMsgBox. Its called at address 0046E031.
Looking at this code inside W32Dasm, you should easily trace back this as being the deciding jump.
:0046DEF4 CALL 00446720 <-- Function call.
:0046DEF9 MOV CX, WORD PTR [0047B070] <-- Set value of CX.
:0046DF00 CMP CX, WORD PTR [0047B308] <-- Check CX against ptr 0047B308.
:0046DF07 JNZ 0046DFDF <-- Jump_Bad_Registration_Code.
So lets just think carefully and logically, its amazing how many reversers just blindly reverse jumps without feeling the code. See how reversing the jump results in a call to RtcMsgBox being made at address 0046DFC7 after which the code will just jump over our bad message box. So the code in between must perform some kind of user configuration, say for example, placing the information in the registry.
You might like also to think about which functions would be worth tracing, just examine the code before our critical jump. So lets jump in with SoftICE, I would suggest >bpx __VbaR8IntI2 but you can also reach here with >bpx Hmemcpy, allowing 3 breaks, using F12 to reach msvbvm50.dll and then F10 work.
Trace inside 00446720 and look around, note some of these e-mail addresses you'll see being copied into various locations as well as the table there seems to be at location 0041FD00, as you might expect, patience and gentle code examination is the key, and eventually you'll trace to this very simple compare routine.
:00446910 MOV EAX, DWORD PTR [0047B05C] <-- Code entered,
use ? EAX.
:00446915 TEST EAX,EAX <-- Check code was entered.
:00446917 JZ 00446949 <-- Bad_jump.
:00446919 MOV ECX, DWORD PTR [0047B058] <-- Real good code, use ? ECX.
:0044691F TEST ECX,ECX <-- Was good code 0, (a strange check).
:00446921 JZ 00446949 <-- Bad_jump.
:00446923 CMP EAX,ECX <-- Compare.
:00446925 JNZ 00446935 <-- Bad_jump.
Several things you could have done differently here, firstly the zen way, you knew from the first code snippet that locations [0047B070 & 0047B308] were significant, if you had searched the disassembly for these locations you would have located the above compare very easily (look at the 2 instructions directly after JNZ 00446935). The good code is of course written to a registry key, HKEY_USERS/.Default/Software/VB and VBA Program Settings/NPP2.1/data, the relevant key being 'misd'. I didn't actually look to see how the program calculates the good code (I know it must use the key because the user name and organisation do not make any difference). Note that you may also have been able to reverse engineer this protection using RegMon and bpx's on registry functions.
Courtesy of Prophecy [tNO '98]
In this tutorial, I will use a target called News Poster Pro v5.3.4 (NPP for short). If you haven't read CrackZ's essay above, this program employs a linear algorithm. There are various ways to approach VB targets. I always like to try SmartCheck first, because sometimes it produces a high level deadlisting of what's going on, which makes reversing quite enjoyable. However, SmartCheck doesn't seem to work effectively sometimes, e.g quite often the serial algorithm is not present. Unfortunately, this is the case with NPP, so we'll have to use SoftICE, which is no problem.
BTW, while in SmartCheck I saw these strings:
WARNING! WARNING! WARNING! WARNING! This program is equipped with S.I.P.V Secure IP Verification v5.4 (C)1998 SIPV.INC Your IP address is being sent to vendor: #123097-NPP Copy of encrypted addresses: (Total=3) email@example.com firstname.lastname@example.org email@example.com
I thought this was incredibly lame... so lame I didn't even laugh. Anyway, if you read on, you'll discover that the only thing lamer than the above is the serial algorithm itself!.
What happens usually with unlock codes is:
Unlock code fed into serial generator => spits our good code
This is exactly what happens with NPP. We want to find the place where this happens. There are 2 options:
With NPP option 1 occurs.
We want to find where our unlock code is stored. This means we can set a breakpoint on the opening of a registry key etc... unfortunately with NPP, no dice. Couldn't find the unlock code, so it's stored in some encrypted form, somewhere... doesn't matter, move onto plan B. Plan B involves monitoring the strings on startup until we reach our unlock code.
Visual Basic uses these two APIs to manipulate strings:
1. MultiByteToWideChar 2. WideCharToMultiByte
MultiByteToWideChar converts a string to unicode, meaning, it puts the null character (0x0) between each character. e.g. the string "Bob" which is 42 6F 62. In SoftICE, it gets converted to "B.o.b" which is 42 00 6F 00 62 00. (Don't confuse the '.' with the fullstop, I'm using it to represent the null character). WideCharToMultiByte converts a unicode string back to normal.
We'll be interested in what the API references say about these functions:
int MultiByteToWideChar( UINT CodePage, // code page DWORD dwFlags, // character-type options LPCSTR lpMultiByteStr, // address of string to map int cchMultiByte, // number of characters in string LPWSTR lpWideCharStr, // address of wide-character buffer int cchWideChar // size of buffer );
WideCharToMultiByte is similar.
I have put in bold the important parameters. The first one is the address of the string we're going to convert, the second one is the place where it will end up. Initially we're interested in the first bold parameter. Once it's converting the unlock code we'll turn our attention to the second bold parameter. Because the 1st bold parameter is the 3rd parameter in the list, it will be the 4th DWORD on the stack. We therefore set the following breakpoints:
:bpx multibytetowidechar do "d *(esp+4*3)" :bpx widechartomultibyte do "d *(esp+4*3)"
This will break on the above APIs and automatically display the string that is being converted. Once you've set those two bpx's fire up NPP, it will break quite a few (50?) times before reaching our user key.
With NPP, it manipulates each character 1 by 1 with the WideCharToMultiByte. You should also find your full user key close to the address that SoftICE displays. For example, my unlock code is: 42R8YT. When the first char was accessed via WideCharToMultiByte I saw this in memory:
013F:0059334C 34 00 00 00 52 00 38 00 59 00 00 00 24 00 00 A0 4···R·8·Y···$··· 013F:0059335C 0C 00 00 00 34 00 32 00 52 00 38 00 59 00 54 00 ····4·2·R·8·Y·T· 013F:0059336C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ················ 013F:0059337C 24 00 00 A0 0C 00 00 00 34 00 32 00 52 00 38 00 $·······4·2·R·8· 013F:0059338C 59 00 54 00 00 00 00 00 00 00 00 00 00 00 00 00 Y·T············· 013F:0059339C 00 00 00 00 65 CC 00 A0 0C 00 4A 00 3C 00 4A 00 ····e·····J·<·J· 013F:005933AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ················
The first char of my user key is being converted to usual format. As you can see, our full user key is also sitting just there in memory.
We now type:
To examine the parameters stack (ESP means extended stack pointer, i.e. it shows you what is on the stack), this should look something like this (numbers may be slighly different for you):
013F:006CF71C 0F03C8D0 00000000 00000000 0059334C ............L3Y. 013F:006CF72C 00000001 006CF746 00000002 00000000 ....F.l......... 013F:006CF73C 00000000 0F01F8DA 0043FBFC 006CF7CC ..........C...l. 013F:006CF74C 0043FC0A 0059334C 0F01F8A3 00000000 ..C.L3Y......... 013F:006CF75C 00000001 004A0000 00000024 00000000 ......J.$....... 013F:006CF76C 00000000 19E013D1 004A0000 00000000 ..........J.....
The parameter in red is the return address of the function. It is always the first parameter on the stack. The parameters used by the actual function (e.g. WideCharToMultiByte) itself (i.e. the ones listed in the API reference guide) start after that. As you can see, the two parameters in bold are shown (these correspond to the bold parameters mentioned earlier). Typing:
will display the string that's about to be converted.
We now type:
....and hit f11, hey presto, the character (for me 4) is now sitting at 6cf746. Note it is a bit tedious hitting f5 all the time on startup until you reach the desired conversion.
For the purpose of this essay, you can set this bpx to speed things up:
:bpx widechartomultibyte if (**(esp+4*3)&ff)=='x' do "d *(esp+4*3)"
This expression looks quite scary, but it isn't too bad really... *esp means what ESP is pointing to (e.g. what you see when you type d esp). 'x' is the first char of your user key. So all the expression does is breaks if the char it is about to convert is x, where x is the first char of your user key.
I didn't employ this tactic because you don't know in which order the user key is going to be accessed, etc. You may have to hit f5 a few times before reaching the desired spot.
Anyway, we've now just hit f11, and our char is at 6cf746. We're here:
f03c8cf call [kernel32!widechartomultibyte] cmp eax,02 <--- here
We immediately set a breakpoint on the char:
:bpr 6cf746 6cf746+0 rw
Hit f5, and we end up here:
movsx ax, byte ptr [ebp-2]
ax now contains the first char of our user key. Now f8 trace until you reach:
0043fc10 movsx eax,ax add eax,ecx ;checksum . . mov [0047b058],eax ;store checksum in 47b058
Our first char is having ecx (which is initially 0) added to it... this looks immediately like some form of checksum. It is being stored in 47b058, so once you reach that point set a bpr on it:
:dd 47b058 ;shows the checksum in a convenient way :bpr 47b058 47b058+3 rw
Now hit f5, and you'll land here:
0043fc3a imul ecx,[0047b058] ;[0047b058] == our checksum . . mov [0047b058],ecx
This is a straightforward multiplication of our checksum with ecx, which is initially 6. Again our checksum is stored in 47b058 so we don't need to set a new bpr. Now if you used:
:bpx widechartomultibyte if (**(esp+4*3)&ff)=='x' do "d *(esp+4*3)"
clear this breakpoint and replace it with:
:bpx widechartomultibyte do "d *(esp+4*3)"
and hit f5. The next char of your user key is about to be converted,
so hit f11, and repeat the above procedure. I'll let you nut out
exactly what happens with the remaining chars, but take it from
me, you'll be hard pressed to find a simpler algorithm (you'll
be able to work it out from my source code shown later on, anyway).
After it's looped through all the chars of your name, disable the widechartomultibyte and multibytetowidechar breakpoints, but keep the bpr on where the checksum is stored. NPP will continue loading until it breaks here:
446919 mov ecx,[0047b058] ;move checksum into ecx test ecx,ecx ;checksum=0 (totally pointless check) jnz ... cmp eax,ecx jnz display_nagscreen
Type ?eax, you will see it has a value of 99999. This is actually the default value of our code. (Inspect the value of HKEY_CURRENT_USER\Software\VB and VBA program settings\NPP2.1\data\misd as stated in CrackZ's essay - you will see it is 99999). This means that with the default value, it will never equal the checksum, and hence will always be unregistered (which is obvious I hope).
Now hit f5, and register the program (use a code like 678678). Use any name/company as we know from CrackZ's essay that they are not used in the algorithm. SoftICE breaks again, and compares with 99999 twice, before finally comparing with your entered code:
?eax=code you entered
?ecx=what code should be
Normally, you'd want to set a bpr on your entered code and see if anything happens to it, but as we already know it is a linear algorithm (meaning that your serial is not manipulated in any way (apart from conversion to hex), and is therefore not necessary). You'd again set bpx's on widechartomultibyte and multibytetowidechar.
The process is quite tedious, your number gets converted to unicode format, then gets stripped of ASCII and put in a register, then moved into the floating point register, then moved around more, before finally being stored from a floating point register as a hex dword (what do you expect? this is VB).
Coding a key generator for this is an absolutely trivial exercise, only look at it if you really are stumped and haven't nutted out the above serial algorithm yourself: That's it folks, the show's over. If you liked my essay, e-mail me -- it gives me that warm fuzzy feeling whenever someone sends me some praise mail :).
I can be contacted via email at firstname.lastname@example.org or catch me on IRC Efnet #cracking4newbies.