Number of crackmes:
Number of writeups:
Comments:
Name | Author | Language | Arch | Difficulty | Quality | Platform | Date | Writeups | Comments |
---|
Crackme | Infos |
---|---|
KeygenMe again! | This KeygenMe challenge was a great experience for me. Thank you. |
Very easy disassembly execise | Static analysis of the main function shows a simple hardcoded comparison with value 124816. Password = 124816. |
emulation loader | # CrackM Writeup - karabatik ## Overview This writeup details the solution for the `crackmemain.exe` CrackMe challenge. The binary implements function pointer obfuscation and anti-debugging techniques, making it an interesting reverse engineering exercise. ## Initial Analysis ### File Information - **Filename**: crackmemain.exe - **Size**: 0xCE00 (52,736 bytes) - **MD5**: 592f7a190c872f6091dfad0e4fb9ee61 - **Architecture**: x64 Windows PE - **Base Address**: 0x140000000 - **Entry Point**: 0x14000a280 ### Runtime Behavior Running the executable shows a simple login prompt: ``` **********Welcome to CrackMe Loader..... Enter Login: test Enter Password: password Fail! Total pause: 0ms Press Enter to exit... ``` ## Static Analysis with IDA Pro ### Main Function Analysis The main function is located at `0x140001ab0` and contains several interesting features: 1. **Function Pointer Obfuscation**: All function calls are obfuscated using a magic number `0x2CC634AC8CA6AD95` 2. **Anti-Debug Protection**: Multiple conditional checks against global variables 3. **String Encryption**: All strings are encrypted and decrypted at runtime ### Key Functions Identified - `0x140001ab0` - Main function (1466 bytes) - `0x140002070` - Dynamic API loading - `0x140002600` - Anti-debug mechanism with 5x500ms delays - `0x140002d30` - **Validation function (TARGET)** - `0x140001230` - String processing utilities ### Deobfuscating Function Pointers The obfuscated function pointers follow this pattern: ```c ((void (*)(void))((char *)off_14000D150 - 0x2CC634AC8CA6AD95LL))() ``` To deobfuscate: `real_address = obfuscated_pointer - 0x2CC634AC8CA6AD95` ## Deep Dive: Validation Function ### Function Analysis The critical validation logic resides in `sub_140002D30`. After decompiling, the function reveals: ```c strcpy(v43, "panhauzer"); // Expected login strcpy(v42, "2digboob"); // Expected password ``` The function performs complex string comparison using regex-like pattern matching, but ultimately returns: - `1` for successful authentication - `0` for failed authentication ### Hardcoded Credentials Through static analysis, I discovered the valid credentials: - **Login**: `panhauzer` - **Password**: `2digboob` However, testing these credentials would be the "intended" solution. Instead, I chose a more elegant approach. ## Solution: Binary Patching ### Approach Rather than finding the exact password, I decided to patch the validation function to always return success. This demonstrates a common real-world attack vector. ### Patch Implementation **Target Address**: `0x140002d30` (start of validation function) **Original bytes**: ``` 41 57 41 56 56 57 ... (complex validation logic) ``` **Patched bytes**: ``` B8 01 00 00 00 C3 ``` **Assembly equivalent**: ```assembly mov eax, 1 ; Return success (1) ret ; Exit function immediately ``` ### Patching Process 1. Navigate to address `0x140002d30` in IDA Pro 2. Switch to Hex View 3. Press `F2` to enter edit mode 4. Replace the first 6 bytes: `41 57 41 56 56 57` → `B8 01 00 00 00 C3` 5. Press `F2` to exit edit mode 6. Save the patched binary ## Results After applying the patch, any login/password combination is accepted: ``` **********Welcome to CrackMe Loader..... Enter Login: anything Enter Password: whatever DLL Inject Success! [EMU] Total pause: 0ms Press Enter to exit... ``` The success message "DLL Inject Success! [EMU]" confirms the bypass worked perfectly. ## Alternative Solutions ### Method 1: Using Hardcoded Credentials The "intended" solution would be using the discovered credentials: - Login: `panhauzer` - Password: `2digboob` ### Method 2: Runtime Patching Instead of modifying the binary, one could: - Use a debugger to set breakpoint at `0x140002d30` - Modify the EAX register to return 1 - Continue execution ### Method 3: DLL Injection Advanced users could inject a DLL to hook the validation function and force it to return success. ## Technical Insights ### Anti-Analysis Techniques Observed 1. **Function Pointer Obfuscation**: Makes static analysis more difficult 2. **String Encryption**: Hides meaningful strings from basic analysis 3. **Anti-Debug Delays**: 5x500ms delays slow down dynamic analysis 4. **Complex Control Flow**: Multiple nested loops and conditions ## Tools Used - **IDA Pro 9.0**: Static analysis and disassembly - **Hex Editor**: Binary modification --- **Author**: karabatik **Date**: 08/18/2025 **Difficulty**: 4/10 **Primary Technique**: Binary Patching |
Comment | Link |
---|---|
Hey buddy, can you contact me on Discord? dc: karabatik | ==> |
I confirmed that the first stage is a Rust-based dropper that generates and executes a payload at %USERPROFILE%\AppData\Local\cache_*.bin. After capturing the payload, I examined it and found ReadConsoleW/WriteConsoleW wrappers, prompt strings, and a straightforward memcmp validation routine. The expected password is hardcoded as "SecretPass123" (clearly visible through memcmp xrefs in the IAT). When entered correctly, the program outputs "Correct password! Here is your flag: " followed by the first 15 bytes of a global data header XORed with 0x2A, which is why you see symbols instead of human-readable text on the console—it's an intentional decoy design. I verified this live by running the payload directly from CMD/PowerShell and entering the password; failed attempts yield "Wrong password! Try harder." while successful ones produce the prefix plus the XORed 15-byte output mentioned above. Aside from TLS callbacks and Rust runtime artifacts, there's no meaningful anti-debug or cryptographic complexity involved. The solution boils down to identifying the payload's hardcoded password and understanding that the success path outputs an XORed header rather than plaintext. | ==> |
Nice challenge, man. Here's the breakdown: It's a standard 64-bit console app compiled with MSVC. The first thing that caught my eye was that it dynamically resolves MessageBoxA using LoadLibraryA/GetProcAddress instead of static linking. The strings inside were obfuscated with a simple XOR (0x27); breaking that revealed the usual messages like 'Great job' and 'Try better'. It also had a neat little anti-patching mechanism: a separate thread constantly checks the CRC32 of the code section, and if it changes, the program kills itself with the exit code 0xDEADC0DE. The real logic starts in main. After getting user input, it uses cpuid to read the processor's family/model info and generates a constant C from it. It then calculates a checksum using each character of the input key, its index, and this constant C. The condition for a valid key is that the final checksum must equal C + 1505, so the key is completely CPU-specific. I just reversed the equation for my machine. C came out to be 164, making the target checksum 1669. I set the key length to 7, calculated the contribution of the trailing newline character, and used 'A' for the first six characters. From there, I just solved for the last character to satisfy the equation, which gave me the key 'AAAAAA''. Hitting Enter displayed the 'Great job' message, and the program exited successfully. | ==> |
In Binary Ninja, user input in main is compared with the target string generated by sub_140002f50 from XOR-ed 256-bit constants using memcmp. After decrypting and XOR-ing the constants, the license key was revealed: 123_isthekey-crkm. | ==> |
I stopped the binary right after its decode stub by planting an INT3 at RVA 0x92F00, dumped the entire in-memory image, opened the dump in Binary Ninja, and followed the actual control flow start → sub_7ff63656F40B → sub_7ff636592F00; Under a flag-driven, pushf/popf-based control flow flattening, I identified the actual password check in sub_7ff6365542a1, which reads certain indices from the input buffer via sub_7ff6365778c4(&buf, idx) and compares them to movsx/cmp imm8 literals in the order '2', '3', 's', '2', 'N', 'o', 'R', 't', 'e'; using x64dbg with ReadFile/WriteFile breakpoints, I traced the input and branch results to confirm the success path matches these comparisons; resulting password: 23s2NoRte. | ==> |
Opened the binary in IDA Pro. Found the username and key validation checks. Patched two jump instructions, flipped the conditions from jne to je. Now it accepts any password. Done. | ==> |
ret = (ret | ==> |
This was a nice little challenge. I analyzed the binary with Ghidra and found that the auth() function compares the hash result with 0x4262d2e9. Instead of trying to reverse the hash algorithm (which would be quite tedious with the modified djb2), I decided to patch the comparison directly. I located the CMP EAX,0x4262d2e9 instruction at the binary level and replaced it with CMP EAX,EAX followed by NOPs. This way, the comparison always results in true, and the program grants access regardless of the input. The hash function itself was interesting - starting with 0xA5A5A5A5 and using ret = (ret | ==> |
I opened it with Ghidra, and in the main function, there's an XOR operation. After decrypting with the key 0x2a, it revealed "easi123". | ==> |