Exploring simple crackme's (part 3)

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


    1. Manual unpacking


    From the tools I needed:
    • 1. OllyDbg
    • 2. Olly Dump Plugin
    • 3. ImpREC
    • 4. PE tools
    • 5. PEid

    Theory

    The packed program works as follows:
    First, the unpacker code is launched, which begins to decrypt the packed program code. After decryption is completed, a jump is made to the OEP program and then the already unpacked program code starts to run.

    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 - ImageBasewhere:
    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) - the virtual address of the item in memory
    Relative Virtual Adress (RVA) - the relative virtual address. The address is relative to ImageBase.
    Well, for example, we found OEP equal to 00301000, and ImageBase equal to 00300000, then RVA OEP will be equal to 1000. The value of ImageBase can be found by looking in any editor of PE headers.
    After we find the RVA OEP we need to remove the dump program. Dump means - the area (part) of memory or a file saved to disk from memory. To remove a dump means to save the desired memory area (usually occupied by the program) to the hard drive. 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 names of the imported functions are located in the file, i.e. functions used during program operation. When the program starts, these addresses (this is all in the memory) are overwritten by the direct addresses of the imported functions. It is necessary to restore it because those cells that originally contain the addresses of the names of functions used to get 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 about the addresses of the names of the functions can no longer be restored, and when starting such a program, the direct addresses of the functions already recorded will be used.
    And finally, restore OEP. This can be done using any PE header editor.
    That’s the whole theory.

    Practice

    In this article we will consider two packers. These are UPX and ASPack. Unpacking other packers will not differ much from unpacking these two.

    UPX

    Download the latest version. We pack something. Run this under the debugger.
    During the decryption of the packed code, the packer makes full use of the stack. Naturally, for the packed program to work correctly, the packer needs to save the initial value of the stack and then, after unpacking, restore it. In almost all packers, when they restore the stack before switching to OEP, the value in the stack is read at esp-4.
    Thus, in Olly, we put the breakdown with the command “hr esp-4”. Then we run the program and see that the break worked here: Next, trace the program to OEP (roughly speaking, we get to OEP). Using the Olly Dump plugin, we dump the program.

    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



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

    Aspack

    Everything is similar here, with the exception of some point. After installing the crack, we get here: The rest of the procedure is the same.

    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




    Kryakmis on the subject of unpacking

    Here is this crack.
    Unpack as described above. Everything is perfectly unpacked. (For reference, OEP = 00401000). After that, we squeeze the breaks onto the call to the GetDlgItemTextA function, start it, enter the fake pass, click on the button and get here: In 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 will perform the reverse shl procedure. This shr 78.3 = 0F = 15 valid password length. Then I traced for a very long time to some point and on the way I came across several anti-debugging tricks:

    00401206 . E8 1B060000 CALL // Мы здесь
    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 // Прыжок на "плохую" ветку




    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 the EAX register 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 in their outputs with a certain reference value. The fact is that when the program is executed without a debugger, the clock difference will be small, and when it is under the debugger the difference will be large. You will find many such moments, just patch them or change flags. When you trace to the next moment: Pay attention to 0040127C. Here a jump is made to a nonexistent address, so boldly patch the transition to 00401281. There will be several such moments. Trace to the following code:

    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




    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


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

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

    given that every eight characters (in double line, and I converted it to decimal, so here it is each character framed by a space), was generated after we put a certain character of our entered password in EDX, we can say that the line above is a valid password. Recoded it turned out "welldoneUfindme".

    2. Some anti-debugging methods


    There are a lot of anti-debugging methods, starting from this article we will analyze them in turn, from simple to complex.
    So, on crackmes.de there is just a special crack that is called AntiOlly . We download it, run it and see the following window:

    image

    Here they tell us that nothing was found and we managed. Now we have an idea of ​​what a “good” message looks like. We load kryakmis in Olly and we see the following:

    image

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

    image

    After analyzing the program, we find a piece of anti-debugging code:

    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 performing these functions is measured. This was the second anti-debugging trick.
    Next comes a call to FindWindowA, which searches for a window with the title OllyDbg. And finally, the IsDebuggerPresent call, which simply checks whether the program is being debugged or not. If yes, then in Eax 1, if not then 0.
    here are the checks that the krykmis conducts: I think now it’s clear where to patch or edit the registers. Thank you so much for your attention.

    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




    Also popular now: