Little Drummer Boy - Tutorial

"Well if you didn't know it already, you do now, Visual Basic protections are useless :) and thanks to Bomber Monkey and VBDis anyone writing a remotely intelligent protection like this one will find it easily reversed. I really like the clarity of explanation and also the trend away from "here's how I cracked so-and-so app" in this tutorial, a few suggestions are thrown in for the programmer and the answer isn't given to lamers on a plate. Absorb the subtle knowledge from this essay and remember it for the day you eventually find someone stupid enough to still be using VB3." Very slightly edited by CrackZ".

http://www.lysator.liu.se/~zap/ - Webpage.

Bitwise Logic Crunching

Little Drummer Boy is a drum loop creation program. It lacks the fancy features of some of the more expensive commercial products out there, but it gets the job done, and exports your loop to a .wav file. Or it does if you have a registered copy. Wouldn't it be nice to test the quality of its final output before purchasing this program? It is only twelve dollars in beta form, but paying money for a program that turns out to be shit is never nice. So lets improve our trial version.

To start, note that this program is written in Visual Basic. There are several ways you could notice this, among them is seeing the file vbrun300.dll installed in your windows\system directory. You could check it's dll references, or you could look at its assembly code. The important bit is that you notice it, because doing this bugger in assembly would be needlessly hard (or at least someone with my sad assembly knowledge would find it so).

Enter your registration name and e-mail, and a fake reg code. Close the window. Notice that there's no message box informing us that the code is wrong. Re-open the window. You'll notice that we have an unusual boon: an error code and description of it. This makes it much easier to identify any problems with our key. (MESSAGE TO SHAREWARE PROGRAMMERS: Although this makes life easier if your client can't type or you have their name spelt wrong in your keygen, it is technically a REALLY STUPID THING TO DO). I might have gotten pissed off enough to quit if I didn't know exactly what was wrong with the codes I was entering. Still, thanks for the rewarding feeling of progress from seeing error after error disappear). Now, decompile it back to VB code.

Master Zap was not very inventive with his form naming, as it turns out; all the better for us. In the register.bas file, there are only 5 entries. Command1_Click, Command2_Click, Command3_Click, Form_Activate, and Form_Load. Command1_Click contains some code involving our registration information, but it isn't relevant (HOWEVER, knowing which variables hold your serial number, name, and e-mail is nice) as could be predicted from the lack of a messagebox. Rather, look at Form_Activate. Voila! You shall see this beautifully simple bit of code:

.
.
.
Dim l0044 As Integer
    l0044% = fn0242()
    If l0044% = 0 Then
        If gv078A = -1 Then
            InfoField.Caption = "Registration is active, does not expire."
        Else
            InfoField.Caption = "Registration is active, and will expire in " + Format$(gv078A) + " months"
        End If
    Else
        InfoField.Caption = "Registration is invalid, registration code error is " + Format$(l0044%)
        If l0044% = 1 Then
            InfoField.Caption = InfoField.Caption + " (code the wrong length)"
        End If
        If l0044% = 32 Then
            InfoField.Caption = InfoField.Caption + " (code has expired!)"
        End If
    End If

Now we know what we're looking for. We need fn0242 to return a value of 0, and preferably, have the variable gv078A set to 1 so we can try this program in it's fully functional form for a month. If you were to do something evil, like keep it without paying, you would (in theory) want gv078A to be -1. But that would be wrong. Now, just to find fn0242. It's in module2.bas, and it looks like this:

Dim l0038 As Integer
Dim l003A As Variant
Dim l0042 As Variant
Dim l0046 As Variant
    l003A = 0
    If (Len(fn02B2(gv0756.M0B9B)) <> 16) Then
        l003A = 1
        GoTo L1984
    End If
    For l0042 = 0 To 7
        gv0774(l0042) = Val("&H" + Mid$(fn02B2(gv0756.M0B9B), (l0042 * 2) + 1, 2)
    Next l0042
    l0046 = fn0264(gv0774(7), 0)
    l0038 = fn022B(3)
    If (l0038 <> gv0774(3)) Then
        l003A = l003A + 2
    End If
    l0038 = fn022B(6)
    If (l0038 <> gv0774(6)) Then
        l003A = l003A + 4
    End If
    If gv0774(4) <> fn0275(fn02B2(gv0756.M0B85)) Then
        l003A = l003A + 8
    End If
    If gv0774(5) <> fn0275(fn02B2(gv0756.M0B90)) Then
        l003A = l003A + 16
    End If
    l0038 = fn0258(Date)
    If gv0774(2) > gv0774(1) And (l0038 < gv0774(1) Or l0038 > gv0774(2)) Then
        l003A = l003A + 32
    End If
    If gv0774(0) / 16 <> 2 Then
        l003A = l003A + 64
    End If
    If gv0774(2) <= gv0774(1) Then
        gv078A = -1
    Else
        gv078A = gv0774(2) - l0038
    End If

L1984:
    fn0242 = l003A

A brief glance reveals that there are 6 checks. Each one that fails adds a value to l003A, which is finally the value of the function. The sum of any combination of these numbers is unique, thus the detailed error descriptions in the bottom of the registration window. Right off, the function checks to see if your code is 16 characters long. Fn02B2 simply skims all of the spaces off of the end of the string. If this fails, you are immediately booted out, with an error code 1 (Code Wrong Length, you probably saw this one). Next, the code is broken into 8 sections, each being a hexadecimal number, which is converted to decimal and stored in the array gv0774.

The relevance of l0046 will be covered later, because it is the climax of the crack, and not too much worth worrying about quite yet. The next two checks involve fn022B: these correspond to "Internal Check A" and "Internal Check B" in a completely incorrect, 16 character code. fn022B is not an intimidating function at all. It simply sums all the elements of gv0774 before the number passed to it (after XORing them by AAh) and ANDs the sum by 255.

The third and fourth checks involve your name and e-mail, and here is what fn0275 does:

Function fn0275 (ByVal p006E As String) As Integer
Dim l0070 As Integer
Dim l0072 As Integer
Dim l0074 As Integer
    l0070% = 139
    l0072% = Len(p006E)
    If l0072% > 45 Then l0072% = 45
    For l0074% = 1 To l0072%
        l0070% = ((l0070% * 147 + Asc(Mid$(p006E, l0074%, 1))) Xor &HB5) And &H7F
    Next l0074%
    fn0275 = l0070% And 255
End Function

This would be a pain if it couldn't be circumvented by pasting it into some basic program (like QBasic) and passing it your name and e-mail. The fifth check involves the date (the code can expire). fn0258 converts the time into a number of months after Jan, 1998. From the comparisons, gv0774(1..2) are the registration date and expiration date, also in months since 1998. It makes sure that the registration date wasn't pre-expired, and makes certain that either the date of registration is before the current one, or that the expiration date is after the current one.

The sixth check simply makes sure that the gv0774(0) is equal to 32. Finally, the number gv078A is assigned. This is the number of months until the code expires. Now for the climactic gem: the mysterious l0046. Why assign a variable that is never referred to? Well, lets look at the function.

Function fn0264 (ByVal pv005E As Integer, p0060 As Integer) As Integer
Dim l0062 As Long
Dim l0064
Dim l0066 As Integer
Dim l0068 As Variant
    l0062 = pv005E
    For l0064 = 0 To 7
        l0066 = fn028E(l0062, 255)
        If p0060 = 1 Then l0068 = gv0774(l0064)
        gv0774(l0064) = gv0774(l0064) Xor l0066
        If p0060 = 0 Then l0068 = gv0774(l0064)
        l0062 = l0062 + l0068
    Next l0064
    fn0264 = pv005E
End Function

Function fn028E (p007C As Long, p007E As Integer) As Integer
    fn028E = p007C Mod p007E
    p007C = (p007C * 147) Mod 1000
End Function

Woohoo! If you tried entering a serial number before this, now you may see why it failed. Here we have a cutely tweaked XOR encryption scheme. It loops through XORing the array of your serial number with an ever changing key. This would actually be a somewhat cool low-end file encryption scheme if the "For l0064 = 0 To 7" was replaced with "While Not EOF(1)"; go ahead and rip it off if you want.

Anyway, since you can work out the values that you want to get out, it should not be too hard to get the values that you need. It is simply a matter of finding the key for the next number, which is but mindless number crunching. In fact, the extremely simple code looks like this, pulled out of my QBasic keygen:

FOR t = 0 TO 6
LET encnum = modvalue MOD 255
LET modvalue = (modvalue * 147) MOD 1000
LET dmed(t) = imed(t) XOR encnum
LET modvalue = modvalue + imed(t)
NEXT t
FOR t = 0 TO 7
LET partz$(t) = HEX$(dmed(t))
IF LEN(partz$(t)) = 1 THEN LET partz$(t) = "0" + partz$(t)
NEXT t
FOR t = 0 TO 7
LET serial$ = serial$ + partz$(t)
NEXT t

imed is the name of the array which contains the final values that you want the program to decrypt your serial number to. serial$ is, obviously, the serial number. You should now be able to improve your trial version of Little Drummer Boy. If not, you're close to getting the rest of the way yourself fairly quickly, because you know where your code went wrong. My useless opinion states that there is something to be learned about the use of logical operations in protection schemes, here. Even if you were already above the level of this reversal you might at least get some code for a adorable little file encryption program and a laugh at how such a thorough and well thought out protection scheme was foiled just because it was written in Micro$oft Visual Basic.

Bomber Monkey


Return to Main Index Visual Basic


© 1998,1999,2000 hosted by CrackZ. Bomber Monkey 19th February 1999.