runHashcat
Crack password hashes using dictionary, brute-force, or hybrid attacks to test security and recover credentials during penetration testing.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| hashData | Yes | String containing the password hashes, one per line. | |
| attackMode | No | Attack mode: 0=Straight, 1=Combination, 3=Brute-force, 6=Hybrid Wordlist + Mask, 7=Hybrid Mask + Wordlist | |
| hashType | No | Hash-type, e.g., 0=MD5, 100=SHA1, 1000=NTLM, 1400=SHA2-256, 1800=sha512crypt, 22000=WPA*01/WPA*02 | |
| wordlist | No | Path to wordlist file for dictionary attacks | |
| mask | No | Mask for brute-force attacks (e.g., '?a?a?a?a?a?a?a?a' for 8 chars) | |
| increment | No | Enable incremental mode (start with shorter passwords) | |
| incrementMin | No | Minimum password length for incremental mode | |
| incrementMax | No | Maximum password length for incremental mode | |
| rules | No | Rules file to apply to wordlist | |
| session | No | Session name for resuming attacks | |
| restore | No | Restore a previous session | |
| optimizedKernels | No | Enable optimized kernels (-O) | |
| workloadProfile | No | Workload profile: 1=Low, 2=Default, 3=High, 4=Nightmare | |
| deviceTypes | No | Device types: 1=CPU, 2=GPU, 3=FPGA | |
| force | No | Ignore warnings | |
| potfilePath | No | Path to custom potfile | |
| outfile | No | Output file for cracked hashes | |
| outfileFormat | No | Output format: 1=hash, 2=plain, 3=hex-plain, etc. | |
| runtime | No | Abort session after X seconds | |
| showProgress | No | Show progress every X seconds | |
| quiet | No | Suppress output | |
| loopback | No | Add new plains to induct directory | |
| markovThreshold | No | Threshold X when to stop accepting new Markov-chains | |
| customCharset1 | No | User-defined charset ?1 | |
| customCharset2 | No | User-defined charset ?2 | |
| customCharset3 | No | User-defined charset ?3 | |
| customCharset4 | No | User-defined charset ?4 | |
| options | No | Additional raw hashcat options |
Implementation Reference
- src/index.ts:345-438 (handler)Core handler function that executes hashcat by spawning the process, managing temporary files, parsing output for cracked passwords, handling potfile, and providing full execution output.async function runHashcat(hashData: string, rawOptions: string[] = []): Promise<{ fullOutput: string; cracked: string[]; potfileLocation?: string }> { let options: string[]; try { options = sanitizeOptions(rawOptions); } catch (error: any) { throw error; } if (!hashData) { throw new Error("Error: Hash data is required."); } // Use /tmp directory for temp files to avoid read-only directory issues const tempHashFile = path.join('/tmp', `hashcat_hashes_${Date.now()}.txt`); const tempPotFile = path.join('/tmp', `hashcat_${Date.now()}.potfile`); let fullOutput = ""; let crackedPasswords: string[] = []; try { await fs.writeFile(tempHashFile, hashData); // Build hashcat arguments const crackingArgs = [...options]; // Add custom potfile to avoid conflicts if (!options.some(opt => opt.includes('--potfile-path'))) { crackingArgs.push('--potfile-path', tempPotFile); } // Add hash file as last argument crackingArgs.push(tempHashFile); fullOutput += `--- Hashcat Cracking Attempt ---\nExecuting: hashcat ${crackingArgs.join(' ')}\n`; try { const crackResult = await runSpawnCommand('hashcat', crackingArgs); fullOutput += `Exit Code: ${crackResult.code}\nStdout:\n${crackResult.stdout}\nStderr:\n${crackResult.stderr}\n`; // Parse cracked passwords from stdout (hashcat shows them during execution) const lines = crackResult.stdout.split('\n'); for (const line of lines) { // Look for lines that contain cracked hashes (usually contain colons) if (line.includes(':') && !line.includes('Session.........') && !line.includes('Status..........')) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('[') && !trimmed.startsWith('hashcat')) { crackedPasswords.push(trimmed); } } } } catch (error: any) { fullOutput += `Hashcat command failed to execute: ${error.message}\n`; } // Try to read from potfile if it exists fullOutput += `--- Potfile Check ---\n`; try { const potfileContent = await fs.readFile(tempPotFile, 'utf8'); fullOutput += `Potfile content:\n${potfileContent}\n`; const potfileLines = potfileContent.split('\n').filter(line => line.trim()); crackedPasswords.push(...potfileLines); } catch (error: any) { fullOutput += `Could not read potfile: ${error.message}\n`; } // Show cracked hashes using --show option const showArgs = [...options.filter(opt => !opt.includes('--potfile-path')), '--potfile-path', tempPotFile, '--show', tempHashFile]; fullOutput += `--- Show Cracked Hashes ---\nExecuting: hashcat ${showArgs.join(' ')}\n`; try { const showResult = await runSpawnCommand('hashcat', showArgs); fullOutput += `Exit Code: ${showResult.code}\nStdout:\n${showResult.stdout}\nStderr:\n${showResult.stderr}\n`; const showLines = showResult.stdout.split('\n').map(line => line.trim()).filter(line => line && line.includes(':')); crackedPasswords.push(...showLines); } catch (error: any) { fullOutput += `Show command failed to execute: ${error.message}\n`; } // Remove duplicates and filter valid entries crackedPasswords = [...new Set(crackedPasswords)].filter(line => line && line.includes(':')); // Clean up temp files try { await fs.unlink(tempHashFile); } catch { /* ignore */ } if (currentUserSession.mode === UserMode.PROFESSIONAL) { await logMessage(`Ran Hashcat.\nOptions: ${options.join(' ')}\nCracked: ${crackedPasswords.length} hashes.`); } return { fullOutput, cracked: crackedPasswords, potfileLocation: tempPotFile }; } catch (error: any) { console.error("Fatal error setting up Hashcat execution:", error); try { await fs.unlink(tempHashFile); } catch { /* ignore */ } try { await fs.unlink(tempPotFile); } catch { /* ignore */ } if (currentUserSession.mode === UserMode.PROFESSIONAL) { await logMessage(`Hashcat FAILED fatally before execution.\nOptions: ${options.join(' ')}\nError: ${error.message}`); } throw new Error(`Hashcat setup failed fatally: ${error.message}`); } }
- src/index.ts:852-884 (schema)Zod schema defining the input parameters and description for the runHashcat MCP tool.const hashcatToolSchema = z.object({ hashData: z.string().describe("String containing the password hashes, one per line."), attackMode: z.enum(['0', '1', '3', '6', '7']).optional().describe("Attack mode: 0=Straight, 1=Combination, 3=Brute-force, 6=Hybrid Wordlist + Mask, 7=Hybrid Mask + Wordlist"), hashType: z.string().optional().describe("Hash-type, e.g., 0=MD5, 100=SHA1, 1000=NTLM, 1400=SHA2-256, 1800=sha512crypt, 22000=WPA*01/WPA*02"), wordlist: z.string().optional().describe("Path to wordlist file for dictionary attacks"), mask: z.string().optional().describe("Mask for brute-force attacks (e.g., '?a?a?a?a?a?a?a?a' for 8 chars)"), increment: z.boolean().optional().describe("Enable incremental mode (start with shorter passwords)"), incrementMin: z.number().int().optional().describe("Minimum password length for incremental mode"), incrementMax: z.number().int().optional().describe("Maximum password length for incremental mode"), rules: z.string().optional().describe("Rules file to apply to wordlist"), session: z.string().optional().describe("Session name for resuming attacks"), restore: z.boolean().optional().describe("Restore a previous session"), optimizedKernels: z.boolean().optional().describe("Enable optimized kernels (-O)"), workloadProfile: z.enum(['1', '2', '3', '4']).optional().describe("Workload profile: 1=Low, 2=Default, 3=High, 4=Nightmare"), deviceTypes: z.array(z.enum(['1', '2', '3'])).optional().describe("Device types: 1=CPU, 2=GPU, 3=FPGA"), force: z.boolean().optional().describe("Ignore warnings"), potfilePath: z.string().optional().describe("Path to custom potfile"), outfile: z.string().optional().describe("Output file for cracked hashes"), outfileFormat: z.number().int().optional().describe("Output format: 1=hash, 2=plain, 3=hex-plain, etc."), runtime: z.number().int().optional().describe("Abort session after X seconds"), showProgress: z.boolean().optional().describe("Show progress every X seconds"), quiet: z.boolean().optional().describe("Suppress output"), loopback: z.boolean().optional().describe("Add new plains to induct directory"), markovThreshold: z.number().int().optional().describe("Threshold X when to stop accepting new Markov-chains"), customCharset1: z.string().optional().describe("User-defined charset ?1"), customCharset2: z.string().optional().describe("User-defined charset ?2"), customCharset3: z.string().optional().describe("User-defined charset ?3"), customCharset4: z.string().optional().describe("User-defined charset ?4"), options: z.array(z.string()).optional().describe("Additional raw hashcat options") }).describe( "Crack password hashes using Hashcat. More powerful and faster than John the Ripper for many hash types, especially with GPU acceleration. " + "Supports various attack modes and hash types. Example: `{\"hashData\":\"5d41402abc4b2a76b9719d911017c592\", \"hashType\":\"0\", \"attackMode\":\"0\", \"wordlist\":\"/tmp/wordlist.txt\"}`" );
- src/index.ts:885-1041 (registration)MCP server.tool registration for 'runHashcat', including input validation, option construction based on params, call to core runHashcat function, and response formatting.server.tool("runHashcat", hashcatToolSchema.shape, async (args /*, extra */) => { const { hashData, attackMode, hashType, wordlist, mask, increment, incrementMin, incrementMax, rules, session, restore, optimizedKernels, workloadProfile, deviceTypes, force, potfilePath, outfile, outfileFormat, runtime, showProgress, quiet, loopback, markovThreshold, customCharset1, customCharset2, customCharset3, customCharset4, options } = args; console.error(`Received Hashcat:`, { hashData: `len=${hashData.length}`, attackMode, hashType, wordlist }); if (currentUserSession.mode === UserMode.STUDENT) console.warn("[Student Mode] Executing Hashcat."); try { const constructedOptions: string[] = []; const validationErrors: string[] = []; // Attack mode if (attackMode) { constructedOptions.push('-a', attackMode); } else { constructedOptions.push('-a', '0'); // Default to dictionary attack } // Hash type if (hashType) { constructedOptions.push('-m', hashType); } // Wordlist for dictionary/combination attacks if ((attackMode === '0' || attackMode === '1' || !attackMode) && wordlist) { constructedOptions.push(wordlist); } else if ((attackMode === '0' || !attackMode) && !wordlist && !mask) { validationErrors.push("Dictionary attack requires a wordlist"); } // Mask for brute-force attacks if (attackMode === '3' && mask) { constructedOptions.push(mask); } else if (attackMode === '3' && !mask) { validationErrors.push("Brute-force attack requires a mask"); } // Hybrid attacks if (attackMode === '6' && wordlist && mask) { constructedOptions.push(wordlist, mask); } else if (attackMode === '6') { validationErrors.push("Hybrid Wordlist + Mask attack requires both wordlist and mask"); } if (attackMode === '7' && mask && wordlist) { constructedOptions.push(mask, wordlist); } else if (attackMode === '7') { validationErrors.push("Hybrid Mask + Wordlist attack requires both mask and wordlist"); } // Increment mode if (increment) { constructedOptions.push('-i'); if (incrementMin) constructedOptions.push('--increment-min', incrementMin.toString()); if (incrementMax) constructedOptions.push('--increment-max', incrementMax.toString()); } // Rules if (rules) { constructedOptions.push('-r', rules); } // Session management if (session) { constructedOptions.push('--session', session); } if (restore) { constructedOptions.push('--restore'); } // Performance options if (optimizedKernels) { constructedOptions.push('-O'); } if (workloadProfile) { constructedOptions.push('-w', workloadProfile); } if (deviceTypes && deviceTypes.length > 0) { constructedOptions.push('-d', deviceTypes.join(',')); } // General options if (force) { constructedOptions.push('--force'); } if (potfilePath) { constructedOptions.push('--potfile-path', potfilePath); } if (outfile) { constructedOptions.push('-o', outfile); if (outfileFormat) { constructedOptions.push('--outfile-format', outfileFormat.toString()); } } if (runtime) { constructedOptions.push('--runtime', runtime.toString()); } if (showProgress) { constructedOptions.push('--status'); } if (quiet) { constructedOptions.push('--quiet'); } if (loopback) { constructedOptions.push('--loopback'); } if (markovThreshold) { constructedOptions.push('--markov-threshold', markovThreshold.toString()); } // Custom charsets if (customCharset1) constructedOptions.push('-1', customCharset1); if (customCharset2) constructedOptions.push('-2', customCharset2); if (customCharset3) constructedOptions.push('-3', customCharset3); if (customCharset4) constructedOptions.push('-4', customCharset4); // Raw options if (options) { constructedOptions.push(...options); } if (validationErrors.length > 0) { throw new Error(`Invalid parameters: ${validationErrors.join('; ')}`); } const { fullOutput, cracked, potfileLocation } = await runHashcat(hashData, constructedOptions); const responseContent: any[] = []; if (currentUserSession.mode === UserMode.STUDENT) { responseContent.push({ type: "text", text: `Hashcat finished! Found ${cracked.length} cracked passwords.` }); if (cracked.length > 0) { responseContent.push({ type: "text", text: "\n**🎉 Cracked Passwords:**\n" + cracked.join("\n") }); responseContent.push({ type: "text", text: "\n**What this means:** These are the plain-text passwords that correspond to your hashes. You can now use these for further testing or to demonstrate the importance of strong passwords." }); } else { responseContent.push({ type: "text", text: "\n**No passwords cracked.** This could mean:\n- The passwords are very strong\n- You need a better wordlist\n- The hash type might be incorrect\n- Try different attack modes or longer runtime" }); } if (potfileLocation) { responseContent.push({ type: "text", text: `\n**Tip:** Results are saved in: ${potfileLocation}` }); } } else { // Professional mode responseContent.push({ type: "text", text: `Hashcat completed. Cracked: ${cracked.length} hashes.` }); if (cracked.length > 0) { responseContent.push({ type: "text", text: "\n**Cracked:**\n" + cracked.join("\n") }); } if (potfileLocation) { responseContent.push({ type: "text", text: `Potfile: ${potfileLocation}` }); } } responseContent.push({ type: "text", text: "\n--- Full Hashcat Output ---\n" + fullOutput }); return { content: responseContent }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } });
- src/index.ts:273-291 (helper)Helper function to spawn external commands like hashcat using child_process.spawn, capturing stdout/stderr and exit code. Used by runHashcat.async function runSpawnCommand(command: string, args: string[]): Promise<{ stdout: string; stderr: string; code: number | null }> { return new Promise((resolve, reject) => { console.error(`Attempting to spawn: ${command} ${args.join(' ')}`); // Added for debugging const process = spawn(command, args); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data.toString(); }); process.stderr.on('data', (data) => { stderr += data.toString(); }); process.on('error', (error) => { // Explicitly catch spawn errors (e.g., command not found) console.error(`Spawn error for command "${command}": ${error.message}`); reject(new Error(`Failed to start command "${command}": ${error.message}`)); }); process.on('close', (code) => { console.error(`Command "${command}" exited with code ${code}`); // Added for debugging resolve({ stdout, stderr, code }); }); }); }
- src/index.ts:258-270 (helper)Helper function to sanitize command-line options passed to tools like hashcat, preventing injection attacks via regex validation.const SAFE_OPTION_REGEX = /^(?:-[a-zA-Z0-9]+|--[a-zA-Z0-9\-]+(?:=[^;&|`$\s\(\)\<\>\\]+)?|[^;&|`$\s\(\)\<\>\\]+)$/; function sanitizeOptions(options: string[]): string[] { const sanitized: string[] = []; for (const opt of options) { if (SAFE_OPTION_REGEX.test(opt)) { sanitized.push(opt); } else { throw new Error(`Invalid or potentially unsafe option detected: "${opt}". Only standard flags and simple arguments are allowed.`); } } return sanitized; }