IT shnik often invent exercises for the mind, an inquiring mind constantly needs to warm up. I want to talk about one of the toughest and controversial ways - hacking specially protected puzzle programs (Often called crackme).
One of the places where such puzzles are assembled is
crackmes.de .
There are many interesting programs on which you can try your hand at hacking. No crime - programs are specially written for this purpose (the so-called crackme and reverseme);
')
Often like to say "All protection can be hacked." By digging out some of the crackme you may change your mind.
So let's start:
The general scheme of many crackme's work - 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 a variant, we use SEH (Structured Exception Handling - a mechanism for handling hardware and software exceptions), in which we put a message about a bad password, before transferring control to our “decoded” code, and if the password is correct, then the decrypted code will “Correct” opcodes of commands, and if not, the processor will generate an exception about an invalid opcode and “kosherly” involve SEH, in which we have an error notification. It has to be said that in general this option is not “holy” because after decryption, “semi-valid” opcodes are possible — for example, a jmp-command outside of our procedure.
But at first this is enough.
So we take the cracks 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 into the debugger - I used Olja (OllyDBG) - the program is very small, the code segment is only 0x2B6 = 694 bytes, it is very easy to find 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, on the basis of this, we see that the password length should be in the range of 8-32 characters, and if this is the case, then jump to the function of converting the initial 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 lives the decrypter of the code:
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 is clear: but how to find the correct hash code?
Brute force
This is very long: you must first decipher, then calculate the “entropy” of the received code and perhaps the code with a minimum of “entropy” is working, but for this you need to write or use already written disassembler engines;
or 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 the program execution.
But I noticed that although the program was written in pure assembler, the author still used a lot of “open” stack in his code, so let's think that the first correct 4 bytes in our “encrypted” code:
PUSH EBP MOV EBP,ESP
Value: 0x55, 0x8V, 0xEC
And now (before going through the encryption procedure) the values ​​are: 0x66, 0x71, 0x77, but we remember (we can see) the “Xor” properties of the function and see that then the final hash becomes known:
Do not forget about the "little-endian" agreement:
77 71 66 ^ ^ ^ ********** 8 55
So, having considered this case in the “Calculator” we see that CC = 0x33; BB = 0xC9; AA = 0x61
The resulting sequence: XX61C93.
The last byte, unfortunately, had to be searched through the use of “self-keygening” a (binary patch), and at the end we get: E961C933.
And the decrypted procedure actually contains a lot of garbage (“obfuscation”)
But the “opening of 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 original password - Well, here - brute force to help you))))
The conversion procedure can be “ripp” at our program and arrange this matter 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 initial password is 005sj [Vg
Only if brute force writes under some kind of exotic type of cuda or under shaders, remember there are no instructions for cyclic shift — rol eax, 5 and it should be replaced with two simple shifts (one to the left and one to the right) and then “or” over the received ; implementation in the form: #define ROT (n, m) (((n) << (m)) | ((n) >> (32- (m))))
In the following posts you will learn about other interesting ways to break unbreakable defenses.
Ps. The author of the article (ash - it is not available yet) asked me to publish this article.