finding-secrets
practicalLesson 5 — Finding Secrets
The Systematic Approach
Ad-hoc grepping finds nothing reproducibly. A systematic workflow applied consistently across firmware images produces results and lets you compare findings across versions. This lesson covers that workflow in order of signal-to-noise ratio.
Work from squashfs-root/ as your working directory throughout.
Step 1: Credential Files
Start with the most obvious targets. These are files whose entire purpose is to store credentials:
# /etc/passwd and /etc/shadow — always check first
cat etc/passwd
cat etc/shadow 2>/dev/null
# Look for non-standard shadow files (some vendors use custom names)
find etc/ -name "shadow*" -o -name "passwd*" 2>/dev/null
# Look for htpasswd files used by web servers
find . -name ".htpasswd" -o -name "htpasswd" 2>/dev/null
cat www/.htpasswd 2>/dev/null
Crack any MD5-crypt hashes immediately — they are fast:
john --format=md5crypt --wordlist=/usr/share/wordlists/rockyou.txt etc/shadow
# or
hashcat -m 500 etc/shadow /usr/share/wordlists/rockyou.txt
Step 2: Config File Scan
Scan all configuration files for credential keywords. Use -l first to get a file list, then read the specific files — this avoids noise from binary files:
# Get list of files with credential-related strings
grep -rEil "passw(or)?d|passwd|secret|apikey|api_key|token|credential" \
etc/ usr/ lib/ 2>/dev/null
# Then read the relevant config sections
grep -rEi "passw(or)?d|passwd|secret|apikey|api_key|token" \
etc/ --include="*.conf" --include="*.cfg" --include="*.ini" \
--include="*.json" --include="*.yaml" --include="*.xml" 2>/dev/null
OpenWrt UCI configs — always worth a direct read:
cat etc/config/wireless # WiFi passwords
cat etc/config/network # PPPoE credentials, sometimes
cat etc/config/system # System password, NTP, hostname
Vendor-specific config files:
# TP-Link often uses /etc/reduced_data_model.xml or similar
find etc/ -name "*.xml" | xargs grep -li "pass\|key\|secret" 2>/dev/null
# Asus uses nvram-style storage
find etc/ -name "nvram*" 2>/dev/null
Step 3: Private Key and Certificate Search
# PEM-encoded private keys
grep -rl "BEGIN RSA PRIVATE KEY\|BEGIN PRIVATE KEY\|BEGIN EC PRIVATE KEY" . 2>/dev/null
find . -name "*.pem" -o -name "*.key" -o -name "id_rsa" -o -name "id_ecdsa" 2>/dev/null
# Self-signed certificates (the private key may be bundled)
grep -rl "BEGIN CERTIFICATE" . 2>/dev/null
# PKCS12 / JKS files (binary, but findable)
find . -name "*.p12" -o -name "*.pfx" -o -name "*.jks" 2>/dev/null
A private key found in firmware is a significant finding. If all devices of that model ship the same key (common), any device with that key installed can be impersonated, or the key can be used to decrypt TLS sessions captured from any device of that model.
Extract and inspect any certificate you find:
openssl x509 -in etc/ssl/server.crt -noout -text
# Check: Issuer == Subject = self-signed, which means vendor-generated
# Check: Valid dates — often set to 2037 or 2099, indicating static cert
Step 4: String Analysis on Binaries
Stripped ELF binaries do not have symbol names, but string constants remain in the .rodata section. This is where hardcoded credentials live.
# The httpd binary is the primary target for web auth credentials
strings -n 8 usr/sbin/httpd | grep -Ei "admin|pass|key|secret|auth|login"
# -n 8 sets minimum string length to 8 bytes, reducing noise
# Check all interesting binaries
for bin in usr/bin/cloud_client usr/sbin/telnetd usr/sbin/tr069d; do
echo "=== $bin ==="; strings -n 8 "$bin" | grep -Ei "pass|key|secret|token"
done 2>/dev/null
# Find hardcoded IPs — may indicate cloud endpoints or debug servers
strings usr/sbin/httpd | grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}"
# Find hardcoded URLs
strings usr/sbin/httpd | grep -Ei "https?://|ftp://"
Specific pattern for encoded or obfuscated values:
# Base64 strings (often contain encoded credentials)
strings -n 20 usr/sbin/httpd | grep -E "^[A-Za-z0-9+/]{20,}={0,2}$"
# Decode any candidates
echo "YWRtaW46cGFzc3dvcmQ=" | base64 -d
# admin:password
Step 5: NVS-Style Binary Storage
Many embedded devices use non-filesystem key-value storage: NVRAM dumps, NVS partitions (common on ESP32 in IoT sensors), or proprietary binary config blobs. These appear as binary files with no obvious structure but contain readable strings interspersed with binary data.
# Find binary files that contain readable strings (candidates for NVS/NVRAM)
find . -type f -name "*.bin" -o -name "*.nvs" -o -name "nvram" 2>/dev/null | while read f; do
count=$(strings -n 4 "$f" | wc -l)
echo "$count strings: $f"
done
# Run strings with short minimum length to catch short keys/values
strings -n 4 etc/nvram.bin | less
# Look for key=value patterns in binary files
strings -n 4 etc/nvram.bin | grep -E "=.{3,}"
ESP32 NVS format specifically: The NVS partition uses a page-based format with 32-byte entries. Each entry contains a namespace, key, and value. The esp-idf-nvs-partition-gen tool can parse these, or you can extract strings directly:
# Direct string extraction from NVS dump
strings -n 4 nvs_partition.bin | grep -A1 "wifi\|pass\|ssid\|key\|user"
This is the technique used in the Dead Bytes challenge — the flag is stored as an NVS-style key-value pair within the firmware blob.
Step 6: WiFi Credentials
# WPA supplicant configs
find . -name "wpa_supplicant.conf" 2>/dev/null
grep -r "psk\|PSK\|passphrase\|password" etc/ 2>/dev/null | grep -i "wpa\|wifi\|wlan\|ssid"
# hostapd configs (access point mode)
find . -name "hostapd.conf" 2>/dev/null
cat etc/hostapd.conf 2>/dev/null | grep -E "ssid|wpa_passphrase|wep_key"
Step 7: Hardcoded API Keys and Tokens
Cloud-connected devices often have hardcoded API keys for their vendor's cloud service:
# AWS credentials
grep -r "AKIA[0-9A-Z]{16}" . 2>/dev/null
# Generic API key patterns
strings -n 20 usr/bin/cloud_agent 2>/dev/null | \
grep -E "[A-Fa-f0-9]{32,}|[A-Za-z0-9_\-]{40,}"
# MQTT credentials (common in IoT cloud connectivity)
grep -r "mqtt\|broker\|clientid\|username\|password" etc/ usr/ 2>/dev/null | \
grep -v ".pyc\|Binary"
Connecting to the Challenge
The Dead Bytes challenge provides a firmware binary. The workflow for solving it:
# 1. Initial identification
binwalk dead_bytes.bin
# 2. Extract
binwalk -e dead_bytes.bin
cd _dead_bytes.bin.extracted/
# 3. If NVS-style blob detected, run strings
strings -n 4 *.bin | grep -i "pass\|user\|secret\|key\|flag\|espilon"
# 4. If a squashfs-root exists, apply the full workflow:
grep -rl "passw\|secret\|key" squashfs-root/etc/ 2>/dev/null
strings -n 8 squashfs-root/usr/sbin/httpd | grep -i "pass\|admin\|secret"
# 5. NVS key-value extraction
strings -n 4 squashfs-root/etc/nvs_storage.bin | grep -E ".+=.+"
The credential is stored using an NVS-style key-value structure. strings -n 4 with a targeted grep reveals it. The flag format is ESPILON{...}.
Summary Workflow
# ---- Complete secret-hunting workflow ----
# 0. Set working directory
cd _firmware.bin.extracted/squashfs-root
# 1. Direct credential files
cat etc/passwd; cat etc/shadow 2>/dev/null
# 2. Config file scan
grep -rEi "passw|secret|key|token|api" etc/ usr/ \
--include="*.conf" --include="*.cfg" --include="*.ini" \
--include="*.json" --include="*.xml" 2>/dev/null -l
# 3. Private keys
find . -name "*.pem" -o -name "*.key" -o -name "id_rsa" 2>/dev/null
grep -rl "BEGIN.*PRIVATE KEY\|BEGIN CERTIFICATE" . 2>/dev/null
# 4. Binary secrets
strings -n 8 usr/sbin/httpd | grep -Ei "admin|pass|key|secret|auth"
# 5. NVS / NVRAM blobs
find . -name "*.nvs" -o -name "nvram*" -o -name "*.cfg" \
-type f 2>/dev/null | xargs -I{} strings -n 4 {} | grep -E "=.{3,}"
# 6. WiFi creds
grep -r "psk\|passphrase\|wpa_key" etc/ 2>/dev/null
# 7. Certificates
grep -rl "BEGIN CERTIFICATE" . 2>/dev/null | xargs -I{} openssl x509 -in {} -noout -subject 2>/dev/null
Each step narrows the target. Most firmwares yield at least one credential within the first three steps.