C:\philes\demian-jmp: Cracking a GUI CrackMe with a plaintext strcmp check
_×

Cracking a GUI CrackMe with a plaintext strcmp check

posted: August 04, 2026

Grabbed another random crackme off the pile, this time an actual GUI one instead of a console app — "CrackMe GUI v1", one edit box, one button that says "Check". Popped it into IDA and figured this one would at least take me a few minutes. It didn't.

Step 1: Poking around WndProc

Since this is a GUI app, there's no main() doing the work in a straight line like the last one — everything runs through the window procedure, WndProc, reacting to Windows messages. So the first thing I did was go looking for whatever handles the button click, since that's obviously where the "am I right or wrong" decision gets made.

Found it pretty fast. WndProc pulls lParam off the stack and compares the low word against 0x66:

mov     rax, [rbp+lParam]
cmp     ax, 66h         ; control ID of the "Check" button
jnz     loc_1400019F6

That's the control ID check — WM_COMMAND packs the control/menu ID into the low word of wParam (IDA's showing it off lParam here depending on how the stack frame's laid out, but functionally it's the same idea: this is the dispatch check that says "was it specifically the Check button that got clicked, and not something else"). If it doesn't match, jump straight past all of this and go back to the message loop. Nothing interesting down that path.

WndProc dispatching on the Check button's control ID, then reading the edit box and calling into the comparison function
WndProc dispatching on the Check button's control ID, then reading the edit box and calling into the comparison function

Step 2: Reading the password out of the edit box

Once we know it's the Check button, WndProc calls GetWindowTextA on the edit control to pull whatever the user typed into a local buffer:

mov     rax, cs:hEdit_0
lea     rdx, [rbp+String]   ; lpString
mov     r8d, 80h            ; nMaxCount
mov     rcx, rax            ; hWnd
mov     rax, cs:__imp_GetWindowTextA
call    rax

Standard stuff, nothing to see here — grabs up to 0x80 bytes from the edit box into String. The interesting part is what happens right after:

lea     rax, [rbp+String]
mov     rcx, rax
call    v3
test    eax, eax
jz      short loc_14001199A
COmpare Logic Check Against The Right password
COmpare Logic Check Against The Right password

That call v3 is the actual check. eax coming back non-zero means we're good, zero means denied — the classic boolean-return pattern.

Step 3: v3 turned out to be a two-line function

Jumped into v3 expecting some kind of transform, maybe another XOR like the last one, maybe a hash comparison. Instead:

push    rbp
mov     rbp, rsp
sub     rsp, 30h
mov     [rbp+Str1], rcx
lea     rax, aSw0rdf1sh      ; "sw0rdf1sh"
mov     [rbp+Str2], rax
mov     rdx, [rbp+Str2]      ; Str2
mov     rax, [rbp+Str1]
mov     rcx, rax             ; Str1
call    strcmp
test    eax, eax
jnz     short loc_140001792
mov     eax, 1
jmp     short loc_140001797
loc_140001792:
mov     eax, 0
loc_140001797:
add     rsp, 30h
pop     rbp
retn

That's it. That's the whole "algorithm". It loads a pointer to the string aSw0rdf1sh, loads the pointer to whatever the user typed, and hands both straight to the CRT's strcmp. No XOR mask, no length gate, no per-character loop, nothing computed at all — genuinely just comparing two pointers with strcmp and returning 1 or 0 off the result.

lea loading the plaintext password string directly by name, right there in the disassembly, no decoding needed
lea loading the plaintext password string directly by name, right there in the disassembly, no decoding needed

And because it's a named symbol instead of a stripped byte array, IDA is just handing me the plaintext right there in the operand comment: " sw0rdf1sh". No decompiling required for this one, no manual XOR table — you can solve the entire crackme from the disassembly view alone, before even opening Pseudocode.

Step 4: confirming it, and finding the success/fail strings while I was in there

Typed sw0rdf1sh into the box just to make sure I wasn't missing a second check somewhere downstream, hit Check, and got the popup straight away.

While I had .rdata open I also grabbed the two message strings sitting right next to each other — "Access granted. Well done!" and "Access denied.", both plain ASCII, both referenced directly from WndProc's MessageBoxA calls depending on which branch v3 took.

the "Access granted. Well done!" and "Access denied." strings sitting in plaintext in .rdata, cross-referenced straight from WndProc
the "Access granted. Well done!" and "Access denied." strings sitting in plaintext in .rdata, cross-referenced straight from WndProc

Back in WndProc, that's the branch that fires when v3 returns non-zero — it loads the "CrackMe" caption, loads the "Access granted. Well done!" text, and throws it straight into MessageBoxA:

lea     rcx, Caption        ; "CrackMe"
lea     rdx, Text           ; "Access granted. Well done!"
mov     rax, [rbp+hWnd]
mov     r9d, 40h            ; uType
mov     r8, rcx             ; lpCaption
mov     rcx, rax            ; hWnd
mov     rax, cs:__imp_MessageBoxA
call    rax
jmp     short loc_1400019F6

And sure enough:

the actual popup — Access granted, well done, courtesy of the exact same string I found five minutes earlier in .rdata
the actual popup — Access granted, well done, courtesy of the exact same string I found five minutes earlier in .rdata

Conclusion

Whole thing took longer to write up than it did to crack. Control ID check to confirm the button, GetWindowTextA to grab the input, one strcmp against a plaintext string that IDA hands you for free, one MessageBoxA on success. No obfuscation on the password at all this time, not even the single-byte XOR mask from the last one — just a named string sitting in .rdata in the clear.

Fun for a coffee-break crackme, not much of a writeup as a technique. Tomorrow I want to grab something that at least tries — some kind of runtime string decryption, a hash-based check instead of strcmp, maybe something that pulls out a debugger check first. This one didn't put up a fight.

Start Start
github
buffering...
9:41 AM