您的位置:首页 > 编程语言 > ASP

An in depth analysis of ASProtect 2.22 by zyzygy

2007-03-12 11:16 561 查看
The name ASProtect sends shivers the spine to any want to be reverse engineer. Every time a new version comes out, new tricks follow. My target is Notepad.exe and I have packed it with ASProtect 2.22 demo version. This version of ASPR has advanced import protection. Let’s begin to tackle it.

DISCLAIMER: THIS ARTICLE IS INTENDED FOR EDUCATIONAL PURPOSES ONLY! I DON’T BEAR ANY CONSEQUENCES FOR YOUR ACTIONS!!! IF YOU LIKE THE PROGRAM THEN BUY IT!! PAY THE AUTHORS FOR THEIR HARD WORK!!!!

Tools needed:

OllyDbg
OllyDump
Command line plug-in for OllyDbg
Imprec v 1.6f (that’s the last ever built)

Target: Notepad

Turn off any other plug-in that hides OllyDbg (HideDebugger/IsDebugger etc.)We are going to remove all debugger checks manually.

I would suggest you to take note of the locations as and when required.

Load the protected notepad.exe (pnote) in the debugger and it says that the code seems to be compressed or encrypted etc. etc.Press Yes.

01001000 >/$ 68 01400101 PUSH asprnote.01014001 01001005 |. E8 01000000 CALL asprnote.0100100B

0100100A /. C3 RETN
0100100B $ C3 RETN
Throughout the unpacking process you will notice that the author has played deftly and copiously with the stack using RETN statements as jumps.

After these statements are executed you will land at 01014001.But this is not how we are going to begin. Press F9 to execute the application and you see a message box with the message that the debugger has been detected. A minor setback. Reload the application and then set a breakpoint on MessageBoxA for the error was being called with this API. Execute it again and this time the debugger breaks at the address of MessageBoxA.

A look at the stack should tell you from where it was called, usually. Even the call stack says nothing about the location of the caller. Scroll up a bit and you should see a similar message about debugger detection. Just above it is a location .If you follow it in the disassembler you will see the code for debugger detection.

0006FE08 00847565  Follow this 0006FE0C 00847580 ASCII "Protection Error" 0006FE10 008B6AD0 ASCII "Debugger detected - please close it down and restart! Windows NT users: Please note that having the WinIce/SoftIce service installed means that you are running a debugger!" 0006FE14 0006FE34 Pointer to next SEH record 0006FE18 0084756F SE handler

@ 00847565:

0084752D E8 96FFFFFF CALL 008474C8
00847532 8BD8 MOV EBX,EAX
00847534 84DB TEST BL,BL
00847536 75 22 JNZ SHORT 0084755ACulprit Jump
00847538 E8 9FFEFFFF CALL 008473DC
0084753D 84C0 TEST AL,AL
0084753F 75 19 JNZ SHORT 0084755A Culprit Jump
00847541 A1 50BA8400 MOV EAX,DWORD PTR DS:[84BA50]
00847546 8378 24 00 CMP DWORD PTR DS:[EAX+24],0
0084754A 74 19 JE SHORT 00847565 By pass jump
0084754C A1 50BA8400 MOV EAX,DWORD PTR DS:[84BA50]
00847551 8B40 24 MOV EAX,DWORD PTR DS:[EAX+24]
00847554 FFD0 CALL EAX
00847556 84C0 TEST AL,AL
00847558 74 0B JE SHORT 00847565 By pass jump
0084755A 56 PUSH ESI
0084755B 68 80758400 PUSH 847580 ; ASCII "Protection Error"
00847560 E8 33DCFDFF CALL 00825198
00847565 33C0 XOR EAX,EAX We land here
The above code has been commented .We now know where to cause the jump so as to bypass the protection. But how do we get there because this is a dynamically allocated memory (you will realize this on fiddling with it a bit), hence created at runtime.

Reload the application and start tracing. Thumb rule: Always go into the calls, never trace above it (while in the notepad code, when tracing the allocated memory, trace over), because there is always something funny going on inside the calls. If you disobey then you will not unpack the application.

Now what you will encounter during the trace is loops, loops and even more loops. A general pattern you will see is :

Pattern #1: Single loop Pattern #2: Nested loop
_loop: loop1:

loop2:
jmp _loop2:
jmp _loop jmp _loop1

If you have traced correctly, then your first loop should be here:

01014117 8B041F MOV EAX,DWORD PTR DS:[EDI+EBX]
0101411A B1 15 MOV CL,15
0101411C 81E8 AAFCBD5A SUB EAX,5ABDFCAA
01014122 0FBFC9 MOVSX ECX,CX
….code snipped…. code snipped…. code snipped…. 01014145 81FB A4F7FFFF CMP EBX,-85C 0101414B ^0F85 C6FFFFFF JNZ asprnote.01014117

Set a breakpoint right after 0101414B and execute the application and you will notice the code changes. Tracing further this shall be your second iteration.

010141AA 8B38 MOV EDI,DWORD PTR DS:[EAX]
010141AC 8BCF MOV ECX,EDI
….code snipped…. code snipped…. code snipped….
010141D1 E8 0D000000 CALL asprnote.010141E3
….code snipped…. code snipped…. code snipped….
010141E3 8AFD MOV BH,CH
010141E5 5B POP EBX Removes the return address.
010141E6 8F00 POP DWORD PTR DS:[EAX]
….code snipped…. code snipped…. code snipped…. 01014201 ^0F85 A3FFFFFF JNZ asprnote.010141AA

Now set a breakpoint after 01014201 and execute it and the code changes, nothing surprising. This will now sound repetitive. This is the third loop:

01014254 FF3417 PUSH DWORD PTR DS:[EDI+EDX] ….code snipped…. code snipped…. code snipped…. 0101427D 34 5D XOR AL,5D 0101427F D2A3 A0591EFF SHL BYTE PTR DS:[EBX+FF1E59A0],CL 01014285 CC INT3 ….code snipped…. code snipped…. code snipped….

010142B3 E8 05000000 CALL asprnote.010142BD
010142B8 A9 2ECF5C65 TEST EAX,655CCF2E
010142BD 5B POP EBX Removes the return address.
….code snipped…. code snipped…. code snipped…. 010142D2 0F85 11000000 JNZ asprnote.010142E9 010142D8 E9 26000000 JMP asprnote.01014303 ….code snipped…. code snipped…. code snipped….

010142E9 66:8BCE MOV CX,SI
010142EC ^E9 63FFFFFF JMP asprnote.01014254
010142F1 F2: PREFIX REPNE: ; Superfluous prefix
Now this is a complicated loop. If we set a breakpoint just after 010142EC, then it will display the error message. In order to save the effort of locating the correct location after the loop lets ask Olly to take the trouble. We shall trace.

Press Ctrl+T and then select EIP is in the range and enter the range as 010142F1 – 010143FF.Why 010143FF you may ask? After the loop terminates, I am assuming that the location will be greater than 010142F1 and hence to be careful, I gave a huge range (3FF-2F1=10Eh=270 bytes). And always trace into(Ctrl+F11).Why? Try trace over and then you shall know.

The above code (all loops) decrypts further Asprotect code in the memory.

We now land at 01014303.If you trace further you will encounter 3 more loops wherein you will notice that the addresses of GetProcAddress, LoadLibraryA , VirtualAlloc, VirtualFree and GetModuleHandleA are being calculated.

01014500 E8 9C020000 CALL asprnote.010147A1 This call calculates the addresses.

Encountering one more loop what you will see is some memory is being allocated and freed etc. and then you are taken to some other location.

01014656 68 00A08500 PUSH 85A000
0101465B C3 RETN
Nice isn’t it. Trace further (F8). We are now in the dll. Let me remind you so as to why are we taking so much trouble. We want to reach the location of the debugger check. Go to 00847565(the location of the debugger check on my machine) and you will find that the code isn’t there as nothing has been written .So we trace further and check if code has been written ,if it has then we set a breakpoint there. For now trace further. It is now necessary that we go into the call. Upon tracing further you will see lots of loops, again. Set breakpoints at appropriate locations and you should see this:

0085A5C1 61 POPAD
0085A5C2 75 08 JNZ SHORT 0085A5CC
0085A5C4 B8 01000000 MOV EAX,1
0085A5C9 C2 0C00 RETN 0C
0085A5CC 68 309D8400 PUSH 849D30
0085A5D1 C3 RETN You are here.
Now the memory has been written and we can set a breakpoint. Before that if you execute the return you will land in the ASProtect dll. 

We shall now be debugging in the ASProtect dll. Go ahead set a breakpoint at 0084752D and execute. It should break.

Note: There are many ways to reach the ASProtect dll, this was the lengthiest ,but it did tell you more about the packer .Another simpler way to reach the dll memory area ,is set a breakpoint on GetModuleHandleA, the second time it breaks you will land in the code mentioned above and viola you are in the dll.

0084752D E8 96FFFFFF CALL 008474C8
00847532 8BD8 MOV EBX,EAX
00847534 84DB TEST BL,BL
00847536 75 22 JNZ SHORT 0084755A
00847538 E8 9FFEFFFF CALL 008473DC
0084753D 84C0 TEST AL,AL
0084753F 75 19 JNZ SHORT 0084755A
00847541 A1 50BA8400 MOV EAX,DWORD PTR DS:[84BA50]
00847546 8378 24 00 CMP DWORD PTR DS:[EAX+24],0
0084754A 74 19 JE SHORT 00847565
0084754C A1 50BA8400 MOV EAX,DWORD PTR DS:[84BA50]
00847551 8B40 24 MOV EAX,DWORD PTR DS:[EAX+24]
00847554 FFD0 CALL EAX IsDebuggerPresent
00847556 84C0 TEST AL,AL
00847558 74 0B JE SHORT 00847565
0084755A 56 PUSH ESI
0084755B 68 80758400 PUSH 847580 ; ASCII "Protection Error"
00847560 E8 33DCFDFF CALL 00825198
00847565 33C0 XOR EAX,EAX
We must make sure that we execute only the jump that takes us to 00847565 directly .We can simply edit it to make it jump to 00847565. The debugger check has now been defeated. If you happen to trace into the calls or keep the INT3 checks disabled in the exceptions tab of debugging options, you will see that ASProtect also checks for SoftICE. No problem for us though. Press F9 and you will see another exception.

008475DB 74 1A JE SHORT 008475F7
008475DD 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]
008475E0 8A00 MOV AL,BYTE PTR DS:[EAX] Memory access violation.
@ 008475F7:

008475F7 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]
008475FA 8A00 MOV AL,BYTE PTR DS:[EAX]
008475FC 84C0 TEST AL,AL
It’s the same code. We will change JEJMP and then the exceptions won’t be encountered. Reload it and do the needful.

Well now this is a loop. We will trace it using a tracer as we had used earlier. Select EIP is in range and then give the appropriate range. Now if you execute the application, then it will run. Hence no more exceptions. Now if the executable were to run then it has to execute from the code section. Hence we set a breakpoint on access at the code section.

Open the memory map (ALT+M) and select the code section of the pnote and then set it. After you alter the jump set the breakpoint on access and execute the application. It will land on the OEP .Yes we are now at the Original Entry Point of notepad. The actual code however to reach the OEP is embedded deep inside the polymorphic code and the final instruction that will cause you to jump on the OEP would be:

JMP DWORD PTR [ESP-4]

For me it was 01007397.Press Ctrl+A to analyze .Well dump it using OllyDump. The import won’t be fixed though. Run Imprec and select the protected notepad, enter the correct OEP and select Get Imports and it will display a list of APIs. Select Fix Dump and you have the Import Address Table fixed. Well not completely.

We now have to deal with the Advanced Import Protection. It’s not hard either. Firstly remove the memory breakpoint on the code section.

If you view All intermodular calls then you may be looking at quite a few calls to the same address(00FE0000)

How does Advanced Import Protection work?

It is highly polymorphic in nature.
It calculates a hash every time it is called .The hash is dependent on the address which calls it. This is how it determines which API to call.
There are pre-computed hashes for various APIs. The computed hash and the pre-computed hash are compared till found and then the address is calculated. More on this later.
After the address has been calculated it isn’t directly called. Instead some other call has this address and it returns to the address of the API called instead of returning to the next instruction from where it was called. The necessary parameters are also given to it which are pushed on the stack prior to the address. The parameters are also present in the main code.
Sometimes certain operations are also carried out(for which in the original exe there would have been some code).It returns to the normal code with this instruction : JMP DWORD PTR [ESP-4]
At this point all the stolen bytes/ crypted bytes are also resolved.
Set a breakpoint on every call to 00FE0000.

It will break at 010074DF:

010074DF . E8 1C8BFDFF CALL 00FE0000

Trace into the call.

Trace and trace till you see call <register>.Due to the polymorphic nature of the code the register doesn’t always remain the same, neither does the address at the next execution. For me it was:

00FE0132 8D9A 64668400 LEA EBX,DWORD PTR DS:[EDX+846664]

00FE0138 2BDA SUB EBX,EDX
00FE013A FFD3 CALL EBX ,This is the call.
Trace into it. You will encounter a loop.

The final statements for the loop are:

008468B3 FF45 EC INC DWORD PTR SS:[EBP-14]
008468B6 FF4D E4 DEC DWORD PTR SS:[EBP-1C]
008468B9 ^0F85 62FFFFFF JNZ 00846821

Hence the loop will terminate when the value at [EBP-1C] will be 0.Its initial value is 1C.Set a breakpoint on 008468B9 and then execute it till the count is 01. Once the count is 1 start tracing. Go into the calls and till you find that this is the one :

008468AB E8 B4F1FFFF CALL 00845A64 Go into this call
008468B0 EB 01 JMP SHORT 008468B3
008468B2 9A FF45ECFF 4DE4 CALL FAR E44D:FFEC45FF ; Far call
008468B9 ^0F85 62FFFFFF JNZ 00846821

Well somewhere in this region is the API address is being calculated. We shall just trace and upon finding something interesting we shall look at it more closely. If you trace and trace you will notice that the address of the caller is found on the stack. Note: There is an indirect anti-bp code hence, if we have set a breakpoint on the 0FE0000 call which is being currently called, before the hash has been calculated, we should remove the breakpoint. How does it detect it will be explained later.

Trace carefully and at 00845BE7 (for me), you will see the API being called. At 00845BE8 you will see the location from where it is called.

00845BE7 50 PUSH EAX ; kernel32.GetStartupInfoA

Now we need to know where the stolen bytes are. Trace on further till you see

00FF00D0 FF6424 FC JMP DWORD PTR SS:[ESP-4]

This will take you to the next execution address. Execute it and you will see the so called stolen bytes. Note it and replace them in the dump.

For me before the call to GetStartupInfoA 00FE0000 the code was:

010074DF . E8 1C8BFDFF CALL 00FE0000

010074E4 . 20F6 AND DH,DH
010074E6 . 45 INC EBP
010074E7 . AC LODS BYTE PTR DS:[ESI]
010074E8 . 017411 0F ADD DWORD PTR DS:[ECX+EDX+F],ESI
010074EC . B7 45 MOV BH,45
After the tracing through the call 00FE0000:

010074DF E8 88A3E576 CALL 0FE0000 call to GetStartupInfoA
010074E4 20 ???
010074E5 . F645 AC 01 TEST BYTE PTR SS:[EBP-54],1 This where the
execution resumes
010074E9 . 74 11 JE SHORT notepadd.010074FC
010074EB . 0FB745 B0 MOVZX EAX,WORD PTR SS:[EBP-50]
010074EF . EB 0E JMP SHORT notepadd.010074FF
Hence to determine the APIs being called set a hardware breakpoint(there aren’t detected) at the locations where you can see the APIs name ,address from where they are called and at JMP DWORD PTR SS[EBP-4].This will take you to the stolen bytes.

Perform certain activities with the program so that it calls APIs and then due to the hardware breakpoints set they will show you the details you look for.

As I said I will discuss the how the AIP actually calls the APIs. Here it is.

00845B52 8BF8 MOV EDI,EAXsome value but important one for hash
00845B54 8B45 D0 MOV EAX,DWORD PTR SS:[EBP-30]
00845B57 0145 E0 ADD DWORD PTR SS:[EBP-20],EAX
00845B5A 8B45 F0 MOV EAX,DWORD PTR SS:[EBP-10] Offset from where
it is called.
00845B5D 40 INC EAX
00845B5E 2B38 SUB EDI,DWORD PTR DS:[EAX] Sub. From opcodes at
offset+01 byte --1
00845B60 2B7D D0 SUB EDI,DWORD PTR SS:[EBP-30]
00845B63 8B45 F0 MOV EAX,DWORD PTR SS:[EBP-10]Offset from where it
is called
00845B66 0FB600 MOVZX EAX,BYTE PTR DS:[EAX]First Opcode --2
00845B69 03F8 ADD EDI,EAX
00845B6B 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]
00845B6E 8B40 2C MOV EAX,DWORD PTR DS:[EAX+2C] 00FE0000
00845B71 2B45 F0 SUB EAX,DWORD PTR SS:[EBP-10][ebp-10]=offset
0FE0000-Offset
00845B74 83E8 05 SUB EAX,5
00845B77 03F8 ADD EDI,EAXhash computed in edi
00845B79 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4]
00845B7C 50 PUSH EAX
00845B7D 66:8B4D E0 MOV CX,WORD PTR SS:[EBP-20]
00845B81 8BD7 MOV EDX,EDIHash for the api
00845B83 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]
00845B86 E8 E5050000 CALL 00846170 hash search and match fn.
Anti-Breakpoint Description:

1- The hash incorporates the address from where the function was called. Mathematical operations are performed on the opcodes from where it is being called. Firstly E8 0A8FFDFF (in reverse order) are subtracted.

2- Then at 00845B66 the first opcode from the address called is loaded. E8 0A8FFDFF and then is used for further hash computation. But now if we had set a breakpoint there, the debugger substitutes CC (int 3h) byte instead of E8.Hence the value in EAX after executing 00845B66 will be CC instead of E8.Hence a wrong hash is calculated and API isn’t found.

In the call there are 2 loops. One loop is for finding the dll to load and once it is loaded the search for the hash begins.

008461FA 8B5E 04 MOV EBX,DWORD PTR DS:[ESI+4] Hash table

Whenever we enter the call the first time, the first loop is always executed to locate the dll. Then the loop for searching the hash function is executed. Once the hash is found it is then computed for the API name. Once the name has been found, it doesn’t call GetProcAddress.Instead it uses the Name Table of the export table to calculate the address. This way even though we set a breakpoint on GetProcAddress will do no good.

00846547 8BD3 MOV EDX,EBX
00846549 8B45 E8 MOV EAX,DWORD PTR SS:[EBP-18]
0084654C E8 1BDFFDFF CALL 0082446C Calculates the name, return has name in EAX
Thereafter the dll base address is loaded into the memory using LoadLibraryA

00846557 50 PUSH EAX ; eax=”KERNEL32.DLL”
00846558 FF56 40 CALL DWORD PTR DS:[ESI+40] ; kernel32.LoadLibraryA
008465A9 8BD3 MOV EDX,EBX
008465AB 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
008465AE E8 39EDFFFF CALL 008452EC Calculates the address
Thereafter this call executes the API:
00845BF4 E8 6FFDFFFF CALL 00845968

As you trace into the call, you will notice that the address of the API to be executed is on the top of the stack.

Stack at the RET statement:

0006FC64 77E7949D kernel32.LocalSize Top of stack 0006FC68 008459B0 0006FC6C 0009B500
0006FC70 00000000
0006FC74 01000000 asprnote.01000000

So this gets executed.

Now you know how AIP works. Fix the dump with all such APIs which are being called. How? Perform all possible operations on the executable (Open/Save etc.) and hence you can determine the APIs used and the stolen bytes.

Last topic. I am assuming that you have fixed the necessary APIs. Before GetStartupInfoA is called 4 other APIs are also being called. Reload the application, reach the oep, set the hardware breakpoints and then execute.

You have fixed the dump. But it doesn’t run. It doesn’t generate an error yet it exits perfectly. Take 2 instances of Olly. Load one with the protected notepad and the other with the fixed dump. And watch the behavior in both.

It will be easy to deduce that after the call to LocalSize things go wrong. Actual Code:

010040EB ? D1EB SHR EBX,1
010040ED ? 75 04 JNZ SHORT asprnote.010040F3
The jump is taken. Whereas the in the dump the jump isn’t taken because the value in EBX is different. After executing 010040EB ,EBX holds 816h as value. Hence in the dump we can write this instruction:

010040E8 BB 16080000 MOV EBX,816
010040ED ? 75 04 JNZ SHORT notepadd.010040F3
This way the jump is always taken . If you now run the program, it will run beautifully. Let’s test it. You will notice that the file doesn’t open ,but gives a message Operation completed successfully!

Again compare the behavior. The problem arises after MapViewOfFile API. In the protected notepad the jump :

0100528A . 3BFB CMP EDI,EBX
0100528C . 75 1C JNZ SHORT asprnote.010052AA This jump isn’t taken
EBX=0 and but EDI points to the buffer which has the contents of the file. Whereas in the dump both are zero. The value of EDI last gets modified at this instruction: 01005271 > 8BBD C4FDFFFF MOV EDI,DWORD PTR SS:[EBP-23C]

This means that [EBP-23C] should have the buffer location. After MapViewOfFile has been called ,the buffer is returned in EAX. This if this value is put in [EBP-23C] then it should run perfectly. So right after the call to MapViewOfFile, write the these instructions in place of the garbage bytes:

MOV DWORD PTR SS:[EBP-23C],EAX

This should do the needful. Now run the executable and viola, it opens!!!

There you have it ASProtected notepad fully unpacked. The features in the demo version aren’t as strong as the ones in the registered. But this article should give you an insight on how your approach should be.

It was fun unpacking this target. Nice tricks used.

Comments and suggestions are welcome.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐