← Back to Writeups
CUSTOMMediumReversing

Обычная капибара (Usual Capybara)

XESXOR9/2/20264 min read
#reversing#custom#medium#aes_128_cbc_decryption#embedded_pe_extraction#hex_string_as_raw_key#openssl_static_analysis#single_byte_xor_decryption#upx_unpacking#windows

Обычная капибара (Usual Capybara)

Platform: hackerlab | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Windows | Author: XESXOR | Date: 2026-04-06 | Status: Solved Techniques: aes_128_cbc_decryption, embedded_pe_extraction, hex_string_as_raw_key, openssl_static_analysis, single_byte_xor_decryption, upx_unpacking

Summary

Task: UPX-packed PE64 with oversized .data section containing a decoy JPEG dropper and a hidden XOR-encrypted PE binary. Solution: unpack UPX, discover hidden PE via single-byte XOR 0xFA, extract it, identify AES-128-CBC parameters from OpenSSL-linked code, decrypt embedded image to reveal flag.

Обычная капибара (Usual Capybara) — hackerlab

Description

Что такое, это обычная капибара. (What is this, it's an ordinary capybara.)

English summary: we are given usual_capybara.zip containing capybara.exe — a UPX-packed PE64 Windows executable. The goal is to reverse engineer the binary and find the hidden flag. The challenge involves multiple layers of obfuscation: UPX packing, a decoy JPEG dropper, a hidden XOR-encrypted PE binary, and AES-128-CBC encrypted image containing the flag. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

Step 1: UPX Unpacking

The binary capybara.exe is a PE32+ executable (console) x86-64 packed with UPX. Standard unpacking works without issues:

bash
upx -d capybara.exe -o capybara_unpacked.exe

UPX unpacked successfully — no anti-unpacking tricks this time (unlike the related "Замок Капибар" challenge from the same event).

Step 2: Outer Binary — Decoy JPEG Dropper

After unpacking, analysis with radare2 reveals the structure of the outer binary:

  • The binary has a large .data section: 0x638C0 = 408,064 bytes — suspiciously oversized
  • The main function (fcn.004015ad) performs XOR decryption on embedded data at VA 0x451020 using keys 0xCD and 0x0B (effectively XOR 0xC6), with size 0x167CA bytes (~92KB)
  • It writes the decrypted result to capybara.jpg and prints: "I'm an ordinary capybara, what do you need?"
  • The extracted JPEG is a meme image of a capybara at a desk — a decoy/distraction ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key strings found in the outer binary:

  • capybara.jpg
  • I'm an ordinary capybara, what do you need?
  • UPX0, UPX1

Step 3: Discovery of Hidden PE (the key breakthrough)

The .data section is 408KB but the JPEG is only ~92KB. That leaves approximately 315KB of unexplained data before the JPEG region.

Examining the raw bytes revealed a pattern: large runs of 0xFA bytes. Since 0xFA XOR 0xFA = 0x00, this strongly suggests XOR encryption with key 0xFA over data that contains many null bytes — exactly what you'd expect from a PE binary's padding regions. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Testing XOR 0xFA at .data offset +0x20:

python
data[0x2620] ^ 0xFA  # = 0x4D = 'M'
data[0x2621] ^ 0xFA  # = 0x5A = 'Z'

MZ header found! A complete PE64 executable is hidden inside the .data section, encrypted with single-byte XOR 0xFA.

Step 4: Hidden Binary — OpenSSL + AES

The extracted hidden PE is a MinGW-compiled binary with OpenSSL 1.0.2o statically linked (AES, RSA, SHA functions visible in strings).

Key function fcn.004016a7 (called from main at 0x4013c2) reveals:

  1. Sets locale to "Rus"
  2. Constructs two hex strings on the stack via movabs instructions:
    • Key string: "7128793add980992ffabbca234123ff"
    • IV string: "12390dfffaabb24312ccaff123ddbca"
  3. Sets encrypted data size: 0x1a3a0 (107,424 bytes)
  4. Calls fcn.00401550 which:
    • Passes the key string to AES_set_decrypt_key (via fcn.00401790) with 128 bits
    • Calls AES-CBC decryption (fcn.004027d0) on encrypted data at VA 0x41b020
    • Writes decrypted output to ./capy.jpg ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The AES implementation is OpenSSL's optimized x86-64 assembly, recognizable by MixColumns operations with rol, xor, shr 0x20, and GF(2^8) multiply patterns.

Solution

Extraction Script

python
#!/usr/bin/env python3
"""
Solve script for Usual Capybara (hackerlab)
Multi-layer reverse: UPX -> XOR 0xFA hidden PE -> AES-128-CBC decryption -> flag image
"""
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from pathlib import Path

# Step 1: Extract hidden PE from unpacked outer binary
outer = Path('capybara_unpacked.exe').read_bytes()
blob_start = 0x2620   # .data + 0x20
blob_size = 315392     # size of hidden PE
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# XOR decrypt with single-byte key 0xFA
embedded = bytes([outer[blob_start + i] ^ 0xFA for i in range(blob_size)])
Path('hidden.exe').write_bytes(embedded)
print(f"[+] Extracted hidden PE: {len(embedded)} bytes")
assert embedded[:2] == b'MZ', "MZ header check failed!"

# Step 2: Decrypt the AES-encrypted image from hidden PE
hidden = Path('hidden.exe').read_bytes()
enc_offset = 0x19620   # encrypted blob offset in hidden.exe (.data section)
enc_size = 0x1a3a0      # 107,424 bytes

enc_data = hidden[enc_offset:enc_offset + enc_size]
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Key insight: hex strings are used as raw ASCII bytes, NOT hex-decoded
# AES-128 uses first 16 characters of each string
key = b"7128793add980992"   # first 16 chars of "7128793add980992ffabbca234123ff"
iv  = b"12390dfffaabb243"   # first 16 chars of "12390dfffaabb24312ccaff123ddbca"

cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
dec = cipher.decryptor()
result = dec.update(enc_data) + dec.finalize()

Path('capy_decrypted.jpg').write_bytes(result)
print(f"[+] Decrypted image: {len(result)} bytes")
print(f"[+] JPEG header: {result[:3]}")
print("[+] Flag is in the decrypted image: CODEBY{REDACTED}")

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

Step-by-Step Summary

  1. Unpack UPX: upx -d capybara.exe -o capybara_unpacked.exe
  2. Analyze outer binary: discover it drops a decoy capybara.jpg (meme image) but has ~315KB of unexplained data in .data
  3. Find hidden PE: recognize 0xFA byte pattern → XOR 0xFA reveals MZ header at .data+0x20
  4. Extract hidden PE: XOR decrypt 315KB blob → valid PE64 with statically linked OpenSSL
  5. Analyze hidden binary: identify AES-128-CBC decryption with key/IV constructed on stack via movabs
  6. Decrypt image: use first 16 ASCII chars of each hex string as raw key/IV bytes
  7. Read flag: decrypted capy.jpg shows capybara with coffee cup and flag text overlay ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍