← Back to Writeups
CUSTOMMediumReversing

Kitten and fl@g

XESXOR9/2/20266 min read
#reversing#custom#medium#upx_header_repair#false_flag_identification#patched_libc_function_analysis#xor_chain_decoding#overlapping_memory_write_recon#environment_variable_flag_veri

Kitten and fl@g

Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: upx_header_repair, false_flag_identification, patched_libc_function_analysis, xor_chain_decoding, overlapping_memory_write_reconstruction, environment_variable_flag_verification

Summary

Walkthrough of the CUSTOM Reversing Kitten and fl@g. Task: UPX-packed statically linked ELF binary (Tamagotchi game) with corrupted UPX header and a false flag in the game-win path. Solution: repaired UPX magic bytes, identified give_flag as a trap, dis

Description

Это AAA-котик? Tags: C, патчинг функций

English summary: we are given a ZIP archive containing kitten.elf — a 64-bit x86 ELF binary, statically linked and UPX-packed. The binary implements a Tamagotchi-style kitten simulator. The task is to find the real flag hidden inside the binary. The author's tags explicitly hint at "C" and "function patching" as the key concepts.

Analysis

Initial Recon

The file kitten.elf is identified as an ELF 64-bit LSB executable, x86-64, statically linked. Running strings reveals UPX markers, confirming it's UPX-packed. However, standard upx -d fails because the UPX magic bytes at offset 0xec have been tampered with — they read Vupx! instead of the expected UPX!. This is a deliberate anti-analysis technique. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

UPX Header Corruption

At offset 0xec in the binary, the bytes are lowercase upx preceded by V instead of the standard uppercase UPX! magic. This prevents the UPX decompressor from recognizing the file format.

The False Flag Trap

After unpacking, the binary reveals a Tamagotchi game where you feed, water, and put a kitten to sleep. When all three parameters (satiety, water, sleep) reach 100, the function give_flag() is called. This function:

  1. Constructs a template string C0DEBY{F#t_$#t_a#t_aN!_r*v!rs*}
  2. Substitutes values from game state into the template
  3. Encodes with 24 sequential XOR/ADD/SUB operations
  4. Compares with hardcoded target bytes
  5. If matched, decodes and prints a flag: C0DEBY{F@t_C@t_aN2_rEv2rsE} ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This is a FALSE FLAG! The decoded result uses C0DEBY (with zero) instead of CODEBY and doesn't match the flag format. The author explicitly designed this as a honeypot to waste time.

The Real Solution: Patched printf

The key insight comes from the author's hint about "function patching" (патчинг функций). Since the binary is statically linked, all standard library functions are embedded directly in the binary. The author replaced the standard printf implementation with a custom one containing the real flag logic. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The call chain is:

  • printf (at 0x410c50) → calls __stack_chk_fail_global (at 0x402bac)
  • __stack_chk_fail_global → calls __vfxsprintf (at 0x402bc1)

The function __stack_chk_fail_global is suspicious — instead of handling stack canary failures (its normal purpose), it redirects to __vfxsprintf, which contains the real flag construction and verification logic.

Solution

Step 1: Repair UPX Header

Fix the corrupted magic bytes at offset 0xec to allow standard UPX unpacking:

python
#!/usr/bin/env python3
# Fix corrupted UPX magic bytes
data = bytearray(open('kitten.elf', 'rb').read())
data[0xec] = ord('U')  # was 'u' (lowercase)
data[0xed] = ord('P')  # was 'p' (lowercase)
data[0xee] = ord('X')  # was 'x' (lowercase)
open('kitten_fixed.elf', 'wb').write(data)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Then unpack:

bash
upx -d kitten_fixed.elf -o kitten_unpacked.elf

Step 2: Identify the False Flag

Analyze the give_flag() function and recognize it as a trap. The decoded flag C0DEBY{F@t_C@t_aN2_rEv2rsE} uses C0DEBY (zero instead of O) and doesn't validate against the submission system.

Step 3: Reverse the Hidden __vfxsprintf Function

The __vfxsprintf function performs the following operations:

1. Decode environment variable name "g00d_oR_bad": Raw bytes 07 50 50 04 3f 0f 32 3f 02 01 04 60 XORed with 0x60 produce g00d_oR_bad. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

2. Decode environment variable name "cDb_rEv2rE": Raw bytes 20 07 21 1c 31 06 35 71 71 31 06 43 XORed with 0x43 (11 iterations) produce cDb_rEv2rE.

3. Build the flag buffer using overlapping movabs instructions:

python
import struct

buf = bytearray(28)
# Four movabs instructions write 8 bytes each, with overlap at offset 12
struct.pack_into('<Q', buf, 0,  0x742B091215383013)
struct.pack_into('<Q', buf, 8,  0x613C0F334924531C)
struct.pack_into('<Q', buf, 12, 0x25165A15613C0F33)  # overwrites bytes 12-19
struct.pack_into('<Q', buf, 20, 0x502D1E206162331E)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

4. Apply individual XOR corrections on specific positions:

python
buf[9]  ^= 0x43
buf[11] ^= 0x50
buf[17] ^= 0x55
buf[24] ^= 0x1f
buf[1]  ^= 0x2f
buf[2]  ^= 0x2c

5. Check environment variable "g00d_oR_bad": If getenv("g00d_oR_bad") returns NULL, the function exits without printing anything.

6. XOR the entire 29-byte flag buffer with 0x50:

python
for i in range(29):
    buf[i] ^= 0x50

7. Conditional path based on env value: If the first byte of g00d_oR_bad equals '1' (0x31), then buf[8] ^= 0x18 (correct path). Otherwise buf[1] ^= 0x7f (wrong path, corrupts the flag). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

8. Verify against second environment variable: Calls getenv("cDb_rEv2rE") and compares its value with the decoded flag buffer using strncmp with length 27.

9. If match: Decodes format string %s\0 (from bytes 0x06, 0x50, 0x23 XORed with 0x23), prints the flag via printf("%s", env_value), and calls exit(0).

Step 4: Compute the Flag

After all transformations, the flag buffer decodes to:

CODEBY{REDACTED}

Step 5: Verify

Run the unpacked binary with the required environment variables:

bash
export g00d_oR_bad=1
export cDb_rEv2rE='CODEBY{REDACTED}'
./kitten_unpacked.elf

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The binary prints the flag and exits.

Full Solve Script

python
#!/usr/bin/env python3
"""
Solve script for Hackerlab - Kitten and fl@g
Reconstructs the flag from __vfxsprintf function logic
"""
import struct

# Step 1: Build flag buffer from overlapping movabs instructions
buf = bytearray(28)
struct.pack_into('<Q', buf, 0,  0x742B091215383013)
struct.pack_into('<Q', buf, 8,  0x613C0F334924531C)
struct.pack_into('<Q', buf, 12, 0x25165A15613C0F33)  # overlaps bytes 12-19
struct.pack_into('<Q', buf, 20, 0x502D1E206162331E)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Step 2: Individual XOR corrections
buf[9]  ^= 0x43
buf[11] ^= 0x50
buf[17] ^= 0x55
buf[24] ^= 0x1f
buf[1]  ^= 0x2f
buf[2]  ^= 0x2c

# Step 3: XOR entire buffer with 0x50
for i in range(len(buf)):
    buf[i] ^= 0x50

# Step 4: Conditional XOR (correct path: g00d_oR_bad == "1")
buf[8] ^= 0x18

flag = buf.decode('ascii', errors='replace').rstrip('\x00')
print(f"Flag: {flag}")
# Output: CODEBY{REDACTED}

Lessons Learned

  • In CTF reverse challenges, the obvious path (winning the game → getting the flag) can be a deliberate trap. Always verify the flag format before investing more time.
  • Always check if standard library functions have been patched in statically linked binaries — printf, puts, __stack_chk_fail are common targets.
  • The task description "Это AAA-котик?" and tags "C, патчинг функций" were direct hints pointing to function patching as the core technique.
  • The flag name itself is a meta-reference: REDACTED = "Static Lie Function" — the statically linked printf function was replaced with a "lie" (false implementation) that secretly contained the real flag logic.
  • When a flag doesn't work, look for hidden code paths, especially in functions that shouldn't contain complex logic (like __stack_chk_fail). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering upx_header_repair, false_flag_identification, patched_libc_function_analysis, xor_chain_decoding, overlapping_memory_write_reconstruction, environment_variable_flag_verification was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-04-06 • CUSTOM • Kitten and fl@g • Difficulty: Medium