
image.png (55.91 KB, 下载次数: 0)
下载附件
2025-9-28 17:18 上传
2. 可以看到这次使用的是nuitka那这个就不像之前那么简单了
添加了upx壳直接 upx -d 脱壳

image.png (46.21 KB, 下载次数: 1)
下载附件
2025-9-28 17:22 上传
脱壳后,可以看到是nuikta直接打包单文件

image.png (45.39 KB, 下载次数: 0)
下载附件
2025-9-28 17:23 上传
3. 提取相关文件,可以通过github上的一个库提取内置的二进制文件,可以通过监控程序查看释放路径
地址:https://github.com/extremecoders-re/nuitka-extractor

image.png (20.15 KB, 下载次数: 1)
下载附件
2025-9-28 17:28 上传
其中main.dll是关键代码所在

image.png (89.8 KB, 下载次数: 0)
下载附件
2025-9-28 17:28 上传
4. 打开ida开始分析main.dll
导入表有一个run_code,显然是主要内容

image.png (9.71 KB, 下载次数: 1)
下载附件
2025-9-28 17:29 上传
继续跟进

image.png (9.02 KB, 下载次数: 1)
下载附件
2025-9-28 17:30 上传
这个地方就是关键,可以把这个代码扔给AI

image.png (50.57 KB, 下载次数: 0)
下载附件
2025-9-28 17:31 上传
关键赋值:冻结模块表
PyImport_FrozenModules = PyImport_FrozenModules_0;
sub_180001F20(v6);
sub_1800D3090(v6);
这是最接近源码的地方!
建议你现在立即查看:
.ro0000018011A300 PyImport_FrozenModules_0 dq offset frozen_name_main
.ro0000018011A308 dq offset code_main
.ro0000018011A310 dd length_main
如果能找到类似结构,你就拿到了原始模块的编译后代码对象(虽然是 marshal 序列化的字节码,但可提取)!
知道了这个地方获取冻结模块表,那就需要找到PyImport_FrozenModules_0来源

image.png (36.86 KB, 下载次数: 1)
下载附件
2025-9-28 17:35 上传
继续跟进

image.png (36.22 KB, 下载次数: 1)
下载附件
2025-9-28 17:35 上传
这里获取了资源,nuitka会将python的字节码写入在资源段中

image.png (32.21 KB, 下载次数: 0)
下载附件
2025-9-28 17:36 上传
5. 提取资源
使用Resource Hacker提取id为3的资源,可以看到里面的一些字节码,还有一些库的注释,这就是我们需要的

image.png (114 KB, 下载次数: 0)
下载附件
2025-9-28 17:37 上传
6. 解析字节码
但是我们不知道如何解析,上网搜索后找到如下帖子:
https://goatmilkk.notion.site/Nuitka-a3ac9ee7f3f240f3baa345c17f2b8aa3
https://blog.svenskithesource.be/posts/flare10-07-(nuitka)/
https://services.google.com/fh/files/misc/7-flake-flareon10.pdf
最后一篇是google官方教你如何逆向一个nuitka的CTF挑战,非常有帮助
根据文档编写解析代码
[Python] 纯文本查看 复制代码import io
import struct
def read_uint32(bio):
return struct.unpack("[I]
运行后结果如下:
[Python] 纯文本查看 复制代码hash: 0x705322c1
size: 0x6075a9
name: .bytecode, size: 0x5f750a, count: 0x156
name: , size: 0x361, count: 0x69
name: PIL._version, size: 0x8d, count: 0xc
name: PIL, size: 0x75a, count: 0x23
name: __main__, size: 0x1490, count: 0x28
name: pystray._appindicator, size: 0x5c0, count: 0x60
name: pystray._base, size: 0x3590, count: 0x11c
name: pystray._darwin, size: 0xed9, count: 0xcc
name: pystray._dummy, size: 0xf7, count: 0xd
name: pystray._gtk, size: 0x4ed, count: 0x4a
name: pystray._util, size: 0x3c3, count: 0x29
name: pystray._util.gtk, size: 0xb7c, count: 0x97
name: pystray._util.notify_dbus, size: 0x4b6, count: 0x4b
name: pystray._util.win32, size: 0xd22, count: 0x103
name: pystray._win32, size: 0x17e6, count: 0xf8
name: pystray._xorg, size: 0x196a, count: 0x114
name: pystray, size: 0x37a, count: 0x3c
name: six, size: 0x37f0, count: 0x223
name: tkinter-preLoad, size: 0xbb, count: 0x11
name: watchdog, size: 0xd9, count: 0x10
可以看到一个关键__main__
我们继续编写函数解析这个main,文档下面也有解析的地方我们抄过来
[Python] 纯文本查看 复制代码import io
import sys
import struct
def read_uint8(bio):
return struct.unpack("[B]
我们运行后发现报错了,提示没有类型B:

image.png (121.68 KB, 下载次数: 1)
下载附件
2025-9-28 17:47 上传
那大概率是nuitka版本变更导致解析方式也变了,我们只能返回ida找到新的解析办法,也是成功找到的解析函数,直接把仍给AI,让AI仿照文章中的代码给出一个

image.png (38.84 KB, 下载次数: 1)
下载附件
2025-9-28 17:49 上传
很快啊,AI给出了代码:
[Python] 纯文本查看 复制代码import io
import sys
import struct
def read_uint8(bio):
return struct.unpack("[B]"
elif type_char == 'f':
# 浮点数 (float): 读取8字节双精度浮点数
float_bytes = bio.read(8)
o = struct.unpack('")
elif type_char == 'O':
# 动态属性获取: 这是一个复杂的操作,通常用于获取内置模块的属性。
# 它会一直读取直到遇到空字节,然后调用 PyObject_GetAttrString。
# 我们无法在静态解析中模拟其结果,只能记录其行为。
attr_name = ""
while True:
c = bio.read(1)
if c == b"\x00" or not c:
break
attr_name += c.decode("utf-8")
o = f""
elif type_char == 'Q':
# 特殊值: 类似'M',读取一个字节来区分 NotImplmented, Ellipsis 等。
special_type = read_uint8(bio)
special_map = {
0: "Ellipsis",
1: "NotImplemented",
2: "" # 文档中对应 ::AttrString
}
o = special_map.get(special_type, f"")
elif type_char == 'X':
# 字节偏移: 用LEB128读取一个长度,然后将数据指针向前移动该长度。
# 这不是一个独立的对象,而是一种跳过数据的方式。
# 在Nuitka的上下文中,它可能用于对齐或填充。
skip_length = read_leb128(bio)
# 移动文件指针
bio.seek(bio.tell() + skip_length)
# 这种类型不产生一个可以放入列表的Python对象。
# 我们可以跳过它,或者用一个标记表示。
continue # 跳过此条目,不添加到container中
elif type_char == 'Z':
# 预定义浮点数: 读取一个字节索引,类似于'd'。
# 也需要特殊处理。
index = read_uint8(bio)
o = f""
elif type_char == 'C':
# 代码对象: Nuitka用于创建Python函数/方法的复杂结构。
# 包含版本、参数数量、标志等信息。
# 解析它需要非常深入的知识,这里只做简单展示。
version = read_leb128(bio)
argcount = read_leb128(bio)
flags = read_leb128(bio)
# 后续还有更多字段,如默认参数、注解等...
o = f""
elif type_char == 'A':
# 泛型别名: 用于typing模块,如 List[int]。
# 需要两个参数:原始类型和参数。
origin = decode_blob(bio, 1)[0]
args = decode_blob(bio, 1)[0]
o = f""
elif type_char == ';':
# Lambda表达式: 一种特殊的代码对象。
# 解析方式与'C'类似但更简单。
code_obj = decode_blob(bio, 1)[0]
defaults = decode_blob(bio, 1)[0]
closure = decode_blob(bio, 1)[0]
o = f""
elif type_char == ':':
# 切片对象: 创建一个slice(start, stop, step)。
start = decode_blob(bio, 1)[0]
stop = decode_blob(bio, 1)[0]
step = decode_blob(bio, 1)[0]
o = slice(start, stop, step)
elif type_char == 'p':
# 堆栈引用: 引用前面已经解码过的对象。
# 这是一个相对引用(通常是前一个对象)。
# 在我们的线性解码中,这很难准确模拟,因为它依赖于解码顺序。
o = ""
else:
raise ValueError(f"unhandled type {type_char} (0x{type_byte:02X})")
container.append(o)
return container
def main():
with open("main.bin", "rb") as f_in:
bs = f_in.read()
bio = io.BytesIO(bs)
hash_ = read_uint32(bio)
size = read_uint32(bio)
while bio.tell()
运行后:其中的base64代码省略
[Python] 纯文本查看 复制代码Decoding '__main__' blob (Name: '__main__', Size: 0x1490, Count: 0x28)...
0: __mro_entries__
1: bases
2: __iter__
3: __getitem__
4: %s argument after * must be an iterable, not %s
5: __name__
6: keys
7: %s argument after ** must be a mapping, not %s
8: %s got multiple values for keyword argument '%s'
9: called
10: star_arg_dict
11: star_arg_list
12: kw
13: b''
14: decrypted
15: append
16: key_len
17: XOR 解密函数
18: __doc__
19: __file__
20: __cached__
21: __annotations__
22: base64
23: {'data': 'key', 'return': '', '': ''}
24: xor_decrypt
25: my_secret_key_9456821
26: ONEKEY
27: utf-8
28: SECRET_KEY
29: YHM2HhUMABFULBIWdFVQWUVZSkYR.........M+d/cs48YFhwTRHRV
30: ENCRYPTED_B64
31: b64decode
32: encrypted_data
33: decrypted_script
34: {'__name__': '__main__'}
35:
36: exec
37: main.py
38:
39: ('data', 'key', 'key_len', 'decrypted', 'i', 'byte')
通过这个,我们可以还原原始的python代码了:
[Python] 纯文本查看 复制代码import base64
# 第一步:Base64解码
b64_data = ("YHM2HhUMABFULBIWdFVQWUVZSkYRDxgsFlNXf28SLQQIWTtYQFBCUV9UTRAyAwoRBkUQPh8ADTZUUTg8NThuLjAPOyA"
"xUlhUeD4OPxJjBllTWXVnWyE1Nh8uWTwmEQIBNQ0LWFFXf2QeNT0aBCg3MwwhGjkqIzIAUlQEYgUWYHMAOCA6UlhUeD"
"0gPw9jBgxQWQBnWCElOhIuGDAceGZvJgxtZnx4f2ERUFkEVJX86OVUuvvKnNWR0Zuo3Iy6TTgDHUJPUkKEwP/1WbeWg"
"92Iq9e0yJzw9YLD84rIxUxJWXjbqLAW0Zi9hdbele3zl+/rsNfkJTEeGBURyK2i6Fm6zvaG++iSyN2M7usDFBJrNTg8"
"ZzYPLDU2IS1UYktVAW8IOT95aG19IjgbLDY3IEVJf1sdSWk0PnpmZ3FwITV/TkVTClVHUmEqKQBzeWVpcXRuIzx/TkV"
"TClVAUmEqKQBxdXliGA8RXQFvRmhpf28QOg1FJjtcV0dPSEYZCRgrEklDGUxOUmFFWX8ZRlBCTUBfTRsmBwAQWgdUAU"
"sOIjYZERVaXVwZBlACUwMMAEUdc0sHWTZXFFBYTV9UHxgrFk0HExEVdkJoc1IzUFBQGG1VBxttWxZKSGh+f0tFWTcZC"
"RUDCwoAYHN/U0VDFAoGfwhFEDEZRw87MhIRTVl/U0VDGkVJf0NNEX8FCBUDERIaTRF2UztDEWh+f0tFWS1cQEBEVhJZ"
"TV9/Qx0lNCMyGS0jP1IzOT9VVFNCHlkJPl9ueEVUf0sBHDkZa2pfVltFMiZ3AAAPFElUPAQBHHYDOT8WGBIRTVl/UxY"
"GHgNaPAQBHH8EFFZZXFc8Z1l/U0VDUkVULA4JH3FJVxULGAI8Z1l/U0VDUkVULA4JH3FKQFRVUxIMTSICfm9DUkVUf0"
"tFWSxcWFMYSldWTUR/KFVPUlUpUmFFWX8ZFBUWGEFUAR9xGwQNFgkRLEtYWQRkOT87MhIRTVk7FgNDABAadxgAFTkQD"
"jg8GBIRTVl/U0UXABxOUmFFWX8ZFBUWGBIRTVkoGwwPF0UHOgcDVy9aFAkWVFdfRQo6HwNNEQoQOkJfdFUZFBUWGBIR"
"TVl/U0VDUkVUMBtFRH9KUVlQFlFeCRwEAAAPFEsEPDZoc38ZFBUWGBIRTVl/U0VDUkUHOgcDVy9aFB4LGAM8Z1l/U0V"
"DUkVUf0tFWX8ZFBVfXhJeHVliTkUsIjokCjgtQ1IzFBUWGBIRTVl/U0VDUkVUf0tFWX9QUhVFXV5XQwk8U05DRkVKfw"
"cAF3dKUVlQFlFeCRx2SWhpUkVUf0tFWX8ZFBUWGBIRTVl/U0VDUkVUPRkAGDQ0PhUWGBIRTVl/U0VDUkVUf0tFWX8ZQ"
"hULGFtfGVc5AQoOLQcNKw4WUSxcWFMYW11VCCIsFgkFXBUXZRgAFTkXRFYdDG8dTV4zGhEXHgBTdmZvWX8ZFBUWGBIR"
"TVl/U0VDUkVUf0sWHDNfGkZCWVFaQxgvAwANFk0CdmZvWX8ZFBUWGBIRTVl/U0VDUkVUf0sWHDNfGkVVGBkMTU1SeUV"
"DUkVUf0tFWX8ZFBUWGBJUARA5UwoTUlhJfyQ1JhN2dXFpa2ZjV3RVU0VDUkVUf0tFWX8ZFBUWGBIRTVk2FUUQFwkScR"
"sGWWEEFFlTVhpCCBU5XQYMFgBdZWZvWX8ZFBUWGBIRTVl/U0VDUkVUf0tFWX8ZVkdTWVk8Z1l/U0VDUkVUf0tFWX8ZF"
"BUWGBIRHhwzFUsRFwIvbzZFRH9KUVlQFlFeCRwEAAAPFEsEPDZoc38ZFBUWGBIRTVl/U0VDUkVUf0tFCjpVUhtGWxIa"
"UFlufm9DUkVUf0tFWX8ZFBUWGBIRCBU2FUUMAkVJYksqKQB6dXl6Aj87TVl/U0VDUkVUf0tFWX8ZFBUWGBJYC1ksFgk"
"FXBUXf1VYWTNcWh1FXV5XQxowFwBKSGh+f0tFWX8ZFBUWGBIRTVl/U0VDUkVUf0tFGy1cVV47MhIRTVl/U0VDUkVUf0"
"tFWX8ZFBUWS1ERUFksFgkFXAYbOw4+CjpVUhtGW288Z1l/U0VDUkVUf0tFWX8ZFBUWGBIRHhwzFUsTEUVfYktUdFUZF"
"BUWGBIRTVl/U0VDUkVUf0tFWTZfFEZVGA8MTUllfm9DUkVUf0tFWX8ZFBUWGBIRTVl/U0VDUkUELQILDXdmZ2FkcXx2"
"PiIsFgkFXBcRODBVJAIVFFBYXA8WSlV/FQkWAQ1JCxkQHHY0PhUWGBIRTVl/U0VDUkVUf0tFWX8ZUVlfXhJCDlliTkV"
"SSGh+f0tFWX8ZFBUWGBIRTVl/U0VDUkVUf0tFCn8EFFxYSEdFRVBxABERGxVcdkUAFzxWUFAeET87TVl/U0VDUkVUf0"
"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"
"+d/cs48YFhwTRHRV")
decoded_b64 = base64.b64decode(b64_data)
# 第二步:XOR解密
key = b"my_secret_key_9456821"
decrypted_bytes = bytearray()
for i in range(len(decoded_b64)):
decrypted_byte = decoded_b64 ^ key[i % len(key)]
decrypted_bytes.append(decrypted_byte)
# 结果是原始的Python源代码
original_code = decrypted_bytes.decode('utf-8')
print(original_code)
运行结果:
[Python] 纯文本查看 复制代码import sys
import base64
from datetime import datetime
_CIPHER = 'UkFMZ2leaGV6XjEzM+YRNidLR2ldaGV/XDEwMTAiUEROZm9fa2Z7'
_KEY = 'VEFPZ29fa2V5XzIwMjUh'
_STRINGS = ['🚀 启动实例 A\n', '🔐 请输入密码:', '✅ 验证成功!\n', '📅 当前时间: ']
OP_PUSH = 0x01
OP_LOAD_STR = 0x06
OP_CALL = 0x03
OP_JMP_IF_NE = 0x04
OP_HALT = 0x05
def _decrypt(data, k):
return bytes(b ^ k[i % len(k)] for i, b in enumerate(data))
def _djb2(s):
h = 5381
for c in s:
h = ((h len(self.code):
break
v = int.from_bytes(self.code[self.pc:self.pc+4], 'little')
self.stack.append(v)
self.pc += 4
elif op == OP_LOAD_STR:
if self.pc >= len(self.code):
break
self.reg[0] = self.code[self.pc]
self.pc += 1
elif op == OP_CALL:
if self.pc >= len(self.code):
break
sc = self.code[self.pc]
self.pc += 1
if sc == 0:
print(_STRINGS[self.reg[0]], end='', flush=True)
elif sc == 1:
s = input().strip().encode()
self.handles.append(s)
self.reg[0] = len(self.handles) - 1
elif sc == 2:
code = self.stack.pop() if self.stack else 1
sys.exit(code)
elif sc == 3:
h = self.reg[0]
self.reg[1] = _djb2(self.handles[h]) if 0 = len(self.code):
break
target = self.code[self.pc]
self.pc += 1
actual = self.reg[1]
expected = self.stack.pop() if self.stack else 0
if actual != expected:
if 0
7. 破解密码
我们已经得到了作者的原始代码,现在观察这个代码,是一个简单的虚拟机,字节码如下
让AI自动去分析,{:301_997:} 太懒了{:301_1007:}
可以直接必须输入一个hash 是 1717711059 才能通过
8. 编写破解hash的代码,github:https://github.com/m9psy/DJB2_collision_generator/tree/master/djb2_collision_generator
魔改一下:
generator.py
[Python] 纯文本查看 复制代码# -*- coding: utf-8 -*-
"""
增强版 DJB2 哈希碰撞生成器
功能:
✅ 给定目标哈希值,生成能产生该哈希的输入字符串
✅ 支持指定生成数量(如只生成 5 个)
✅ 可选仅使用“可见字符”(可打印 ASCII)
✅ 自动扩展长度以寻找更多碰撞
"""
from __future__ import print_function
import warnings
import random
import copy
import time
import sys
# 导入哈希函数
from hash_functions import djb2_32, djb2_64, djb2_xor_32
class GreedyGenerator:
"""
贪心式哈希碰撞生成器(支持可见字符限制)
"""
DEFAULT_LENGTH = 16 # 减小默认长度以便更快找到结果
TOTAL_ROUNDS = 10 # 每轮优化次数
MUTATION_MOD = 2 # 突变比例:修改一半字节
VERBOSE = False # 是否输出调试信息
# 定义可见字符范围(ASCII 32 ~ 126)
PRINTABLE_RANGE = list(range(32, 127))
def __init__(self, hash_function, target_value, total_letters=DEFAULT_LENGTH, only_printable=False):
"""
初始化生成器
:param hash_function: 哈希函数(如 djb2_32)
:param target_value: 目标哈希值
:param total_letters: 字符串长度
:param only_printable: 是否仅使用可见字符
"""
if total_letters 0")
old_val = self.string_to_guess[:min(self.TOTAL_LETTERS, new_size)]
self.TOTAL_LETTERS = new_size
self.BYTES_TO_MUTATE = max(1, self.TOTAL_LETTERS // self.MUTATION_MOD)
new_guess = bytearray(b'\x00') * self.TOTAL_LETTERS
for i in range(len(old_val)):
new_guess = old_val
self.string_to_guess = new_guess
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
"""生成下一个可能的碰撞(返回 bytes)"""
self.string_to_guess = self.mutate_full(self.string_to_guess)
while True:
prev_state = None
for round_idx in range(self.TOTAL_ROUNDS):
improved = False
for pos in range(self.TOTAL_LETTERS):
candidates = self._get_hash_for_position(pos)
match = self._check_exact_hit(pos, candidates)
if match is not None:
return match
# 在允许的字节范围内选择最优值
best_byte = min(
self.allowed_bytes,
key=lambda b: abs(candidates.get(b, float('inf')) - self.target)
)
if self.string_to_guess[pos] != best_byte:
self.string_to_guess[pos] = best_byte
improved = True
current_hash = self.checker(self.string_to_guess)
if self.VERBOSE:
print(f"第 {round_idx} 轮 | 哈希={current_hash} | 误差={abs(current_hash - self.target)}")
if not improved or self.string_to_guess == prev_state:
break
prev_state = copy.copy(self.string_to_guess)
self.string_to_guess = self.mutate_partial(self.string_to_guess)
def _get_hash_for_position(self, index):
"""枚举当前位置所有允许字节对应的哈希值"""
original = self.string_to_guess[index]
results = {}
for b in self.allowed_bytes:
self.string_to_guess[index] = b
results = self.checker(self.string_to_guess)
self.string_to_guess[index] = original
return results
def _check_exact_hit(self, index, candidates):
"""检查是否有字节能命中目标哈希"""
for byte_val, h in candidates.items():
if h == self.target and byte_val in self.allowed_bytes:
self.string_to_guess[index] = byte_val
return bytes(copy.copy(self.string_to_guess))
return None
def mutate_partial(self, data):
"""部分突变:只在允许字符集中随机替换"""
for _ in range(self.BYTES_TO_MUTATE):
pos = random.randint(0, self.TOTAL_LETTERS - 1)
new_val = random.choice(self.allowed_bytes)
data[pos] = new_val
return data
def mutate_full(self, data):
"""完全随机初始化(仅用允许字符)"""
return bytearray(random.choices(self.allowed_bytes, k=self.TOTAL_LETTERS))
if __name__ == "__main__":
"""
主入口:支持参数控制
用法: python generator.py [选项...]
示例:
python generator.py 1717864659
python generator.py 123456789 -n 5 -p -l 20
"""
if len(sys.argv) [-n 数量] [-p] [-l 长度]", file=sys.stderr)
print("选项:")
print(" -n N : 只生成 N 个结果(默认无限)")
print(" -p : 仅使用可见字符(可打印 ASCII)")
print(" -l LENGTH : 设置初始字符串长度(默认 16)")
sys.exit(1)
# 解析目标哈希
try:
target_hash = int(sys.argv[1])
except ValueError:
print("❌ 错误:目标哈希必须是整数", file=sys.stderr)
sys.exit(1)
# 默认参数
max_count = None # 默认不限制数量
only_printable = False # 是否仅可见字符
string_length = 16 # 初始长度
# 解析命令行参数
args = sys.argv[2:]
i = 0
while i = 2**64:
print("❌ 哈希值必须在 [0, 2^64) 范围内", file=sys.stderr)
sys.exit(1)
# hash_func = djb2_64 if target_hash >= 2**32 else djb2_32
hash_func = djb2_xor_32
bits = "64位" if target_hash >= 2**32 else "32位"
# 输出配置信息
print(f"🎯 开始寻找哈希值 {target_hash}")
print(f"🔧 使用 {bits} DJB2 算法")
print(f"📏 初始长度: {string_length}")
print(f"🔤 {'仅限可见字符' if only_printable else '允许所有字节'}")
print(f"📊 {'最多生成 %d 个' % max_count if max_count else '无限生成'}")
start_time = time.time()
generator = GreedyGenerator(hash_func, target_hash, string_length, only_printable=only_printable)
found_collisions = set()
consecutive_misses = 0
try:
while True:
try:
candidate = next(generator)
if candidate not in found_collisions:
try:
# 尝试以 UTF-8 解码显示(非必须)
# decoded = candidate.decode('utf-8', errors='backslashreplace')
print(f"{candidate.decode('utf-8', errors='backslashreplace')}")
except:
print(candidate)
found_collisions.add(candidate)
consecutive_misses = 0
# 如果达到指定数量,退出
if max_count and len(found_collisions) >= max_count:
break
else:
consecutive_misses += 1
except StopIteration:
print(f"➡️ 当前长度({generator.TOTAL_LETTERS})无新解,扩展至 {generator.TOTAL_LETTERS + 1}...")
generator.set_collision_size(generator.TOTAL_LETTERS + 1)
consecutive_misses = 0
# 提前结束判断
if max_count and len(found_collisions) >= max_count:
break
# 主动结束输出统计
end_time = time.time()
elapsed = end_time - start_time
print("\n" + "=" * 60)
print("✅ 任务完成!")
print(f"🔖 目标哈希: {target_hash}")
print(f"📦 生成数量: {len(found_collisions)}")
print(f"⏱ 耗时: {elapsed:.2f} 秒")
if elapsed > 0:
print(f"🚀 平均速度: {len(found_collisions) / elapsed:.4f} 个/秒")
print("=" * 60)
except KeyboardInterrupt:
end_time = time.time()
elapsed = end_time - start_time
print("\n\n⏸️ 用户中断")
print(f"📌 共生成 {len(found_collisions)} 个碰撞")
print(f"⏰ 耗时: {elapsed:.2f} 秒")
if elapsed > 0:
print(f"⚡ 速度: {len(found_collisions) / elapsed:.4f} 个/秒")
hash_functions.py
[Python] 纯文本查看 复制代码# -*- coding: utf-8 -*-
from functools import partial
MOD_32 = 2 ** 32
MOD_64 = 2 ** 64
def djb2(data, modulo=MOD_32):
h = 5381
for c in data:
h = (h * 33 + c) % modulo
return h
def djb2_xor_32(data, modulo=MOD_32):
"""
⚠️ 非标准“异或型”DJ B2(常出现在 CTF 或虚拟机中)
公式: h = h * 33 ^ c
注意:这不是原始 DJB2!但常被误称为 DJB2
用途:混淆、反分析、小型 VM 验证
"""
h = 5381
for c in data:
h = ((h
运行后生成符合标准的密码:
[Python] 纯文本查看 复制代码🎯 开始寻找哈希值 1717711059
🔧 使用 32位 DJB2 算法
📏 初始长度: 6
🔤 仅限可见字符
📊 最多生成 3 个
nT`Vlv
ou`X#W
i1"WO4
============================================================
✅ 任务完成!
🔖 目标哈希: 1717711059
📦 生成数量: 3
⏱ 耗时: 0.01 秒
🚀 平均速度: 428.0484 个/秒
============================================================
最终得到正确的密码,不唯一

image.png (8.78 KB, 下载次数: 1)
下载附件
2025-9-28 18:09 上传

image.png (7.97 KB, 下载次数: 1)
下载附件
2025-9-28 18:10 上传

image.png (8.24 KB, 下载次数: 1)
下载附件
2025-9-28 18:10 上传