SoftICE, IceDump, ProcDump, W32Dasm, Hiew.
This tutorial was written for newbies. First some words on the protection system itself. ASProtect is a powerful protector, and it can be hard to defeat if you are unexperienced in the art of cracking. It involves the packing routine you see in ASPack, but it uses totally different memory locations for its unpacking, and it is a little bit more complicated. The unpacking routine includes anti-SoftICE code and CRC checking. In this case (AZPR) you will find only one kind of anti-SoftICE code (often referred to as MeltICE), but in more recent ASProtected programs you will also see the SEH protection. (If you want to see in detail what this and other types of anti-debugging code really looks like, take a look at the help/text file that follows FrogsICE).
I will explain in detail how to unpack this program manually,and
then how to patch the packed program. Ill do it in this
order, because during the unpacking process I get some information
I need in the patching process. The unpacked program can be
run without trouble of any kind, but the patched packed file will still show the *debugger detected* message if you have SoftICE active. (I havent figured out how to get rid of that without unpacking it).
The first thing we need to do is to check out the program in
ProcDump. We need the virtual address and the virtual size of
the .idata section. We need this so we know where to look for
unpacking activity when we are stepping through the unpacking
routine, and to know how many bytes of the code we need to dump
later on. The virtual address is where in memory the .idata section
is located while the program is loaded into memory, and the size
shows how many bytes it occupies in memory. I found
the virtual address to be 23000 and the size to be 2000. We have to add the ImageBase of the program (which in this case is 400000), so the address is 423000. Make a note of this number. We have to dump the import section while were in the midst of the unpacking process. This is the only way we can get a fully working .idata section we can paste into the unpacked program later on.
We can not use the .idata section in the unpacked program as
it turns out in the end. After the import section is loaded into
memory, and the program has utilized the information contained
in the .idata section, the program overwrites some of the addresses
within the .idata section with some other information as the unpacking
continues. This is why we cant get a working dump of the
program at the end of the unpacking routine. Now, activate IceDump.
We need this gem of a tool to dump some
code while we unpack the program.
Then put a bpx _lopen in SoftICE, and run AZPR. When SoftICE breaks, push F5 and SoftICE breaks again. Push F11, and you should be back here :-
:00660581 CALL KERNEL32!_lopen
:00660586 INC EAX <-- You are here.
:00660587 JZ 006605AE
:00660589 LEA EDX,[EBP-1C]
:0066058C XOR EAX,EAX
:0066058E CALL 00652728
:00660593 MOV EAX,[EBP-1C]
:00660596 LEA EDX,[EBP-18]
:00660599 CALL 00654B88
:0066059E MOV EAX,[EBP-18]
:006605A1 CALL 00653378
:006605A6 MOV EDX,[EBX+03]
:006605A9 CALL 0065FFD0
:006605AE MOV DL,
:006605B4 MOV EAX,[EBP-08]
:006605B7 CALL 0065FECC
:006605BC MOV EBX,EAX
:006605BE TEST EBX,EBX
:006605C0 JZ 006606BA
:006605C6 MOV EAX,[EBX+03]
If you check EAX's value now, you will see that it is non-zero.
Change it FFFFFFFF (-1). (Type
r eax ffffffff on
the command line). Then disable the breakpoint in SoftICE, we
dont need it anymore. By doing this you have defeated the
anti-SoftICE code. We could have entered the call to the function
_lopen, and then patched that call to return the correct value
in EAX. Or we could have changed some data strings while tracing
through this call but I like to keep things simple. The only thing
we need here is to make sure the program jumps at address 00660587.
After forcing the program to jump, and after a few instructions,
you end up at the conditional jump at 006605C0. If you force the
program to jump here, you avoid the CRC check, and you can go
on with the unpacking routine. But we need to reach the CRC check
point in order to find out what the hardcoded checksum is. So
dont jump and keep on stepping until you reach this code
:00660669 MOV EAX,[EBP-14]
:0066066C CALL 0065291C
:00660672 JMP 00652CB0
:00660677 JMP 00660669
:00660679 MOV EAX,[EBP-10]
:0066067C CMP EAX,[EBP-0C] <-- CRC check.
:0066067F JZ 006606BA
:00660681 MOV DL,[0066660C]
:00660687 MOV EAX,[EBP-08]
If the program jumps at address 0066067F, you will not see
the Invalid checksum message. So if you just want
to unpack it, force the program to jump here. The code you see
from the address 00660679 through 0066067F is characteristic for
ASProtected programa. You will find it in all of them, I think.
But we need the original checksum value when we are about to patch
the packed file later on. At address 00660679 the calculated checksum
is placed in EAX, and at the next instruction that sum is compared
with the one stored at the location pointed to by [EBP-0C]. This
is the original checksum. Write it down, you will need it later.
(If you make changes in the code in the raw file, and then check
the values here, you will see that
the EAX value changes, not the value pointed to by [EBP-0C]). If you type
d ebp-0C on the SoftICE command line,
you will see this in the data window :-
:0060FDDC 51 33 FA 62 D8 02 B9 00-EC FD 60 00 10 FE 60
The four first bytes are the checksum. In reversed order they
are 62FA3351. Now, let us concentrate on tracing and dumping the
.idata section before it gets overwritten. Type in dd 423000 on
the SoftICE command line. You will see that your data window changes
its outlook to show four eight digit rows. So far they are
all question marks. That means nothing has been
loaded into this memory address yet. Now, just keep on single stepping until you see something written into the data window. After the program has executed the call at address 006609AD, you will see that some data is written into address 423000.
This is not the correct data we need. This is only a part of the unpacking of the .idata section. Just keep on stepping until some data is written once more into address 423000. When that happens you should be here :-
:00660A0A MOV EAX,[EAX+08]
:00660A0D CALL 00660130
:00660A12 XOR EAX,EAX <-- dump .idata section.
:00660A14 POP EDX
And the data window should look like this :-
:00423000 000230DC 00000000 00000000 00023864 .0..........d8.. :00423010 000234A0 000230F4 00000000 00000000 .4...0.......... :00423020 00023871 000234B8 00023104 00000000 q8...4...1...... :00423030 00000000 0002387E 000234C8 00023128 ....~8...4..(1.. :00423040 00000000 00000000 0002388A 000234EC .........8...4..
Use IceDump to dump the .idata section by typing the following :-
pagein d 423000 2000 c:\idata.bin
We are dumping 2000 bytes of the program starting at address 423000. That leaves us a fully working .idata section for later use. The next thing to do is to step through the code until we reach the end of the unpacking routine in order to get hold of the original entry point of the program. After some stepping you get here :-
:00660B2E MOV EAX,[EBP+08]
:00660B31 MOV EDX,[EAX]
:00660B33 MOV EAX,[EBP+08]
:00660B36 ADD EDX,[EAX+18]
:00660B39 MOV EAX,[EBP+08]
:00660B3C MOV EAX,[EAX+1C]
:00660B3F CALL 006604C4
:00660B44 POP EDI
:00660B45 POP ESI
:00660B46 POP EBX
:00660B47 POP ECX
Enter the call at 00660B3F, and step carefully through the code until you reach a ret instruction. The code your tracing through now is self modifying, so keep a close look. Eventually you end up here :-
:006604CE JMP 006604D2
:006604D0 JMP 30838FE9
:006604D5 ADD BL,CH
:006604D9 ADD EBX,EBP
:006604DB MOV [EBX+ESP],EAX
:006604DE JMP 006604E2
:006604E0 JMP 006604E4
:006604E3 JMP 006604E6
:006604E5 CALL E968F03A
:006604EA POP SS
:006604EB RET <-- You are here.
When youre at the ret instruction, make a note of the eax value. It shows the original entry point (OEP) of the unpacked program. Here it is 401000. Lets dump the program. Make sure you have disabled all breakpoints before you do this. While youre still at the ret instruction, type a eip, enter, jmp eip, enter, enter and then exit SoftICE. You have now put the program loaded into an endless loop, ready to be dumped. Run ProcDump, mark the file azpr.exe, right click on it and choose full dump. Place the dumped file in the azpr directory after renaming it. Then remember to kill the task.
Now, let us rebuild the dumped file into a fully working copy. Open the PE editor in ProcDump, and enter the file you just dumped. Change the entry point from 000AF001 to 00001000. Next the sections, we have to change some more numbers. As a start change the characteristics for the text section from C0000040 to E0000020. By doing this you make sure that the dumped file can be disassembled in W32Dasm. Change the raw size for the .bss section to 00000000. If you dont do this, the program will not run properly. (Frankly I do not exactly know what happens here. I learned it from tiamath). Next thing is to write down the raw offset for the .idata section. We have to know where in the unpacked file to insert our dumped .idata section.
One more thing to do here. Open up directory in ProcDump and change the values you see for the import table to the values you saw listed under the virtual address and virtual size for the .idata section. (I changed 000AFD1C and 000001D8 to 00023000 and 00002000). Now we are finished with ProcDump. Open your hex editor. Load the dumped file and the dumped .idata section. (I use Hex Workshop). Open the dumped file, and go to the raw offset for the.idata section. Scroll down to the end of it, and mark the whole block. (I marked the block from 21800 through 22FC0). The last word you see in the .idata section should be WriteFile. Delete the whole block, open up idata.bin and copy the exact same amount of bytes, starting from the beginning of the file, into the offset 21800 in the dumped azpr.exe. By doing this you have replaced a damaged .idata section with a fully working one.
You now have have a fully working unpacked copy of AZPR. So let us crack it, and then patch a packed copy.
There are several ways to crack this program. I will go for
patching it into a fully working program, so we dont have
to enter any serial at all. I must admit that I dont exactly
remember how I first came up with the approach Im about
to explain. But it works. Put a bpx regqueryvalueexa in SoftICE,
and run AZPR. SoftICE breaks and after hitting F5 and a few F102,
you will end up here :-
:00412F69 CALL 00412E97
:00412F6E LEA EAX,[EBP-40] <-- You are here.
:00412F71 CALL 00412D7A
I think I once tried to put a bpx on regqueryvalueexa in SoftICE, and then checked the dictionary box in AZPR. If I recall this correctly, this function used to be disabled. After ending up as shown, I stepped over the instruction at 00412F71, and found that the returned EAX value was 0. I changed it to 1, and tried to run the program. And the dictionary function worked. Call it a hunch, but after seeing this, I put a bpx at 00412F76, ran AZPR, and everytime SoftICE broke I changed EAX=0 to EAX=1. When the startup was completed, I had a fully functional program. So why not check out the call at 00412F71 and make sure it always returns EAX=1?. There are several ways to patch this call so it always returns EAX=1 :-
Change the conditional jump at 00412D8F from 7D07 to EB00, and the JMP instruction at 00412D98 from E9E8000000 to E9E9000000. Check it out yourself. Or try to find another way to do it. After this patch is made, we have a fully functional copy of AZPR. The next thing to do :- Patch a packed copy into a fully functional copy. I got really inspired by reading Predators tutorial on the subject, and this is how I did it with AZPR.
We have to do two things : Patch the program to avoid the CRC check, and to patch the program so it runs as a fully functional program. Its logical to make the latter patch first. We know the OEP. It is 401000. If we remove the ImageBase, we have 00001000. Let us search for this value in a Hex Editor. First reverse the value. It then looks like this : 00100000. When we search for this, we find 14 occurances, and they are located at the following offsets :-
A4, B0, E4, EB, 17C, 290, 2867D, 2875B, 287D8, 28B16, 28B22, 28D31, 29817, 2993F.
This looks like a lot, but it is logical to exclude the first 6 occurences. They are located very early in the program and are a part of the PE header (some of them are). So let us concentrate on the last 8 ones. Load the packed AZPR in Hiew and check out the addresses (you can of course calculate them if you prefer) :-
2867D - 4AF07D
2875B - 4AF15B
287D8 - 4AF1D8
28B16 - 4AF516
28B22 - 4AF522
28D31 - 4AF731
29817 - 4B0217
2993F - 4B033F
This is all the places in the raw file where the value of the OEP is located. When we run the program, the unpacking routine reads this value from one of these locations. We need to find out which one. Then we can change that value to another one in order to redirect the program to another location. At this location we will add some code, and then direct the program back to the OEP. Start by going through the routine we did earlier. Bypass the anti-SoftICE code and the CRC check and go to where the .idata section has been loaded.
Then put a bpm xxxxxx r on the first four addresses you just found (the xxxxxx is the actual address). By doing this we command SoftICE to break every time some information is read from the addresses where we put a breakpoint. After putting a bpm r at the four first addresses (SoftICE only allows four bpms at the time), it breaks here after hitting F5 :-
:00660B36 ADD EDX,[EAX+18]
:00660B39 MOV EAX,[EBP+08] <-- You are here.
:00660B3C MOV EAX,[EAX+1C]
:00660B3F CALL 006604C4
:00660B44 POP EDI
:00660B45 POP ESI
:00660B46 POP EBX
:00660B47 POP ECX
:00660B48 POP ECX
:00660B49 POP EBP
:00660B4A RET 0004
This code should be familiar to you. It is the code you step through right before the call that brings up the final ret instruction (and the OEP), and before the unpacked program starts to run. The command window in SoftICE reveals this information for us :-
Break due to BPMB #0177:004AF516 R DR0 (ET=3.24
At 00660B36 the value pointed to by [EAX+18] is added to EDX. If you check the EDX value in the register window youll see 00401000, and if you type d eax+18 on the command line, the data window shows this :-
:004AF516 00 10 00 00 1C FE 60 00-9F B5 CE 0C 00 10 00
Its quite clear that the address were looking for is 4AF516. Look up the offset 28B16 again. After finding it, take a look a couple of lines further down. Theres a lot of empty space there. This looks like a good place to write in some new code. Why not use offset 28B70 as a starting point. When I looked that offset up in Hiew, I found the address to be 4AF570. The value originally written at offset 28B16 is 00100000, which reversed will be the offset 00001000 (address 00401000). This makes the program to jump to the address 00401000. We want the program to jump to the address 004AF570. If we remove the ImageBase value from the address, we get 000AF570. Reversed this gives 70F50A00, which is the value we have to replace the 00100000 at offset 28B16 with.
After doing that we have to write in some new code at address 004AF570. In order to patch the program into a fully functional copy I have to change the 7D07 instruction at address 00412D8F to EB00, and the E9E8000000 instruction at address 00412D93 into E9E9000000 (actually I have only to change one byte). The code to be written into the program at location 004AF570 will look like this :-
:004AF570 66C7058F2D4100EB00 MOV WORD
:004AF579 C605942D4100E9 MOV BYTE PTR [00412D94],E9
:004AF580 E97B1AF5FF JMP 00401000
After this patching is done, the program will always go through this code first, and therefore continue as a registered copy.
The last thing we have to do is to find and change the checksum.
First, go through the steps described above once more, and step
through the code until you reach the CRC checkpoint. The value
stored in EAX at address 0066067C is the new one (for me it was
C9AAB61C). Reverse it (1CB6AAC9). Remember the one you found earlier
on?, reversed it was 5133FA62, load the patched copy in a Hex
Editor and search for this value. You will find it only at one
place. Replace that occurence with the
reversed value of the new checksum. Then you will never see the Invalid checksum message again.
Thats it. This program is now cracked, patched and unpacked. But remember, in the patched, packed version you will still get the debugger message when SoftICE is active.
*Remember, the best way to keep something is to give it away.*
Hobgoblin, May 2000.