From 2962b9d52e2b982103a37eac28e45e5a74b950e8 Mon Sep 17 00:00:00 2001 From: Jens Steube Date: Tue, 3 Jun 2025 07:10:50 +0200 Subject: [PATCH] - Improved strategy to detect pyenv managed python libraries - Improved documents on python bridge --- ...hashcat-python-plugin-development-guide.md | 8 +- docs/hashcat-python-plugin-quickstart.md | 9 +- src/bridges/bridge_python_generic_hash_mp.c | 255 +++++++++++++---- src/bridges/bridge_python_generic_hash_sp.c | 259 ++++++++++++++---- 4 files changed, 405 insertions(+), 126 deletions(-) diff --git a/docs/hashcat-python-plugin-development-guide.md b/docs/hashcat-python-plugin-development-guide.md index b9a825970..6c4b6035d 100644 --- a/docs/hashcat-python-plugin-development-guide.md +++ b/docs/hashcat-python-plugin-development-guide.md @@ -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: diff --git a/docs/hashcat-python-plugin-quickstart.md b/docs/hashcat-python-plugin-quickstart.md index e50f5ea34..dff602f69 100644 --- a/docs/hashcat-python-plugin-quickstart.md +++ b/docs/hashcat-python-plugin-quickstart.md @@ -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 plugin’s `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. diff --git a/src/bridges/bridge_python_generic_hash_mp.c b/src/bridges/bridge_python_generic_hash_mp.c index 36d61768f..c75563ba5 100644 --- a/src/bridges/bridge_python_generic_hash_mp.c +++ b/src/bridges/bridge_python_generic_hash_mp.c @@ -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"); diff --git a/src/bridges/bridge_python_generic_hash_sp.c b/src/bridges/bridge_python_generic_hash_sp.c index 3d2a3fdcd..87c51d32c 100644 --- a/src/bridges/bridge_python_generic_hash_sp.c +++ b/src/bridges/bridge_python_generic_hash_sp.c @@ -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) \