Decode the Calldata: Peeling Hex, Base64 and a Repeating XOR
The challenge
A long hex string was pulled from a suspicious smart-contract transaction and tagged as 'EVM calldata' by the indexer. It is not really calldata - it is a message hidden under three stacked layers. Reverse them in the workbench and submit the hidden flag (HDNA{...}).
What you'll learn
- Recognise that hex-only characters point to a hex transport layer
- Peel three stacked transforms in the correct inverse order
- Identify a base64 blob sitting inside the hex layer by its character set and padding
- Apply a short repeating-key XOR to recover the underlying plaintext
- Rule out decoy operations whose input format does not match the data
Skills tested
Prerequisites
- Comfort reading hex and base64
- Understanding that XOR is its own inverse
- Basic idea of what EVM calldata looks like
How it works
On-chain data is just bytes, and bytes are easy to disguise. Here someone took a short plaintext, XOR-encrypted it with a tiny repeating key, base64-encoded the result so it survives as text, then hex-encoded that string so it would pass for transaction calldata. Real calldata starts with a 4-byte function selector and is ABI-encoded; this blob has none of that structure, which is the first tell that the 'calldata' label is a disguise.
Layers come off in the reverse order they went on. The outermost layer is hex, because every character is in 0-9a-f - decode it and you get a printable base64 string. Decode that and you are holding raw bytes that are still unreadable, because they were XOR-encrypted. XOR is symmetric: re-applying the same key with the same operation cancels it, since A XOR B XOR B = A. The key here is just the two bytes tx, repeated across the message.
The workbench models this as a recipe where each operation feeds the next. Add From Hex, then From Base64, then XOR with key tx, and the flag resolves live in the output. The other operations (ROT, Vigenere, atbash, reverse, URL-decode) are decoys - their input shapes never match what you are holding at each stage.
Common mistakes
- Trusting the 'calldata' label. The data has no ABI structure or function selector - it was never calldata, just bytes dressed up as it.
- Peeling in the wrong order. Trying base64 before hex, or XOR before either decode, yields garbage. Outermost layer first.
- Guessing a long XOR key. The key is two bytes; do not brute-force what the brief hands you.
- Adding decoy operations. ROT and Vigenere act on letters, not raw bytes - check the data shape before adding an op.
How to defend against it
For defenders, the takeaway is that wrapping a payload in hex and base64 over a static XOR key is obfuscation, not protection - it only slows a casual look, never a determined analyst. Treat any field that 'should' be structured (calldata, a token, a cookie) but is not as suspicious, and decode it.
- Validate that on-chain calldata actually matches the contract ABI before trusting its label.
- Auto-decode and XOR-scan unstructured hex and base64 blobs during triage to surface hidden messages.
- Use authenticated encryption with a managed key when data must truly be confidential.
- Treat any short repeating-key XOR as fully recoverable, by you and by adversaries alike.