Challenge Description
Leaked Flag Checker is an Easy Reverse challenge from AlpacaHack. We are given the binary and, in this case, the source code as well. The checker is very small, but it exposes a useful property: every input character is XORed with 7 before being compared against a constant string stored in the binary.
Source Code Analysis
The source code already tells us almost everything. The program reads our input, checks the length, and then compares each character using input[i] ^ 7.
// gcc -o challenge challenge.c
#include <stdio.h>
#include <string.h>
int main(void) {
char input[32];
const char xor_flag[] = "REDACTED";
size_t flag_len = strlen(xor_flag);
printf("Enter flag: ");
fflush(stdout);
scanf("%31s", input);
if(strlen(input) != flag_len) {
printf("Wrong length\n");
return 1;
}
for(size_t i = 0; i < flag_len; i++) {
if((input[i] ^ 7) != xor_flag[i]) {
printf("Wrong at index %zu\n", i);
return 1;
}
}
printf("Correct\n");
return 0;
}
This immediately gives us two useful observations:
1. the flag length is equal to the length of xor_flag
2. the real flag can be recovered by applying XOR with 7 one more time
Solution 1 - DogBolt + XOR
The first solution is the fastest one and, in my opinion, probably unintended. We upload the binary to DogBolt and immediately notice a constant string copied into a local buffer:
__builtin_strcpy(&var_46, "Fkwfdf|krdl~z");
We also see the comparison logic very clearly:
char rax_7 = var_38[var_58_1] ^ 7;
if (rax_7 != *(var_58_1 + &var_46)) {
printf("Wrong at index %zu\n", var_58_1);
}
At this point we use the main XOR property:
(x ^ 7) ^ 7 = x
So if the stored string is Fkwfdf|krdl~z, we only need to XOR every character with 7 again to recover the original flag.
Script used:
XORED = "Fkwfdf|krdl~z"
for i in XORED:
print(chr(ord(i) ^ 7), end="")
Run
$ python3 solve.py
Alpaca{lucky}
Solution 2 - Brute Force via the Checker
The second solution relies only on the checker behavior and does not need the constant to be read from decompilation. The program tells us either Wrong length or Wrong at index X, which makes it a very convenient oracle for brute force.
The idea is straightforward:
1. find the correct length by trying A, AA, AAA, ...
2. for each position, brute force printable ASCII characters
3. when the answer is no longer Wrong at index i, that character is correct
4. repeat until the full flag is recovered
Script used:
from pwn import *
elf = ELF("./challenge")
LenghtPayload = "A"
context.log_level = "error"
for i in range(1000):
p = elf.process()
p.sendline(LenghtPayload.encode())
Response = p.recvline(timeout=5)
p.close()
if "Wrong length".encode() not in Response:
break
LenghtPayload += 'A'
print(f"Length : {len(LenghtPayload)}")
GenerativePayload = ["A"] * len(LenghtPayload)
for i in range(len(LenghtPayload)):
for ascii_code in range(32, 127):
p = elf.process()
GenerativePayload[i] = chr(ascii_code)
p.sendline(''.join(GenerativePayload).encode())
Response = p.recvline(timeout=5)
p.close()
if f'Enter flag: Wrong at index {i}'.encode() not in Response and "Wrong length".encode() not in Response:
break
print(''.join(GenerativePayload))
Run
$ python3 solve2.py
Length : 13
Alpaca{lucky}
Flag
Alpaca{lucky}