AudioGrabber 1.2

Reversing ListViews in a Delphi application and then some

 

by

Lord Soth

 

All rights reserved to the Immortal Descendants

Visit us on the web at : http://www.ImmortalDescendants.org/

 

Published by +Tsehp, May 2000.

 

Greetings everyone. In this essay I'll describe how to remove limitations in a Delphi application.

The target in question is AudioGrabber 1.2, an app used to rip digital audio tracks from CDs.

I must admit though that at the beginning when I set out to crack it, I didn't know it was a Delphi

application untill I saw some familiar markings. Nonetheless this was one of the toughest cracks

I've ever made, and it took a combined time of roughly 10 or 12 hours, spread on several days.

If you wish to follow the essay to its full extent I advise you to get version 1.2 of AG, although

that might be a little hard to come by, since the only available version right now is 1.62.

If you are really into it, I can send you a copy of AG 1.2.

 

Tools Needed :

 

SoftIce 3.xx            (your fav. version will do :)

A Disassembler    (I used W32dasm, but if you prefer IDA, I salute you)

A Hex Editor         (Hexworkshop for me)

WinShow              (The little app that shows window classes and handles...)

PE Browse Pro      (For easy analysis of the PE file)

 

Diving into AG

 

Ok, lets first give a first look into AG, and see what it's all about. I assumed you installed

the program and running it already. The splash screen tells us that the program is Freeware.

This is good, but not good enough. The author of this program wants his app to be freely

distributed indeed, but added some limitations to it.

The first most obvious limitation is that only half of the CD tracks can be selected for ripping,

at one time. This is annoying of course. You can rerun the program and find that it randomly

chooses different tracks for you to rip. If you wanna rip many tracks, its gonna take you a few

times, and you'll probably be annoyed too :)

Another limitation (at least in version 1.2) is that you can't start and stop the rip process from

places other than the start/end of the track. Usually though, this is not a problem since you

want the whole song obviously, so we'll not even bother dealing with it.

One word of caution for those who rip, each CD-quality 1 Minute play time is equivalent to

around 10 MB of HD space, so a 4 minute song will take approx. 40 MB of space, give or take.

 

Now that we know whats the problem, lets figure out a way around it. First of all, lets try selecting

a track that doesn't have a check box next to it. Before we do that, just a note on the display in

this app. Those checkboxes for example, are not normal windows checkboxes of course, as anyone

can see it easily. They are something else, and therefore when we'll wanna tackle them we'll need

to find how they are implemented. We'll get there soon enough.

Try selecting a track that doesn't have a checkbox next to it, and you'll see a MessageBox telling

you that in the unregistered version, one can handle only half the tracks.

We figure, here's a good place to start, and work in reverse, so lets put a BPX on MessageBox :

 

Load the program into the symbol loader, and at the entry point, enter :

 

bpx messageboxa

 

This will activate the BP we wanted. Try to click again on a track to generate the message, and

we break into SI. One F12 (P RET) is needed of course to display the message box and when it

closes we return to the place where it was called from.

 

:004101A5        8B4810                         mov ecx, dword ptr [eax+10]

:004101A8        894DF0                        mov dword ptr [ebp-10], ecx

:004101AB       837DF0FF                    cmp dword ptr [ebp-10], FFFFFFFF

:004101AF       7407                              je 004101B8

:004101B1        8B45F0                         mov eax, dword ptr [ebp-10]

:004101B4        40                                  inc eax

:004101B5        48                                  dec eax

:004101B6        7D32                             jge 004101EA

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004101AF(C)

|

:004101B8        C6057AA24A0001      mov byte ptr [004AA27A], 01

:004101BF        6830400000                  push 00004030

 

* Possible StringData Ref from Data Obj ->"Unregistered version limitation"

                                  |

:004101C4        6822684900                  push 00496822

 

* Possible StringData Ref from Data Obj ->"This freeware version can only "

                                        ->"handle half of the tracks."

                                  |

:004101C9        68C4674900                  push 004967C4

:004101CE        A1B0894A00               mov eax, dword ptr [004A89B0]

:004101D3        E88CD00300                call 0044D264

:004101D8        50                                  push eax

 

* Reference To: USER32.MessageBoxA, Ord:0000h

                                  |

:004101D9        E810FD0700                                Call 0048FEEE

:004101DE        C6057AA24A0000     mov byte ptr [004AA27A], 00            ; <<< We are HERE

:004101E5        E991000000                  jmp 0041027B

 

This code snippet is of course copied from W32Dasm.

Lets take a close look at the code. At 004101D9 our MessageBox is called from, and we landed right

behind it. When we look at the code above it we can easily see that the only way to reach the

CALL to the MessageBox is if the jump condition at 004101AF is met, that is if [EBP-10] has the value

FFFFFFFFh in it, or as we know it as -1.

Notice however, that the value is originally taken from [EAX+10], then its transferred to [EBP-10] for

testing. We'll therefore need to display the memory area pointed to by EAX+10. In order to do this,

disable your message box BP and put a BPX at 004101A5. Either double-click the line in SI, or do it

manually : bpx 4101a5 .

Exit SI and try to choose another track that is disabled. Remember the track number you chose.

When you click, SI will pop up at address 4101A5. This time, display the contents at EAX+10 :

 

d eax+10

 

You can easily see what's going on there. Of course at eax+10 itself you see FF FF FF FF. This is the -1

that the program reads initially. Notice the other data in the data view, especially on the ASCII side.

You can see that there is a list of the tracks, their sizes, and some other data, and for each track, there's

a certain memory area where either FFFFFFFF is stored, or 00000000. This is a very nice pattern as this

will tell us which tracks are enabled and which are disabled. You can find out to which track each "flag"

belongs beause it appears just before it. I understood that because I remembered which track I clicked on,

and a few lines down the data view that track's name and number appeared. What we'll now try to do is

change one of those so called flags and see if that alone enables the track. Lets see, edit the memory

contents and instead of FF FF FF FF, put 00 00 00 00.

Wow, look, suddenly we have a checked checkbox next to the disabled track!! Let's try and see if it works,

click the Grab button, and as soon as you do, the program will display next to the track info, a message

saying : "Could not open temp.wav as temporary record file".

Doh we think, what is this ??

 

You didn't really think it would be THAT simple, did you ?? :)

 

Now, out of curiousity, lets see if the program remembers we enabled the track. Click the Refresh button

in AG, and you'll see the checkbox near the newly "enabled" track has disappeared.

This could only mean one thing of course : The place that decides wheather to enable or disable a track

is someplace ELSE.

How can we find it ? One thought that came to my mind is obviously to put a memory BP at that address

and see which code writes the FFFFFFFF to it. However, as we're about to discover, this might not be so

trivial.

I wanted to know more about how everything is arranged in the app main window. That is, how the checkboxes

and names and info are implemented. To do that, I used WinShow, the Hex version of course :)

Run WinShow and use it to look at all the main window attributes, wheather its buttons or menus, or whatever.

The first thing to notice is that the main window is called TForm1. This is consistent with either VB or

Delphi applications. Since this is obviously NOT a VB app (there are no VB function imports at all !!),

I assume Delphi, and I was correct. For us that only means that this program is extremely overbloated

and we have lots of unneeded code in it, such as fault checks, initialization of windows and controls

and many other stuff we don't care about.

Back to our issue at hand, when the cursor goes over the track names, you can see its window class.

Its a ListView control, and is one of MicroSoft's Common Controls. This means that the common controls

library is managing the ListView and therefore and data associated with it is taken care of internally by

the library. This of course means that those nifty FFFFFFFF flags we saw earlier are probably managed by

the library and the code that put them there is NOT a code of AG.

Thats hardly good for us because we need to know where the real place that holds the information is at.

For this we'll do some research into ListView controls. In any Windows compiler comes a platform SDK,

and using it we can read all we want about the controls and messages used by them.

To make long things short, I'll explain what I read from the SDK.

 

In order to create a ListView control, the app needs to call CreateWindowExA, with the "LISTVIEW"

window class parameter. We assume this has been taken care of by Delphi.

The LV is initialized with 0 items at creation time, and in order to add items to it, one must send a message.

The message that adds an item to a LV is LVM_INSERTITEM. As you may or may not know, each and

every Windows message has 2 parameters, called LPARAM and WPARAM, and are used to transfer

important information about the message.

Looking in the SDK for LVM_INSERTITEM I saw the following :

 

 

LVM_INSERTITEM

 wParam = 0;

 lParam = (LPARAM) (const LV_ITEM FAR *) pitem;

 

The wParam is 0, and the lParam is a far pointer (32 bit) to an LV_ITEM structure that contains info

about the inserted item. Example information is the text string to be displayed at each column, an

image if wanted (remember those checkboxes ??), and some other stuff.

The message is sent to the LV window procedure using the SendMessageA function. Using the API

reference, you can see that SendMessageA receives 4 parameters :

 

SendMessage( hWnd, uMsg, wParam, lParam);

 

The first parameter is the window handle of the LV, and is no concern to us. The second parameter

is the message ID, which in this case is LVM_INSERTITEM. The wParam would be 0, and the lParam

would be the pointer to the LV_ITEM structure. Remember that params are passed in reverse so that

the lParam is the first to be PUSHed.

How are we gonna catch the program sending the LVM_INSERTITEM message when a typical

Windows program sends so many messages all the time ??

What we'll do is conditionalize the BP so that it only breaks on the message we want it to.

For that we need to know what LVM_INSERTITEM is gonna look like when pushed on the stack.

The only way to do that is to open the commctrl.h file in the compiler's /INCLUDE directory and in it

look for LVM_INSERTITEM. It is defined as LVM_START+7, and LVM_START equals 1000h, so

LVM_INSERTITEM will be 1007h.

Now we put the BP :            bpx sendmessagea if esp->8 == 1007

 

Why ESP->8 ?? Because ESP+0 is the return address for the CALL. ESP+4 contains the first parameter,

which is the window handle, and the second (the message) is located at offset 8 from the stack pointer.

Rerun the program all over again, and you soon enough as expected, SI pops.

When at the beginning of the SendMessageA function, we'll used the famour PARAMS macro to display

the parameters currently on the stack ( I forgot who invented it, sorry). The PARAMS macro is defined as:

DD SS:ESP.

This command will display the parameters. As you can see in the data view, for each time we break, there

is information about a track that is passed. Everytime we'll press F5, and use PARAMS again, we're gonna

see slightly different information, such as track number and length. Another thing that changes is a

pointer we see a few lines down, the fifth line down to be more exact. Display the contents of the pointer

a few times, each time you break on the function (but making sure you used PARAMS each time), and

soon you'll see the same list you saw earlier with track names and length, etc..

Notice again that a line down from where the pointer is pointing to, you're gonna see a line with some FF's,

and if a track is disabled, the beginning of the line is going to be FF FF FF FF, just as we found out earlier.

This is good, we're catching the disabling/enabling while the program is inserting items into the LV.

In order to see who is deciding wheather to write a 0 or a -1 for each track, we'll do the following.

Select a place you know a "flag" such as this is going to be written to. Doesn't matter which, and put a

range memory BP on it for write access. We want the break to occur only if what is written is a -1, so we

can trace who wrote it there, and maybe take care of it.

This can be done by the following command : bpr xxxx yyyy w if *(xxxx) == ffffffff

Assuming of course that XXXX is pointing exactly at the flag. Another way to go is without the BP

condition, but then you'll break even when a 0 is written, so you'll want to run it a few times and wait for

a -1.

The best way to do this is to wait untill you see some track inforamtion appear, when when you know where

the NEXT track information is going to appear, put a range BP there.

The NEXT track flag is always 12 lines from where the previous track flag was. In the end I used :

bpmd xxxxxxxx w

When SI popped, I saw this :

 

:00444720        53                   push ebx

:00444721        83C4DC         add esp, FFFFFFDC

:00444724        8BD8             mov ebx, eax

:00444726        83EA01         sub edx, 00000001

:00444729        720A              jb 00444735

:0044472B        743B              je 00444768

:0044472D        4A                 dec edx

:0044472E        7462               je 00444792

:00444730        E985000000   jmp 004447BA

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00444729(C)

|

:00444735        894B10          mov dword ptr [ebx+10], ecx             

:00444738        C7042402000000          mov dword ptr [esp], 00000002          ;<<< We stop HERE

:0044473F        C744241CFFFFFFFF   mov [esp+1C], FFFFFFFF

 

We've stopped right after the instruction that changed the memory location. This was the line at

adress 00444735. It stored the value of ECX into our flag location. But where did the program

set ECX ??

We see that at 444720 is a new function. If you don't believe me, rerun AG and do : g 444720.

Look at the code, there is nothing infront of 444720. The program must have set ECX prior to

calling this function. This is not normal, since parameters are usually passed on the stack, and ECX's

value is just set outside the function and used within.

Press F12 and  we'll be back at where we came from, which looks like this :

 

:00406013        0FB75584                      movzx edx, word ptr [ebp-7C]                             ;Take track #

:00406017        833C9500A84A0000    cmp dword ptr [4*edx+004AA800], 00000000 ;Check

:0040601F        7525                               jne 00406046                                                          ;if not 0, disable track

:00406021        8B0DB0894A00           mov ecx, dword ptr [004A89B0]

:00406027        8B81C8010000              mov eax, dword ptr [ecx+000001C8]

:0040602D        8B8030010000             mov eax, dword ptr [eax+00000130]

:00406033        33D2                              xor edx, edx

:00406035        E8AAE90300                call 004449E4

:0040603A        83C9FF                        or ecx, FFFFFFFF                                                 ;if 0, disable

:0040603D        33D2                             xor edx, edx

:0040603F        E8DCE60300                                call 00444720                                                         ;disable function

:00406044        EB21                              jmp 00406067                                                         ;continue execution

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0040601F(C)

|

:00406046        A1B0894A00                mov eax, dword ptr [004A89B0]

:0040604B        8B88C8010000             mov ecx, dword ptr [eax+000001C8]

:00406051        8B8130010000              mov eax, dword ptr [ecx+00000130]

:00406057        33D2                              xor edx, edx

:00406059        E886E90300                  call 004449E4

:0040605E        33C9                              xor ecx, ecx                                                            ;zero ECX

:00406060        33D2                              xor edx, edx

:00406062        E8B9E60300                  call 00444720                                                         ;enable function

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00406044(U)

|

:00406067        66C745985C00              mov [ebp-68], 005C

 

Look whats happening here. At 00406013, we load EDX with a track number.

Then we perform a check of some sort (we'll look at it a bit later), and according

to the result, we either jump or not. If the memory location in the check is not

00000000, we jump to address 00406046, which will lead us to zeroing ECX at address

0040605E, and then we'll call the function we met earlier, at 444720, that will store ECX

and make the LV item enabled.

If the memory location is 0, we won't jump at 0040601F, but continue to execute,

reach the code that puts -1 (FFFFFFFF) into ECX (address 40603A), and then call 444720

which will disable the LV item.

It seems that the memory location checked is some sort of enable/disable flag. Further more,

you see that the track number stored in EDX is multiplied by 4 and added to 4AA800, and then

checked. For every track number, we'll get a different address, in increases of 4 bytes. This is

in fact a DWORD flag array, where EDX points to the flag corresponding to the current track.

If you put a BP at address 00406013, you'll see that EDX enumerates all tracks in the CD.

In any case, lets see what is stored from the beginning of the array, at address 4AA800.

 

dd 4aa800.

 

This will display an array of DWORDs as we expected. Some of them are going to be 00000000,

and some are going to be 00000001. Those that are 1 signify the tracks to be enabled, and those

that are 0 are going to be disabled.

Notice that at exactly 4AA800, there will always be 00000000, since there is no 0 track, the tracks

begin at track 1 and thus the first element will always be 4AA804.

Before we rush off and jump to conclusions lets see where the program is making this array.

The obvious way to do this is with a memory BP, so write :

 

bpr 4aa800 4aa830 w

 

I leave it up to you to manage your BPs and decide which are needed at the moment and which are not, because as you know, SI can only do 4 memory BPs at a time using debug registers (DR).

Rerun the program, and wait for it to break on our new BP. Once it does, take a look, this is the heart of

the protection, and the source of all evil  ;)

 

:00405D92        EB1E                                             jmp 00405DB2

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00405DE6(C)

|

:00405D94        E81DA20200                               call 0042FFB6

:00405D99        66894584                                      mov word ptr [ebp-7C], ax

:00405D9D        0FB75584                                    movzx edx, word ptr [ebp-7C]

:00405DA1        C7049500A84A0001000000     mov dword ptr [4*edx+004AA800], 00000001

:00405DAC        FF8574FFFFFF                         inc dword ptr [ebp+FFFFFF74]

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00405D92(U)

|

:00405DB2        0FB70D249B4A00                     movzx ecx, word ptr [004A9B24]

:00405DB9        0FB705269B4A00                      movzx eax, word ptr [004A9B26]

:00405DC0        2BC8                                            sub ecx, eax

:00405DC2        41                                                 inc ecx

:00405DC3        898D54FDFFFF                          mov dword ptr [ebp+FFFFFD54], ecx

:00405DC9        DB8554FDFFFF                         fild dword ptr [ebp+FFFFFD54]

:00405DCF        D80DC5684000                          fmul dword ptr [004068C5]

:00405DD5        DC05C9684000                           fadd qword ptr [004068C9]

:00405DDB        E810A60700                               call 004803F0

:00405DE0        3B8574FFFFFF                           cmp eax, dword ptr [ebp+FFFFFF74]

:00405DE6        7DAC                                           jge 00405D94

:00405DE8        0FB715249B4A00                       movzx edx, word ptr [004A9B24]

:00405DEF        899574FFFFFF                           mov dword ptr [ebp+FFFFFF74], edx

 

 

Since there is lack of space to add comments after most of these instructions, I'lll describe what goes on here.

We break when a 1 is written to the element of the array, after the instruction at 405DA1, but only if a 1 was written. As you can see for yourself, zeroes fill the array from the beginning.

Trace a little and you can see that you jump back from 405dE6 to 405D94. Therefore I advise to disable or clear the memory BP for the array and put a BPX at 405D94.

Now look what happens here. Step OVER the CALL at 405D94, and EAX will change and will be a number.

This is not just a number, as you're about to see, AX will be put into [EBP-7C], and from there EDX will take

its value. EDX will be used to put a 1 into the array at a certain location. What this means is of course that

the function call at 405D94 generates a random track number which is then used to enable the track.

Continueing, we see that at 405DB2 , a number is loaded into ECX. Check your Audio CD, and you'll see that

the value in ECX (in Hex of course) is the number of tracks in the CD. Write this down, it could be important.

What to write down you say ??  That the global variable at 004A9B24 contains the number of tracks in the

Audio CD of course!! :-)

EAX is then loaded with a number (guess which :) and  then we have some floating point

calculations, and a CALL. When stepping over the CALL and reaching the compare right adter it I can see

that the memory location [EBP+FFFFFF74] contains the number of tracks in the CD divided by 2.

It checks EAX (this is the counter if you haven't figured it yet) against half the number of tracks, to detemine

when to exit the loop !!

This loop will therefore randomize half the number of tracks in a CD and enable their flags in the flag array at

4AA800.

When we reach 00405DE8, the whole process is finished. Also notice that the CALL at 405D94 will not just

randomize a number, it will generate a random number that HASN'T been used before! Very subtle difference.

 

Ok, now its time for some experiments. After the whole loop ends, look into the array, choose some places

with 0's and change them to 1's. Don't change all of them because you'll see the program locks. All of them

means you have 1 for every track, and if you have 15 tracks in a CD, you're gonna have 15 DWORDs with

a 1 stored in them. We'll deal later with the lockup.

For now, change a few tracks to 1 as well, and exit SI to view the results. Isn't it cool ? Now you see a few

more tracks with checkboxes next to them!

Press the refresh button to see if the program indeed remembers the "change", and YES, it DOES remember.

These tracks are permanently enabled!!

Pleased with ourselves, we have done a few steps in the right direction but its far from over. Try to grab a

newly changed track you enabled, and you get the same error : "Could not open temp.wav as.....".

Something still isn't right after all.

What to do ? What to do ?

 

Well this time we'll do some nice reversing and locating of important things. We already know where the

program stores its flags for the LV display, now we need to understand what is happening when a track

is enabled, so we could enable them all.

My first thought was that the rest of the code after the loop, initializes some kind of a virtual file or something

for each enabled track. This is a pain of course, finding where it does that. Soon I'll show that I was way off

base in my guess, as no one is that sophisticated :)

I wanted to find two things. First I wanted to know when the program decides the track is indeed invalid

and disabled and program flow changes so we DON'T grab, or if it's enabled, continue to grab.

This is the REAL check we were after all along. Yes, having checkboxes near the tracks is important, but

it won't do us any good if we can't grab them!

Ok, so I thought, what happens when the program grabs a track ? Naturally, that nifty looking DialogBox

appears and shows the rip process. So I naturally looked in PE Browse for any DialogBox related imports,

only to find there were NONE!!!

This was puzzling, as I'm sure a DialogBox is created , there's no mistake in that!

Another way a program can make a DialogBox is to make it itself. Create each control and button using the

almighty function CreateWindowExA.

So, when the program was at the main window, I entered SI and put a BP on the funtion :

 

bpx createwindowexa

 

Then, clicked a GOOD track and grabbed it. The dialog appeared, but our beloved debugger didn't even

twinch!! What gives ?????

Tried it again, and again I found that the function is not called at all. This was time to look at the window

handles this program has. In SI, type the HWND command, adding AUDIOGRA afterwards (to tell it to show

only AG owned windows) :

 

hwnd audiogra

 

And we receive quite a list !

Now go out of SI, and grab a track. Using WinShow, at the same time, position it on the window caption

of the dialog to see what's the handle and window name. Surprisingly enough, its called TForm2.

Laughing myself till I fell I realized that the window is probably always open !!

In SI, look at all of AG's windows, and you'll see a TForm2. Abort the rip process, and look again.

WOAH, it's still there!!

 

Delphi Note : Delphi creates ALL the Windows the program is ever gonna need in advance, and hides

those that are not needed at the moemnt. At least in this case. Always remember this!

 

Naturally enough, the only way to enable such a hidden window is using the ShowWindow function.

ShowWindow can minimize, maximize, restore, hide and show a window. Therefore, when we grab, we

expect it to be called.

Clear the CreateWindowExA BP and put one on ShowWindow : bpx showwindow.

Grab a track, and walla, SI pops!!

Try to grab a "disabled" track, and SI doesn't pop!

 

Grab a good track again, and SI pops at ShowWindow. Do a F12 and return to caller. Now we need to

figure out what the hell is going on.

First I tried to return as much as I can. This lead me back to Windows code, and thinking about it for

a second, its no surprise, the Window procedure is continueing to work.

How are we going to get to the end of the rip process where we can see a CALL that inside it the window

is unhidden ?

Well, lets try something else, lets try waiting for the rip process to end. Grabbing and then pressing F12

untill we're back with Windows will do it for us.

The rip process continues and when it finishes, well, program stops!!! This is so cool, now we can try

to find where exactly the program either shows the window or not. But its not over yet.

We don't have a clue where to return to.

What we'll do is elimination of possibilities. After the return, lets do some stepping. There more calls and

conditional jumps we know nothing about, just staring at our face. However soon after we start stepping

over stuff, we see that a jump takes us to the end of the subroutine, and we soon see a RET instruction.

After returning, we again see a RET instruction close by. After that, we see some interesting code.

Soon after returning (can't recall if it was 2 or 3 times), we are placed after the CALL at address 00409285.

We assume this is where all the interesting stuff started from. From somewheer within that CALL, the

ShowWindow is being called from. Again we'll need to step over each call inside that CALL instruction

and see when it is called. After that, run the process again, and this time enter the next call in the list.

If you don't understand what I'm saying, I'll elaborate. The CALL at 00409285 contains several other calls

and conditional jumps. In order to figure out which call is the right one, we'll step over them all, while

the BPX for ShowWindow is atill active. Its what we call a "sticky" BP and will be armed when we step

over any function call. If it breaks while we are trying to step over a certain call, that means that inside

that call the call to ShowWindow is at. The next time we run the process we'll trace into it, and again

step over all calls, untill we figure out exactly where the ShowWindow call is at. This might seem futile,

because you can find an occurance of ShowWindow with a disassembler, but a disassembler can't show

you the program flow like it happens in real-time. What if the function in question is being called from many

places ? where will you look ?

One thing in particular to look for is a conditional that will cause the program somehow NOT to reach

the point where the ShowWindow is called from. That happens after entering a CALL inside the main call

at 409285. There lies a conditional jump that will jump over a call that is probably containing the ShowWindow call, and among it we assume the rip process code.

Here it looks, as I found it :

 

:0042C973        0FBF4DC0                                   movsx ecx, word ptr [ebp-40]

:0042C977        66833C4D98B3550000                cmp word ptr [2*ecx+0055B398], 0000              ;<<< look here!!

:0042C980        7557                                              jne 0042C9D9

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0042C971(C)

|

:0042C982        66A190B35500                            mov ax, word ptr [0055B390]

:0042C988        668945C2                                      mov word ptr [ebp-3E], ax

:0042C98C        EB3E                                            jmp 0042C9CC

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0042C9D7(C)

|

:0042C98E        668B55C2                                     mov dx, word ptr [ebp-3E]

:0042C992        52                                                  push edx

:0042C993        E80794FFFF                                 call 00425D9F

:0042C998        59                                                  pop ecx

:0042C999        8945C4                                          mov dword ptr [ebp-3C], eax

 

Take a look. I went through all the conditionals and calls before this part, only to find that they are executed in exactly the same way both when trying to rip a 'good' track, and when ripping a track we 'forced' to be enabled.

Everything is the same untill the check at 0042C977. There a value is checked, probably a flag of some sort.

It looks somewhat like an array just like we saw before, however, this time an array element is only 2 bytes as we can see from the instruction itself. ECX is the track we want to rip (duh, what a surprise..), and it is multiplied by 2. Lets view the data area starting from 55B398.

We'll do that without ruining the fact that we're homed in on the array at 4AA800, or any other data for that matter.

Change a data window with the command : data 1.

This will change to a different data view. As you may or may not know, SI has 4 data view, 0 to 3.

Now type : dw 55b398.

 

This will show us what's in 55B398 in WORD alignment, and unsurprisingly, we see yet ANOTHER array of

flags. Whats incriminating this array of course is that in every place there is a 1, there is also an enabled track,

that we CAN rip!

Do you recall which tracks you enabled in the 4AA800 array ? Go ahead and enable them now in this new array, and go back to AG and try to rip them. This time it works perfectly! :)

Halleluya !!!

 

Now that we know what needs to be fixed, lets see where exactly we need to fix it. We still have to figure out

where the program stores these values of 0 and 1 to this new array. Naturally, we'll use a range memory BP.

 

bpr 55b398 55b3b8 w

 

Will give us a good enough range. Lets rerun the program, make sure the BP is active, and disable all others.

Soon enough as expected, SI will pop up and we'll end up here :

 

:00425D66        668945FE                                     mov word ptr [ebp-02], ax

 

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:

|:00425D2C(C), :00425D4B(U)

|

:00425D6A        0FBF55FE                                   movsx edx, word ptr [ebp-02]

:00425D6E        66833C5598B3550001                                cmp word ptr [2*edx+0055B398], 0001  ;already set ?

:00425D77        74D4                                             je 00425D4D                                              ;is so, go back

:00425D79        0FBF4DFE                                   movsx ecx, word ptr [ebp-02]                 ;get track #

:00425D7D        66C7044D98B355000100           mov word ptr [2*ecx+0055B398], 0001  ;enable flag

:00425D87        668B45FE                                     mov ax, word ptr [ebp-02]                       ;ready AX

:00425D8B        EB02                                            jmp 00425D8F

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00425CF6(C)

|

:00425D8D        33C0                                            xor eax, eax

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00425D8B(U)

|

:00425D8F        59                                                  pop ecx

:00425D90        59                                                  pop ecx

:00425D91        5D                                                 pop ebp

:00425D92         C3                                                                ret

 

Now what goes down here ??

We see at address 00425D6E that the program checks to see if the selected track has already been enabled. If it has, it goes back. Back in this case is to randomly select another track.

If not, it'll retake the track's number, and use it to store a 1 in the new array, starting with 55B398.

Once that is done, the track's value will be put into AX, and we'll exit the CALL we are at. Go back twice, and

suddenly you find yourself at a familiar place!

We're right after the CALL that was at address 00405D94. That was the CALL that generated a random track number to enable!! As it turned out, it didn't just randomize a number, it also stored it in the secret array!!

Lets think about this for a moment. This might seem like a trivial matter. Anyone tracing the function call could have found this out too, however, its not as simple as it appears to be. Crackers, when going into code

to reverse, prefer to trace through as little code as possible, and use the process of elimination in order to find

checks (or tests as I prefer to call them).

Now, this secret array got stored in the function call, but nested two levels down, assuring that crackers will

not enter the call unless absolutely necessary. This is in fact an elaborate trap that programmers can use to

trick us crackers. It causes us to lose valuable time when cracking.

However, now that we know where all the goods are at, we can work around the problem.

How will we fix it so that the program can have all the tracks enabled ?

First of all, the function call at address 405D94 is redundant. It randomizes a number and uses it to put a 1 in the flag array at 55B398. If we're gonna make all the tracks enabled, all the flags should have a 1 and therefore

this whole function is not needed.

What about the code following the function call ? Just as a remider, I've pasted it here as well :

 

:00405D92        EB1E                                             jmp 00405DB2

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00405DE6(C)

|

:00405D94        E81DA20200                               call 0042FFB6

:00405D99        66894584                                      mov word ptr [ebp-7C], ax

:00405D9D        0FB75584                                    movzx edx, word ptr [ebp-7C]

:00405DA1        C7049500A84A0001000000     mov dword ptr [4*edx+004AA800], 00000001

:00405DAC        FF8574FFFFFF                         inc dword ptr [ebp+FFFFFF74]

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00405D92(U)

|

:00405DB2        0FB70D249B4A00                     movzx ecx, word ptr [004A9B24]

:00405DB9        0FB705269B4A00                      movzx eax, word ptr [004A9B26]

:00405DC0        2BC8                                            sub ecx, eax

:00405DC2        41                                                 inc ecx

:00405DC3        898D54FDFFFF                          mov dword ptr [ebp+FFFFFD54], ecx

:00405DC9        DB8554FDFFFF                         fild dword ptr [ebp+FFFFFD54]

:00405DCF        D80DC5684000                          fmul dword ptr [004068C5]

:00405DD5        DC05C9684000                           fadd qword ptr [004068C9]

:00405DDB        E810A60700                               call 004803F0

:00405DE0        3B8574FFFFFF                           cmp eax, dword ptr [ebp+FFFFFF74]

:00405DE6        7DAC                                           jge 00405D94

:00405DE8        0FB715249B4A00                       movzx edx, word ptr [004A9B24]

:00405DEF        899574FFFFFF                           mov dword ptr [ebp+FFFFFF74], edx

 

Now, we know the loop ends at 405DE8, that is the first instruction being executed after the JGE before it fails to jump. That area of code is ONE BIG portion of memory for us to use !!!

Lets do this then : we'll erase the whole damn thing and program our patch there.

First of all disable all BPs, and rerun the app.

At the entry point type : g 405d94

This will get us to exactly where we wanna patch. I don't know if this program has NOP detection or not, so lets use XOR EDX,EDX to fill in the whole memory area. The opcodes for XOR EDX,EDX are 33D2.

So in SI , when we're at address 405D94, enter the following commands :

 

? 405de8 - 405d94

 

This will give us the length of the block we wanna fill. The result is 54h.

 

Now enter :

 

f  405d94  l 54 33,d2

 

This will fill the entire block with XOR EDX,EDX.

Now we can start working on the new patch. What we want to do is to run on all tracks and put 1's in all array elements, both the DWORD array at 4AA800, and the WORD array at 55B398.

First though, we need the track count. We wrote where it is located before, at memory address 4A9B24.

 

So here's what we need to assemble at 405D94 :

 

a 405d94 :

 

mov ecx, dword ptr [4A9B24]

mov dword ptr [ecx*4+4AA800],1

mov word ptr [ecx*2+55B398],1

dec ecx

jnz 405d9B

nop

jmp 405de8

 

OK, why this code in particular ? note also that there is still plenty of room left the author could have spared if he had made the program without limitations :)

First of all, the first line of code takes the CD's number of tracks and puts it in ECX. The next instruction writes a 1 at the DWORD array starting from 4AA800. The next does the same for the 55B398 array.

Then ECX is decreased, and checked for 0. When it reaches zero, we'll continue execution.

Notice the NOP instruction. This was done to fix the following instructions so that they appear as they should, otherwise the CPU interprets the wrong opcodes. See how it looks when assembled in the SI code window.

The final jump is to jump to code location 405DE8, which is the end of the original loop. We're doing that just for the sake of being nice. We jump over all the XOR EDX,EDX's that fill the remaining block. Just a matter of preference by myself.

Now what's left to do is to try and run the app and see if it works the way we engineered it to!

Backup the original exe, change the bytes (I found the beginning of the loop at offset 5394h in the original EXE file).

You can assemble the instructions in SI and copy or dump the opcodes so you can enter them in a hex editor.

Or you can look at this dump from SI :

 

0177:00405D94      0F B7 0D 24 9B 4A 00 C7-04 8D 00 A8 4A 00 01 00          ...$.J......J...

0177:00405DA4     00 00 66 C7 04 4D 98 B3-55 00 01 00 49 75 E8 90              ..f..M..U...Iu..

0177:00405DB4     EB 32 33 D2 33 D2 33 D2-33 D2 33 D2 33 D2 33 D2           .23.3.3.3.3.3.3.

0177:00405DC4     33 D2 33 D2 33 D2 33 D2-33 D2 33 D2 33 D2 33 D2         3.3.3.3.3.3.3.3.

0177:00405DD4     33 D2 33 D2 33 D2 33 D2-33 D2 33 D2 33 D2 33 D2         3.3.3.3.3.3.3.3.

0177:00405DE4      33 D2 33 D2 0F B7 15 24-9B 4A 00 89 95 74 FF FF          3.3....$.J...t.

 

After I assembled the instructions I did : d cs:405d94  l  60

That was the result. I then saved the history and entered the opcodes into HexWorkShop.

Okie, run the program after its patched, and what do you get ???

 

Access violation at address 34354947, read of address is 34354947.

 

Or something similar to this...

Naturally, my immediate thought was a CRC is in place and kicking our butt around the block.

I set out to search for it.

As you may or may not know, CRCs check the current exe file's opcode values at some places, and use those to calculate a checksum value. This is the general way of doing it. The calculation can be anything the programmer want. But in all CRCs, the app needs to open the exe file and read some data from it.

So I set my BPs on CreateFileA and OpenFile (just in case), and _lopen.

Running the program, CreateFileA did break, but using the PARAMS macro, I checked the first parameter (which should be the file path pointer), and didn't see the program opening the exe file at all !!

Also, none of the other APIs have been used !

This was puzzling to me. However, there is one more to try. You can also read data from files by mapping the file's content into memory. This is done with the MapViewOfFile function.

Setting a BP on it gave me absolutely no results at all, so this was striken out as well.

Now what can we do ??

The program refuses to run, and we have no clue why !!

Rerun the program, and lets at least try to see if we even reach the patch location 405D94.

At entry point write : g 405d94.

 

You won't get there, I promise. Something along the way causes an access violation. An access violation is when the program tries to access a memory page that is not allocated to the process' virtual memory map.

Identify those by the question marks in the data window, we all know them.

How can we find the CRC ?

The program lets us know about the problem using a message box, so put a BPX on MessageBoxA, once again.

After returning, I did some more tracing and ended back at windows code. I couldn't figure out the program's flow before the message box was called from. I even tried W32Dasm to figure this out, only to look at lots of

code I didn't understand and didn't help me when I tried breaking on in SI.

Soon though, everything will be clear.

In such cases where you need to know the program flow before a certain code location, you might be tempted to use trace simulation mode. I admit I never used it in my life. Its a nice feature, but with possibly many headaches.

What I did was something else. We can always count on overbloated code to have many CALLs in it. So it's safe to assume that the message box was called from within several nested calls. If we want to backtrace to the point of origin, we'll need to know where it came from, and therefore we'll look at the stack !!

The Stack contains the return address for every CALL that was executed. It also contains variables and stuff, but those are easily distinguishable.

so when the MessageBox returns, lets type : d ss:esp.

 

And now lets look at the stack. Positive offsets from ESP means stuff that has already been stored before.

Look for any addresses that are in the legal range of process memory, which is between 00400000 and 7FFFFFFh.

This is the normal process space, and is almost 2 GB in size (virtual of course).

Find the first occurance of such a thing and display its contents in the data view, to make sure its not pointing to a string or some other known data type. You can also go about it a different way, if you look at how many bytes have been pushed on the stack prior to the CALL, you can find the next return address.

When you find one, rerun the program and put a BPX on the code. We need it to break prior to the access violaion. Soon enough I found such a code location. I stepped over some CALLs to figure out where the offensive code is at, and when I found it, I traced into it. Nothing special so far.

After some more tracing, I ended up in once again the most familiar place, our patch location !!!

What happened was that just before we got to it, there was a JMP instruction (at 405D92), that jumped INSIDE one of our patch instructions!

That was of course not wanted, because it would change the whole thing the CPU executes. I traced through this JMP to see what happens next, and as it jumped INTO one of the MOV instructions in our patch, the code reshaped itself and now I saw this :

 

CALL 34354947

 

Familiar ?? Indeed!

That's our error message! Display whats in location 34354947 and you'll see its paged out!

This is why we get an access violation. There was NO CRC checksum, there was a bug in our patching!

To fix it, we'll want to kill that JMP instruction that jumped us into our patch code. Since its only 2 bytes long, and ECX is already in use in the next instruction, I'll replace it with XOR ECX,ECX, which is 33C9.

Open your Hex Editor and make the change, its at offset 5392h.

 

IMPORTANT NOTE : When patching make sure that the code is going to be executed EXACTLY as you entered it, without any surprises, such as jumps to the middle of it. This is an important lesson !!

 

NOW run the program again. What goes on now ?

No error message, thats good. We wait, and we wait some more, and after a few seconds we realize something is wrong. The program locks up. This was the lockup I mentioned earlier.

Hmmf, Now what have we got to do ?

We'll need to figure out where and why the lockup occurs. You can always kill the AG task using CTRL+ALT+DEL.

First of all, we'll rerun AG. We'll wanna break at the patch location, so do  : g 405d94.

Now lets see if the lockup occurs in THIS subroutine. do an F12 to return to the caller routine. So far so good, SI broke again. What you see is this :

 

:004131B4        6A01                             push 00000001

:004131B6        E86E26FFFF                                call 00405829

:004131BB        59                                  pop ecx                  ;<<< We are HERE!!

:004131BC        6A03                            push 00000003

:004131BE        E8270CFFFF                call 00403DEA

:004131C3        59                                  pop ecx

:004131C4        E8BD10FFFF               call 00404286

:004131C9        59                                  pop ecx

 

Ok, we returned from the CALL and now we're on the POP instruction.  Lets again step over calls to see which one is the one we want. The first one we step over is at address 004131BE, and is harmless, however the second one at address 004131C4 is causing us to exit SI and make AG lock up.

After some tracking inside I found this :

 

:0040453C        0FB715249B4A00       movzx edx, word ptr [004A9B24]       ;number of LV items in EDX

:00404543        0FB70D269B4A00       movzx ecx, word ptr [004A9B26]

:0040454A        2BD1                            sub edx, ecx

:0040454C        42                                  inc edx

:0040454D        3B55B4                         cmp edx, dword ptr [ebp-4C]              ;compare EDX to # of tracks

:00404550        750A                              jne 0040455C                                         ;<< Look at THIS!!

:00404552        837DB402                     cmp dword ptr [ebp-4C], 00000002

:00404556        0F8FEDFEFFFF           jg 00404449

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00404550(C)

|

:0040455C        8B45D8                         mov eax, dword ptr [ebp-28]

:0040455F        64A300000000             mov dword ptr fs:[00000000], eax

:00404565        8BE5                              mov esp, ebp

:00404567        5D                                  pop ebp

:00404568        C3                                  ret

 

What's that ??

You can see that at 40453C , EDX is loaded with the numbers of items that are enabled in the LV. I found this out because when I just added one flag to the 4AA800 array, this value changed. Now look at [EBP-4C].

No matter what, it will contain the number of tracks in the CD. The check is done between those, and if they are equal (if all tracks are NOT enabled, we will jump to 0040455C, which is good).

This is a clever precaution our programmer put in order to lock the program if its being crack, and guess what, it IS being cracked!! :)

I immediatly wrote down 40454D and put a BPX on it.

So we'll wanna make that conditional a permanent jump. This is standard procedure. You can use your disassembler to find out where this line of code is and at what offset at the EXE file, and change the 75 to EB, which will make the JNE a JMP.

Now the program can continue, and we see ALL the tracks enabled!! How cool we think to ourselves.

Try to click on one, and the BP I put at 40454D will pop us again. The jump is fixed, and no problems there.

Lets disable the BP on  40454D and try to grab some tracks.

Sure enough we grab alright, but when we try either the Abort of Skip buttons, the program yet again locks up on us !!

DOH!!! Another trap someplace else in the program.

Now we need to find another such trap.

I don't really recall the exact way I did it. It might have been another CALL to ShowWindow (but this time for the HIDING of the TForm2 window), or it was something else. I managed to find myself into this code, using some zen I believe :

 

:0040B374        6A02                             push 00000002

:0040B376        E84DF60000                 call 0041A9C8

:0040B37B        59                                  pop ecx

:0040B37C        85C0                             test eax, eax           ;if a file for this track exists...

:0040B37E        751E                              jne 0040B39E

:0040B380        EB07                             jmp 0040B389

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0040B390(C)

|

:0040B382        C68573FCFFFF00       mov byte ptr [ebp+FFFFFC73], 00

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0040B380(U)

|

:0040B389        80BD73FCFFFF00      cmp byte ptr [ebp+FFFFFC73], 00     ;<< FLAG

:0040B390        74F0                              je 0040B382                                           ;This could potentially make trouble

:0040B392        EB13                             jmp 0040B3A7

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0040B3A5(C)

|

:0040B394        A1FCFF5500               mov eax, dword ptr [0055FFFC]

:0040B399        E872380500                  call 0045EC10

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0040B37E(C)

|

:0040B39E        80BD73FCFFFF00      cmp byte ptr [ebp+FFFFFC73], 00

:0040B3A5        74ED                            je 0040B394                                           ;again troubles..

 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0040B392(U)

|

:0040B3A7        833D5C15490001       cmp dword ptr [0049155C], 00000001

:0040B3AE        755F                            jne 0040B40F

 

First of all, this will be the last code snippet I quote from AG :)

Second, I traced through this code several times to figure out what everything means.

The first thing I noticed is that the 40B376 will return 1 in EAX if a file already exists with a name we want to write to. When AG grabs a file, it copies it to temp.wav, and afterwards it renames it to TrackX.wav, where X is the number of the track. If a file with that name already exists (if we ripped it before), it will return 1.

Thats of no concern to us. Now the flag at [EBP+FFFFFC73] will be checked. I found this flag will be 0 if the number of CD tracks is equal to the number of LV items. It is probably calculated elsewhere.

If they are equal, we're jumped to 40B382, which will zero it out again, and we'll get stuck in an endless loop.

This is not permissable :)

Another thing to notice is that if we made it to the code after this loop, the CALL at adress 40B399 will also change the memory location [EBP+FFFFFC73] to 0 (in our case because the number of tracks is equal to the number of LV enabled items, we engineered it that way..).

So afterwards, if again it equals 0, we'll go back to 40B394, and the CALL would be performed again, resulting in the flag being set to 0 again and again....

What we need to do is to patch the conditional at 40B390 to NOP and NOP (why not hehe), and the next conditional at 40B3A5 to NOP and NOP again.

After that, check out how AG rips all the CD tracks and all the buttons do what they are supposed to. Note that the Skip button takes a couple of seconds to respond. This has always been this way.

The patching of those are left as a finishin exercise for the reader :)

 

Final Words of wisdom

 

Well, I learned at least 3 things in this reversing endeavor.

I learned to use the Stack to backtrace program flow. I learned to pay careful attention to what and where I patch, and HOW things interact with my new code.

I also learned how to do some elimination on subroutine CALLs, and finally, I learned how to reverse a Delphi application. How it works, how its extremely overbloated, and so on...

I made some more reversing when attempting several ideas I had, but I didn't write them here because this tutorial was long enough and they are irrelevant to the protection.

It took me quite a while to crack this program, and at times I thought I would just go ask for help on the message boards, but taking a break for a few hours each time gave me some fresh ideas to try. I didn't give up as I knew I could do it, and its DONE!! :)

 

Ok ok, enough with that, greetings go to ALL the Immortal Descendants members, all the +HCU members, the +Sandman (I owe him special thanks , since because of him I am what I am today), The Snake (my dear friend), The Hobgoblin (just love that guy :), Lazarus (love him too, loved the Laz_Calc.. hehe), and Jeff of course, all those that visit the Newbies Forum, +Fravia, +Frog's print, +tsehp, DQ, _mammon, and all those gods of knowledge that roam the cyberspace and occasionally rear their heads either at +Fravia's forum or the newbies forum...

Of course, Ghirrizibo (not sure how to type it) for his wonderful IceDump util, and many other I probably forgot, if I did, I apologize!!

 

Whew, that was one HELL of a typing project!!!

 

For comments, bug reports (ya hehe) and just to bug me, please contact me at :

 

LordSoth@ImmortalDescendants.org

lordsoth8@hotmail.com

or via ICQ : 5178515

 

Have fun reversing!

 

Lord Soth

 

23.5.2000