By 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 (newspost.htm), 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) alert@fbi.gov abuse@internic.net piracy@newsposterpro.comI 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!.
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:
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. WideCharToMultiByteMultiByteToWideChar 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:
:dd espTo 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:
:d 59334cwill display the string that's about to be converted.
We now type:
:d 6cf746....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.
f03c8cf call [kernel32!widechartomultibyte] cmp eax,02 <--- hereWe immediately set a breakpoint on the char:
:bpr 6cf746 6cf746+0 rwHit 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 47b058Our 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 rwNow hit f5, and you'll land here:
0043fc3a imul ecx,[0047b058] ;[0047b058] == our checksum . . mov [0047b058],ecxThis 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).
446919 mov ecx,[0047b058] ;move checksum into ecx test ecx,ecx ;checksum=0 (totally pointless check) jnz ... cmp eax,ecx jnz display_nagscreenType ?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).
cmp eax,ecx?eax=code you entered
I can be contacted via email at prophecy_@usa.net or catch me on IRC Efnet #cracking4newbies.