
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.
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:
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:
And here the code decoder lives:
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:
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:
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):
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.
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.
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.