Published on

PCC CTF - Reverse Flag Validator Writeup

Authors
  • avatar
    Name
    Muhammad Huzaifa
    Twitter

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:

  1. The program prompts for flag input
  2. It checks the length
  3. It validates the flag
  4. 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:

  1. For each character in the input (21 iterations)
  2. XOR the character with 0x5A
  3. Compare result with encrypted data at 0x2060
  4. 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:

decrypt.py
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:

test.sh
$ echo "PCC{`H4lWa_f0r_R34s0n`}" | ./chal
Type the flag: Correct! Flag: PCC{`H4lWa_f0r_R34s0n`}

Success! ✓

Detailed Decryption Table

Encrypted ByteXOR KeyDecrypted CharASCII
0x0a0x5a'P'80
0x190x5a'C'67
0x190x5a'C'67
0x210x5a{123
0x120x5a'H'72
0x6e0x5a'4'52
0x360x5a'l'108
0x0d0x5a'W'87
0x3b0x5a'a'97
0x050x5a'_'95
0x3c0x5a'f'102
0x6a0x5a'0'48
0x280x5a'r'114
0x050x5a'_'95
0x080x5a'R'82
0x690x5a'3'51
0x6e0x5a'4'52
0x290x5a's'115
0x6a0x5a'0'48
0x340x5a'n'110
0x270x5a}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

  1. Simple XOR Cipher: The challenge uses a single-byte XOR key (0x5A)
  2. Static Analysis Sufficient: No need for dynamic analysis; the encrypted data is embedded in the binary
  3. Length Check: Always verify input constraints first (21 characters)
  4. Reversible Encryption: XOR is its own inverse: (x ^ k) ^ k = x
  5. Helpful Binary: The program actually contains code to decrypt and display the flag on success

Tools Used

  • file - File type identification
  • strings - Extract printable strings
  • objdump - Disassembly and section dumping
  • xxd - Hex dump
  • python3 - Decryption script
  • objdump - 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)