Number of crackmes:
Number of writeups:
Comments:
| Name | Author | Language | Arch | Difficulty | Quality | Platform | Date | Downloads | Writeups | Comments |
|---|
| Crackme | Date | Infos | Actions |
|---|---|---|---|
| Product Activation | 2026-09-18 00:21 | activate interprets libcerberus.so as bytecode for a custom vm with an mmu. traced it via ptrace, reversed the opcode set and the seeded rolling hash check, key fell out of the recurrence | View |
| sygil (gui version for linux) | 2026-09-17 23:19 | Keygen for any alias: FNV-1a-32 hash, fold (h>>16)^(h&0xFFFF), format syg-%08x-%04x-%08x. TracerPid anti-debug XORs 0xDEADBEEF when traced. Verified in GUI. | View |
| Crackme | Comment | Date |
|---|---|---|
| Obfuscated Crackme 2 | session update, and this one finally cracked the check wide open structurally. the emulator was hiding a villain in plain sight: I was serving host-time rdtsc values, and since the anti-aliasing state is seeded from rdtsc, every differential comparison I did was poisoned by timing noise. once I pinned the TSC to a deterministic counter (and seeded getrandom too), the emulator became a byte-identical oracle for the whole 24.7M instruction run, and clean A/B diffs finally became possible. with that, the pipeline fell apart completely: the collected string gets split into two 16-char halves and each half goes through strtol with base 16. the password is literally two u64s. then a struct goes on the stack with the username, both parsed integers, some salt constants, and three 16-byte values that mix username and password. then four threads spawn, one per username character (I watched the SSO buffer get read one char per thread), each running a memory-hard KDF over a 128MB work buffer, and the compare happens inside the threads. the threads are mean: process set non-dumpable, so not even the process's own ancestors can attach, and the relay only ever traced the main thread. I got their stacks at join time through the child-side file channel but the final expected pair only ever lives in thread registers, and they exit before I can anchor on them. also fully broke the boring-string crypto: the rodata table stores interleaved [cipher, key] byte pairs and the decryptor is a period-2 XOR against a 4-byte seed with a redundant dual path that computes the same plaintext twice. verified on every string that decrypts in a run. the flag message path is a family of 14 weave blocks referencing a 33-byte blob that never execute on the fail path, each decrypting a small chunk with a state-derived seed byte. nice touch, that one. so the keygen equation is now fully stated: expected 128 bits = KDF(username), password = those bits as 32 hex chars. everything around it is mapped, the KDF core itself is the last wall. the 128MB working set makes my unicorn emulation take hours per run, so that's where the fight continues. @Zydak the layered design here is genuinely vicious, every time I clear one wall there's a better one behind it. respect. | 2026-09-20 05:45 |
| Obfuscated Crackme 2 | update from the trenches: I promised I was eating the emulator one bite at a time, and this week I finally swallowed the whole thing. I gave up on fighting the process live and built a full unicorn emulation of the check instead. snapshot the child right at the length check, replay it instruction by instruction, and rebuild the entire runtime around it: the nanomite signal frames, the sigreturn path (your dispatcher patches the ucontext and returns into a sigreturn trampoline, that one took me a stupid amount of time to see because my emulator kept skipping two bytes past every syscall), the alt stack frames, even the vdso had to be mapped back in because your timing checks call it directly. the funny part was the cpu. my emulator does not know what an avx512 instruction is, and every ifunc slot in the binary resolved to the avx512 variant of its function, so the thing kept dying inside memmove with vpbroadcastb. I ended up repointing something like thirty got slots to the sse2 fallbacks and neutering the avx512 cpu feature bits, and after that it just ran. all of it. banner, prompts, the dots, the wrong password message, byte for byte identical to a real run. which is how I found your four threads. clone3, four of them, right after the post loop hash. my first scheduler deadlocked instantly, main holds a lock the workers want and vice versa, and my second one corrupted a dispatch table because I preempted mid critical section. still fighting that part, but the fun discovery is that the fail message prints fine without the threads ever running, so the message selection itself lives in the main thread. also, respect for the branchless check. I traced two passwords that differ in exactly one character for two hundred thousand instructions and the instruction pointer sequence is byte identical. not one branch. the character folds into this running value and the whole verdict just rides the state machine. the six packed copies of the password your weave writes, the ones nobody ever reads back, those were a nice red herring to burn a day on. so current score: full deterministic oracle of the whole binary, checkpointable, can feed it any username and password pair I want. still no flag, the expected value never exists as a contiguous blob anywhere in memory, it is scattered into the obfuscated state from birth. but now I can run experiments that used to take minutes in seconds, so I am not complaining. still grinding. | 2026-09-20 01:12 |
| Obfuscated Crackme 2 | [Click to reveal]progress update since your last message, and the 32 char hint was the unlock, so thank you for that. I rebuilt my whole tracing setup (new machine, long story) and this time I skipped the emulator entirely and went back to riding the relay, but properly: I intercept the relay's ptrace syscalls at entry, cancel the pending one, inject my own GETREGS / POKEUSER / even mmap and wait4 calls through it, then re-execute its original CONT with the same signal so the event count stays perfect. that last part took me embarrassingly long, I kept desyncing the relay's wait4 loop and the child would just die mid run. the fun part is I can now detour the child itself for one instruction: set its regs to a write() call, single step it, consume the step event with an injected wait4, restore, and continue. full memory reads out of a process that has dumpable=0 set, while the relay's own loop never notices a thing. anyway, with that running I could finally see what you meant. the collected hex string length check is exactly where you said, and with 32 hex chars the trace diverges hard after the check loop in a way it never does with 31 or 33. the loop itself is cute by the way, it walks the string as 16 pairs and only the odd indexed chars ever show up in the visible register at the trap sites, the even ones ride along uppercased on the stack. I also owe you a small apology because I found your nanomites table and decoded about 1100 of the traps, the entry relative negative offsets are a nice touch, took me a minute to stop looking for absolute addresses. seeing which helper functions the traps actually resolve to is what finally made the structure click, especially the little word writer functions in the weave phase that emit the replacement characters. so where I am: full trace, full memory access, trap to function map, and a per pair divergence oracle that moves exactly to the pair I change. the actual check math is still winning though, the state mixing through the anti aliasing buffer makes every run's registers look different even for identical inputs, so differential analysis on registers is dead and the real semantics live in the block chain. I am grinding on it. no flag yet, but I am eating my emulator one bite at a time as promised | 2026-09-19 17:08 |
| Obfuscated Crackme 2 | big update: I stopped poking at the live process and went full emulation, and the emulator just told me something weeks of tracing could not. the setup: snapshot the child at the username prompt trap through the relay (the ptrace injection trick from my first comment), dump all memory and registers, then replay it in unicorn. had to fake the /proc reads, rebuild the ucontext frames for the nanomite handler, and the avx512 ifunc picks were a fun surprise since qemu only does up to sse4 — turns out you can just repoint the got slots to the sse2 fallbacks pulled out of each resolver, after that it runs clean. byte identical output to the real run, ~36x faster, fully deterministic. now the interesting part. I fed it different passwords, same length, different values. identical instruction count. identical registers through the whole check region. identical memory at every dump point. the extracted hex string gets built byte by byte into a heap buffer and then freed without a single read — literally nobody touches it. table state after the check loop is the same for every value, every length, and the username is completely irrelevant too. so either the comparison lives so far down a path nothing reaches, or it is not there at all. given how the fail message reads, I am starting to think the word impossible is literal and that is the joke. what I did find: a 60 byte encrypted blob sitting next to the fail message ciphertext in rodata that this path never touches, plus three code sites that read it and never execute. that is where the real flag lives as far as I can tell, behind a table state no input can produce — the machine would have to be driven with the right state directly. small correction to my last comment: the ctype patch collects hex digits, not vowels. 0-9 and a-f pass, everything else gets dropped, so if the password means anything it is hex chars, not a vowel thing. @Zydak if the intended solution is that the check is a decoy and you find the flag by other means, this is evil and I respect it. if there is an actual password that changes the table state I will eat my emulator | 2026-09-19 01:51 |
| Obfuscated Crackme 2 | update from the rabbit hole, been digging since my last comment: the 'encrypted strings' theory kind of dies here — the interesting strings aren't stored anywhere at all. I threw a hardware watchpoint on the heap buffer where the fail message lands and the characters get produced one at a time by the dispatcher blocks themselves, the text is basically woven into the state machine. the boring stuff (banner, prompts, the box) is per-use xor with the key living inside the mangled arithmetic, that part was easy once spotted. also wasted a fun evening trying to outsmart the dispatch — the block chain boils down to something like next = (v * 0x74fafc1d + c) & 0x7f pulled from a table the check loop keeps rewriting. redirecting states manually just gets you an empty line or a std::length_error in your face :D one interesting bit: two completely different wrong passwords collapse to the exact same machine state after the check loop, so 'wrong' is a single rail no matter what you type. and that ctype table patch (vowels reporting as hex digits, tolower returning capitals) still makes me think the vowel pattern of the password means something. still no key. @Zydak the amount of engineering in this thing is honestly rude | 2026-09-18 22:34 |
| Obfuscated Crackme 2 | the fork at the start had me going in circles for a while. child does PTRACE_TRACEME on itself, so the parent becomes its debugger and no external tracer can even attach. but the parent turns out to be a dumb relay, it just wait4s and CONTs with the same signal back. so I stopped fighting it and started riding it instead: intercept the relay ptrace calls at syscall level and inject my own GETREGS/PEEKDATA/POKEDATA in between. that gives full register and memory access to the child through the parent, and since the relay forwards every SIGTRAP, the whole nanomite machinery keeps working while you watch from behind it. the part I did not expect at all: the ctype table is patched at startup. the per-char check in the validation loop is __ctype_b[c] & 0x1000, which should be isalnum, but only vowels return it. so the whole "isalnum" in the disassembly is actually a vowel test. the ones that pass get a second lookup and end up collected uppercased into a buffer (testpass -> "EA"). the validation itself is fully branchless, two different passwords execute the exact same 82k instruction path through the flattened blocks, the verdict only exists as data. and the anti-aliasing buffer gets rebuilt every loop iteration seeded by the running hash, so the dispatch table entries themselves are input dependent. the final dispatch only lands on the right block if the whole table state is right. that is a genuinely nasty design, took me a while to even understand what I was looking at. also worth mentioning the /proc scans, the comm blacklist with strstr against 52 tool names plus a second strcmp wave, and the integrity check that eats software breakpoints before the validation region. hardware watchpoints via POKEUSER were the only thing that survived, and only because the child cannot read its own debug registers when it is already being traced. key still holding on me @Zydak, but I am not done with it. solid work, this is way above its difficulty rating | 2026-09-18 21:13 |
| alpmira | no imports anywhere (kernel32 gets resolved by walking the PEB), a VEH counting your int3 hits, a background thread hashing chunks of the image, frida string check, rdtsc timing — the works. under it all sits a register VM, ~30 handlers, 3.6M dispatched ops for a single password attempt. validation is a sponge construction: 16 byte state, ~3000 rounds, each one absorbing your input through add/rol8/xor mixing with some feistel swaps. at the end the state runs through two chained ARX stages and the second one feeds an 8 byte compare against a constant. the twist: the flag key is taken from the intermediate state between those two stages, so the check and the flag decryption are parallel paths — forcing the compare just prints garbage. flag region is also rebuilt at runtime from an input dependent keystream, nothing static to rip. password still holding. threw all of rockyou at it, the sponge diffusion says no. working on inverting the final ARX rounds now, the key has to fall out of there. solid work @alpjs, the difficulty rating is earned | 2026-09-18 19:07 |
| Product Activation | finally got this one to activate after way too long. the whole thing being a custom vm caught me off guard at first, and figuring out where the check actually runs was most of the battle for me. really clean design, you can tell a lot of effort went into it. writeup sent with the full walkthrough | 2026-09-18 00:20 |
| sygil (gui version for linux) | finally sealed the curse on this one. the theme work is great, even the anti debug fits the story once you figure out who the watcher is lol. hints on the insight page are fair, just read them slowly and it clicks. the ending animation is a really cool payoff, i let it run a few times just to watch it draw. hundred hours well spent man, easily one of the more original ones ive seen on here. writeup sent too, hope it passes moderation soon | 2026-09-17 23:26 |