Legal hacking as a workout for the mind

    IT employees often come up with exercises for their minds; an inquiring mind constantly requires warm-ups. I want to talk about one of the toughest and most controversial methods - hacking specially protected puzzle programs (Often they are called crackme).

    One of the places where such puzzles are collected is crackmes.de .

    There are many interesting programs where you can try your hand at hacking. No crime - programs are specially written for this purpose (the so-called crackme and reverseme);

    Often they like to say "All defenses can be hacked." By digging through some of the crackme you might change your mind.


    So let's get started:


    The general scheme of work of many crackme - and let's encrypt some procedure in the code, and the "correct-incorrect password" - depending on the hash made from the data decrypted with this password?

    Or, as an option, we hook SEH (Structured Exception Handling - a mechanism for processing hardware and software exceptions), into which we put a message box about a bad password, before that we transfer control to our “decrypted” code, and if the password is correct, then in the decrypted code “True” command opcodes, and if not, the processor will throw an exception about the wrong opcode and “kosher” will use SEH, in which we have an error notification. I must say that, in general, this option is not "holy" because after decryption, "semi-invalid" opcodes are also possible, for example, a jmp-command outside of our procedure.

    But at first this is enough.

    So we take the creams from here: crackmes.de/users/sharpe/unlockme_crackme_8_by_sharpe/download or from here:
    crackmes.de/users/sharpe/unlockme_crackme_7_by_sharpe/download (the seventh, by the way, is not hacked yet) and load it into the debugger () - the program is very small - the code segment is only 0x2B6 = 694 bytes, it is very easy to find the part of the code responsible for reading the password:

    0040107F  |. 3D F3030000    CMP EAX,3F3
    00401084  |. 75 4E          JNZ SHORT 004010D4                       
    00401086  |. 6A 21          PUSH 21                                 	 ; /Count = 21 (33.)
    00401088  |. 68 88314000    PUSH 403188                            	 ; |Buffer = eight.00403188
    0040108D  |. 68 F1030000    PUSH 3F1                                	 ; |ControlID = 3F1 (1009.)
    00401092  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]   ; |hWnd
    00401095  |. E8 04020000    CALL 0040129E                           ; \GetDlgItemTextA
    0040109A  |. 83F8 07        CMP EAX,7		здесь сравниваем длину пароля
    0040109D  |. 76 1F          JBE SHORT 004010BE                       переход на смерть
    0040109F  |. 83F8 20        CMP EAX,20		здесь тоже сравниваем длину пародя
    004010A2  |. 73 1A          JNB SHORT 004010BE                       
    004010A4  |. E8 D9000000    CALL 00401182                            преобразователь пароля
    004010A9  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                
    004010AC  |. E8 FD000000    CALL 004011AE                            расшифровщтк кода
    004010B1  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                
    004010B4  |. E8 61010000    CALL 0040121A                            не интересно
    004010B9  |. E9 85000000    JMP 00401143                             ;  eight.00401143
    004010BE  |> 6A 30          PUSH 30                                  а здесь прыгать не надо)))
    004010C0  |. 68 34314000    PUSH 403134                              ; |Title = "-=[ Unlock Code Error"
    004010C5  |. 68 4A314000    PUSH 40314A                              ; |Text = "The entered Unlock Code is invalid.
    004010CA  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hOwner
    004010CD  |. E8 D8010000    CALL 004012AA                            ; \MessageBoxA
    004010D2  |. EB 6F          JMP SHORT 00401143                       ;  eight.00401143


    So, based on this, we see that the password length should be in the range of 8-32 characters, and if so, we jump into the function conversions of the original password:

    00401182  /$ 57             PUSH EDI
    00401183  |. 33FF           XOR EDI,EDI
    00401185  |. BE 88314000    MOV ESI,403188                           ;  здесь в регистр пишем указатель на пароль 
    0040118A  |. B9 20000000    MOV ECX,20
    0040118F  |. C705 A8314000 >MOV DWORD PTR DS:[4031A8],0 ;здесь мы запишем преобразован «хеш»
    00401199  |> AC             /LODS BYTE PTR DS:[ESI]
    0040119A  |. 85C0           |TEST EAX,EAX
    0040119C  |. 74 08          |JE SHORT 004011A6                       ;  eight.004011A6
    0040119E  |. 8BC8           |MOV ECX,EAX	;здесь  мы сохраним «хеш» с пароля
    004011A0  |. 03F8           |ADD EDI,EAX	;процедура преобразования пароля
    004011A2  |. D3C7           |ROL EDI,CL		; процедура преобразования пароля
    004011A4  |.^EB F3          \JMP SHORT 00401199                      ;  eight.00401199
    004011A6  |> 893D A8314000  MOV DWORD PTR DS:[4031A8],EDI; сохраняем и выходим 
    004011AC  |. 5F             POP EDI
    004011AD  \. C3             RETN


    And here the code decoder lives:

    004011AE  /$ 55             PUSH EBP
    004011AF  |. 8BEC           MOV EBP,ESP
    004011B1  |. 83EC 04        SUB ESP,4
    004011B4  |. 68 88314000    PUSH 403188                              ; здесь вновь проверяем длину пароля 
    004011B9  |. E8 C2000000    CALL 00401280                            ; но это – не обязательно
    004011BE  |. 8945 FC        MOV DWORD PTR SS:[EBP-4],EAX
    004011C1  |. 837D FC 01     CMP DWORD PTR SS:[EBP-4],1
    004011C5  |. 77 16          JA SHORT 004011DD                        ;  eight.004011DD
    004011C7  |. 6A 30          PUSH 30                                  
    004011C9  |. 68 34314000    PUSH 403134                              ; |Title = "-=[ Unlock Code Error"
    004011CE  |. 68 4A314000    PUSH 40314A                              ; |Text = "The entered Unlock Code is invalid.
    004011D3  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hOwner
    004011D6  |. E8 CF000000    CALL 004012AA                            ; \MessageBoxA
    004011DB  |. EB 1E          JMP SHORT 004011FB                       ;  eight.004011FB
    004011DD  |> B8 49114000    MOV EAX,401149                ;  А вот здесь начинается расшифровка пароля
    004011E2  |. B9 7F114000    MOV ECX,40117F
    004011E7  |. 2BC8           SUB ECX,EAX				
    004011E9  |. 33DB           XOR EBX,EBX
    004011EB  |> 8B1418         /MOV EDX,DWORD PTR DS:[EAX+EBX]
    004011EE  |. 3315 A8314000  |XOR EDX,DWORD PTR DS:[4031A8]; как видем простая ксор-функция
    004011F4  |. 891418         |MOV DWORD PTR DS:[EAX+EBX],EDX
    004011F7  |. 43             |INC EBX
    004011F8  |. 49             |DEC ECX
    004011F9  |.^75 F0          \JNZ SHORT 004011EB                      ;  и выходим
    004011FB  |> C9             LEAVE
    004011FC  \. C2 0400        RETN 4


    So, now it’s clear: but how to find the correct hash od?
    Busting?

    It takes a very long time: you must first decrypt, then calculate the "entropy" of the resulting code and possibly a code with a minimum of "entropy" is working, but for this you need to write or use already written disassemble engines;
    and you can use SEH, in whose body you can write the bromforser code, but then even one “false-correct” instruction can completely change the correct course of program execution.
    But I noticed that even though the program is written in pure assembler, the author still used a lot of “opening” the stack in his code, so let's think that the first correct 4 bytes in our “encrypted” code are:

    PUSH EBP
    MOV EBP,ESP
    

    Value: 0x55, 0x8B, 0xES

    And now (before going through the encryption procedure) the values ​​are: 0x66, 0x71, 0x77, but we remember (we can see) the properties of the “XOR” function and see that then the final hash will be known:
    Do not forget about the “little-endian” agreement:

    77 71 66 ^
    АА ВВ СС ^
    ВВ СС    ^
    СС
    **********
    ЕС   8В   55


    So, counting this case in the "Calculator" we see that SS = 0x33; BB = 0xC9; AA = 0x61,
    The obtained sequence: XX61C93.

    Unfortunately, I had to search for the last byte using brute force using “self-keygening” a (binary patching), and in the end we get: E961C933.

    And the decrypted procedure actually contains a lot of garbage (“obfuscation”)
    But the “opening the stack” saved us, and the decrypted procedure (in the body):
    00401149   $ 55             PUSH EBP
    0040114A   . 8BEC           MOV EBP,ESP
    0040114C   . D3C8           ROR EAX,CL
    0040114E   . 58             POP EAX
    0040114F   . EB 04          JMP SHORT 00401155                       ;  eight.00401155
    00401151     D6             DB D6
    00401152     FE             DB FE
    00401153   . 32C9           XOR CL,CL
    00401155   > BE 9C314000    MOV ESI,40319C                           ;  ASCII "Secret: Marius!"
    0040115A   . C706 53656372  MOV DWORD PTR DS:[ESI],72636553
    00401160   . C746 04 65743A>MOV DWORD PTR DS:[ESI+4],203A7465
    00401167   . C746 08 4D6172>MOV DWORD PTR DS:[ESI+8],6972614D
    0040116E   . C746 0C 757321>MOV DWORD PTR DS:[ESI+C],217375
    00401175   . EB 00          JMP SHORT 00401177                       ;  eight.00401177
    00401177   > 58             POP EAX
    00401178   . FFE0           JMP EAX
    0040117A     F7             DB F7
    0040117B     ED             DB ED
    0040117C     12             DB 12
    0040117D     DA             DB DA
    0040117E     3F             DB 3F                                    ;  CHAR '?'
    0040117F     4E             DB 4E                                    ;  CHAR 'N'
    00401180     40             DB 40                                    ;  CHAR '@'
    00401181     C4             DB C4


    All that this procedure does is write the word: Secret: Marius! according to the corresponding index.
    Then it remains to find out the initial password - Well, here - brute force to help you))))

    The conversion procedure can be "ripp" at our program and arrange this case as an assembler insert.

    	mov  dword ptr [znach], 0	
    	xor  edi, edi
    	mov  esi, [str1]
        m:
    	lodsb
    	test al, al
    	jz   m1
    	mov  ecx, eax
    	add  edi, eax
    	rol  edi, cl
    	jmp  m


    Yes, the original password is 005sj [Vg

    Only if you write bruteforce for some kind of exotic like cuda or for shaders, remember there is no cyclic shift instruction there — rol eax, 5 and it must be replaced with two simple shifts (one left and one right ) and then “or” over the received one; implementation in the form: #define ROT (n, m) (((n) << (m)) | ((n) >> (32- (m))))
    In the following posts you will learn about other interesting methods of breaking unbreakable protections

    PS. The author of the article (ash - he is not yet on the hub) asked me to publish this article.

    Also popular now: