| Tricky challenge or is it ? |
[Click to reveal]Quick and fun little challenge. Stripped x86-64 ELF with two tricks bolted on: a ptrace-based anti-debug check and an XOR-encrypted password that lives on the stack. Took me about 15 minutes of static analysis.
**Tools:** objdump, Python, a coffee.
**Step 1: recon**
`file` says it's a 64-bit PIE ELF, dynamically linked, stripped. `readelf -d` shows the usual libc imports plus one that jumps out: `ptrace`. That's the anti-debug. Strings also gives away the story:
```
Debugger detected! Get outta here.
Enter the secret password:
Access Granted! You are a master.
Access Denied. Try again.
```
And two weird ASCII-looking strings: `& %0'&06H` and `06'0!dgfH`. Those are not strings, they're the XOR-encrypted password bytes that happen to be printable.
**Step 2: anti-debug**
`objdump -d -M intel` shows a function around `0x1189` that does:
```
mov ecx, 0 ; request = PTRACE_TRACEME
mov edx, 1
mov esi, 0
mov edi, 0
call ptrace@plt
cmp rax, -1
jne ok
lea rdi, "Debugger detected! Get outta here."
call puts@plt
mov edi, 1
call exit@plt
ok: ret
```
Classic `ptrace(PTRACE_TRACEME, ...)`. If a debugger is already attached, this returns -1 and the program bails. I didn't even bother running it under gdb, I just went full static.
**Step 3: the password**
The main function, around `0x11cd`, does this after `scanf("%49s", buf)`:
```
movabs rax, 0x3630262730252026
mov [rbp-0x4e], rax
movabs rax, 0x6667642130273630
mov [rbp-0x48], rax
mov DWORD [rbp-0x8], 0xe ; length = 14
mov DWORD [rbp-0x4], 0 ; i = 0
loop:
movzx eax, BYTE [rbp-0x4e + i]
xor eax, 0x55
mov [rbp-0x80 + i], al
inc i
cmp i, 0xe
jl loop
mov BYTE [rbp-0x80 + 0xe], 0 ; null terminate
strcmp(buf, decrypted)
```
So it XORs 14 bytes starting at `rbp-0x4e` with `0x55` and compares the result against user input.
The catch: the two `movabs` stores overlap. The first writes 8 bytes at `rbp-0x4e` (offsets 0-7), the second writes 8 bytes at `rbp-0x48` (offsets 6-13). So bytes 6 and 7 of the first store get clobbered by bytes 0 and 1 of the second store.
Here's the Python to reconstruct the actual buffer and decrypt:
```python
buf = bytearray(16)
first = (0x3630262730252026).to_bytes(8, 'little')
second = (0x6667642130273630).to_bytes(8, 'little')
for i, b in enumerate(first): buf[i] = b
for i, b in enumerate(second): buf[6 + i] = b
password = bytes(b ^ 0x55 for b in buf[:14])
print(password.decode())
```
**Step 4: verify**
```
$ echo "supersecret123" | ./hard_crackme
Enter the secret password: Access Granted! You are a master.
```
**Password:** `supersecret123`
The "tricky" part is just the overlap. If you read the disasm as two independent 8-byte writes you get a wrong password (`supersececret1`, which is what I tried first and got laughed at by `strcmp`). Once you remember that x86 stores are byte-wise and the compiler placed these 6 bytes apart, everything falls into place.
Nice little binary, thanks for the puzzle.
|
2026-09-17 22:08 |