crackmes Easy

FLRSCRNSVR.SCR

Overview

Field Value
Category Easy
Points 846
Author Moritz
Binary PE32+ x64 Windows GUI (.SCR screensaver)
Zip Password flare
Flag CMO{frogt4s7ic_r3vers1ng}

Recon

The binary is a Windows screensaver (.SCR). Running file on it:

PE32+ executable (GUI) x86-64, for MS Windows, 6 sections

PDB path leaked in the binary hints at the author’s environment:

C:\Users\flare\wow\a\crackmesone\screensaver\i\love\it\thank\you\xoxoxox.pdb

Key imports that shape our analysis direction:

  • ADVAPI32: RegCreateKeyExW, RegSetValueExW, RegOpenKeyExW, RegQueryValueExW - registry I/O
  • USER32: DialogBoxParamW, GetDlgItemTextW, SetDlgItemTextW - dialog with text input
  • KERNEL32: GetComputerNameW, GetWindowsDirectoryW, FindFirstFileW, GetTickCount, IsDebuggerPresent - system info + anti-analysis
  • CRT: rand, srand, wcschr, wcsncmp, wcstoul - string manipulation + RNG

Junk Code

The binary is heavily padded with dead code that does nothing meaningful. Nearly every function contains repeated sequences of:

  • GetDC(0) / CreateCompatibleDC / CreateCompatibleBitmap / DeleteObject / DeleteDC / ReleaseDC - creates and immediately destroys GDI objects
  • GetTickCount() % 100 + 5 / SetLastError(...) - sets error codes to random-ish values
  • GetDesktopWindow() / IsWindow(...) - checks the desktop exists
  • GetWindowsDirectoryW / FindFirstFileW("*.dll") / FindClose - enumerates DLLs for no reason
  • GetComputerNameW / GetSystemMetrics - reads system info into unused variables
  • Counting loops: do { ++counter; --n; } while (n); with result discarded

All of this is noise. The actual logic is a small fraction of each function.

Program Flow

WinMain (0x140002360)

Parses command-line arguments following standard screensaver conventions:

Flag Mode Behavior
/c or -c Configure Shows a dialog box via DialogBoxParamW with DialogFunc
/p or -p Preview Minimal GDI setup, does nothing interesting
/s or -s Screensaver Full-screen screensaver with bouncing bitmap animation

The configure mode (/c) is where the flag check lives.

DialogFunc (0x140001f30)

Handles the configuration dialog:

  • WM_INITDIALOG: Calls sub_140001AE0(String) to load existing text, then populates dialog item 1001 with the result.
  • WM_COMMAND (OK button): Reads user input from the dialog, writes it to registry key HKCU\Software\FLRSCRNSVR\Text, and writes a hardcoded encrypted blob to HKCU\Software\FLRSCRNSVR\Quak.

sub_140001AE0 - Validation Logic (0x140001AE0)

This is the core check, called on dialog init and during screensaver rendering:

  1. Opens HKCU\Software\FLRSCRNSVR and reads the Text value (user’s previous input).
  2. If the registry key doesn’t exist, defaults to "Crackmes.one".
  3. Length gate: If strlen(input) != 25, skip validation entirely.
  4. Copies input to a working buffer, calls sub_140001300 to transform it.
  5. Calls sub_140001890 to load the target (from registry Quak value, or hardcoded fallback aQj at 0x1400064d0).
  6. Compares transformed input against target character by character.
  7. If match: sets global byte_140008898 = 1 (success flag, changes screensaver rendering).

The Transform - sub_140001300 (0x140001300)

Three operations applied sequentially to the 25-character input (wchar_t):

Step 1: Substitution Cipher

Two 66-character alphabets define a monoalphabetic substitution:

Source: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789}_{=-
Dest:   -={_}9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba

Each character is looked up in Source and replaced with the character at the same index in Dest. Characters not in the alphabet are left unchanged.

Step 2: XOR with Key + Index

A repeating key derived from embedded DWORD constants:

1
2
3
4
5
v32[0] = 4980806;  // 0x004C0046 -> 'F', 'L'
v32[1] = 5374017;  // 0x00520041 -> 'A', 'R'
v32[2] = 5374021;  // 0x00520045 -> 'E', 'R'
v32[3] = 4980801;  // 0x004C0041 -> 'A', 'L'
v32[4] = 70;       // 0x00000046 -> 'F', '\0'

This spells FLARERALF (9 characters, a palindrome of “FLARE”). The XOR operation is:

input[j] ^= key[j % 9] + j

Step 3: String Reversal

The transformed string is reversed in place by swapping characters from both ends toward the middle.

The Target

The hardcoded target blob aQj at 0x1400064d0 (also written to registry as Quak):

3c 00 51 00 6a 00 09 00 02 00 07 00 25 00 03 00
30 00 08 00 04 00 29 00 68 00 24 00 01 00 24 00
18 00 6b 00 77 00 0f 00 70 00 36 00 02 00 0e 00
0b 00 00 00

As 25 wchar_t values:

[0x3c, 0x51, 0x6a, 0x09, 0x02, 0x07, 0x25, 0x03, 0x30, 0x08,
 0x04, 0x29, 0x68, 0x24, 0x01, 0x24, 0x18, 0x6b, 0x77, 0x0f,
 0x70, 0x36, 0x02, 0x0e, 0x0b]

Solve

Invert the three transform steps in reverse order:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
const target = [0x3c, 0x51, 0x6a, 0x09, 0x02, 0x07, 0x25, 0x03, 0x30, 0x08,
                0x04, 0x29, 0x68, 0x24, 0x01, 0x24, 0x18, 0x6b, 0x77, 0x0f,
                0x70, 0x36, 0x02, 0x0e, 0x0b];

const srcAlpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789}_{=-';
const dstAlpha = '-={_}9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba';
const key = [0x46, 0x4C, 0x41, 0x52, 0x45, 0x52, 0x41, 0x4C, 0x46]; // FLARERALF

// 1. Un-reverse
let work = [...target].reverse();

// 2. Un-XOR
for (let j = 0; j < work.length; j++)
    work[j] ^= (key[j % 9] + j);

// 3. Un-substitute (dest -> source)
let flag = work.map(v => {
    const idx = dstAlpha.indexOf(String.fromCharCode(v));
    return idx !== -1 ? srcAlpha[idx] : String.fromCharCode(v);
}).join('');

console.log(flag); // CMO{frogt4s7ic_r3vers1ng}

Flag

CMO{frogt4s7ic_r3vers1ng}