Crackme - Krypto's Simple login crackme
Challenge Info
| Field | Details |
|---|---|
| Platform | crackmes.one |
| Author | Kryptos |
| Language | C/C++ |
| Architecture | x86-64 |
| Difficulty | 2.0 |
| Date | 2026-04-16 |
Tools Used
| Tool | Purpose |
|---|---|
| Detect-It-Easy | Binary identification |
| PEStudio | More Binary identification |
| PowerShell | Fingerprinting |
| floss | extract strings |
| IDA Free | Debugging and analysis |
Static Analysis
Binary Identification
| Key | Value | Tool used |
| SHA1 | 9B3253DDEE0908C0AA69B0FFE99C4AA79423FEC3 | PowerShell (Get-FileHash) |
| SHA256 | F5E0D7EEF306E78B0C937D735931D73F542470449B94BE15597E437E1E27A1AA | PowerShell (Get-FileHash) |
| name | crackmepls.exe |
explorer.exe |
| language | C/C++ |
Detect It Easy |
| compiler | MSVC | Detect It Easy |
| type | Windows PE, 64-bit, console | Detect It Easy / PEStudio |
| pdb path | C:\Users\Kryptos\source\repos\crackme-lvl1\x64\Release\crackme-lvl1.pdb |
PEStudio |
This binary is a Windows PE 64-bit executable compiled using MSVC. The binary requests a username and a password, after which it performs a hashing function based on the username to generate an expected password. The binary then compares the provided password with the hash generated password. If they match, the binary prints “Access granted”, else it prints “Access denied”.
Prerequisites for success
The crackme has been successfully cracked, if the hashing algorithm is being well understood and if a script can be written that calculates the password based on the username to consistently “guess” the correct password for each corresponding user.
Reconnaissance
The first stage usually involves finding out what kind of metadata / strings the file contains.
Strings
floss.exe crackmepls.exe
From this command (which extracts strings from a binary), the following can be deducted:
- WinAPI Functions:
QueryPerformanceCounter,GetCurrentThreadId,GetSystemTimeAsFileTime,InitializeSListHead,SetUnhandledExceptionFilter,GetModuleHandleW; - Imported DLLs:
MSVCP140.dll,VCRUNTIME140.dll; - Miscellaneous:
User:,Pass:,Access granted,Access denied.
If this would’ve been malware, a nice resource to cross reference WinAPI calls against would be MalAPI.io.
The miscellaneous strings already give a clear picture of where this CrackMe is heading:
...
User:
Pass:
Access granted
Access denied
...
memcmp
memcpy
strlen
...
Decompilation
Loading the binary into IDA and running the autoanalysis, IDA provides a graph view of the binary.
F5 opens the Pseudocode Window that reveals a disassembled version of the code.
Note: Personally, I find the pseudocode window useful to get a general idea of how a function works. But when debugging the application, I tend to prefer looking at the assembly
Identifying the main function
Going back to the graph view and pressing space to go to the linear view. Here the main function can be found at address 0x140001290. If the main address is different, That’s likely a byproduct with the Address Space Layout Randomization (ASLR).
Note: Default 64-bit PE executables their base address tends to be
0x140000000. At runtime, ASLR slides the binary to a random base. To configure this in IDA, go to:edit -> Segments -> Rebase program -> Set value to 0x140000000
Flow of the binary
Following the main function, the C++ functions are being called:
- std::cout: To print “User: “ (0x1400012EC);
- std::cin: To save the string of the user (0x1400012FD);
- std::cout: To print “Pass: “ (0x140001310);
- std::cin: To save the string of the password (0x140001321);
Hashing Algorithm
The actual hashing functions starts at 0x140001350. Certain operations are performed on the username to calculate what the password would be.
hashing_function:
lea rax, [rsp+0xC8+username_input] ; rax = stack buffer (SSO)
cmp rbp, 0xFh ; is string len <= 15?
cmova rax, rbx ; if len > 15, use heap ptr (rbx) instead
movsx ecx, byte ptr [rax+rdx] ; ecx = (signed) char[i]
lea eax, [rdx+1] ; eax = i + 1
imul eax, ecx ; eax = (i + 1) * char[i]
add eax, r8d ; eax = hash + (i+1) * char[i]
lea r8d, ds:0[rax*8] ; r8d = eax * 8
xor r8d, eax ; r8d = (eax * 8) ^ eax
inc rdx ; i++
cmp rdx, r9 ; i < len?
jb short hasing_function ; go to hashing_function
Once the hashing algorithm is completed, the calculated value (hash) is stored in r8d:
imul edx, r8d, 0x539 ; edx = r8d * 1337
xor edx, 0x5A5A ; edx ^= 23130
The final hash is stored in edx.
hash_to_string
Once the hash is calculated, it’s only a decimal value. To compare it against the “Pass” provided by the user, the decimal value must be converted to a string. That happens within this function called at 0x14000138F.
Verify Password
After the “hashed” password, has been converted to a string, will the comparison start.
This starts directly after the has_function beind called at 0x140001394:
mov r13, [rsp+0xC8+var_48] ; load capacity of the user-provided password (likely 0xF)
lea rdx, [rsp+0xC8+password_input] ; rdx will hold the address of the string buffer (on the stack)
mov rdi, [rsp+0xC8+password_input] ; moves the existing heap pointer into rdi (in case it is needed in cmova)
cmp r13, 0xF
cmova rdx, rdi ; if the previous comparison was > 15 (0xF), move the value of rdi into rdx
mov r8, [rsp+0xC8+Size] ; Moves size of provided password into r8
This snippet explains a check to see if the characters provided as the password exceed the length of 15 (0xF), if this is the case, rdx will point to heap memory where the password lives and use that instead of the stack buffer. This pattern will be once more repeated for the hash calculated based on the username that was converted into a string.
mov r15, [rsp+0xC8+var_88] ; loading capacity of actual password into r15
lea rdx, [rsp+0xC8+Buf1] ; rdx will hold the address of the string buffer
mov rsi, [rsp+0xC8+Buf1] ; moves the existing heap pointer into rdi
cmp r15, 0xF
cmova rcx, rsi ; if > 15 (0xF), move rsi into rcx
After the length of both passwords have been identified, they will be compared. If the lengths of the passwords differ, it’s not worth it comparing them to each other anyway:
cmp r8, [rsp+0xC8+var_50] ; Verify lengths of passwords are equal
jz short passwords_are_same_length ; If previous comparison == true, go to this function
xor r14b, r14b ; if not true, xor r14b with itself (resulting in 0)
jmp short after_memcmp ; jump to code executed after the memcmp (skips memcmp call)
If the passwords were the same length, a next verification will be done to verify they weren’t a length of 0
passwords_are_same_length:
test r8, r8 ; is r8 == 0?
jnz short compare_passwords ; if not, go here
mov r14b, 1 ; if yes, set r14b to 1
jmp short after_memcmp ; skip memcmp and go to this function
If the passwords weren’t a length of 0, they will be compared with memcmp:
compare_passwords:
call memcmp ; Obvious what happens here
test eax, eax ; check if eax is 0? (memcmp returns the result into eax, if password1 == password2? eax = 0 else eax != 0)
setz r14b ; if eax is zero, comparison was successful and r14b will hold the value 1
This concludes how the password is verified, later in the code it’ll show:
test r14b, r14b ; is r14b 0?
lea rdx, aAccessGranted ; move into rdx a pointer to this string
jnz short print_result ; go to the function that prints the string in rdx
lea rdx, aAccessDenied ; if r14b is 0, load this instead.
print_result:
Which is responsible for printing the “Access granted” string, or if the password was incorrect, it will print “Access denied”.
Password Generator
Knowing how this hash function operates, it is now possible to write a password generator to always create the right password for each username.
I decided to write the generator in Rust, because I already had my IDE for Rust open anyway.
fn main() {
let text = "alcidius";
let mut r8 = 0;
for i in 0..text.len() {
let cx = text.as_bytes()[i];
let mut ax = i + 1;
ax *= (cx as usize);
ax += r8;
r8 = ax * 8;
r8 = r8 ^ ax;
}
let dx: i32 = (((r8 * 1337) ^ 23130) & 0xFFFFFFFF) as i32;
println!("dx: {}", dx);
}
Let’s go over it line by line:
- Settings the username (
text) as “alcidius”, as well as settingr8to0; - Introduce a
forloop goes from0until the length of thetext, in this case7; - Introduce a constant variable
cx, this variable represents the current character oftext[i]; - Introduce a mutable variable
axand setting it to the iterator (i) +1; - Multiplying
axby itself and the ascii value the character variablecxholds (e.g.,a=0x61,l=0x6C, etc.); - Adding the value of
r8toax, which in the first iteration will be0; - Setting the value of
r8equal to the current value ofaxmultiplied by8; - Setting the value of
r8equal tor8xor’d byax; - Going back to the beginning. Every iteration,
r8will become bigger; - After the
forloop, the variabledxis introduced that first performs a multiplication onr8by1337or as noted in the assembly code:(0x)539h. After the multiplication, the value will be once more xor’d by23130or as noted in the assembly code:(0x)5A5A, followed by a bitwiseANDoperation. - That’s all there is to the calculation of the hash. It will be printed to the screen at that’ll be the password for the username.
Example
Let’s go over the password generator manually
| Iteration | cx | ax+1 | ax*=cx | ax+=r8 | r8=ax*8 | r8^=ax |
| 0 | 97 | 1 | 97 | 97 | 776 | 873 |
| 1 | 108 | 2 | 216 | 1089 | 8712 | 9801 |
| 2 | 99 | 3 | 297 | 10098 | 80784 | 72930 |
| 3 | 105 | 4 | 420 | 73350 | 586800 | 649910 |
| 4 | 100 | 5 | 500 | 650410 | 5203280 | 4622842 |
| 5 | 105 | 6 | 630 | 4623472 | 36987776 | 41086960 |
| 6 | 117 | 7 | 819 | 41087779 | 328702232 | 300247611 |
| 7 | 115 | 8 | 920 | 300248531 | 2401988248 | 2664301387 |
r8 = 2664301387;
result = ((r8 * 1337) ^ 23130) & 0xFFFFFFFF;
result = (3562170954419 ^ 23130) & 0xFFFFFFFF;
result = 3562170968297 & 0xFFFFFFFF;
result = 1643079913;
Conclusion
Despite it being a beginner difficulty crackme, the binary’s hash algorithm was rather challenging at first to undertake. Only using dynamic debugging and writing along in my notebook was I able to fully understand the workings of the hashing algorithm. Once the algorithm was well understood, was writing the password generator rather straightforward. The hash is fully deterministic, meaning the correct password can always be computed given any username.
I hope this writeup was in any way informative. Feel free to reach out if anything is unclear or if you spot a mistake. Thanks for reading!