探索 John the Ripper 外部模式脚本编写

Kali LinuxBeginner
立即练习

引言

John the Ripper (JtR) 是一个流行且强大的开源密码安全审计和密码恢复工具。它可以使用多种模式执行密码破解,例如“单次破解”模式、“字典”模式和“增量”模式。

除了这些标准模式之外,JtR 还提供了一个极其灵活的“外部模式”(External Mode)。此模式允许你使用外部程序或脚本来生成密码候选。这让你能够完全控制密码生成逻辑,从而创建其他模式无法实现的、高度定制化和复杂的密码模式。

在本实验中,你将学习 JtR 外部模式的基础知识。你将从理解基本概念开始,然后编写、编译和使用自己的自定义 C 程序来生成密码。你还将学习基本的调试技术,并将你的知识应用于更实际的场景。

理解外部模式基础

在本步骤中,你将学习 John the Ripper 外部模式的基本概念。此模式通过执行你定义的外部程序来工作。John the Ripper 然后读取该程序的标准输出,将每一行视为潜在的密码候选。

这些外部模式在 john.conf 配置文件中定义,通常位于 /etc/john/john.conf。每个外部模式都在一个类似 [List.External:ModeName] 的节(section)中定义。

让我们检查一下默认配置文件中一个现有的外部模式定义,看看它的结构。我们将查找 Keyboard 模式,该模式模拟基于键盘行走模式的密码候选。

执行以下命令查看 Keyboard 模式的配置:

grep -A 10 "\[List.External:Keyboard\]" /etc/john/john.conf

你将看到一段类似 C 语言的代码块。最重要的函数是 generate(),它负责生成密码候选。

要查看外部模式的实际运行情况,你可以使用 --stdout 标志运行 john。这告诉 John 将生成的候选密码打印到屏幕上,而不是尝试破解哈希。让我们测试一下 Keyboard 模式。

john --stdout --external=Keyboard | head -n 5

此命令选择 Keyboard 外部模式,并将其输出通过管道传递给 headhead 会显示你生成的头 5 个密码候选。输出将是一系列代表键盘模式的字符。

q
w
e
r
t

这演示了基本原理:John the Ripper 运行外部模式中定义的逻辑,并使用其输出来作为密码的来源。在接下来的步骤中,我们将创建自己的程序来充当此来源。

编写一个简单的外部模式脚本

在本步骤中,你将使用 C 编程语言编写并编译你的第一个简单的外部模式脚本。我们的目标是创建一个生成固定密码列表的程序,然后配置 John the Ripper 来使用它。

首先,使用 nano 编辑器创建一个名为 simple_gen.c 的 C 源文件。

nano simple_gen.c

现在,将以下 C 代码复制并粘贴到 nano 编辑器中。此程序将简单地将三个不同的密码打印到标准输出,每个密码占一行。

#include <stdio.h>

int main() {
    printf("pass1\n");
    printf("pass2\n");
    printf("pass3\n");
    return 0;
}

Ctrl+X,然后按 Y,再按 Enter 来保存文件并退出 nano

接下来,使用 gcc 编译器将此 C 代码编译成一个名为 simple_gen 的可执行文件。

gcc -o simple_gen simple_gen.c

现在我们有了可执行文件,需要告诉 John the Ripper 如何使用它。我们将通过在项目目录中创建配置文件的一个本地副本,并添加一个新的外部模式定义来实现这一点。

cp /etc/john/john.conf ./my_john.conf

使用 nano 打开新的 my_john.conf 文件。

nano my_john.conf

滚动到文件末尾,并添加以下配置块。这定义了一个名为 MySimple 的新外部模式,该模式执行我们的 simple_gen 程序。

[List.External:MySimple]
void generate()
{
  exec("./simple_gen");
}

保存并退出 nanoCtrl+XYEnter)。

最后,让我们测试一下我们的新外部模式。我们将再次使用 --stdout 标志来查看输出,并使用 --config 标志将 John 指向我们的自定义配置文件。

john --stdout --external=MySimple --config=./my_john.conf

你应该会看到我们 C 程序的确切输出,这证实了 John the Ripper 正在成功运行我们的自定义脚本。

pass1
pass2
pass3

实现自定义密码生成逻辑

在本步骤中,你将增强你的脚本以实现更动态的密码生成逻辑。我们不再使用固定列表,而是根据一个模式生成密码:一个基础单词后跟一个数字序列。这是弱密码的常见模式。

让我们修改 C 程序,使其生成类似 labex0labex1labex2 等密码。

再次使用 nano 打开 simple_gen.c 文件。

nano simple_gen.c

将现有代码替换为以下内容。这个新版本使用一个 for 循环,将数字 0 到 199 追加到基础单词 "labex" 后面。

#include <stdio.h>

int main() {
    char *base_word = "labex";
    for (int i = 0; i < 200; i++) {
        printf("%s%d\n", base_word, i);
    }
    return 0;
}

保存并退出 nano。现在,使用 gcc 重新编译程序以应用更改。

gcc -o simple_gen simple_gen.c

让我们使用 --stdout 标志测试更新后的生成器,以查看新输出的示例。

john --stdout --external=MySimple --config=./my_john.conf | head -n 5

输出现在应该显示新的模式。

labex0
labex1
labex2
labex3
labex4

现在进行真正的测试。我们将使用自定义的外部模式来破解我们在设置过程中准备的密码哈希。testuser 的密码是 labex123。我们的脚本会生成这个候选密码,因此应该能找到匹配项。

运行以下命令开始破解过程。请注意,我们已移除 --stdout 并添加了哈希文件 hashes.txt 的路径。

john --external=MySimple --config=./my_john.conf ./hashes.txt

John 将运行你的脚本,生成候选密码,并与哈希进行测试。它应该会很快找到密码。输出看起来会像这样:

Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, $6$ [SHA512 256/256 AVX2 4x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
labex123         (testuser)
1g 0:00:00:00 DONE (2024-05-20 08:30) 100.0g/s 12300p/s 12300c/s 12300C/s labex123..labex130
Use the "--show" option to display all of the cracked passwords reliably
Session completed

要确认破解的密码,请使用 --show 选项。

john --show ./hashes.txt

这将显示用户名旁边的破解密码。

testuser:labex123:1001:1001::/home/testuser:/bin/sh

1 password hash cracked, 0 left

调试外部模式脚本

在本步骤中,你将学习一些调试外部模式脚本的基本技术。当脚本未能按预期工作时,诊断起来可能会很棘手,因为它是由另一个程序(John the Ripper)运行的。

一种常见且有效的方法是从脚本向单独的文件写入日志消息。这允许你跟踪其执行流程并检查变量值。

让我们修改 C 程序以写入一个名为 debug.log 的日志文件。使用 nano 打开 simple_gen.c

nano simple_gen.c

将代码替换为以下版本。此代码以写入模式打开 debug.log,然后在执行期间使用 fprintf 将状态消息写入其中。

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *log_file = fopen("debug.log", "w");
    if (log_file == NULL) {
        // Cannot open log file, exit
        return 1;
    }

    fprintf(log_file, "Debug: Script started.\n");

    char *base_word = "labex";
    for (int i = 0; i < 200; i++) {
        printf("%s%d\n", base_word, i);
        fprintf(log_file, "Debug: Generated candidate %s%d\n", base_word, i);
    }

    fprintf(log_file, "Debug: Script finished.\n");
    fclose(log_file);
    return 0;
}

保存并退出 nano,然后重新编译程序。

gcc -o simple_gen simple_gen.c

现在,再次运行 John the Ripper。我们不需要查看密码候选,因此可以将标准输出重定向到 /dev/null。关键在于我们的脚本将运行并创建日志文件。

john --stdout --external=MySimple --config=./my_john.conf > /dev/null

该命令将运行片刻然后完成。现在,你的项目目录中应该存在一个 debug.log 文件。让我们查看其内容。

cat debug.log | head -n 5

你应该会看到我们添加到程序中的调试消息。

Debug: Script started.
Debug: Generated candidate labex0
Debug: Generated candidate labex1
Debug: Generated candidate labex2
Debug: Generated candidate labex3

这种技术对于查找逻辑中的问题(如不正确的循环、错误的变量值或文件访问错误)非常有价值,而且不会受到 John the Ripper 自身输出的干扰。

将外部模式应用于特定场景

在本步骤中,你将把所学知识应用于一个更实际的场景。你的脚本将不再使用硬编码的基础单词,而是从文件中读取基础单词列表,并为每个单词生成变体。这是一种更强大、更真实的实现方式。

首先,让我们创建一个名为 words.txt 的简单单词列表文件,其中包含几个潜在的基础单词。

echo "admin" > words.txt
echo "user" >> words.txt
echo "guest" >> words.txt

接下来,我们将修改 C 程序以读取此文件。程序需要接受单词列表文件名作为命令行参数。使用 nano 打开 simple_gen.c

nano simple_gen.c

将代码替换为以下内容。此版本从命令行读取文件名,打开该文件,并为读取的每个单词生成三个密码候选:单词本身、单词后跟 "123",以及单词后跟 "2024"。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <wordlist_file>\n", argv[0]);
        return 1;
    }

    FILE *file = fopen(argv[1], "r");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    char line[256];
    while (fgets(line, sizeof(line), file)) {
        // Remove newline character from the end of the line
        line[strcspn(line, "\n")] = 0;

        // Generate variations for the word
        printf("%s\n", line);
        printf("%s123\n", line);
        printf("%s2024\n", line);
    }

    fclose(file);
    return 0;
}

保存、退出并重新编译程序。

gcc -o simple_gen simple_gen.c

现在,我们必须更新 my_john.conf 文件,将 words.txt 文件名作为参数传递给我们的脚本。让我们为此创建一个名为 MyAdvanced 的新外部模式。使用 nano 打开 my_john.conf

nano my_john.conf

滚动到文件末尾并添加此新部分。请注意,"words.txt" 如何作为第二个参数传递给 exec 函数。

[List.External:MyAdvanced]
void generate()
{
  exec("./simple_gen", "words.txt");
}

保存并退出 nano。最后,测试你的新高级外部模式。

john --stdout --external=MyAdvanced --config=./my_john.conf

输出现在应该是从 words.txt 文件中的单词生成的密码候选列表,并对每个单词应用了指定的变体。

admin
admin123
admin2024
user
user123
user2024
guest
guest123
guest2024

你已成功为 John the Ripper 创建了一个灵活的、由文件驱动的密码生成器。

总结

恭喜你完成了本次实验!你已成功探索了 John the Ripper 强大的外部模式。

在本次实验中,你学习了:

  • John the Ripper 外部模式的基本概念,以及它如何使用外部程序生成密码候选。
  • 如何编写、编译和使用一个简单的 C 程序作为密码生成器。
  • 如何在 john.conf 文件中配置自定义外部模式。
  • 如何实现基于模式的动态密码生成逻辑。
  • 如何使用你的自定义模式成功破解密码哈希。
  • 一种通过向文件记录日志来调试外部脚本的基本但有效的方法。
  • 如何创建一个更高级、更实用的脚本,该脚本从文件中读取基础单词。

你所获得的技能为你创建针对特定目标和场景的高度专业化的密码破解规则奠定了基础。你可以通过使用 Python 或 Perl 等其他脚本语言,或者实现更复杂的密码变异和生成逻辑来进一步探索此主题。