⬆️ ⬇️

A study of simple crackme's (part 3)

Hello, Habraludi.

I present to you the third part of my series of articles on the study of kryakmis. In this topic we will talk with you about manual unpacking of some packers and about overcoming not complicated anti-debugging methods.





1. Manual unpacking



From the tools I needed:



Theory


The packed program works as follows:

First, the unpacker code is launched, which begins to decrypt the packed program code. After the decryption is completed, a jump is made to the OEP program and then the already unpacked program code starts to be executed.



The unpacking algorithm will be as follows:

1. Find the RVA OEP.

2. Dump the program.

3. Restore the import table.

4. Change the entry point to the original.

')

So, the OEP address we need is calculated by the formula:

RVA OEP = VA OEP - ImageBase , where:

Image Base is the address in memory from which the program is loaded into memory.

OEP (Original Entry Point) is the address from which the program would start if it were not packed.

Virtual Address (VA) - virtual address of the element in memory

Relative Virtual Adress (RVA) is a relative virtual address. Address relative to imagebase.

Well, for example, we found OEP equal to 00301000, and ImageBase is equal to 00300000, then RVA OEP will be equal to 1000. The ImageBase value can be found by looking in any PE headers editor.

After we find the RVA OEP we need to remove the dump program. Dump means - area (part) of memory or a file saved to disk from memory. To remove a dump means to save the required area of ​​memory (usually occupied by the program) to the hard disk. As a result, we get the unpacked program.

Next we need to restore the import table. The import table stores information about the functions used by the program during its operation. Initially, the import table stores the addresses at which the file contains the names of imported functions, i.e. functions used when running the program. When the program is started, these addresses (this is all in memory) are overwritten by the direct addresses of the functions being imported. It is necessary to restore it because those cells in which the addresses of the names of functions used to obtain direct addresses of functions in any version of the operating system are already filled with the addresses of these functions in the system in which the program was dumped. In this case, the information on the addresses of the names of the functions can no longer be recovered and when starting such a program, the direct addresses of the functions already recorded will be used. And this leads to the inoperability of the program on other versions of the OS.

Finally, restore the OEP. This can be done using any PE editor headers.

That's the whole theory.



Practice


In this article we will look at two packers. This is UPX and ASPack. Unpacking other packers will not be much different from unpacking these two.



UPX


Download the latest version. We pack something. Run it under the debugger.

During the decryption of the packed code, the packer uses the stack to its fullest. Naturally, in order for the packed program to work correctly, the packer needs to save the initial value of the stack and then, after the decompression is completed, restore it. In almost all packers, when they restore the stack before switching to OEP, the value in the stack is read to the address esp-4.

Thus, in Olly we set the breakpoint with the hr esp-4 command. Then we run the program and see that the breakpoint worked here:



00472176 . 8D4424 80 LEA EAX,DWORD PTR SS:[ESP-80] //

0047217A > 6A 00 PUSH 0 //

0047217C . 39C4 CMP ESP,EAX //

0047217E .^75 FA JNZ SHORT 111.0047217A //.

00472180 . 83EC 80 SUB ESP,-80

00472183 .^E9 386EFEFF JMP 111.00458FC0 // OEP




Next, we trace the program to OEP (roughly speaking, we become OEP). With the help of the Olly Dump plugin, dump the program.

Now it remains only to restore the import. We run our packed program and ImpREC. In the list of processes ImpREC'a find our program. In the RVA field, enter RVA OEP (described above as how to find it). Click AutoSearch. After the message that most likely something is found appears, click Get Imports and, if functions appear in the list, then click Fix Dump and select our dump. That's all the program unpacked.



Aspack


It's all the same, except for some point. After installing the breakpoint we get here:



0046F416 75 08 JNZ SHORT Test_Com.0046F420

0046F418 B8 01000000 MOV EAX,1

0046F41D C2 0C00 RETN 0C

0046F420 68 C08F4500 PUSH Test_Com.00458FC0 // OEP

0046F425 C3 RETN // OEP




The rest of the procedure is the same.



Kryakmis on unpacking


Here is this kryakmis.

Unpack as described above. Everything is perfectly unpacked. (For reference, OEP = 00401000). After this, save the breakpoints on the call to the GetDlgItemTextA function, run, enter the fake pass, press the button and get here:



00401206 . E8 1B060000 CALL <JMP.&user32.GetDlgItemTextA> //

0040120B . 8B35 00604000 MOV ESI,DWORD PTR DS:[406000]

00401211 . 81C6 7F010300 ADD ESI,3017F

00401217 . 81EE 66060000 SUB ESI,666

0040121D . 81F6 ADDE0000 XOR ESI,0DEAD

00401223 . BB 33604000 MOV EBX,dddddddd.00406033

00401228 . C0E0 03 SHL AL,3 // !

0040122B . 83F8 78 CMP EAX,78 // !

0040122E . 0F85 9A050000 JNZ dddddddd.004017CE // ""




At EAX, we have the length of the password entered. With the SHL al, 3 command, we perform a logical shift of the AL value to the left by 3 and ideally we should get 78. We carry out the inverse shl procedure. This shr 78.3 = 0F = 15 is the length of a valid password. Then I traced for a very long time up to a certain point and on the way I came across several anti-debugging tricks:



004012B0 0F31 RDTSC //

004012B2 8BC8 MOV ECX,EAX

004012B4 0F31 RDTSC //

004012B6 2BC8 SUB ECX,EAX

004012B8 F7D1 NOT ECX

004012BA 81F9 00500000 CMP ECX,5000

004012C0 -7F FE JG SHORT crackme2.004012C0 //




The RDTSC instruction returns to EAX the number of clock cycles since the last processor reset. In the code above, we see two calls to this instruction and then a comparison of the difference between their conclusions and a certain reference value. The fact is that when a program is executed without a debugger, the time difference will be small, and when it is under the debugger, the difference will be large. You will meet a lot of similar moments, just patch them or change flags. When you prior to trace to the next point:



0040126A 0F31 RDTSC

0040126C 8BC8 MOV ECX,EAX

0040126E 0F31 RDTSC

00401270 2BC8 SUB ECX,EAX

00401272 F7D1 NOT ECX

00401274 81F9 00500000 CMP ECX,5000

0040127A 7C 05 JL SHORT crackme2.00401281

0040127C -E9 139C04EC JMP EC44AE94

00401281 EB 0D JMP SHORT crackme2.00401290




Pay attention to 0040127C. There is a jump to a non-existent address, so feel free to patch the transition to 00401281. There will be several such moments. Trace to this code:



004014F1 0FB613 MOVZX EDX,BYTE PTR DS:[EBX] ; EBX, EDX

004014F4 B9 08000000 MOV ECX,8

004014F9 AC LODS BYTE PTR DS:[ESI] ; EAX -

004014FA 24 01 AND AL,1 ; and 1

004014FC 74 04 JE SHORT crackme2.00401502

004014FE D0E2 SHL DL,1

00401500 72 08 JB SHORT crackme2.0040150A

00401502 D0E2 SHL DL,1

00401504 0F82 BF020000 JB crackme2.004017C9 ;

0040150A ^E2 ED LOOPD SHORT crackme2.004014F9

0040150C 43 INC EBX

0040150D 58 POP EAX

0040150E 48 DEC EAX

0040150F 0F84 9A020000 JE crackme2.004017AF

00401515 50 PUSH EAX

00401516 ^EB D9 JMP SHORT crackme2.004014F1




Over this moment I thought for a very long time. It turned out that this is a password generation procedure. That is, the password is stored in the program is not in the clear. AL throughout the generation takes the values ​​1 or 0. So, having traced the whole procedure of generating a password and writing out all the values, I got a huge string of binary values ​​(for convenience, I transferred it to the decimal system):



119 101 108 108 100 111 110 101 85 102 105 110 100 109 101



Considering that each eight characters (in a double string, and I translated into decimal, so here is every character framed by a space), generated after we put a certain character of our entered password in EDX, we can say that the string is higher and there is valid password. Having recoded it it turned out "welldoneUfindme".



2. Some anti-debug methods



There are a lot of anti-debugging methods, starting with this article we will analyze them in turn, from simple to complex.

So, on crackmes.de there is just a special quackery called AntiOlly . Download it, launch it and see the following window:



image



Here we are told that nothing was found and we managed. Now we have an idea of ​​what a “good” message looks like. Load the mall into Olly and see the following:



image



The error is caused by the fact that Olly, having analyzed the title of our mall, found errors in it (the header). But it is fixable. We load our cracks in PE edior and go to the Optional header tab. My attention was attracted by the “too large” value of the parameters NumberOfRVAandSize, Base of Code and Base of Data. Usually NumberOfRVAandSize = 0x00000010, Base of Code = 00001000, Base of Data = 00002000. We change these parameters to “normal” and run the cracks under the debugger. Now the abusive message is not visible (that is, it remains, but does not affect the analysis) and we can safely analyze the mall. So this was the first anti-debugging trick.

Having started kryakmis under debugging, we see a “bad” message:



image



After analyzing the program, we find the anti-debug code section:



00401010 |. FFD7 CALL EDI // GetTickCount

00401012 |. 6A 00 PUSH 0

00401014 |. 68 34214000 PUSH AntiOlly.00402134

00401019 |. 8BF0 MOV ESI,EAX

0040101B |. FF15 DC204000 CALL DWORD PTR DS:[4020DC] //FindWindowA

00401021 |. 85C0 TEST EAX,EAX

00401023 |. 75 04 JNZ SHORT AntiOlly.00401029

00401025 |. 884424 0F MOV BYTE PTR SS:[ESP+F],AL

00401029 |> FF15 04204000 CALL DWORD PTR DS:[402004] //IsDebuggerPresent




So, the first is the GetTickCount function. This function returns the time that has passed since the start of the system in milliseconds. The fact is that there is another call to this function. Further, in the following code, the difference between the values ​​obtained as a result of these functions is measured. This was the second anti-debugging trick.

This is followed by a call to FindWindowA, which searches for a window with an OllyDbg header. Finally, the call IsDebuggerPresent, which simply checks whether the program is being debugged or not. If so, then in Eax 1, if not, then 0.

Here are the checks that conducts mall:



0040102F |. 85C0 TEST EAX,EAX // IsDebuggerPresent

00401031 |. 75 02 JNZ SHORT AntiOlly.00401035 // 00401035

00401033 |. 32DB XOR BL,BL // BL

00401035 |> FFD7 CALL EDI // GetTickCount

00401037 |. 2BF0 SUB ESI,EAX

00401039 |. 83FE 64 CMP ESI,64 //

0040103C |. 76 0D JBE SHORT AntiOlly.0040104B // ok 0040104B

0040103E |. A1 44204000 MOV EAX,DWORD PTR DS:[402044]

00401043 |. 50 PUSH EAX

00401044 |. 68 3C214000 PUSH AntiOlly.0040213C

00401049 |. EB 3F JMP SHORT AntiOlly.0040108A // ok

0040104B |> 84DB TEST BL,BL

0040104D |. 74 14 JE SHORT AntiOlly.00401063 // IsDebuggerPresent

0040104F |. 8B15 44204000 MOV EDX,DWORD PTR DS:[402044]

00401055 |. A1 60204000 MOV EAX,DWORD PTR DS:[402060]

0040105A |. 52 PUSH EDX

0040105B |. 68 3C214000 PUSH AntiOlly.0040213C

00401060 |. 50 PUSH EAX

00401061 |. EB 2E JMP SHORT AntiOlly.00401091

00401063 |> 807C24 0F 00 CMP BYTE PTR SS:[ESP+F],0 // FindWindow

00401068 |. 74 15 JE SHORT AntiOlly.0040107F




I think now it is clear where to patch or edit registers.

Thank you very much for your attention.

Source: https://habr.com/ru/post/102126/



All Articles