Tuesday, May 03 2005 @ 09:43 PM CEST Contributed by: wizard Views: 5517
Level : beginner
Hands on tutorial for inline patching packed targets.
Target: Zoom Player Professional (inmatrix.com)
Tools used:
OllyDbg 1.09d
PE iDentifier v0.91
Note. A product version (as the product itself) doesn't matter, but it's
important that all worked and you understand what you do. The second thing that
you have to keep in mind is what all the addresses, values and numbers, which
corresponds to a disassembled code are represented in hexadimal system, the
rest numbers are decimal.
Author:
Wizard
Date:
16.04.2004
Difficulty:
beginner
Origin:
Don't wish it were easier, wish you were better.
Michelangelo
Introduction
Zoom Player Professional is one of the most advanced Media Players
and DVD Front-End on the PC today, Designed to be simple at first
glance while being remarkably dynamic and flexible when used to
its full potential.
Zoom Player works in two modes. A Media mode which can play any
file supported by DirectShow (any file which plays in MediaPlayer)
and a DVD mode which uses pre-installed DirectShow DVD filters to
play DVD content.
Reconnoitre
First of all, I downloaded file zp331pro.exe from the official site and then
installed it. Once I've done that, I checked out the main executable
(zplayer.exe) file. It was sorta packed. To be sure in that I used PEiD to
detect the exact packer. PEid detected some old UPX version, maybe because it
was modified a bit, but maybe it was so indeed. Anyway PEid said this:
"UPX 0.89.6 - 1.02 / 1.05 - 1.24 (Delphi) stub -> Markus & Laszlo".
Now run the program to see the startup NAG.
You have to wait for 3 seconds and then press the appropriate button. Once
you've pressed the button - program starts and runs fine. Check what do we have.
Go to the options menu by pressing Ctrl+P or by clicking the appropriate button on
the player's panel (at the bottom-right corner).
Click information bar in the left column. We see the strings:
"Registered to: This copy is unregistered". You may also notice the register button
at the bottom of the form. If you press it, it will bring you to the internet register
page. That means, if the program hasn't a password/name dialog it should use some
other registration way.
Usually it's a registration file (keyfile). Why? Simple, why does it need inet
for? Of course to register you as a legal user for money. After that the registered
user will receive some file with key and he'll just put it into the program's
folder (usually in the root). It's 99% of cases. Nobody (I mean "any" programs' authors)
won't do such tricks as hidden password dialog somewhere by pressing the magic keys
combinations or something else even more tricky. That's because usual users don't need a
haemorrhoids in their ass, which they couldn't heal. Other words, end-users need simple
registration procedure. Of course, it's only my subjective point of view and I can be wrong,
but I just confide in my intuition and rely on my experience.
Get a spade and dig
Fire up Olly, press F3 and open file zplayer.exe from your ZoomPlayer
directory (e.g. "C:Program FilesZoom Player"). You'll see this dialog, telling
that the content (of our executable) could be compressed with some packer.
Press "Yes" button to start analyzing the code. Once it's done you will be directly
at UPX's entry point (00683ED0).
All we need now is to find Real (Original) Entry Point (REP/OEP). You might ask: how to do that?
Well, it depends on a cracker, his experience and intuition. Each man will do different.
As we know that the program is packed with UPX we can simply scroll down with PageDown
key. Personally I pressed it 8 times until I came to the right place. There's also
the most simplest method to get there. Press Ctrl+F to start find command dialog and type
in it "POPAD", but drop the flag "Entire block", press "Find" button to search the
instruction... We're right at the place.
... skipped ...
0068402B : 55 PUSH EBP
0068402C : FF96 7CA62800 CALL DWORD PTR DS:[ESI+28A67C]
00684032 : 09C0 OR EAX,EAX
00684034 : 74 07 JE SHORT zplayer.0068403D
00684036 : 8903 MOV DWORD PTR DS:[EBX],EAX
00684038 : 83C3 04 ADD EBX,4
0068403B : EB D8 JMP SHORT zplayer.00684015
0068403D : FF96 80A62800 CALL DWORD PTR DS:[ESI+28A680]
00684043 : 61 POPAD ; <= the place we've found
00684044 : E9 9774F4FF JMP zplayer.005CB4E0 ; jump to the REP
00684049 : 00 DB 00
0068404A : 00 DB 00
0068404B : 00 DB 00
We searched for "POPAD" (61) instruction, because the program is packed with UPX.
I do the same, when unpacking ASPack, but there I need to search a few more
times, but for UPX just once (usually), or more times (seldom).
Note. Of course everything could be much harder sometimes, but if it's
a standard of UPX (even a little bit modified) you will always find out. Besides,
you can try to find the REP (OEP) with automatic tracing, but this way sometimes
(very seldom) can be wrong.
Put a breakpoint with F2 at address 00684044. Press F9 to run the proggy.
Ok, we're right at the place. Now press F7 or F8 (it doesn't matter now, 'cause it's
just a jump) once to get to the REP.
Now, let's remember what inscriptions have we seen at the NAG.
We will need only some phrase. Let it be "This is a Trial". We have to search
these words in dump. To do this, do a right click at the address we're now, and
then select "Follow in Dump/Selection".
Go to the dump window. Its address should be now equal to the REP (005CB4E0).
We did it to set the cursor in dump window to one of memory cells of the executable
we're cracking. This's very important, 'cause otherwise we won't find our string.
That's because such search doesn't cover all the windows memory and you have to
select the memory range in which you will search by yourself (just like in SoftIce).
Note. We have set the cursor the that address, because we're now right in the
unpacked code. Thus, it has another memory address than UPX entry point. But as we're
gonna search for a NAG's string, we must set the cursor to the appropriate memory
range. That's because we won't find the string we wanna search in the packed part of
the executable. We can only find it in the unpacked memory range.
Press Ctrl+B in dump to start binary search dialog. Type "this is a trial" in
ASCII field, set the flag "Entire block" (if you won't set this flag, the search
will only go down from the position your cursor has now, but with it the search
will cover all the executable related memory). Press "OK" and wait a bit. No luck,
mate! Don't worry, just try to search the same string as Unicode. Type the same
string, but in UNICODE field. Trying... Success! Personally I found that string at
address 00541790.
Note. Never set "Case sensitive" flag, when searching for a string in the memory.
In dump select only the first byte of the string we've found. We select only one byte,
because it's always enough to catch a string treatment (if the last is present). Now it
should be marked with grey color. Right click on this (selected) byte, then go up through
the menu and choose "Breakpoint/Memory, on access".
Now just press F9 to let the program run and debugger will catch the moment,
when the string (from NAG) will be read. You will land somewhere in kernel32 module.
Remove the breakpoint we've set before. To do this, right click at the address
you're now in code window (or in dump window) and select
"Breakpoint/Remove memory breakpoint" from the appeared pop-up menu.
Once you've done that go to address 77E7AB26 and press F4 to trace right
until the command we need. That's because we don't need to run in the cycle below
like a squirrel in a wheel.
... skipped ...
77E7AB26 : C2 1800 RETN 18 ; trace out of here
77E7AB29 : 2BF7 SUB ESI,EDI
77E7AB2B : 8975FC MOV DWORD PTR SS:[EBP-4],ESI
77E7AB2E : E901FEFFFF JMP kernel32.77E7A934
After you will out of this call you have to trace with F8 for a long time until
you will see the NAG. Yes, that screen we've seen before. We're doing all that stuff,
because if the program treats to the trial string (before the dialog), that means the
NAG will appear soon.
You maybe wondering, why to do all that tricky stuff, if we can just set a breakpoint
to some API call. Well, it's partly correct. First, we don't know on function to set
that breakpoint, because it looks like that the program was written in Delphi. We can
conclude that because of many unnecessary calls (too deep recursion). Secondly, we just
don't have the import table. You may try to Press Ctrl+N to check the names. You will see
only kernel names (when you'll be in the range 77E7...) and only the UPX functions
(when you'll be in the range 404E..), but there're no real (unpacked) executable names,
which can observed. Of course if we were cracking with SoftIce, we would try to set
some breakpoint, using our intuition. But for you to say, if this's a Delphi composed
proggy then standard API functions won't work, 'cause Delphi uses its own drawing style.
You may only try some non-standard functions, like DestroyWindow, CreateWindow or others.
All those things you can do with SoftIce, but let's get back to Olly.
Note. I have to warn you. Don't try to make your life easier by pressing F9 to
reach the dialog. That won't help, because in such case program just runs. I can give
you a hint how to trace "without the brains". Just after you will be out of the call at
address 77E7AB26, trace with F8 (yes, just hold it) till you see the NAG. Olly will
stop the cursor at the needed address for you.
Olly stopped at address 0057ACA7. Now click "Zoom Player" process in your
taskbar to see the NAG. Once you'll see it wait for 3 seconds then press appropriate
button to continue tracing.
After the "accident"
Ok, after you pressed the appropriate button Olly took the control again.
0057ACA5 : 8B00 MOV EAX,DWORD PTR DS:[EAX]
0057ACA7 : 8B10 MOV EDX,DWORD PTR DS:[EAX]
0057ACA9 : FF92 EC000000 CALL DWORD PTR DS:[EDX+EC] ; the NAG itself
0057ACAF : 837D DC00 CMP DWORD PTR SS:[EBP-24],0 ; <= you should be here
0057ACB3 : 0F84FA000000 JE zplayer.0057ADB3
0057ACB9 : B201 MOV DL,1
Take a look around. If we're here (at 0057ACAF) then it must be a way to
get here. Scroll a bit up to check, if is there something interesting.
Do you see what I see? At address 0057AC1E we see an interesting compare.
It looks like after that compare the conditional jump at address 0057AC25 won't jump, because we're not registered users. Other words, that jump "should" direct the program over the NAG, right at address where we're now (0057ACAF).
That also means that the memory cell with address [5D3190] contains the
registration status (0 ain't registered, > 0 is registered).
All we need to know now is when the program changes the status to unregistered.
Other words, when it will write zero value in that memory cell.
A simple majority
Press Ctrl+F2 to terminate the program. We need to start the program again to catch
a moment, when the cell [5D3190] will be written with 0 (unregistered status).
But if we had seen the NAG already we won't catch that moment. That's why we have to
start it over. So, run the program again. You will break before the REP at our
first breakpoint (at 00684044). Press F8 once. Now we're at the start of the
real (unpacked code) again at 005CB4E0.
Now go to dump window. Press Ctrl+G, type 5D3190 and press "OK" to go
for it. You will be at the right place.
Do the same as we do above (when catching a treatment to the trial string). Other words,
first, right click the selected byte (at 5D3190) in dump window. After that
select "Breakpoint/Memory, on write". We need only to know when the program will
write zero in the memory cell. We already know, where it will read it. Besides,
we don't need the last thing at all, because if we patch the memory cell at
the right place once (change unregistered status to registered) we'll get registered
program without caring about where it will read that memory cell next time to
avoid the NAG (maybe also enable disabled/hidden features, whatever).
Let the program run with F9... Bang! Olly broke-up here.
Aha, let's remember this place as the first place we've met unregistered state
saving. By the way, if you'll trace a bit down you'll see that the program tries to
load file "zplayer.regkey" and then to check it for validity. But we're keep
going, 'cause we don't care about that key much, our goal is to register program
without finding valid serials/keys, etc., but with reversing the unregistered
state to registered. That's why keep on going by pressing F9 to see next place
(if the program has it).
Trying... Program ran and NAG has appeared. Ok, that means here's a classic algorithm. First,
program moves to a cell unregistered status, and then it tries to load a license
file/license ini-file/license key from registry (it doesn't matter, 'cause
these are only the methods). Secondly, if it fails to load the license key or it's
not valid then it just runs as usual, shows NAG, all pay-features are disabled, etc.
If the key is valid (after checking it) the program will definitely move the
registered status (in our case something > 0 [1..FF], because of that JNZ, remember)
and it will be registered.
Note. Everything can be not so easy sometimes. Register state cell could
be initialized with unregistered status already (yes, during the compilation). Or
there could be a few such memory cells. Besides, there could be not such a simple construction
like ours:
CMP BYTE PTR [RegisteredState],0
JNZ GOOD_BOY
But there could be some other compare tricks like this:
MOV EAX,[RegisteredState]
PUSH EAX
... other commands ...
XCHG DWORD PTR [EBP+A],EDX
CMP EDX,00BAD000
JZ BAD_BOY
Or something else even more perky. Everything depends on an author's fantasy. But it doesn't mean
that you can't catch it. It only means that everything ain't so simple.
Now we know that we have standard (classic) scheme, which means registered state
memory cell can be written only twice (next situations only as examples):
once during the compilation (unregistered/registered state) and then during the
program runs (if the key is invalid/valid);
only during the program runs, first, before the key-check procedure and after that
if the key is valid/invalid.
Therefore, we just need to replace one byte in command MOV BYTE PTR DS:[5D3190],0 to
MOV BYTE PTR DS:[5D3190],1 at address 0057A291.
We don't know exactly what value it should be, but if there was a JNZ instruction,
then we suggest that it should be ANY value bigger than zero in byte interval
(01..FF).
By knowing all the above, all we've left is to try will it work or not. It's simple
to test. Restart the program again. Press F9. It should break at REP once more.
Press F7/F8 to get at REP. We're at address 005CB4E0 again. Press Ctrl+G,
type 0057A291 (it's the address we've found earlier) in the field,
press "OK". Now, press space and type "MOV BYTE PTR DS:[5D3190],1" instead of
"MOV BYTE PTR DS:[5D3190],0", press "Assemble" to confirm the changes.
As you can see only one byte is changed (from 00 to 01) at address 0057A297.
Remember that fact for further patching. After that press F9 for a beta-testing.
Hooray!!! It runs fine without the NAG. Besides, if you will go to the options again
you won't find the "Register" button in the information bar. Nevertheless, that
string still remains "This copy is unregistered". I suppose that's because the
program didn't find the reg-file with code and name, but it thinks that it's
registered anyway.
Ok, restart the program again (with Ctrl+F2) and get ready to patch this baby.
Making an inline patch on the fly
Run the program with F9 to stop at well-known breakpoint (at 00684044).
We know what to patch and where. All we need is to find a place where to make
our inline patch. To do this we have to find what ImageBase has the executable.
That info we need to find some free space (a sequence of zeroes), which we will
use as our patch.
To find out what ImageBase has the executable open the memory map with Alt+M
shortcut. Find the line "zplayer" in owner column and "PE header" in type column.
Notice the value in the address column. It equals 00400000. That's ImageBase
itself. Of course you may also click that line and scroll down in the appeared window
to find out the same value (it'll have the appropriate name - Image Base). Actually we
need this value only to find a free space from the beginning of the executable.
Exactly from "MZ" signature (first two bytes of the program). You will always
find a sequence of zeroes in any executable's header. It's a reserved space,
which never used. Thus, it can be used.
We've got all the info to make a patch. Close the memory map and go to the dump
window. Press Ctrl+G, type 00400000 (the value we've got earlier) and
press "OK". We're at the beginning of the executable (right at the header's
beginning of the unpacked executable). Scroll a bit down and look for a sequence of zeroes. Scrolling...
What are we're seeing now? A little space at address 00400080. Well, you
can use this space, but you rather scroll a bit down more. There should be a space
from 00400300. Scrolling again... Aha! Just like I told you, there's a lot
of free space at the forenamed address. Most of executables have it, and exactly
from that address.
Go to the code window. Your cursor should still stand at address 00684044.
Press space to change the jump from "JMP 005CB4E0" to "JMP 00400300".
Other words, we just changing jump from jump to REP to our patch.
Press enter at the command you just changed, to go to 00400300. You see,
it's full of zeroes. Let's create our patch now. Press space and type
"MOV BYTE PTR [0057A297],1". Drop the flag "Fill with NOP's" and press
"Assemble" (don't close assemble window). That's our patch!
The one thing we have to do is to direct the program to the REP. Simply, type
"JMP 005CB4E0", press "Assemble" again and close the assemble window.
We're changing 00 to 01 at address [0057A297], because it's the last byte
of the command. Other words, it's only the changes we've made to the command. Thus,
there's no need to move to the memory the same commands (bytes), but only those, which
need to be changed.
We're done for now. All you have to do is to save all modifications to another
or to the same file.
The spirit of the sea
I used Zoom Player for a few weeks, but when I started it one day I saw a strange
message, which says something about the integrity check. I didn't even treat it
seriously and pressed "OK", but the program just exited to my surprise.
After that, I started the program again and I suddenly understood that it's a
hidden protection, which wasn't detected before, because it just couldn't be
detected. It seems that this somekind of simple self-check of executable. Other
words, it's just a simple CRC check of the module content.
All we need to do is to eliminate this unexpected message, which stops the
program from running. To do this restart the program once again (if it runs).
Set a breakpoint at address 00684044, and then press F9. Olly breaks at that
address. Now press F8 to step to our inline patch at 00400300. Step till you
reach the REP of the program (005CB4E0).
Now do the same as we do when we were searching the NAG. Other words, do a right
click at the address we're now, and then select "Follow in Dump/Selection". Press
Ctrl+B, and try to search the "integrity check" as a Unicode string (don't forget
to set the flag "Entire block" to cover all the memory range of the executable)...
Uh, no luck! Hm, strange. Try to search as an ASCII string. Yes! I found it at 0054A444.
Look a bit above, you will see the beginning of the string ("Zoom Player Professional").
I think that we didn't find it as Unicode, 'cause probably the author made it to
save the space. Yes, that's right. He made a simple string ("Zoom Player Professional")
to concatenate it with other strings. That's it. That's why we didn't find it as
Unicode string. Of course it's only my theory, so don't treat it as a dogma.
Now set a memory breakpoint on access to the first byte of the string we've found.
As you know we don't need, just like with the NAG string we just need to catch a
treatment to that string. Therefore, select only one byte at address 0054A444,
right click on this (selected) byte, then go up through the menu and choose "Breakpoint/Memory,
on access". Now press F9 and wait for approximately 10 seconds. When Olly will break at
some address (my was 004D1E82) remove the breakpoint. To do it, right click in
code or dump window, then select "Breakpoint/Remove memory breakpoint" from the appeared
pop-up menu. Trace with F8 a little until this place:
0057DF3C : 803D8C315D0000 CMP BYTE PTR DS:[5D318C],0 ; the reason of jump
0057DF43 : 7544 JNZ SHORT zplayer.0057DF89 ; jump over the NAG
0057DF45 : 833D64245D0000 CMP DWORD PTR DS:[5D2464],0 ; the second reason
0057DF4C : 753B JNZ SHORT zplayer.0057DF89 ; another jump
0057DF4E : 6A00 PUSH 0
0057DF50 : 8D45F4 LEA EAX,DWORD PTR SS:[EBP-C]
0057DF53 : 8B0D58245D00 MOV ECX,DWORD PTR DS:[5D2458] ; @("Zoom Player Professional")
0057DF59 : 8B1554245D00 MOV EDX,DWORD PTR DS:[5D2454] ; @("has failed integrity...")
0057DF5F : E84C71E8FF CALL zplayer.004050B0 ; it concatenates two strings
0057DF64 : 8B55F4 MOV EDX,DWORD PTR SS:[EBP-C] ; <= you will be here
0057DF67 : 8D45F8 LEA EAX,DWORD PTR SS:[EBP-8]
0057DF6A : E8C177E8FF CALL zplayer.00405730 ; converts string to Unicode
0057DF6F : 8B45F8 MOV EAX,DWORD PTR SS:[EBP-8] ; EAX points to Unicode string
0057DF72 : 668B0DC8E25700 MOV CX,WORD PTR DS:[57E2C8]
0057DF79 : 33D2 XOR EDX,EDX
0057DF7B : E884E0F2FF CALL zplayer.004AC004 ; this is that crazy message
0057DF80 : 33D2 XOR EDX,EDX
As you can see, if the content of the memory cell with address [5D318C] or
with address [5D2464] is not zero, then you will see no message. That's the way
we need. Ok, restart the program once again, press F9. You're at address00684044 again. Trace until the REP. Right click, select "Follow in Dump/Selection". Press
Ctrl+G, in the appeared dialog type "005D318C", and press "OK". All we need now is to
catch a moment, when this cell will be written with some value (probably zero, because
the original [not patched] exe's CRC differs from the patched exe's CRC).
Note. We need only to know, when one of the cells we be written, because
both of 'em compares with zero, and after that jump (or don't) over the message. That's
why we need only one of 'em. So, choose any. I chose the first, because it's a BYTE
(the second is DWORD).
To know, when the cell [5D318C] will be written with a value, right click
on address 005D318C, select "Breakpoint/Memory, on write". Now just press F9.
Once you've done that, you'll see something like this:
Remember this place (00562E01) and press F9 again to see, if is there any
other place before that compare at address 0057DF3C... Bams! And you see the
the same message now. It means that there's only one place, where our cell [5D318C] is written with zero value. All we've left to do is to write some positive & bigger than zero
value (e.g. 01) into that cell.
Improving the inline patch
First of all, you have to decide how to patch it, and then to add that patch
you've decided into our inline patch. There're many way of patching it. Your
first thought will probably be such. Let's change "MOV BYTE PTR DS:[5D318C],AL" (A2 8C 31 5D 00 => 5 bytes) to "MOV BYTE PTR DS:[5D318C],01" (C6 05 8C 31 5D 00 01 => 7 bytes).
Well, not a bad idea, but alas it will increase the command from 5 to 7 bytes.
You maybe say, so what. Nothing, but there's a command "XOR EAX,EAX" (33 C0) at
address 00562E06, which's exactly two bytes long. If we'll patch in such way
we'll just lose the command. As we didn't write this program, then we don't know,
how the program will react without that two bytes long command.
You may patch it in other different ways, but leave it. I think the most "correct" way is to patch so. Don't forget, that we're making an inline patch, so this patch
differs from a usual patch (in not packed exe). With such kind of patch you can
just imagine what to patch, but don't patch it directly in exe. Because you can't
patch it directly in unpacked exe, you have to wait when exe is unpacked and then
patch it. That's the main sense of the inline patch.
What do we need for the final patch. Not too much, just think a bit. Imagine,
that we just don't have the command "MOV BYTE PTR DS:[5D318C],AL" in the unpacked exe.
Just imagine it. If so, how will the program act in such case? Right, it will always
bring us the message, if of couse the second memory cell ([5D2464]) will be zero
too. So, if we'll just fill that 5 byte long command with NOP's, then the program will never
write the value with zero. That's what we need, but it's not quit enough. The last thing to
do is to write the memory cell [5D318C] with some value bigger than zero (e.g. 01).
The actual patch will be such. Restart the program in the last time, press F9 to
get to the well-known breakpoint at 00684044. Press F8 once to get to our patch
at address 00400300. We're gonna to add it a bit. Press F8 again (because our
first patch works fine, so leave it) to set the cursor to 00400307. Press space now and
type "MOV EAX,562E01", press "Assemble". It's the first byte of the address we wanna
fill with NOP's. Type "MOV DWORD PTR DS:[EAX],90909090" to fill the first 4 bytes with
NOP's. Then type "MOV BYTE PTR DS:[EAX+4],90" to fill the last (the 5th) byte with NOP.
After that, type "MOV BYTE PTR DS:[5D318C],1" to set a value we need instead of zero. We
set one (01), because so the program will always think that it's CRC is ok. And finally,
type the last command of the patch "JMP 005CB4E0", which will return the control to the
unpacked program after our patch.
Yes, such patch is much bigger, but it works every time. Save the changes in the
last time and run the program. It works fine.
The last thing to say
It's not so hard to find a place or a way to make an inline patch, especially
for UPX. I just showed you one of ways of patching. By the way, if you don't familiar
too much (or at all) with UPX inline patching, then I recommend you to read
Detten's tutorial about how to do it. I recommend you his tutorial, because it's
short, simple and pretty clear to start with. Besides, it contains some theory,
which I didn't mention in this investigation.
As the final words of this essay, I have to say, that there're as many ways as
many people to do the same as we did above. You can even use different methods of
cracking, nothing to say about inline patching, because the last thing might
vary from cracker's experience, his programming knowledge, intuition, and of
course from his logic. That's why don't worry about anything you can't do.
Just keep on going forward and you will do it one day.
All rights reversed (c)2004 - BiW Reversing - http://www.reversing.be - The challenge is yours