1
0
mirror of https://github.com/hashcat/hashcat.git synced 2025-07-03 21:32:35 +00:00

- Improved strategy to detect pyenv managed python libraries

- Improved documents on python bridge
This commit is contained in:
Jens Steube 2025-06-03 07:10:50 +02:00
parent e8052a004b
commit 2962b9d52e
4 changed files with 405 additions and 126 deletions

View File

@ -4,7 +4,7 @@ This document is a comprehensive guide for writing custom hash modes in Python v
## 1. Introduction
The The Assimilation Bridge enables developers to implement complete hash mode logic in languages other than C, most notably Python. Traditionally, customizing Hashcat required writing a module in C and a kernel in OpenCL/CUDA. With the bridge, you can now implement a complete hash mode in Python.
The Assimilation Bridge enables developers to implement complete hash mode logic in languages other than C, most notably Python. Traditionally, customizing Hashcat required writing a module in C and a kernel in OpenCL/CUDA. With the bridge, you can now implement a complete hash mode in Python.
The bridge supports two hash modes to run python code:
@ -81,7 +81,7 @@ def term(ctx):
hcsp.term(ctx)
```
This should be used in case you had open files, open networking connection, or similar. We are good citizen!
This should be used in case you had open files, open networking connection, or similar. We are good citizens!
Here's our main function `kernel_loop()` where we spend almost all our time:
@ -180,7 +180,7 @@ The `salt` variable is one of the parameters from the calc_hash():
def calc_hash(password: bytes, salt: dict) -> str:
```
Note that if fully exhaust the Hashcat keyspace, your function has been called X times Y. X is the number of candidates, and Y is all the salts (except if a salt has been cracked). What's important to realize that within your function, you implement hashing logic only for precisely that situation where you have one password and one salt.
Note that if you fully exhaust the Hashcat keyspace, your function has been called X times Y.. X is the number of candidates, and Y is all the salts (except if a salt has been cracked). What's important to realize that within your function, you implement hashing logic only for precisely that situation where you have one password and one salt.
### Merging Salts and Esalts into a Single Object
@ -196,7 +196,7 @@ Initially, salts and esalts are unpacked separately from their respective binary
## 6. Python generic hash mode `-m 72000` and `-m 73000`
The "generic hash" support in hashcat is using python. The main idea behind "generic" is the goal is to write freely ideal for rapid prototyping.
The "generic hash" support in hashcat is using python. The main idea behind "generic" is to write freely. Ideal for rapid prototyping and achieving your goal.
The most straight-forward way is to edit the following files directly:

View File

@ -1,13 +1,14 @@
# Hashcat Python Plugin Quickstart
## Introduction
This guide walks you through building custom hash modes in **pure Python** using Hashcat v7's Python plugin interface via the new assimilation bridge.
Hashcat v7 introduces a `Python plugin bridge` that allows you to write and integrate custom hash-matching algorithms directly in Python. This plugin system fits into the regular cracking workflow by replacing or extending internal kernel routines.
Whether you're experimenting with a new algorithm, supporting a proprietary format, prototyping a new feature, or simply prefer writing in a high-level language, this plugin interface makes development fast and straightforward.
When enabled, Hashcat uses the plugins `calc_hash()` function to compute hash candidates for verification, making it easy to experiment with new or obscure algorithms without modifying core C code or writing OpenCL/CUDA kernels.
No C required. No recompilation. Just write your logic in Python in the `calc_hash()` function.
This guide demonstrates how to quickly customize such an algorithm using pure Python. Whether you're prototyping a new hash mode, supporting a proprietary format, or simply prefer high-level development, Python plugins make the process fast and straightforward.
No C required. No recompilation. Just write your logic in Python using `calc_hash()`, and you're ready to crack.
You can use any Python modules you like.

View File

@ -191,26 +191,197 @@ static char *DEFAULT_SOURCE_FILENAME = "generic_hash_sp";
static char *DEFAULT_SOURCE_FILENAME = "generic_hash_mp";
#endif
#if defined (_WIN)
#define DEVNULL "NUL"
#else
#define DEVNULL "/dev/null"
#endif
static int suppress_stderr (void)
{
int null_fd = open (DEVNULL, O_WRONLY);
if (null_fd < 0) return -1;
int saved_fd = dup (fileno (stderr));
if (saved_fd < 0)
{
close (null_fd);
return -1;
}
dup2 (null_fd, fileno (stderr));
close (null_fd);
return saved_fd;
}
static void restore_stderr (int saved_fd)
{
if (saved_fd < 0) return;
dup2 (saved_fd, fileno (stderr));
close (saved_fd);
}
static char *expand_pyenv_libpath (const char *prefix, const int maj, const int min)
{
char *out = NULL;
#if defined (_WIN)
const int len = asprintf (&out, "%s/python%d%d.dll", prefix, maj, min); //untested
#elif defined (__MSYS__)
const int len = asprintf (&out, "%s/msys-python%d.%d.dll", prefix, maj, min); //untested could be wrong
#elif defined (__APPLE__)
const int len = asprintf (&out, "%s/lib/libpython%d.%d.dylib", prefix, maj, min); //untested
#elif defined (__CYGWIN__)
const int len = asprintf (&out, "%s/lib/python%d%d.dll", prefix, maj, min); //untested
#else
const int len = asprintf (&out, "%s/lib/libpython%d.%d.so", prefix, maj, min);
#endif
if (len == -1) return NULL;
struct stat st;
if (stat (out, &st) != 0)
{
free (out);
return NULL;
}
return out;
}
static int resolve_pyenv_libpath (char *out_buf, const size_t out_sz)
{
// prefix
FILE *fp1 = popen ("pyenv prefix", "r");
if (fp1 == NULL) return -1;
char prefix_path[PATH_MAX];
if (fgets (prefix_path, sizeof (prefix_path), fp1) == NULL)
{
pclose (fp1);
return -1;
}
pclose (fp1);
superchop_with_length (prefix_path, strlen (prefix_path));
int maj = 0;
int min = 0;
// local
FILE *fp2 = popen ("pyenv local", "r");
if (fp2 == NULL) return -1;
if (fscanf (fp2, "%d.%d", &maj, &min) == 2)
{
pclose (fp2);
char *pyenv_libpath = expand_pyenv_libpath (prefix_path, maj, min);
if (pyenv_libpath != NULL)
{
strncpy (out_buf, pyenv_libpath, out_sz - 1);
free (pyenv_libpath);
return 0;
}
return -1;
}
pclose (fp2);
// global
FILE *fp3 = popen ("pyenv global", "r");
if (fp3 == NULL) return -1;
if (fscanf (fp3, "%d.%d", &maj, &min) == 2)
{
pclose (fp3);
char *pyenv_libpath = expand_pyenv_libpath (prefix_path, maj, min);
if (pyenv_libpath != NULL)
{
strncpy (out_buf, pyenv_libpath, out_sz - 1);
free (pyenv_libpath);
return 0;
}
return -1;
}
pclose (fp3);
return -1;
}
static bool init_python (hc_python_lib_t *python)
{
char *pythondll_path = NULL;
char pythondll_path[PATH_MAX];
python->lib = NULL;
// let's see if we have pyenv, that will save us a lot of guessing...
int saved_stderr = suppress_stderr ();
const int pyenv_rc = resolve_pyenv_libpath (pythondll_path, sizeof (pythondll_path));
restore_stderr (saved_stderr);
if (pyenv_rc == 0)
{
#if defined (_WIN)
python->lib = hc_dlopen (pythondll_path);
#elif defined (__MSYS__)
python->lib = dlopen (pythondll_path, RTLD_NOW | RTLD_GLOBAL);
#elif defined (__APPLE__)
python->lib = dlopen (pythondll_path, RTLD_NOW | RTLD_GLOBAL);
#elif defined (__CYGWIN__)
python->lib = hc_dlopen (pythondll_path);
#else
python->lib = dlopen (pythondll_path, RTLD_NOW | RTLD_GLOBAL);
#endif
}
#define MIN_MAJ 3
#define MAX_MAJ 8
#define MIN_MIN 0
#define MAX_MIN 50
#if defined (_WIN)
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
if (python->lib != NULL) break;
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
#if defined (_WIN)
// first try %LocalAppData% default path
char expandedPath[MAX_PATH];
char expandedPath[MAX_PATH - 1];
char *libpython_namelocal = NULL;
@ -224,7 +395,7 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = hcstrdup (expandedPath);
strncpy (pythondll_path, expandedPath, sizeof (pythondll_path) - 1);
hcfree (libpython_namelocal);
@ -245,7 +416,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_namepath;
strncpy (pythondll_path, libpython_namepath, sizeof (pythondll_path) - 1);
hcfree (libpython_namepath);
break;
}
@ -254,18 +427,8 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_namepath);
};
if (python->lib != NULL) break;
}
#elif defined (__MSYS__)
if (python->lib != NULL) break;
}
#elif defined (__MSYS__)
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "msys-python%d.%d.dll", maj, min);
@ -274,7 +437,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -283,19 +448,8 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
if (python->lib != NULL) break;
}
#elif defined (__APPLE__)
if (python->lib != NULL) break;
}
#elif defined (__APPLE__)
// untested
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "libpython%d.%d.dylib", maj, min);
@ -304,7 +458,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -313,19 +469,8 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
if (python->lib != NULL) break;
}
#elif defined (__CYGWIN__)
if (python->lib != NULL) break;
}
#elif defined (__CYGWIN__)
// untested
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "python%d%d.dll", maj, min);
@ -334,7 +479,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -343,18 +490,8 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
if (python->lib != NULL) break;
}
#else
if (python->lib != NULL) break;
}
#else
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "libpython%d.%d.so", maj, min);
@ -363,7 +500,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -372,14 +511,14 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
#endif
if (python->lib != NULL) break;
}
if (python->lib != NULL) break;
}
#endif
if (python->lib == NULL)
{
fprintf (stderr, "Awww, unable to find Python shared library.\n");

View File

@ -187,29 +187,200 @@ typedef struct
static char *DEFAULT_SOURCE_FILENAME = "generic_hash_sp";
#if defined (_WIN)
#define DEVNULL "NUL"
#else
#define DEVNULL "/dev/null"
#endif
static int suppress_stderr (void)
{
int null_fd = open (DEVNULL, O_WRONLY);
if (null_fd < 0) return -1;
int saved_fd = dup (fileno (stderr));
if (saved_fd < 0)
{
close (null_fd);
return -1;
}
dup2 (null_fd, fileno (stderr));
close (null_fd);
return saved_fd;
}
static void restore_stderr (int saved_fd)
{
if (saved_fd < 0) return;
dup2 (saved_fd, fileno (stderr));
close (saved_fd);
}
static char *expand_pyenv_libpath (const char *prefix, const int maj, const int min)
{
char *out = NULL;
#if defined (_WIN)
const int len = asprintf (&out, "%s/python%d%dt.dll", prefix, maj, min); //untested
#elif defined (__MSYS__)
const int len = asprintf (&out, "%s/msys-python%d.%dt.dll", prefix, maj, min); //untested could be wrong
#elif defined (__APPLE__)
const int len = asprintf (&out, "%s/lib/libpython%d.%dt.dylib", prefix, maj, min); //untested
#elif defined (__CYGWIN__)
const int len = asprintf (&out, "%s/lib/python%d%dt.dll", prefix, maj, min); //untested
#else
const int len = asprintf (&out, "%s/lib/libpython%d.%dt.so", prefix, maj, min);
#endif
if (len == -1) return NULL;
struct stat st;
if (stat (out, &st) != 0)
{
free (out);
return NULL;
}
return out;
}
static int resolve_pyenv_libpath (char *out_buf, const size_t out_sz)
{
// prefix
FILE *fp1 = popen ("pyenv prefix", "r");
if (fp1 == NULL) return -1;
char prefix_path[PATH_MAX];
if (fgets (prefix_path, sizeof (prefix_path), fp1) == NULL)
{
pclose (fp1);
return -1;
}
pclose (fp1);
superchop_with_length (prefix_path, strlen (prefix_path));
int maj = 0;
int min = 0;
// local
FILE *fp2 = popen ("pyenv local", "r");
if (fp2 == NULL) return -1;
if (fscanf (fp2, "%d.%d", &maj, &min) == 2)
{
pclose (fp2);
char *pyenv_libpath = expand_pyenv_libpath (prefix_path, maj, min);
if (pyenv_libpath != NULL)
{
strncpy (out_buf, pyenv_libpath, out_sz - 1);
free (pyenv_libpath);
return 0;
}
return -1;
}
pclose (fp2);
// global
FILE *fp3 = popen ("pyenv global", "r");
if (fp3 == NULL) return -1;
if (fscanf (fp3, "%d.%d", &maj, &min) == 2)
{
pclose (fp3);
char *pyenv_libpath = expand_pyenv_libpath (prefix_path, maj, min);
if (pyenv_libpath != NULL)
{
strncpy (out_buf, pyenv_libpath, out_sz - 1);
free (pyenv_libpath);
return 0;
}
return -1;
}
pclose (fp3);
return -1;
}
static bool init_python (hc_python_lib_t *python)
{
char *pythondll_path = NULL;
char pythondll_path[PATH_MAX];
python->lib = NULL;
if (getenv ("PYTHON_GIL") == NULL)
putenv ((char *) "PYTHON_GIL=0");
// let's see if we have pyenv, that will save us a lot of guessing...
int saved_stderr = suppress_stderr ();
const int pyenv_rc = resolve_pyenv_libpath (pythondll_path, sizeof (pythondll_path));
restore_stderr (saved_stderr);
if (pyenv_rc == 0)
{
#if defined (_WIN)
python->lib = hc_dlopen (pythondll_path);
#elif defined (__MSYS__)
python->lib = dlopen (pythondll_path, RTLD_NOW | RTLD_GLOBAL);
#elif defined (__APPLE__)
python->lib = dlopen (pythondll_path, RTLD_NOW | RTLD_GLOBAL);
#elif defined (__CYGWIN__)
python->lib = hc_dlopen (pythondll_path);
#else
python->lib = dlopen (pythondll_path, RTLD_NOW | RTLD_GLOBAL);
#endif
}
#define MIN_MAJ 3
#define MAX_MAJ 8
#define MIN_MIN 0
#define MAX_MIN 50
#if defined (_WIN)
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
if (python->lib != NULL) break;
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
#if defined (_WIN)
// first try %LocalAppData% default path
char expandedPath[MAX_PATH];
char expandedPath[MAX_PATH - 1];
char *libpython_namelocal = NULL;
@ -223,7 +394,7 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = hcstrdup (expandedPath);
strncpy (pythondll_path, expandedPath, sizeof (pythondll_path) - 1);
hcfree (libpython_namelocal);
@ -244,7 +415,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_namepath;
strncpy (pythondll_path, libpython_namepath, sizeof (pythondll_path) - 1);
hcfree (libpython_namepath);
break;
}
@ -253,27 +426,19 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_namepath);
};
if (python->lib != NULL) break;
}
#elif defined (__MSYS__)
if (python->lib != NULL) break;
}
#elif defined (__MSYS__)
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "msys-python%d.%d.dll", maj, min);
hc_asprintf (&libpython_name, "msys-python%d.%dt.dll", maj, min);
python->lib = dlopen (libpython_name, RTLD_NOW | RTLD_GLOBAL);
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -282,19 +447,8 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
if (python->lib != NULL) break;
}
#elif defined (__APPLE__)
if (python->lib != NULL) break;
}
#elif defined (__APPLE__)
// untested
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "libpython%d.%dt.dylib", maj, min);
@ -303,7 +457,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -312,19 +468,8 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
if (python->lib != NULL) break;
}
#elif defined (__CYGWIN__)
if (python->lib != NULL) break;
}
#elif defined (__CYGWIN__)
// untested
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "python%d%dt.dll", maj, min);
@ -333,7 +478,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -342,18 +489,8 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
if (python->lib != NULL) break;
}
#else
if (python->lib != NULL) break;
}
#else
for (int maj = MAX_MAJ; maj >= MIN_MAJ; --maj)
{
for (int min = MAX_MIN; min >= MIN_MIN; --min)
{
char *libpython_name = NULL;
hc_asprintf (&libpython_name, "libpython%d.%dt.so", maj, min);
@ -362,7 +499,9 @@ static bool init_python (hc_python_lib_t *python)
if (python->lib != NULL)
{
pythondll_path = libpython_name;
strncpy (pythondll_path, libpython_name, sizeof (pythondll_path) - 1);
hcfree (libpython_name);
break;
}
@ -371,14 +510,14 @@ static bool init_python (hc_python_lib_t *python)
hcfree (libpython_name);
};
#endif
if (python->lib != NULL) break;
}
if (python->lib != NULL) break;
}
#endif
if (python->lib == NULL)
{
fprintf (stderr, "Unable to find suitable Python library for -m 72000.\n\n");
@ -403,7 +542,7 @@ static bool init_python (hc_python_lib_t *python)
fprintf (stderr, " It seems to be a lot slower, and relevant modules such as cffi are incompatibile.\n");
fprintf (stderr, " Since your are on Linux/MacOS we highly recommend to stick to multiprocessing module.\n");
fprintf (stderr, " Maybe 'free-threaded' mode will become more mature in the future.\n");
fprintf (stderr, " For now, we high recommend to stick to -m 73000 instead.\n");
fprintf (stderr, " For now, we high recommend to stick to -m 73000 instead.\n\n");
#endif
#define HC_LOAD_FUNC_PYTHON(ptr,name,pythonname,type,libname,noerr) \