│ Category: Cryptography
│ Difficulty: Medium
│ Points: 200
│ Author: Imattas aka Zemi
────────────────────────────────────────────────────────────────────────────────
--[ Challenge Description ]--
│ i like peppers. (change!)
────────────────────────────────────────────────────────────────────────────────
--[ Recon / Initial Analysis ]--
This challenge involves a pepper in the context of password
hashing/cryptography. The hint "change!" suggests we need to look at something
that changes -- likely a pepper value that is being XORed, concatenated, or
otherwise combined with data before hashing.
:: What is a Pepper?
In cryptography, a pepper is a secret value added to a password (or plaintext)
before hashing, similar to a salt. The key difference:
- A salt is stored alongside the hash (public)
- A pepper is kept secret (not stored with the hash)
In CTF challenges, "pepper" typically means a short secret that is combined with
known data before hashing. Since the pepper is short, it can be brute-forced.
:: The "Black Cobra" + "change!" Hint
The challenge name "Black Cobra Pepper" references the Black Cobra chili pepper,
a real-world hot pepper. The hint "(change!)" likely refers to:
1. The pepper changes between attempts, OR
2. We need to find what changed (the pepper) by brute-forcing, OR
3. The pepper is applied via XOR (change bits)
────────────────────────────────────────────────────────────────────────────────
--[ Vulnerability / Observation ]--
:: Typical Challenge Structure
The server likely provides:
- A known plaintext or known hash
- A hash computed as H(pepper + plaintext) or H(plaintext + pepper)
- The pepper is a short value (1-4 bytes) that can be brute-forced
The approach is to:
1. Obtain the target hash and any known components
2. Brute-force the pepper value by trying all possibilities
3. Once the correct pepper is found, use it to recover the flag
:: Alternative: XOR-based Pepper
If the scheme uses XOR rather than concatenation:
- ciphertext = plaintext XOR pepper (repeated)
- Since the pepper is short (a few bytes), we can brute-force the key
- The flag format picoCTF{...} gives us known plaintext to verify against
────────────────────────────────────────────────────────────────────────────────
--[ Exploitation / Solution ]--
:: Step-by-step:
1. Connect to the challenge server or download the challenge files.
2. Analyze the encryption/hashing scheme to understand how the pepper is
applied.
3. Identify the pepper length and application method (prepend, append, XOR,
etc.).
4. Brute-force the pepper: try all possible pepper values and check which one
produces a valid result (matches the target hash or decrypts to readable text
starting with picoCTF{).
5. Apply the recovered pepper to obtain the flag.
:: Common Pepper Schemes in CTFs:
- Hash pepper: target_hash = SHA256(pepper + message) -- brute-force pepper,
check hash match
- XOR pepper: ciphertext = message XOR repeat(pepper) -- brute-force XOR key
- HMAC pepper: target = HMAC(pepper, message) -- brute-force the key
- AES with pepper-derived key: pepper expands to a key -- brute-force short
pepper
────────────────────────────────────────────────────────────────────────────────
--[ Full Exploit Script ]--
-- python --
#!/usr/bin/env python3
"""
Black Cobra Pepper - picoCTF 2026
Category: Cryptography (200 points)
Description: "i like peppers. (change!)"
This challenge uses a pepper (secret short key) combined with data before
hashing or encrypting. The pepper is short enough to brute-force.
This script covers the most common pepper-based CTF patterns:
1. Hash pepper: H(pepper || message) or H(message || pepper)
2. XOR pepper: ciphertext = plaintext XOR repeated_pepper
3. HMAC pepper: HMAC(pepper, message)
Usage:
python3 solve.py
python3 solve.py --host <HOST> --port <PORT>
python3 solve.py --file <challenge_file>
"""
import hashlib
import hmac
import itertools
import string
import argparse
import json
import sys
try:
from pwn import remote, log
HAS_PWNTOOLS = True
except ImportError:
HAS_PWNTOOLS = False
import socket
# --- Utility Functions ---
def xor_bytes(data, key):
"""XOR data with a repeating key."""
key_len = len(key)
return bytes(d ^ key[i % key_len] for i, d in enumerate(data))
def brute_force_xor_pepper(ciphertext, pepper_len=1, max_pepper_len=4):
"""
Brute-force XOR pepper. Try all pepper lengths from pepper_len to max_pepper_len.
Returns the pepper and plaintext if flag format is found.
"""
for plen in range(pepper_len, max_pepper_len + 1):
print(f"[*] Trying XOR pepper length: {plen}")
for pepper in itertools.product(range(256), repeat=plen):
pepper_bytes = bytes(pepper)
plaintext = xor_bytes(ciphertext, pepper_bytes)
try:
decoded = plaintext.decode('ascii', errors='strict')
if 'picoCTF{' in decoded:
return pepper_bytes, decoded
except (UnicodeDecodeError, ValueError):
continue
return None, None
def brute_force_hash_pepper(target_hash, known_data, hash_func='sha256',
pepper_len=1, max_pepper_len=4, prepend=True,
charset=None):
"""
Brute-force hash pepper. Tries pepper || data and data || pepper.
Returns the pepper if a match is found.
"""
if charset is None:
charset = list(range(256))
hash_functions = {
'md5': hashlib.md5,
'sha1': hashlib.sha1,
'sha256': hashlib.sha256,
'sha512': hashlib.sha512,
}
h_func = hash_functions.get(hash_func, hashlib.sha256)
if isinstance(known_data, str):
known_data = known_data.encode()
if isinstance(target_hash, str):
target_hash = target_hash.lower()
for plen in range(pepper_len, max_pepper_len + 1):
print(f"[*] Trying hash pepper length: {plen} ({'prepend' if prepend else 'append'}) with {hash_func}")
for pepper_tuple in itertools.product(charset, repeat=plen):
pepper_bytes = bytes(pepper_tuple)
if prepend:
test_input = pepper_bytes + known_data
else:
test_input = known_data + pepper_bytes
digest = h_func(test_input).hexdigest()
if digest == target_hash:
return pepper_bytes
return None
def brute_force_hmac_pepper(target_mac, message, hash_func='sha256',
pepper_len=1, max_pepper_len=4):
"""
Brute-force HMAC pepper.
Returns the pepper if found.
"""
hash_map = {
'md5': 'md5',
'sha1': 'sha1',
'sha256': 'sha256',
'sha512': 'sha512',
}
h_name = hash_map.get(hash_func, 'sha256')
if isinstance(message, str):
message = message.encode()
if isinstance(target_mac, str):
target_mac = target_mac.lower()
for plen in range(pepper_len, max_pepper_len + 1):
print(f"[*] Trying HMAC pepper length: {plen}")
# Try printable ASCII characters first (more likely in CTF)
for pepper_tuple in itertools.product(string.printable.encode(), repeat=plen):
pepper_bytes = bytes(pepper_tuple)
mac = hmac.new(pepper_bytes, message, h_name).hexdigest()
if mac == target_mac:
return pepper_bytes
return None
# --- Server Interaction ---
class Connection:
"""Simple connection wrapper."""
def __init__(self, host, port):
if HAS_PWNTOOLS:
self.conn = remote(host, port)
else:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, port))
self.buffer = b""
def recvuntil(self, delim):
if isinstance(delim, str):
delim = delim.encode()
if HAS_PWNTOOLS:
return self.conn.recvuntil(delim)
while delim not in self.buffer:
data = self.sock.recv(4096)
if not data:
break
self.buffer += data
idx = self.buffer.find(delim)
result = self.buffer[:idx + len(delim)]
self.buffer = self.buffer[idx + len(delim):]
return result
def recvline(self):
return self.recvuntil(b"\n")
def sendline(self, data):
if isinstance(data, str):
data = data.encode()
if HAS_PWNTOOLS:
self.conn.sendline(data)
else:
self.sock.sendall(data + b"\n")
def recv(self, n=4096):
if HAS_PWNTOOLS:
return self.conn.recv(n)
if self.buffer:
result = self.buffer[:n]
self.buffer = self.buffer[n:]
return result
return self.sock.recv(n)
def close(self):
if HAS_PWNTOOLS:
self.conn.close()
else:
self.sock.close()
def solve_interactive(host, port):
"""
Connect to the challenge server and solve interactively.
Adapts based on the server's response format.
"""
print(f"[*] Connecting to {host}:{port}...")
conn = Connection(host, port)
# Read initial data from server
try:
initial = conn.recv(4096).decode(errors='replace')
print(f"[*] Server says:\n{initial}")
except Exception as e:
print(f"[!] Error reading initial data: {e}")
return
# Try to parse the challenge data
# Common formats:
# 1. JSON with hash/ciphertext
# 2. Plain text with hex-encoded data
# 3. Base64-encoded data
try:
data = json.loads(initial)
print(f"[*] Parsed JSON response: {data}")
# Look for common keys
ct = data.get("ciphertext") or data.get("ct") or data.get("encrypted")
target_hash = data.get("hash") or data.get("target") or data.get("digest")
message = data.get("message") or data.get("plaintext") or data.get("data")
if ct:
ct_bytes = bytes.fromhex(ct)
print(f"[*] Ciphertext ({len(ct_bytes)} bytes): {ct[:64]}...")
pepper, flag = brute_force_xor_pepper(ct_bytes)
if flag:
print(f"\n[+] Pepper found: {pepper.hex()}")
print(f"[+] FLAG: {flag}")
conn.close()
return
if target_hash and message:
print(f"[*] Target hash: {target_hash}")
print(f"[*] Known message: {message}")
for h in ['sha256', 'sha1', 'md5']:
for prepend in [True, False]:
pepper = brute_force_hash_pepper(target_hash, message, h,
prepend=prepend, max_pepper_len=3)
if pepper:
print(f"\n[+] Pepper found ({h}, {'prepend' if prepend else 'append'}): {pepper}")
print(f"[+] Pepper (hex): {pepper.hex()}")
# Send pepper back to server to get flag
conn.sendline(pepper.hex())
response = conn.recv(4096).decode(errors='replace')
print(f"[+] Server response: {response}")
conn.close()
return
except (json.JSONDecodeError, ValueError):
# Not JSON -- try to find hex-encoded data in the text
import re
hex_pattern = re.compile(r'[0-9a-fA-F]{16,}')
hex_matches = hex_pattern.findall(initial)
if hex_matches:
for hex_str in hex_matches:
print(f"[*] Found hex data: {hex_str[:64]}...")
try:
ct_bytes = bytes.fromhex(hex_str)
pepper, flag = brute_force_xor_pepper(ct_bytes)
if flag:
print(f"\n[+] Pepper found: {pepper.hex()}")
print(f"[+] FLAG: {flag}")
conn.close()
return
except ValueError:
continue
print("[*] Could not auto-solve. Displaying raw server output for manual analysis.")
conn.close()
def solve_file(filepath):
"""
Solve from a downloaded challenge file.
Tries to read and brute-force XOR pepper or hash pepper.
"""
print(f"[*] Reading challenge file: {filepath}")
with open(filepath, 'rb') as f:
data = f.read()
# Try XOR brute-force first
print("[*] Attempting XOR pepper brute-force...")
pepper, flag = brute_force_xor_pepper(data, pepper_len=1, max_pepper_len=4)
if flag:
print(f"\n[+] Pepper found: {pepper.hex()}")
print(f"[+] FLAG: {flag}")
return
# Try interpreting file as hex
try:
hex_data = data.decode().strip()
ct_bytes = bytes.fromhex(hex_data)
print("[*] Decoded hex data, trying XOR brute-force...")
pepper, flag = brute_force_xor_pepper(ct_bytes)
if flag:
print(f"\n[+] Pepper found: {pepper.hex()}")
print(f"[+] FLAG: {flag}")
return
except (ValueError, UnicodeDecodeError):
pass
print("[!] Could not auto-solve from file. Manual analysis needed.")
print(f"[*] File size: {len(data)} bytes")
print(f"[*] First 64 bytes (hex): {data[:64].hex()}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Black Cobra Pepper Solver - picoCTF 2026")
parser.add_argument("--host", default="rescued-float.picoctf.net",
help="Challenge server hostname")
parser.add_argument("--port", type=int, default=50000,
help="Challenge server port")
parser.add_argument("--file", type=str, default=None,
help="Path to downloaded challenge file")
parser.add_argument("--hash", type=str, default=None,
help="Target hash to crack (hex)")
parser.add_argument("--message", type=str, default=None,
help="Known message/plaintext")
parser.add_argument("--hash-type", type=str, default="sha256",
choices=["md5", "sha1", "sha256", "sha512"],
help="Hash algorithm (default: sha256)")
args = parser.parse_args()
if args.file:
solve_file(args.file)
elif args.hash and args.message:
# Direct hash brute-force mode
print(f"[*] Brute-forcing {args.hash_type} pepper...")
for prepend in [True, False]:
pepper = brute_force_hash_pepper(
args.hash, args.message, args.hash_type,
prepend=prepend, max_pepper_len=4
)
if pepper:
pos = "prepend" if prepend else "append"
print(f"\n[+] Pepper found ({pos}): {pepper}")
print(f"[+] Pepper (hex): {pepper.hex()}")
try:
print(f"[+] Pepper (ascii): {pepper.decode()}")
except UnicodeDecodeError:
pass
sys.exit(0)
print("[!] Pepper not found. Try increasing max length or different hash type.")
else:
solve_interactive(args.host, args.port)
────────────────────────────────────────────────────────────────────────────────
--[ Key Takeaways ]--
- A pepper is a secret short value combined with data before hashing/encryption
(unlike a salt, which is stored publicly). Because it is short (1-4 bytes), it
is brute-forceable.
- Three canonical CTF pepper schemes to test: hash pepper H(pepper||msg) /
H(msg||pepper), XOR pepper ct = msg XOR repeat(pepper), and HMAC pepper.
- The known flag prefix picoCTF{ is the oracle: for XOR you brute-force the key
and check the decrypted text starts with it; for hashing you brute-force the
pepper and match the digest.
- Iterate over pepper lengths and both prepend/append positions across multiple
hash algorithms (md5/sha1/sha256/sha512) since the scheme is unknown.