cybersecurity · advanced · ~15 min · safe pentest lab

Spot the ECB Fingerprint

Recognise the ECB-mode ciphertext fingerprint (repeated plaintext -> repeated ciphertext blocks) and write a bounds-safe detector that validates attacker-controlled length and block-size arguments before indexing, denying malformed input by default.

Challenge

The problem

AES in ECB mode encrypts every plaintext block independently, so identical plaintext blocks always produce identical ciphertext blocks. That determinism is a leak: an attacker who can see the ciphertext learns which plaintext regions repeat (the classic "ECB penguin"). During a defensive assessment you want a detector that flags this fingerprint in captured ciphertext so you can tell a developer their crypto is broken.

Asset: a fixed buffer of captured ciphertext baked into the test harness. Threat: confidentiality loss from block-level plaintext structure leaking through ECB. Your detector only reads that in-memory array — never a live target, socket, or file.

Your task

Implement has_ecb_repeats(ct, n, block). Return 1 if any two whole block-sized chunks of ct are byte-for-byte identical, otherwise 0.

The dangerous, insecure assumption to avoid is trusting the caller's sizes. You must deny by default: reject block == 0 and a NULL pointer, and only ever read bytes inside [0, (n / block) * block). A trailing partial block (fewer than block bytes) is ignored, never compared.

Edge cases

  • block == 0 → return 0 (never divide or index by it).
  • ct == NULL → return 0.
  • fewer than two whole blocks (n / block < 2) → return 0.
  • a partial trailing block must not be read past n.

Example

has_ecb_repeats(repeat, 16, 4)   -> 1   /* blocks 0 and 2 are identical */
has_ecb_repeats(distinct, 16, 4) -> 0   /* every block differs */

Input format

A pointer ct to n bytes of ciphertext, and a chunk size block. All three are supplied by the (untrusted) caller; sizes may be zero or larger than the buffer's usable region.

Output format

Return int 1 if at least two whole block-sized chunks of ct are identical, else 0. Invalid inputs (block==0, NULL) return 0.

Constraints

C11, no dynamic allocation. Compare only whole blocks: read strictly within [0, (n/block)*block). block may be 0 and ct may be NULL — handle both without undefined behaviour. Do not modify ct.

Starter code

int has_ecb_repeats(const unsigned char *ct, size_t n, size_t block)
{
    /* TODO: detect two identical `block`-sized chunks in ct.
       This insecure stub trusts the input and reports nothing. */
    (void)ct; (void)n; (void)block;
    return -1;
}

Common mistakes

Computing the block count as (n + block - 1) / block or otherwise rounding up, which reads a partial block past valid data; looping while i*block < n so the final compare overruns the buffer; forgetting to check block == 0 and hitting a division/modulo by zero; dereferencing ct without a NULL guard; using == on pointers or comparing only the first byte instead of memcmp over the whole block; returning a truthy non-1 value like -1 that the grader treats as wrong.

Edge cases to handle

block == 0 (must not divide/index by zero); ct == NULL; n == 0 (empty buffer); n smaller than one block; exactly one whole block (no pair to compare); n not a multiple of block (trailing partial block must be ignored, not read); the minimal positive case of two equal 1-byte blocks.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.