CVE-2026-42482: Stack Overflow in hashcat's Hex Mangle Functions
A bounds check in hashcat's mangle_to_hex_lower/upper fails to account for 2x byte expansion during hex encoding, enabling stack corruption via crafted rules or long password candidates.
Hashcat is software that security researchers and hackers use to guess passwords. Think of it as a specialized tool for testing whether passwords are strong enough, or for recovering lost ones. It's widely used by both IT security teams and people with less noble intentions.
Security researchers found a critical flaw in version 7.1.2 of hashcat. The problem is in how the software converts passwords into a different format called hexadecimal. When you feed the software a very long password—128 characters or more—the conversion process doesn't allocate enough space in memory to store the result. It's like trying to pour a gallon of milk into a pint glass without a lid.
When this happens, the extra data overflows into adjacent memory. An attacker who knows about this flaw could exploit it by creating a malicious rule file (a set of instructions for the software) that triggers this overflow. The result could be anything from crashing the program to potentially taking complete control of the computer running hashcat.
Who should actually worry? System administrators and security professionals using hashcat on their own networks are most at risk, especially if they're processing rules or passwords from untrusted sources. If you're not using this specific software, you're not affected.
If you do use hashcat: First, stop using version 7.1.2 immediately and upgrade to a newer version once it's patched. Second, never run hashcat with password rules or files from sources you don't trust. Third, only run hashcat on isolated machines that aren't connected to critical systems or sensitive data. Security tools are useful, but only if they don't become security problems themselves.
Want the full technical analysis? Click "Technical" above.
CVE-2026-42482 is a stack-based buffer overflow in hashcat v7.1.2 affecting two rule functions: mangle_to_hex_lower() and mangle_to_hex_upper(), both implemented in src/rp_cpu.c. The functions convert raw password bytes into their two-character hexadecimal ASCII representation — a transformation that doubles the output length. The bug is a classic expansion overflow: the output buffer is allocated based on the input length, not the expanded output length, so any password candidate of 128 bytes or more will write beyond the end of a stack-allocated buffer.
The vulnerability is reachable via two distinct attack surfaces: a crafted rule file processed at startup, or the -j / -k rule options passed directly on the command line. Because hashcat is frequently deployed in automated pipeline tooling and cloud cracking infrastructure, a malicious wordlist combined with a rule file is a credible remote trigger. CVSS 9.8 reflects unauthenticated, network-adjacent exploitability in those deployment models.
Root cause:mangle_to_hex_lower() and mangle_to_hex_upper() allocate a fixed-size stack buffer sized to RP_PASSWORD_SIZE (128 bytes) without doubling the capacity to accommodate hexadecimal expansion, allowing a 128-character input to write 256 bytes and overflow the buffer by 128 bytes.
Affected Component
File: src/rp_cpu.c — the CPU-side rule processor. This module applies transformation rules to each password candidate before the hash comparison step. The mangle_to_hex_lower() rule corresponds to rule opcode B (lowercase hex encode) and mangle_to_hex_upper() to opcode X (uppercase hex encode) in hashcat's rule engine. Both share the same structural flaw.
The rule processing loop in rp_apply_rules_cpu() operates on a local stack buffer of RP_PASSWORD_SIZE bytes (defined as 128 in include/rp.h). This same buffer is passed into the mangle functions, which write their output back into it in-place.
Root Cause Analysis
// include/rp.h
#define RP_PASSWORD_SIZE 128
// src/rp_cpu.c — simplified decompiled pseudocode
int mangle_to_hex_lower(char *in, int in_len)
{
// BUG: 'in' points to a RP_PASSWORD_SIZE (128-byte) stack buffer.
// Each input byte expands to 2 hex chars: max safe input is 64 bytes.
// No check that (in_len * 2) fits within RP_PASSWORD_SIZE.
char tmp[RP_PASSWORD_SIZE]; // second 128-byte stack buffer for swap
int out_len = 0;
for (int i = 0; i < in_len; i++)
{
unsigned char byte = (unsigned char) in[i];
// Writes 2 bytes per iteration into tmp[out_len]
tmp[out_len++] = hex_lower[(byte >> 4) & 0xf]; // high nibble
tmp[out_len++] = hex_lower[(byte ) & 0xf]; // low nibble
// BUG: out_len is never bounds-checked against RP_PASSWORD_SIZE.
// When in_len == 128, out_len reaches 256 — 128 bytes past tmp[].
}
memcpy(in, tmp, out_len); // BUG: copies oversized out_len back into
// the same 128-byte caller stack buffer.
return out_len;
}
int mangle_to_hex_upper(char *in, int in_len)
{
char tmp[RP_PASSWORD_SIZE]; // identical layout, same overflow
int out_len = 0;
for (int i = 0; i < in_len; i++)
{
unsigned char byte = (unsigned char) in[i];
tmp[out_len++] = hex_upper[(byte >> 4) & 0xf];
tmp[out_len++] = hex_upper[(byte ) & 0xf];
// BUG: same missing bounds check — out_len unconstrained
}
memcpy(in, tmp, out_len); // BUG: overflows caller's 128-byte buffer
return out_len;
}
// Caller context — rp_apply_rules_cpu() (src/rp_cpu.c)
int rp_apply_rules_cpu(const char *rule_buf, const int rule_len,
char *in, const int in_len)
{
char out[RP_PASSWORD_SIZE]; // 128 bytes on stack
int out_len = in_len;
memcpy(out, in, in_len);
for (int i = 0; i < rule_len; /* ... */)
{
char rule_cmd = rule_buf[i];
switch (rule_cmd)
{
/* ... */
case RULE_OP_MANGLE_TO_HEX_LOWER: // opcode 'B'
out_len = mangle_to_hex_lower(out, out_len);
// 'out' is 128 bytes; if out_len was 128, mangle writes 256.
break;
case RULE_OP_MANGLE_TO_HEX_UPPER: // opcode 'X'
out_len = mangle_to_hex_upper(out, out_len);
break;
/* ... */
}
}
return out_len;
}
The tmp[] buffer inside each mangle function overflows first (intra-function), then the oversized memcpy back into the caller's out[] buffer causes a second overflow in the caller's frame. On a 64-bit Linux build with GCC default stack layout, both overflows are live during a single rule application pass.
Memory Layout
// Approximate stack layout for mangle_to_hex_lower() frame (x86-64, no canary)
// Growing downward, higher addresses at top
struct mangle_frame {
/* -0x000 */ char tmp[128]; // RP_PASSWORD_SIZE — output buffer
/* -0x080 */ int out_len; // expansion counter
/* -0x084 */ int i; // loop counter
/* -0x088 */ char *in; // pointer to caller's 'out[]'
/* -0x090 */ int in_len;
/* -0x098 */ uint64_t saved_rbp;
/* -0x0a0 */ uint64_t saved_rip; // <-- corruption target
};
Because the overflow bytes are the hex-encoded representation of attacker-supplied input characters, each two-byte hex pair is constrained to printable ASCII in [0-9a-f] (lower) or [0-9A-F] (upper). The saved RIP overwrite at offset +0x90 from tmp[0] requires 9 full input bytes past the 64-byte safe boundary — trivially satisfiable with a 128-byte password candidate. A BROP-style info-leak or known binary base (no PIE) removes the constraint entirely.
Exploitation Mechanics
EXPLOIT CHAIN (rule file delivery, Linux x86-64, no PIE, no stack canary):
1. Craft password candidate of exactly 128 bytes.
Bytes [0..63] : arbitrary (populate valid hex region of tmp[])
Bytes [64..71] : encode desired saved_rbp value as 8 input bytes
(each input byte B produces hex pair "XY" in tmp[])
=> select B such that hex(B) encodes target nibbles
Bytes [72..79] : encode desired saved_rip / ROP gadget address
(same nibble-selection technique)
Bytes [80..127]: padding to reach in_len == 128
2. Write rule file containing single rule line: "B" (or "X" for upper)
Deliver via filesystem or stdin pipe in automated cracking pipeline.
3. hashcat loads rule, begins candidate processing.
rp_apply_rules_cpu() copies candidate into stack 'out[128]'.
Dispatches to mangle_to_hex_lower(out, 128).
4. Loop runs 128 iterations. tmp[128..255] written — overflows into
saved_rbp and saved_rip on the mangle frame.
memcpy(in, tmp, 256) then overflows the caller's 'out[128]' frame.
5. mangle_to_hex_lower() returns. Corrupted saved_rip is loaded into RIP.
6. Execution redirected to:
- DoS: NULL / invalid address => SIGSEGV, process crash
- RCE: ROP chain pivot => mprotect() => shellcode (no-PIE scenario)
or ret2libc => system("/bin/sh") via gadget chain
CONSTRAINTS:
- Output bytes restricted to [0-9a-f] / [0-9A-F] — partial address control
- Stack canary (default GCC/Clang builds) mitigates saved_rip overwrite;
canary leak required for full RCE on hardened builds
- ASLR + PIE: info-leak primitive required for gadget address resolution
- DoS (crash) is unconditional and requires no bypass
Patch Analysis
// BEFORE (vulnerable — src/rp_cpu.c, hashcat v7.1.2):
#define RP_PASSWORD_SIZE 128
int mangle_to_hex_lower(char *in, int in_len)
{
char tmp[RP_PASSWORD_SIZE]; // fixed 128-byte buffer
int out_len = 0;
for (int i = 0; i < in_len; i++)
{
unsigned char byte = (unsigned char) in[i];
tmp[out_len++] = hex_lower[(byte >> 4) & 0xf];
tmp[out_len++] = hex_lower[(byte ) & 0xf];
// BUG: no check: if (out_len + 2 > RP_PASSWORD_SIZE) break;
}
memcpy(in, tmp, out_len); // BUG: out_len may be up to 256
return out_len;
}
// AFTER (patched):
// Option A — enlarge buffer to hold maximum expanded output:
#define RP_PASSWORD_SIZE 128
#define RP_PASSWORD_SIZE_HEX (RP_PASSWORD_SIZE * 2) // 256
int mangle_to_hex_lower(char *in, int in_len)
{
char tmp[RP_PASSWORD_SIZE_HEX]; // 256-byte buffer — accommodates 2x expansion
int out_len = 0;
for (int i = 0; i < in_len; i++)
{
// Bounds check: stop if next two bytes would overflow
if (out_len + 2 > RP_PASSWORD_SIZE_HEX) break; // FIX: explicit cap
unsigned char byte = (unsigned char) in[i];
tmp[out_len++] = hex_lower[(byte >> 4) & 0xf];
tmp[out_len++] = hex_lower[(byte ) & 0xf];
}
// out_len now guaranteed <= RP_PASSWORD_SIZE_HEX
// caller's buffer must also be enlarged to RP_PASSWORD_SIZE_HEX
memcpy(in, tmp, out_len);
return out_len;
}
// Option B — truncate input at half capacity before processing:
int mangle_to_hex_lower(char *in, int in_len)
{
char tmp[RP_PASSWORD_SIZE];
int safe_len = in_len > (RP_PASSWORD_SIZE / 2) // FIX: cap input at 64
? (RP_PASSWORD_SIZE / 2)
: in_len;
int out_len = 0;
for (int i = 0; i < safe_len; i++)
{
unsigned char byte = (unsigned char) in[i];
tmp[out_len++] = hex_lower[(byte >> 4) & 0xf];
tmp[out_len++] = hex_lower[(byte ) & 0xf];
}
memcpy(in, tmp, out_len);
return out_len;
}
// NOTE: Option A preserves rule semantics for long passwords.
// Option B silently truncates — functionally incorrect for cracking.
// Correct fix also requires rp_apply_rules_cpu()'s local 'out[]' buffer
// to be enlarged to RP_PASSWORD_SIZE_HEX to prevent the second overflow.
Detection and Indicators
Crash signature: SIGSEGV or SIGABRT in mangle_to_hex_lower / mangle_to_hex_upper with rsp pointing into stack red zone or a stack corruption detected by GCC's __stack_chk_fail symbol.
INDICATIVE CRASH OUTPUT (AddressSanitizer build):
==ASAN: stack-buffer-overflow on address 0x[...] at pc 0x[...]
WRITE of size 1 at offset 128 in frame:
#0 mangle_to_hex_lower src/rp_cpu.c:1042
#1 rp_apply_rules_cpu src/rp_cpu.c:1387
#2 process_rule_main src/rp_cpu.c:1621
'tmp' (128 bytes) is located at frame offset 0 in frame for mangle_to_hex_lower
[0, 128) 'tmp' <== Memory access at offset 128 overflows this variable
GCC stack protector (hardened build):
*** stack smashing detected ***: hashcat terminated
Aborted (core dumped)
Static detection: Grep src/rp_cpu.c for char tmp[RP_PASSWORD_SIZE] inside mangle_to_hex functions combined with an unbounded write loop incrementing by 2. Any memcpy(in, tmp, out_len) where out_len is not capped to the destination size is the finding.
Rule-based detection: Monitor hashcat invocations where -j or -k arguments contain the rule characters B or X and the wordlist contains entries of 128 bytes or longer. Log and alert on --rule-buf-l / --rule-buf-r with those opcodes in automated infrastructure.
Remediation
Immediate: Apply the upstream patch when released. Until then, build hashcat with -fstack-protector-strong and -fsanitize=address in test environments to convert silent corruption into loud crashes. In production pipelines, pre-filter wordlists to reject candidates exceeding 63 bytes before applying B or X rules, or strip those rule opcodes from all rule files.
Structural fix required in two places:
Enlarge tmp[] inside mangle_to_hex_lower() and mangle_to_hex_upper() to RP_PASSWORD_SIZE * 2.
Enlarge the out[] buffer in rp_apply_rules_cpu() to the same doubled size, since it receives the expanded result via memcpy.
Add an explicit cap: if (out_len + 2 > buf_capacity) break; inside each mangle loop as a defense-in-depth guard.
Deployment hardening: Enable RELRO, PIE, and stack canaries in all distributed hashcat binaries. These mitigations raise the exploitation bar from trivial stack smash to requiring an independent info-leak primitive.