Wingroove V0.9e for Windows (v3.1 and Bug '95)
(the 'PrestoChangoSelector' encryption method)

by dph-man

(14 October 1997)


Courtesy of reverser's page of reverse engineering

Well, indeed I said - 'No more serial number tutorials', but what is interesting about this essay is not the serial generation part, but how the protectionist (rather badly) hides his checking routine.
This essay is nevertheless a well-written, EASY TO FOLLOW AND THOROUGH essay about writing a serial generator ripping the code directly off your target with a "live" reversing approach
Here a synopsis of what you'll find here:

- What is this 'PRESTOCHANGOSELECTOR' business?
- encrypted code! Compilers almost NEVER generate the opcodes 'ror' or 'rol'.
- TIMESETEVENT this function is just an indirect call to the protection routine... Treat with suspicion calls to MMSYSTEM!TimeSetEvent which appear in the middle of protection schemes!
- Remember from now on that 'PrestoChangoSelector' is a VERY suspicious routine to be called!
dph presents...
the 'PrestoChangoSelector' encryption method
Target: Wingroove V0.9e for Windows (v3.1 and Bug '95) Function: Wavetable Emulator (Make your pathetic SB16 sound like an AWE32) Get it from: http://www.cc.rim.or.jp/~hiroki/english/ Tools required: --------------- SoftIce 3 (make sure you have the exports loaded for MMSYSTEM.DLL) W32DAsm (any version), TASM v2 or greater (a copy wouldn't hurt) Procedure: ---------- Being lazy, my first impulse when cracking a program is to see if anyone else has cracked it first. Hmm. Searches of KrackaVista (on the glorious http://www.cracking.net/), Astalavista, and Altavista return only two matches. One is a serial number (User: BJG70109, Password: ZAAAAAAA). I detest using serial numbers. That's really lazy. The other is a crack by Madmax! (done in '96), which patches 'WINGROOV.DRV'. Now, I don't like that either. Why crack something when you could simply produce a serial generator? But if Madmax! couldn't do it, perhaps neither could I? So with suitably subdued sentiments, I began cracking. Start 'WG Player' - the player utility which is part of WinGroove. Choose Help, About. Press the 'Register' button in the about box. A dialog box with four radio buttons will pop up. Select 'You got PASSWORD from the author other way', and press OK. Another dialog will pop up, this time with three edit boxes: 'Password from author', 'User ID from author', 'Your Name'. Enter some dummy values, such as Password: ABCDEFGHIJK, UserID: DPHDPHDPHDPH,Your Name:dph-man. These values probably won't be accepted, but every time we get the dialog box telling us that these values are incorrect, go back and enter some values which are correct based upon what we later know. Now we fire up Soft-ICE and breakpoint the usual suspects used for getting text out of a dialog box: bpx GetWindowText bpx GetDlgItemText and hit Ok in the dialog box. SoftIce will immediately pop in the middle of code belonging to 'wingroov.drv': :0005.21E5 FF760E push word ptr [bp+0E] :0005.21E8 6A65 push 0065 :0005.21EA 16 push ss :0005.21EB 8D46AA lea ax, [bp-56] :0005.21EE 50 push ax :0005.21EF 6A20 push 0020 :0005.21F1 9AFFFF0000 call USER.GETDLGITEMTEXT Dumping ss:bp-56 (db ss:bp-56) tells us that our password goes here... :0005.21F6 FF760E push word ptr [bp+0E] :0005.21F9 6A66 push 0066 :0005.21FB 16 push ss :0005.21FC 8D46CA lea ax, [bp-36] :0005.21FF 50 push ax :0005.2200 6A20 push 0020 :0005.2202 9AFFFF0000 call USER.GETDLGITEMTEXT ;bp-36 is now the User ID :0005.2207 FF760E push word ptr [bp+0E] :0005.220A 6A68 push 0068 :0005.220C 16 push ss :0005.220D 8D468A lea ax, [bp-76] :0005.2210 50 push ax :0005.2211 6A20 push 0020 :0005.2213 9AFFFF0000 call USER.GETDLGITEMTEXT ;bp-76 is Your Name :0005.2218 807EAA00 cmp byte ptr [bp-56], 00 :0005.221C 751E jne 223C The above two lines check whether the password is of zero length (first byte is 0, remember windows strings are null-terminated). If it is, then the program sets focus to the corresponding edit box and leaves the routine. :0005.221E FF760E push word ptr [bp+0E] :0005.2221 6A65 push 0065 :0005.2223 9AFFFF0000 call USER.GETDLGITEM :0005.2228 50 push ax :0005.2229 9AFFFF0000 call USER.SETFOCUS :0005.222E B80100 mov ax, 0001 ... :0005.2239 CA0A00 retf 000A * Referenced by a Jump at Address:0005.221C(C) | :0005.223C 807ECA00 cmp byte ptr [bp-36], 00 :0005.2240 751E jne 2260 A similar thing is being done here with the User ID... :0005.2242 FF760E push word ptr [bp+0E] :0005.2245 6A66 push 0066 :0005.2247 9AFFFF0000 call USER.GETDLGITEM :0005.224C 50 push ax :0005.224D 9AFFFF0000 call USER.SETFOCUS :0005.2252 B80100 mov ax, 0001 ... :0005.225D CA0A00 retf 000A * Referenced by a Jump at Address:0005.2240(C) | :0005.2260 807E8A00 cmp byte ptr [bp-76], 00 :0005.2264 751E jne 2284 And again with Your Name... :0005.2266 FF760E push word ptr [bp+0E] :0005.2269 6A68 push 0068 :0005.226B 9AFFFF0000 call USER.GETDLGITEM :0005.2270 50 push ax :0005.2271 9AFFFF0000 call USER.SETFOCUS :0005.2276 B80100 mov ax, 0001 ... :0005.2281 CA0A00 retf 000A * Referenced by a Jump at Address:0005.2264(C) | :0005.2284 33FF xor di, di :0005.2286 16 push ss :0005.2287 8D46AA lea ax, [bp-56] :0005.228A 50 push ax :0005.228B E852FD call 1FE0 Now we're starting to get into checks on the validity of the entered data. The routine at 1FE0 referenced by the call above checks for illegal characters in the entered data (eg. spaces and hardreturns). :0005.228E 83C404 add sp, 0004 :0005.2291 8956FC mov [bp-04], dx :0005.2294 8946FA mov [bp-06], ax :0005.2297 E98500 jmp 231F * Referenced by a Jump at Address:0005.2322(C) | :0005.229A C45EFA les bx, [bp-06] :0005.229D 26803F61 cmp byte ptr es:[bx], 61 ;'a' :0005.22A1 7C17 jl 22BA :0005.22A3 C45EFA les bx, [bp-06] :0005.22A6 26803F7A cmp byte ptr es:[bx], 7A ;'z' :0005.22AA 7F0E jg 22BA Is the character at es:bx lowercase? :0005.22AC C45EFA les bx, [bp-06] :0005.22AF 268A07 mov al , es:[bx] :0005.22B2 04E0 add al, E0 :0005.22B4 C45EFA les bx, [bp-06] :0005.22B7 268807 mov es:[bx], al If so make it uppercase. * Referenced by a Jump at Addresses:0005.22A1(C), :0005.22AA(C) | :0005.22BA C45EFA les bx, [bp-06] :0005.22BD 26803F41 cmp byte ptr es:[bx], 41 :0005.22C1 7C19 jl 22DC ;Beggar off :0005.22C3 C45EFA les bx, [bp-06] :0005.22C6 26803F5A cmp byte ptr es:[bx], 5A :0005.22CA 7F10 jg 22DC ;Beggar off Is it an uppercase letter? If not, then beggar off. The call to 22DC is the 'beggar off' call - you can test this by typing: a cs:ip jmp 22dc press x And you will get a dialog telling you that your registration is incorrect. :0005.22CC C45EFA les bx, [bp-06] :0005.22CF 268A07 mov al , es:[bx] :0005.22D2 88855D19 mov [di+195D], al :0005.22D6 FF46FA inc word ptr [bp-06] :0005.22D9 47 inc di :0005.22DA EB43 jmp 231F The lines above copy the character which is now checked into ds:di+195D. Now have a look at the beggar off routine: * Referenced by a Jump at Addresses :0005.22C1(C), :0005.22CA(C), :0005.233A(C), :0005.2348(C), :0005.2386(C), :0005.2391(C), :0005.23B1(C), :0005.23BC(C), :0005.23E7(C), :0005.2460(C), :0005.246A(C) | :0005.22DC FF760E push word ptr [bp+0E] :0005.22DF 16 push ss :0005.22E0 8D46AA lea ax, [bp-56] :0005.22E3 50 push ax :0005.22E4 6A40 push 0040 :0005.22E6 9AFFFF0000 call USER.GETWINDOWTEXT :0005.22EB 6A10 push 0010 :0005.22ED 9AFFFF0000 call USER.MESSAGEBEEP ; Annoy user :0005.22F2 FF760E push word ptr [bp+0E] :0005.22F5 680000 push 0000 :0005.22F8 68EA0E push 0EEA :0005.22FB 16 push ss :0005.22FC 8D46AA lea ax, [bp-56] :0005.22FF 50 push ax :0005.2300 6A30 push 0030 :0005.2302 9AFFFF0000 call 0001.3895h :0005.2307 FF760E push word ptr [bp+0E] :0005.230A 6AFF push FFFF :0005.230C 9AFFFF0000 call USER.ENDDIALOG ;Bad User. No password for you. :0005.2311 B80100 mov ax, 0001 ... :0005.231C CA0A00 retf 000A Well, that was the beggar off routine. It beeps, and ends the dialog. Let's go onwards: * Referenced by a Jump at Addresses:0005.2297(U), :0005.22DA(U) | :0005.231F 83FF08 cmp di, 0008 :0005.2322 0F8574FF jne 229A Interesting... the above lines suggest that the password is 8 bytes long, and consists entirely of uppercase letters. Hmmm... Ok, now we know that... The protection will pop - (beggar off). Select Register again and this time enter a password which is 8 characters in length and entirely uppercase (eg. AAAAAAAA ) :0005.2326 FF76FC push word ptr [bp-04] :0005.2329 FF76FA push word ptr [bp-06] :0005.232C E8B1FC call 1FE0 :0005.232F 83C404 add sp, 0004 :0005.2332 8BD8 mov bx, ax :0005.2334 8EC2 mov es, dx :0005.2336 26803F00 cmp byte ptr es:[bx], 00 :0005.233A 75A0 jne 22DC :0005.233C C606651900 mov byte ptr [1965], 00 Miscellaneous checks above... :0005.2341 A05D19 mov al, [195D] :0005.2344 3A06340A cmp al , [0A34] :0005.2348 7592 jne 22DC ; If you used password ; 'AAAAAAAA' it will jump here Now this is interesting... 195D is our password. Do a 'db ds:0A34' and see what they are comparing our password with. Hmmm, the letter 'Z' Ok, perhaps the first letter needs to be Z. Ok, let's try 'ZAAAAAAA' as a password... What's that I hear? How do I know that the letter in question will always be 'Z'? The answer is: I don't really. But use a little 'Zen'. Nowhere before in this routine has a value been written to [0A34]. Therefore, it was probably placed there outside this routine. Outside this routine there is no knowledge of the 'user/password/your name' strings you used as yet. Therefore it is unlikely to be a calculated value. Are there any more checks for any more letters below? :0005.234A 33FF xor di, di :0005.234C 16 push ss :0005.234D 8D46CA lea ax, [bp-36] No, the program is starting to process the User ID. Seems that the first letter of the password has to be Z, irrespective of the username. The routines below seem to be pretty much a repeat of the ones above - all uppercase letter checks... :0005.2350 50 push ax :0005.2351 E88CFC call 1FE0 :0005.2354 83C404 add sp, 0004 :0005.2357 8956FC mov [bp-04], dx :0005.235A 8946FA mov [bp-06], ax :0005.235D EB44 jmp 23A3 * Referenced by a Jump at Address:0005.23A6(C) | :0005.235F C45EFA les bx, [bp-06] :0005.2362 26803F61 cmp byte ptr es:[bx], 61 :0005.2366 7C17 jl 237F :0005.2368 C45EFA les bx, [bp-06] :0005.236B 26803F7A cmp byte ptr es:[bx], 7A :0005.236F 7F0E jg 237F :0005.2371 C45EFA les bx, [bp-06] :0005.2374 268A07 mov al , es:[bx] :0005.2377 04E0 add al, E0 :0005.2379 C45EFA les bx, [bp-06] :0005.237C 268807 mov es:[bx], al * Referenced by a Jump at Addresses:0005.2366(C), :0005.236F(C) | :0005.237F C45EFA les bx, [bp-06] :0005.2382 26803F41 cmp byte ptr es:[bx], 41 :0005.2386 0F8C52FF jl 22DC :0005.238A C45EFA les bx, [bp-06] :0005.238D 26803F5A cmp byte ptr es:[bx], 5A :0005.2391 0F8F47FF jg 22DC :0005.2395 C45EFA les bx, [bp-06] :0005.2398 268A07 mov al , es:[bx] :0005.239B 88856619 mov [di+1966], al :0005.239F FF46FA inc word ptr [bp-06] :0005.23A2 47 inc di * Referenced by a Jump at Address:0005.235D(U) | :0005.23A3 83FF03 cmp di, 0003 :0005.23A6 75B7 jne 235F Hey, but that means 3 characters of the User ID are passed through the alphabet check... Is there any more of the username? Let's see... :0005.23A8 EB24 jmp 23CE * Referenced by a Jump at Address:0005.23D1(C) | :0005.23AA C45EFA les bx, [bp-06] :0005.23AD 26803F30 cmp byte ptr es:[bx], 30 ; '0' :0005.23B1 0F8C27FF jl 22DC :0005.23B5 C45EFA les bx, [bp-06] :0005.23B8 26803F39 cmp byte ptr es:[bx], 39 ; '9' :0005.23BC 0F8F1CFF jg 22DC :0005.23C0 C45EFA les bx, [bp-06] :0005.23C3 268A07 mov al , es:[bx] :0005.23C6 88856619 mov [di+1966], al :0005.23CA FF46FA inc word ptr [bp-06] :0005.23CD 47 inc di Aha! The remaining characters in the User ID must be numbers! * Referenced by a Jump at Address:0005.23A8(U) | :0005.23CE 83FF08 cmp di, 0008 :0005.23D1 75D7 jne 23AA And it also appears that the User ID is 8 characters long! Go back, try the 'username' ABC12345 and the 'password' ZAAAAAAA, and whatever you want for 'your name'. There have been no checks on the Your Name field as yet, so perhaps there are none... :0005.23D3 FF76FC push word ptr [bp-04] :0005.23D6 FF76FA push word ptr [bp-06] :0005.23D9 E804FC call 1FE0 :0005.23DC 83C404 add sp, 0004 :0005.23DF 8BD8 mov bx, ax :0005.23E1 8EC2 mov es, dx :0005.23E3 26803F00 cmp byte ptr es:[bx], 00 :0005.23E7 0F85F1FE jne 22DC :0005.23EB C6066E1900 mov byte ptr [196E], 00 :0005.23F0 8C1E4E12 mov [124E], ds :0005.23F4 C7064C126619 mov word ptr [124C], 1966 :0005.23FA 8C1E4A12 mov [124A], ds :0005.23FE C70648125E19 mov word ptr [1248], 195E :0005.2404 9AFFFF0000 call 0001.6569h Hmm, this looks suspicious... let's follow this call... * Referenced by a CALL at Addresses:0001.434B, :0001.483A, :0001.63CF | :0001.6569 B8FFFF mov ax, SEG ADDR of Segment 0003 ;*HEY! :0001.656C 8EC0 mov es, ax :0001.656E 26A00A00 mov al, es:[000A] :0001.6572 260A060B00 or al , es:[000B] :0001.6577 0F848300 je 65FE Interesting... According to W32DASM, segment 3 is a code segment... :0001.657B 1E push ds :0001.657C 56 push si :0001.657D 57 push di :0001.657E B8FFFF mov ax, SEG ADDR of Segment 0003 :0001.6581 50 push ax :0001.6582 9AFFFF0000 call KERNEL.ALLOCSELECTOR What does AllocSelector do? From the API reference... AllocSelector (3.0) (WINPROCS unit) function AllocSelector(Selector: Word): Word; The AllocSelector function allocates a new selector. Do not use this function in an application unless it is absolutely necessary, since its use violates preferred Windows programming practices. Target Windows, DOS Protected Mode (WinAPI unit) Parameter Description Selector Specifies the selector to return. If this parameter specifies a valid selector, the function returns a new selector that is an exact copy of the one specified here. If this parameter is zero, the function returns a new, uninitialized sector. Returns Returns a selector that is either a copy of an existing selector, or a new, uninitialized selector. Otherwise, it returns zero. Ok, they want a copy of the selector of Segment 3 do they? Why don't they just use the existing one? :0001.6587 96 xchg ax,si :0001.6588 B8FFFF mov ax, SEG ADDR of Segment 0003 :0001.658B 50 push ax :0001.658C 56 push si :0001.658D 9AFFFF0000 call KERNEL.PRESTOCHANGOSELECTOR What the h#@$$#%? What is this 'PRESTOCHANGOSELECTOR' business? (This is an indication that someone in Micro$oft is either bored, has a sense of humour, or is Italian. Again, from the API reference: PrestoChangoSelector (3.0) (WIN31 unit) function PrestoChangoSelector(SourceSel, DestSel: Word): Word; The PrestoChangoSelector function generates a code selector that corresponds to a given data selector, or it generates a data selector that corresponds to a given code selector. An application should not use this function unless it is absolutely necessary, because its use violates preferred Windows programming practices. ^^^^^^^^^ Typical: Micro$oft restricts all the good functions to itself... :-( Target Windows, DOS Protected Mode (WinAPI unit) Parameter Description SourceSel Specifies the selector to be converted. DestSel Specifies a selector previously allocated by the AllocSelector function. This previously allocated selector receives the converted selector. Returns Returns the copied and converted selector if the function is successful. Otherwise, it is zero. Comments Windows does not track changes to the source selector. Consequently, before any memory can be moved, the application should use the converted destination selector immediately after it is returned by this function. The PrestoChangoSelector function modifies the destination selector to have the same properties as the source selector, but with the opposite code or data attribute. This function changes only the attributes of the selector, not the value of the selector. This function was named ChangeSelector in the Windows 3.0 documentation. Ok... That all makes sense now, so long as you remember a selector is not the same thing as a segment. A selector is a value loaded into a segment register in protected mode that is a pointer to an entry in the GDT (global descriptor table). A selector can't be a code selector and a data selector at the same time. (eg. mov cs:[bx],ax is an illegal instruction in protected mode.) Ok, so they want to reference one of their code segments as data? Step back, use a little 'Zen'... Can't you feel it? They've encrypted their code! What follows should be a decryptor! :0001.6592 56 push si :0001.6593 8ED8 mov ds, ax :0001.6595 8EC0 mov es, ax :0001.6597 8B160C00 mov dx, [000C] :0001.659B BE1000 mov si, 0010 :0001.659E 8BFE mov di, si :0001.65A0 8A0E0A00 mov cl , [000A] :0001.65A4 8A2E0B00 mov ch, [000B] :0001.65A8 33DB xor bx, bx :0001.65AA 32E4 xor ah, ah :0001.65AC FC cld * Referenced by a Jump at Address:0001.65B6(C) | :0001.65AD AC lodsb :0001.65AE 2AC5 sub al , ch :0001.65B0 D2C8 ror al, cl :0001.65B2 AA stosb That's definately a decryptor. Compilers almost NEVER generate the opcodes 'ror' or 'rol'. (Evidently the programmer has used some inline assembly in his C program. This guy obviously has some slight clue, which should suffice against the average crackers, but is absolutely no good against any average reverse engineer) It appears that the programmer has encrypted part of the code rotating the value of some bytes... :0001.65B3 03D8 add bx, ax :0001.65B5 4A dec dx :0001.65B6 75F5 jne 65AD :0001.65B8 88160A00 mov [000A], dl :0001.65BC 88160B00 mov [000B], dl :0001.65C0 8BF3 mov si, bx :0001.65C2 8B3E0E00 mov di, [000E] :0001.65C6 810E0E000080 or word ptr [000E], 8000 :0001.65CC EAD165FFFF jmp 0001.65D1 :0001.65D1 33C0 xor ax, ax :0001.65D3 8ED8 mov ds, ax :0001.65D5 8EC0 mov es, ax :0001.65D7 9AFFFF0000 call KERNEL.FREESELECTOR :0001.65DC 8BDE mov bx, si :0001.65DE 8BCF mov cx, di :0001.65E0 B80000 mov ax, 0000 :0001.65E3 8ED8 mov ds, ax :0001.65E5 C70668381000 mov word ptr [3868], 0010 :0001.65EB C7066A380000 mov word ptr [386A], 0000 :0001.65F1 5F pop di :0001.65F2 5E pop si :0001.65F3 1F pop ds :0001.65F4 51 push cx :0001.65F5 53 push bx :0001.65F6 90 nop :0001.65F7 0E push cs :0001.65F8 E8F8CD call 33F3 :0001.65FB 83C404 add sp, 0004 * Referenced by a Jump at Address:0001.6577(C) | :0001.65FE B89E00 mov ax, 009E :0001.6601 50 push ax :0001.6602 B80000 mov ax, 0000 :0001.6605 50 push ax :0001.6606 90 nop :0001.6607 0E push cs :0001.6608 E8C8CD call 33D3 ;This routine does bugger all... :0001.660B 83C404 add sp, 0004 :0001.660E CB retf Ok, so now whatever it is has been decrypted. Let us see if they actually try to execute it... :0005.2409 6A64 push 0064 :0005.240B 6A64 push 0064 :0005.240D 680000 push 0000 :0005.2410 68FFFF push WORD_VALUE_AT_ADDRESS 0002.1AFCh :0005.2413 680000 push 0000 :0005.2416 68FFFF push WORD_VALUE_AT_ADDRESS 0003.01BCh :0005.2419 6A01 push 0001 :0005.241B 9AFFFF0000 call MMSYSTEM.TIMESETEVENT @#$#&#&^&*#$!@!!!! What??? Is this a debugger trap? Timing execution? From the API reference: /snip/ timeSetEvent MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, DWORD dwUser, UINT fuEvent); Starts a specified timer event. After the event is activated, it calls the specified callback function. - Returns an identifier for the timer event if successful or an error otherwise. This function returns NULL if it fails and the timer event was not created.(This identifier is also passed to the callback function.) uDelay Event delay, in milliseconds. If this value is not in the range of the minimum and maximum event delays supported by the timer, the function returns an error. uResolution Resolution of the timer event, in milliseconds. The resolution increases with smaller values; a resolution of 0 indicates periodic events should occur with the greatest possible accuracy. To reduce system overhead, however, you should use the maximum value appropriate for your application. lpTimeProc Address of a callback function that is called once upon expiration of a single event or periodically upon expiration of periodic events. dwUser User-supplied callback data. fuEvent Timer event type. The following values are defined: TIME_ONESHOT = 0 Event occurs once, after uDelay milliseconds. TIME_PERIODIC = 1 Event occurs every uDelay milliseconds. Each call to timeSetEvent for periodic timer events must be matched with a call to the timeKillEvent function. /snip/ Very interesting..., It calls the function referenced by 0002.1AFCh every 64 milliseconds. Set a breakpoint on both of the addresses which are pushed (exactly what is pushed as the segment address will change depending on your system...) for me it was: bpx 378F:1AFC bpx 3787:01BC It will pop here: 3787:1AFC mov ax,375F inc bp push bp mov bp,sp push ds mov ds,ax pushad ; Trace into this call... call far [bp+0e] ; bp+0e contains 3787:01BC !!! That was the other address ; we breakpointed! popad pop ds pop bp dec bp retf 0010 ; Now, at 3787:01BC, we have (this is somewhat interpreted by ; me, ie.labels added): push esi les bx,[1248] ; make es:[bx] point to last 7 bytes of password mov cx,0007h xor esi,esi @loop1: mov eax,0000001ah mul esi xchg eax,esi mov al,es:[bx] sub al,41h movzx eax,al add esi,eax inc bx loop @loop1 push esi ; The above routine makes a magic number out of the password. les bx,[124C] ; es:[bx] now points to the User ID mov cx,0008h xor esi,esi xor dl,dl @loop2: mov al,es:[bx] add dl,al sub al,30h cmp al,09h jbe @label1 sub al,11h and al,0fh @label1: ; The reason for the jbe above is that letters and numbers ; are processed seperately. shl esi,04h xor ah,ah or si,ax inc bx loop @loop2 pop eax mov cl,dl and cl,01fh ror eax,cl ; Here they 'play' with the results of the first loop based ; upon the results of the second. xor eax,19670109h sub eax,esi mov [124C],eax ; Ok, the results of this password function are placed in [124C]... ; Hmmm, what value do we want to have here for a password to be correct... xor eax,eax mov [1248],eax ; This appears to be a flag to say that the function has occurred. pop esi retf ; To find out what the value at [124C] should be for a correct ; answer, let us go back to the original code... (the call ; MMSYSTEM.TIMESETEVENT has just occurred...) :0005.2420 8946F4 mov [bp-0C], ax :0005.2423 9AFFFF0000 call USER.GETTICKCOUNT :0005.2428 8956F8 mov [bp-08], dx :0005.242B 8946F6 mov [bp-0A], ax :0005.242E EB1A jmp 244A * Referenced by a Jump at Address:0005.2450(C) | :0005.2430 9AFFFF0000 call USER.GETTICKCOUNT :0005.2435 66C1E010 shl eax, 10 :0005.2439 660FACD0 shrd eax, edx, 10 :0005.243D 10662B adc [bp+2B], ah :0005.2440 46 inc si :0005.2441 F6663D mul byte ptr [bp+3D] :0005.2444 A00F00 mov al, [000F] :0005.2447 007308 add [bp+di+08], dh ; This loop appears to be a do-nothing loop, as a pathetic attempt ; to hide thefact that the real password value routine is elsewhere. ; You might notice that Soft-ICE interprets the above code ; differently to W32DASM, possibly some trick to annoy people. ; (ie. the code is changed during execution) * Referenced by a Jump at Address:0005.242E(U) | :0005.244A 66833E481200 cmp dword ptr [1248], 00000000 :0005.2450 75DE jne 2430 The loop continues running until the password routine flag has been cleared ([1248] has been zeroed...) :0005.2452 FF76F4 push word ptr [bp-0C] :0005.2455 9AFFFF0000 call MMSYSTEM.TIMEKILLEVENT The program now stops the password routine from running again... :0005.245A 66833E481200 cmp dword ptr [1248], 00000000 :0005.2460 0F8578FE jne 22DC ;Beggar off call The above lines are redundant... :0005.2464 66833E4C1200 cmp dword ptr [124C], 00000000 :0005.246A 0F856EFE jne 22DC ;Beggar off call AHA! It expects the value 0 to be returned in [124C]. Change the value in memory at ds:124c to 0 if it isn't already... Keep on tracing... :0005.246E 807E8A00 cmp byte ptr [bp-76], 00 :0005.2472 7507 jne 247B :0005.2474 8CDA mov dx, ds :0005.2476 B86619 mov ax, 1966 :0005.2479 EB05 jmp 2480 * Referenced by a Jump at Address: |:0005.2472(C) | :0005.247B 8CD2 mov dx, ss :0005.247D 8D468A lea ax, [bp-76] * Referenced by a Jump at Address: |:0005.2479(U) | :0005.2480 52 push dx :0005.2481 50 push ax :0005.2482 1E push ds :0005.2483 686619 push 1966 :0005.2486 1E push ds :0005.2487 685D19 push 195D :0005.248A 9AFFFF0000 call 0001.4AC5h Yes! The call above gives a 'Thank you, congratulations' dialog box! Now, all that needs to be done is to reverse engineer the password verification routine. Here is a short assembler program which will is basically a copy of the verification routine. ; regotest.asm .386 .model tiny .code org 100h start: mov ah,9 mov dx,OFFSET beginpassword int 21h mov dx,OFFSET beginusername int 21h mov bx,OFFSET password mov cx,0007h xor esi,esi @loop1: mov eax,0000001ah mul esi xchg eax,esi mov al,es:[bx] sub al,41h movzx eax,al add esi,eax inc bx loop @loop1 push esi mov bx,OFFSET username mov cx,0008h xor esi,esi xor dl,dl @loop2: mov al,es:[bx] add dl,al sub al,30h cmp al,09h jbe @label1 sub al,11h and al,0fh @label1: shl esi,04h xor ah,ah or si,ax inc bx loop @loop2 pop eax mov cl,dl and cl,01fh ror eax,cl xor eax,19670109h sub eax,esi test eax,eax ;If eax is zero now, the rego is valid jz @good mov dx,OFFSET badrego jmp @printit @good: mov dx,OFFSET goodrego @printit: mov ah,09h int 21h @done: mov ax,04c00h int 21h beginpassword db 'Password: Z' password db 'HGXIJFT',13,10,'$' beginusername db 'User ID: ' username db 'DPH01997',13,10,'$' goodrego db 'Congratulations! Correct registration code.',13,10,'$' badrego db 'ERROR: Incorrect registration code.',13,10,'$' END start Now here is the same thing, turned into a key generator: ;wgkey.asm - by dph-man .model tiny .386 .code org 100h start: mov ah,9 mov dx,OFFSET prompt ;Prompt the user for input int 21h ;Input routine ;Gets string of three letters and five numbers from the user mov cx,8 mov di,OFFSET username push di mov ah,8 @cloop: cld int 21h ;Get char in al cmp al,8 ;Is al=backspace? jne @procchar ;If not, go to the processing routines cmp cl,al ;Is this the first character? je @cloop ;If so, you can't backspace! inc cx ;Increment character pointer std jmp @printchar ;Get another character @procchar: cmp cl, 5 ;Is this the third character? jbe @numbers ;If so, it should be a number. Jump to number processing cmp al,'a' ;Is this after the letter 'a'? jb @iscapital sub al,20h ;If so subtract 20h to make it Uppercase. @iscapital: cmp al,'A' jb @cloop ;Is this letter between 'A' and 'Z' cmp al,'Z' jbe @incs ;If its a letter, print it. If not, the numbers ;routine will catch it @numbers: cmp al,'0' ;Is this a number? jb @cloop cmp al,'9' ja @cloop ;If not, get another character. @incs: dec cx ;Decrement the string pointer @printchar: stosb int 29h ;Write the last character to the screen. test cx,cx ;Do we now know the complete string? jne @cloop ;If not, get more of it ;Key generator ;Section 1. This is lifted from the actual program code. ;This calculates what esi should be in the final sub eax,esi, ;based on the user's input. ;This is the second magic number loop. pop si mov cl,08h xor edi,edi xor dl,dl @loop1: lodsb add dl,al sub al,30h cmp al,09h jbe @label1 sub al,11h and al,0fh @label1: shl edi,04h xor ah,ah or di,ax loop @loop1 mov eax,edi ;Section 2. Ok, we have the value which eax and esi should be ;in the final subtraction let us work backwards to what the ;result of the first magic number loop should be. xor eax,19670109h ;Reverse XOR by commutative property mov cl,dl and cl,01fh rol eax,cl ;Reverse ROR by ROL ;The first magic number loop should output eax. As the 2nd ;loop does this: ;eax=((((((((((d1*1a)+d2)*1a)+d3)*1a)+d4)*1a)+d5)*1a)+d6)*1a)+d7 ;This is simply an algebraic reversal of the above. mov di,OFFSET password mov cl,07h mov esi,01269AE40h xor ebx,ebx mov bl,01ah @divloop: xor edx,edx div esi add al,41h stosb mov eax,edx xor edx,edx xchg eax,esi div ebx xchg eax,esi loop @divloop mov al,'$' stosb ;Tell the user our generated password. mov ah,9 mov dx,offset beginpassword int 21h ;Leave... int 20h prompt db 'WinGroove serial number generator',13,10,13,10 db 'Enter three alphabetic and five numeric characters.' db 13,10,'User ID: $' beginpassword db 13,10,'Password: Z' password db 8 dup (?) username db 8 dup (?) END start So there you are, my first key generator. That wasn't so hard. But why did Madmax! implement a crack rather than a generator? I think it was because he was stepping through the region of the call to MMSYSTEM!TIMESETEVENT, and didn't realise that this function was just an indirect call to the protection routine. If you just step through this routine, even with Soft-ICE, the protection routine is never called. His crack simply nops the beggar off jumps immediately after the loop. The second question is, why is only the serial/password pair 'User:BJG70109, Pass:ZAAAAAAA' around in the serial lists? (Usually people contribute more than one serial no. to any list.) The simple answer is this is the easiest set of values to work out manually (without writing a program). Play around with the first assembly listing, forgetting that you ever knew any serial numbers at all, and you'll see what I mean. What was interesting about this scheme is the way he hides his serial number check! Remember from now on that 'PrestoChangoSelector' is a VERY suspicious routine to be called! Treat with suspicion calls to MMSYSTEM!TimeSetEvent which appear in the middle of protection schemes! :-) dph-man Greetingz to all +crackers!
(c) dph-man 1997. All rights reversed
You are deep inside reverser's page of reverse engineering, choose your way out:

redhomepage redlinks redanonymity +ORC redstudents' essays redacademy database
redtools redcocktails redantismut CGI-scripts redsearch_forms redmail_fravia
redIs reverse engineering legal?