home intel cve-2026-42484-hashcat-pkzip-heap-overflow-rce
CVE Analysis 2026-05-01 · 8 min read

CVE-2026-42484: Heap Overflow in hashcat PKZIP Hash Parser

A heap-based buffer overflow in hashcat's hex_to_binary routine allows attacker-controlled PKZIP hash input to corrupt adjacent heap chunks, enabling denial-of-service or arbitrary code execution.

#heap-buffer-overflow#pkzip-hash-parser#input-validation#remote-code-execution#hashcat
Technical mode — for security professionals
▶ Attack flow — CVE-2026-42484 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2026-42484Cross-platform · CRITICALCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-42484 is a heap-based buffer overflow in hashcat v7.1.2 residing in hex_to_binary(), called from the PKZIP hash module parser. The bug surfaces when hashcat processes hash lines for modules 17200, 17210, 17220, 17225, and 17230. When the parsed data_type_enum field is ≤ 1, the parser decodes attacker-supplied hex data directly into a fixed-size stack or heap buffer without first validating that the hex string length is bounded by the destination capacity. A crafted hash file can overflow the buffer by an arbitrary number of bytes into adjacent heap metadata or heap objects.

CVSS 9.8 (Critical) is justified: the attack is unauthenticated, requires only a crafted hash file passed via the standard CLI or through an automated pipeline, and hashcat is commonly embedded in password-auditing services that accept remote hash submissions.

Affected Component

The vulnerable call chain lives inside hashcat's module dispatch layer:

src/modules/module_17200.c  (and _17210, _17220, _17225, _17230)
  └─ module_hash_decode()
       └─ pkzip_parse_hash()           // parses the *pkzip$ format
            └─ hex_to_binary()         // BUG: no length check on input

All five module files share an identical copy of the PKZIP hash parsing logic — a classic copy-paste vulnerability surface. The shared utility hex_to_binary() lives in src/convert.c and is also used by other modules, but only the PKZIP path reaches it with an unchecked length when data_type_enum ≤ 1.

Root Cause Analysis

The PKZIP hash format encodes file data as a hex string following a structured prefix. The parser extracts the hex portion, allocates a fixed-size destination buffer sized to the expected maximum, then calls hex_to_binary() passing the raw pointer to the hex string — without clamping the conversion length to the buffer capacity.

// src/modules/module_17200.c  (reconstructed pseudocode)

#define PKZIP_MAX_DATA_LEN  1024   // 1 KB fixed ceiling for type<=1

typedef struct pkzip_file {
    /* +0x000 */ u32   data_type_enum;
    /* +0x004 */ u32   magic;
    /* +0x008 */ u32   crc32;
    /* +0x00c */ u32   compressed_len;
    /* +0x010 */ u32   uncompressed_len;
    /* +0x014 */ u8    data[PKZIP_MAX_DATA_LEN];  // fixed 1 KB
    /* +0x414 */ u32   checksum;
} pkzip_file_t;

int pkzip_parse_hash(const char *input_line, pkzip_file_t *pf)
{
    char *pos = strstr(input_line, "*pkzip$");
    if (!pos) return PARSER_HASH_LENGTH;

    // Parse metadata fields (abbreviated for clarity)
    pf->data_type_enum   = strtoul(/* field */, NULL, 10);
    pf->compressed_len   = strtoul(/* field */, NULL, 16);
    pf->uncompressed_len = strtoul(/* field */, NULL, 16);

    // Locate the hex payload pointer inside the input line
    char *hex_payload = /* advance pos past metadata fields */;
    size_t hex_len    = strnlen(hex_payload, /* remaining line */);

    if (pf->data_type_enum <= 1)
    {
        // BUG: hex_to_binary() converts hex_len/2 bytes into pf->data[].
        // pf->data is PKZIP_MAX_DATA_LEN (0x400) bytes, but hex_len is
        // derived from attacker-supplied input with no upper-bound clamp.
        // A hex string longer than 0x800 hex chars overflows pf->data[].
        hex_to_binary(hex_payload, hex_len, pf->data);  // BUG: missing bounds check here
    }
    return PARSER_OK;
}
// src/convert.c  (reconstructed pseudocode)

void hex_to_binary(const char *in, size_t in_len, u8 *out)
{
    // Converts pairs of hex chars to bytes.
    // Writes (in_len / 2) bytes to out — no capacity parameter.
    for (size_t i = 0; i < in_len - 1; i += 2)
    {
        out[i / 2] = (hex_char_to_nibble(in[i]) << 4)
                   |  hex_char_to_nibble(in[i + 1]);
        // BUG: out[] capacity is never consulted; i/2 can exceed 0x3FF
    }
}
Root cause: hex_to_binary() accepts no capacity parameter and writes in_len/2 bytes unconditionally, while the PKZIP parser passes an attacker-controlled in_len without clamping it to sizeof(pkzip_file_t.data) (0x400 bytes).

Memory Layout

hashcat allocates pkzip_file_t structures on the heap inside hashes_init_stage1(). In a typical 64-bit glibc run, the allocator places successive hash structures contiguously. The overflow walks from the end of pf->data[] through pf->checksum and into the next heap chunk.

struct pkzip_file_t layout (64-bit):
    /* +0x000 */ u32  data_type_enum      // 4 bytes
    /* +0x004 */ u32  magic               // 4 bytes
    /* +0x008 */ u32  crc32               // 4 bytes
    /* +0x00c */ u32  compressed_len      // 4 bytes
    /* +0x010 */ u32  uncompressed_len    // 4 bytes
    /* +0x014 */ u8   data[0x400]         // 1024 bytes  <-- write starts here
    /* +0x414 */ u32  checksum            // 4 bytes
    /* +0x418 */ u8   _pad[4]             // alignment
    // sizeof == 0x41c (1052 bytes), heap chunk = 0x420 w/ glibc header
HEAP STATE BEFORE OVERFLOW:
  [ glibc chunk hdr  0x421 (PREV_INUSE)           ]
  [ pkzip_file_t #0  @ heap+0x010                 ]
    +0x014: data[0x400] = { target bytes here }
    +0x414: checksum    = 0x00000000
  [ glibc chunk hdr  0x421 (PREV_INUSE)           ]  <-- next chunk header
  [ pkzip_file_t #1  @ heap+0x430                 ]

HEAP STATE AFTER OVERFLOW (hex string of 0x1000 hex chars → 0x800 bytes written):
  [ pkzip_file_t #0 @ heap+0x010                  ]
    +0x014..+0x413: data[]       overwritten
    +0x414:         checksum     overwritten (attacker byte 0x3FF)
    +0x418..+0x41b: pad          overwritten
  [ CORRUPTED chunk hdr @ heap+0x430              ]
    size field = 0x41414141      <-- attacker controlled (bytes 0x41c-0x41f)
    fd pointer = 0x4242424242424242  <-- attacker controlled (bytes 0x420-0x427)
  [ pkzip_file_t #1 CLOBBERED                     ]
    data_type_enum = attacker bytes ...

Exploitation Mechanics

Full exploitation requires bypassing glibc heap metadata integrity checks (safe-linking, tcache key validation). On targets without those mitigations — embedded toolchains shipping hashcat, older musl-libc builds, or Windows builds using hashcat's bundled allocator — exploitation is more direct.

EXPLOIT CHAIN:

1. Craft a malicious PKZIP hash file:
   - Set data_type_enum = 0 or 1 (triggers the vulnerable branch)
   - Set compressed_len to a plausible small value (evades early length checks)
   - Supply a hex payload of 0x1000+ hex characters (→ 0x800+ decoded bytes)
     overflowing 0x400 bytes past the end of pf->data[]

2. Feed the hash file to hashcat:
   $ hashcat -m 17200 crafted.hash /dev/null
   Parsing triggers pkzip_parse_hash() -> hex_to_binary() overflow.

3. Overflow glibc chunk header of pkzip_file_t #1:
   - Write fake size field: satisfies IS_MMAPPED / PREV_INUSE constraints
   - Write fake fd/bk pointers into tcache or unsorted-bin free list

4. Trigger heap consolidation:
   - hashcat's hash deduplication frees duplicate pkzip_file_t entries.
   - free() on the corrupted chunk walks the corrupted size field.
   - On musl / no-tcache builds: unlink() writes fd into bk->fd,
     pivoting a write-what-where primitive.

5. Overwrite a function pointer:
   - Target: hashcat's OpenCL dispatch table (cl_function_table_t*),
     allocated immediately after hash structures in the same arena.
   - Corrupt clEnqueueNDRangeKernel pointer → shellcode / one-gadget.

6. Shellcode executes in the hashcat process context when the first
   kernel dispatch is attempted after hash loading completes.

DENIAL-OF-SERVICE (reliable, no mitigations required):
   - Any overflow of the glibc chunk size field triggers abort() via
     malloc_printerr() in the next allocation after hash parsing:
     *** glibc detected *** hashcat: corrupted size vs. prev_size

Patch Analysis

The correct fix requires two coordinated changes: adding a capacity parameter to hex_to_binary(), and clamping the decoded length inside pkzip_parse_hash() before the call.

// BEFORE (vulnerable) — src/convert.c:
void hex_to_binary(const char *in, size_t in_len, u8 *out)
{
    for (size_t i = 0; i < in_len - 1; i += 2)
        out[i / 2] = (hex_char_to_nibble(in[i]) << 4)
                   |  hex_char_to_nibble(in[i + 1]);
}

// BEFORE (vulnerable) — src/modules/module_17200.c:
if (pf->data_type_enum <= 1)
    hex_to_binary(hex_payload, hex_len, pf->data);


// AFTER (patched):
// src/convert.c — add out_capacity parameter:
int hex_to_binary_safe(const char *in, size_t in_len,
                        u8 *out, size_t out_capacity)
{
    size_t out_len = in_len / 2;
    if (out_len > out_capacity) return -1;  // reject oversized input
    for (size_t i = 0; i < in_len - 1; i += 2)
        out[i / 2] = (hex_char_to_nibble(in[i]) << 4)
                   |  hex_char_to_nibble(in[i + 1]);
    return 0;
}

// src/modules/module_17200.c (and 17210/17220/17225/17230):
if (pf->data_type_enum <= 1)
{
    // Clamp hex_len to what the fixed data[] buffer can hold
    if (hex_len > PKZIP_MAX_DATA_LEN * 2)  // 0x800 hex chars max
        return PARSER_HASH_LENGTH;          // reject malformed input early

    if (hex_to_binary_safe(hex_payload, hex_len,
                            pf->data, sizeof(pf->data)) != 0)
        return PARSER_HASH_LENGTH;
}

The secondary hardening is the early-rejection PARSER_HASH_LENGTH return before the allocation is even used, ensuring the parser never reaches hex_to_binary_safe() with a length that would require the capacity guard — defense in depth across both the caller and callee.

Detection and Indicators

Runtime crash signatures: A reliable DoS trigger produces one of the following on glibc systems:

hashcat: malloc.c:2379: sysmalloc: Assertion `(old_top == initial_top (av)
  && old_size == 0) || ...' failed.
Aborted (core dumped)

-- or --

*** Error in `hashcat': corrupted size vs. prev_size: 0x00007f... ***

Hash file indicators of compromise:

- Mode 17200/17210/17220/17225/17230 hash lines where the hex data field
  length (character count between colons) exceeds 0x800 characters
- data_type_enum field = 0 or 1 with anomalously long trailing hex segment
- Hex payload containing non-hex characters padded with 0x41 ('A') runs
  (indicates fuzzer or PoC tooling)

Static detection rule (YARA):

rule CVE_2026_42484_pkzip_overflow {
    strings:
        // *pkzip$ followed by type<=1 field, then >2048 hex chars
        $pkzip_long = /\*pkzip\$[0-9a-f]+\*[01]\*[0-9a-f]{2049,}/
    condition:
        $pkzip_long
}

Remediation

Immediate: Upgrade to a patched build of hashcat once a fixed release is published. Monitor the official hashcat GitHub repository for a tagged release addressing CVE-2026-42484.

Operational mitigations until patched:

  • Validate hash files with a pre-flight script that rejects any PKZIP hash line (-m 17200/17210/17220/17225/17230) where the hex data segment exceeds 2048 characters before passing to hashcat.
  • Run hashcat inside a sandboxed environment (seccomp-bpf, Bubblewrap, or firejail) that restricts exec and outbound network syscalls to contain post-exploitation impact.
  • If PKZIP cracking (modes 17200–17230) is not operationally required, block those modes via a wrapper script that rejects -m 172{0,1,2}* arguments.
  • On shared cracking infrastructure accepting user-submitted hash files, treat all submitted PKZIP hashes as untrusted and apply the YARA rule above at ingestion time.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →