- Published on
PCC CTF - Reverse Flag Validator Writeup
- Authors
- Name
- Muhammad Huzaifa
Reverse Flag Validator - Writeup
Challenge Overview
Challenge Name: Reverse Flag Validator
Category: Reverse Engineering
Difficulty: Easy-Medium
Flag: PCC{
H4lWa_f0r_R34s0n}
Initial Analysis
First, let's examine what we're dealing with:
$ file chal
chal: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0eed012e7385a796641c8057f4588ab2c3533fb0,
for GNU/Linux 3.2.0, stripped
Key observations:
- 64-bit ELF executable
- Dynamically linked
- Stripped (no debugging symbols)
- Position Independent Executable (PIE)
Let's run it to see its behavior:
$ echo "test_flag" | ./chal
Type the flag: Nope (wrong length).
The program expects a specific flag length.
String Analysis
Using strings
to extract readable strings from the binary:
$ strings chal
Type the flag:
Input error.
Nope (wrong length).
Correct! Flag: %s
Incorrect.
This tells us:
- The program prompts for flag input
- It checks the length
- It validates the flag
- It displays success/failure messages
Disassembly Analysis
Let's disassemble the main function using objdump
:
$ objdump -M intel -d chal
Key Code Sections
1. Input Reading (0x1080-0x10b2)
1081: lea rdi,[rip+0xf7c] # "Type the flag: "
1088: xor eax,eax
108a: sub rsp,0x120
1091: call 1050 <printf@plt>
1096: lea rbx,[rsp+0x20]
109b: mov esi,0x100
10a0: mov rdx,QWORD PTR [rip+0x2f89] # stdin
10a7: mov rdi,rbx
10aa: call 1060 <fgets@plt>
The program:
- Allocates 0x120 bytes on the stack
- Reads up to 256 (0x100) bytes from stdin using
fgets
- Stores input at
[rsp+0x20]
2. Length Validation (0x10b8-0x10d4)
10b8: mov rdi,rbx
10bb: call 1040 <strlen@plt>
10c0: test rax,rax
10c3: je 1129 # Jump to "wrong length" message
10c5: cmpb BYTE PTR [rsp+rax*1+0x1f],0xa # Check for newline
10ca: lea rdx,[rax-0x1]
10ce: je 1140 # Strip newline if found
10d0: cmp rax,0x15 # Compare length with 21 (0x15)
10d4: jne 1129 # Jump to error if not 21
Critical finding: The flag must be exactly 21 characters long!
3. Validation Loop (0x10d6-0x10f4)
10d6: xor eax,eax
10d8: lea rcx,[rip+0xf81] # Load address 0x2060 (encrypted data)
10df: nop
10e0: movzbl edx,BYTE PTR [rbx+rax*1] # Load input byte
10e4: xor edx,0x5a # XOR with 0x5A
10e7: cmp dl,BYTE PTR [rcx+rax*1] # Compare with encrypted data
10ea: jne 114a # Jump to "Incorrect" if mismatch
10ec: add rax,0x1 # Increment counter
10f0: cmp rax,0x15 # Check if processed 21 bytes
10f4: jne 10e0 # Continue loop
The validation algorithm:
- For each character in the input (21 iterations)
- XOR the character with
0x5A
- Compare result with encrypted data at
0x2060
- All characters must match
4. Success Path (0x10f6-0x1127)
10f6: xor eax,eax
10f8: mov rsi,rsp
10fb: nopl 0x0(rax,rax,1)
1100: movzbl edx,BYTE PTR [rcx+rax*1] # Load encrypted byte
1104: xor edx,0x5a # XOR with 0x5A (decrypt)
1107: mov BYTE PTR [rsi+rax*1],edx # Store decrypted byte
110a: add rax,0x1
110e: cmp rax,0x15
1112: jne 1100
1114: lea rdi,[rip+0xf1b] # "Correct! Flag: %s"
111b: xor eax,eax
111d: movb BYTE PTR [rsp+0x15],0x0 # Null terminate
1122: call 1050 <printf@plt>
If validation succeeds, the program decrypts and displays the flag!
Extracting the Encrypted Data
Let's examine the .rodata
section to find the encrypted data at 0x2060
:
$ objdump -s -j .rodata chal
Contents of section .rodata:
2060 0a191921 126e360d 3b053c6a 28050869 ...!.n6.;.<j(..i
2070 6e296a34 27 n)j4'
The encrypted bytes are:
0a 19 19 21 12 6e 36 0d 3b 05 3c 6a 28 05 08 69 6e 29 6a 34 27
Decryption
Since the validation algorithm XORs each input character with 0x5A
and compares it to the encrypted data, we need to reverse this operation:
flag_char XOR 0x5A = encrypted_byte
Therefore:
flag_char = encrypted_byte XOR 0x5A
Let's decrypt using Python:
encrypted = bytes.fromhex('0a19192112 6e360d3b05 3c6a280508 696e296a34 27')
flag = ''.join(chr(b ^ 0x5A) for b in encrypted)
print('Flag:', flag)
Output:
Flag: PCC{`H4lWa_f0r_R34s0n`}
Verification
Let's verify the flag works:
$ echo "PCC{`H4lWa_f0r_R34s0n`}" | ./chal
Type the flag: Correct! Flag: PCC{`H4lWa_f0r_R34s0n`}
Success! ✓
Detailed Decryption Table
Encrypted Byte | XOR Key | Decrypted Char | ASCII |
---|---|---|---|
0x0a | 0x5a | 'P' | 80 |
0x19 | 0x5a | 'C' | 67 |
0x19 | 0x5a | 'C' | 67 |
0x21 | 0x5a | { | 123 |
0x12 | 0x5a | 'H' | 72 |
0x6e | 0x5a | '4' | 52 |
0x36 | 0x5a | 'l' | 108 |
0x0d | 0x5a | 'W' | 87 |
0x3b | 0x5a | 'a' | 97 |
0x05 | 0x5a | '_' | 95 |
0x3c | 0x5a | 'f' | 102 |
0x6a | 0x5a | '0' | 48 |
0x28 | 0x5a | 'r' | 114 |
0x05 | 0x5a | '_' | 95 |
0x08 | 0x5a | 'R' | 82 |
0x69 | 0x5a | '3' | 51 |
0x6e | 0x5a | '4' | 52 |
0x29 | 0x5a | 's' | 115 |
0x6a | 0x5a | '0' | 48 |
0x34 | 0x5a | 'n' | 110 |
0x27 | 0x5a | } | 125 |
Alternative Solution Methods
Method 1: Dynamic Analysis with GDB
You could also solve this by setting a breakpoint after the decryption loop and reading the decrypted flag from memory:
$ gdb chal
(gdb) break *0x1114
(gdb) run
Type the flag: AAAAAAAAAAAAAAAAAAAA
(gdb) x/s $rsp
Method 2: ltrace/strace
Monitor library calls to see comparisons:
$ ltrace ./chal
Method 3: Binary Patching
Patch the comparison to always succeed and let the program decrypt and print the flag for you.
Key Takeaways
- Simple XOR Cipher: The challenge uses a single-byte XOR key (0x5A)
- Static Analysis Sufficient: No need for dynamic analysis; the encrypted data is embedded in the binary
- Length Check: Always verify input constraints first (21 characters)
- Reversible Encryption: XOR is its own inverse:
(x ^ k) ^ k = x
- Helpful Binary: The program actually contains code to decrypt and display the flag on success
Tools Used
file
- File type identificationstrings
- Extract printable stringsobjdump
- Disassembly and section dumpingxxd
- Hex dumppython3
- Decryption scriptobjdump
- Binary analysis
Conclusion
This challenge demonstrates fundamental reverse engineering concepts:
- Binary analysis and disassembly
- Understanding control flow
- Identifying encryption algorithms
- Static vs dynamic analysis approaches
The flag PCC{
H4lWa_f0r_R34s0n}
(perhaps "Halwa for reason" - a play on words?) was successfully recovered through static analysis and understanding of the XOR validation logic.
Author: cipher
Date: October 18, 2025
Challenge Category: Reverse Engineering
Difficulty Rating: ⭐⭐☆☆☆ (2/5)