1. 看到作者这次又升级了,搞了新的花活,有点意思,原帖地址:【新提醒】你敢信!是AI自编!python混淆技术结果demo版 - 吾爱破解 - 52pojie.cn
2. 开始破解,第一步还是看样本信息,pyinstall编写的,不说了,直接上工具,可以看我之前的帖子获取工具
【新提醒】破解 [CrackMe] 自己平时用py写了个一体化打包,来检验一下好破解吗 思路 - 吾爱破解 - 52pojie.cn
3. 解包信息
这里展示部分反编译代码
这里比较重要,导入了dll
4. 这个混淆代码使用了Fernet库作为加密手段,并且加载dll实现代码隐藏
这里写一个简单的解密函数
[Python] 纯文本查看 复制代码 import base64
from cryptography.fernet import Fernet
base64_str = "dlNLRERnYnFCMVdmYXJDMnRtUHl5cXFaWFdKYVIwSkRRbE9qNGV3VWYxZz06Z0FBQUFBQnBPNVJ6MWJVOXFqeHpTa21UTVdQbXAya1hUem8yVy1uRjJ4a0pLRDJJTlR6dkpnV1hZU05Kb0oyZmVrQVZWa1VXMm9iWEFqVDI1clZoVUxKbXk0cDBsamlvSlE9PQ=="
raw_data = base64.b64decode(base64_str)
# 2. 转为字符串(假设是 UTF-8)
combined_str = raw_data.decode('utf-8')
# 3. 按 ':' 分割
parts = combined_str.split(':', 1)
if len(parts) != 2:
raise ValueError("Invalid format: expected 'key:token'")
fernet_key = parts[0]
fernet_token = parts[1]
try:
f = Fernet(fernet_key)
decrypted_bytes = f.decrypt(fernet_token)
# 还原的 Python 代码块
decrypted_code = decrypted_bytes.decode('utf-8')
print("--- 还原的代码块 ---")
print(decrypted_code)
print("--------------------")
except Exception as e:
print(f"解密失败,可能密钥不匹配或数据被篡改: {e}")
简单去除一部分混淆后知道了真实的dll还有使用的函数以及加载的base64编码的信息
5. 编写C语言代码调用这个dll方便我们进行调试,并且把base64信息提取出来放到文件中读取
先看看这个dll信息,加了upx壳
一键去壳
看一下导出表和导入表
编写C++代码
[C++] 纯文本查看 复制代码 #include <windows.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
// 函数指针类型
typedef int(__stdcall* RunProtectedScriptFunc)(const char* payload);
// 读取同级目录下的 "test" 文件
std::string readTestFile() {
std::ifstream file("test", std::ios::binary);
if (!file) {
std::cerr << "[!] Failed to open 'test' file in current directory." << std::endl;
return "";
}
std::ostringstream ss;
ss << file.rdbuf();
return ss.str();
}
// 清理 Base64 中的空白字符
std::string cleanBase64(const std::string& input) {
std::string output;
for (char c : input) {
if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
output += c;
}
}
return output;
}
int main() {
const char* dllName = "libcrypto-03.dll";
const char* funcName = "run_protected_script";
// 1. 加载 DLL
HMODULE hDll = LoadLibraryA(dllName);
if (!hDll) {
std::cerr << "[!] Failed to load DLL: " << dllName << std::endl;
return 1;
}
// 2. 获取函数地址
RunProtectedScriptFunc runScript = (RunProtectedScriptFunc)GetProcAddress(hDll, funcName);
if (!runScript) {
std::cerr << "[!] Failed to get function: " << funcName << std::endl;
FreeLibrary(hDll);
return 1;
}
// 3. 读取 "test" 文件
std::string base64Raw = readTestFile();
if (base64Raw.empty()) {
FreeLibrary(hDll);
return 1;
}
// 4. 清理 Base64
std::string base64Clean = cleanBase64(base64Raw);
std::cout << "[+] Loaded 'test' file, length: " << base64Clean.length() << " chars" << std::endl;
// 5. 调用 DLL
std::cout << "[*] Calling run_protected_script..." << std::endl;
int result = runScript(base64Clean.c_str());
std::cout << "[+] Function returned: " << result << std::endl;
// 6. 清理
FreeLibrary(hDll);
return 0;
}
把编译后的exe文件放入dll目录中,注意还需要补一下python环境
创建lib目录,把base_library.zip内容和cryptography以及cryptography-46.0.3.dist-info都复制进去
开始动调,细节就不说了,在内存中可以找到还原的代码,把这个代码dump出来
6. 处理代码
这个代码很长4000多行,采用了很多手段防止静态分析,添加了很多垃圾代码,还有很多平坦流等等
编写ast解析代码并去除混淆,也是废了很多功夫把混淆去除了一大部分 这里给出部分代码,虚拟机类的run函数还没有完全去除混淆就不贴了
大致逻辑如下:是一个虚拟机,最终对比的hash是否一致
[Python] 纯文本查看 复制代码 import sys
import base64
from datetime import datetime
# 加密的 base64 数据
ENCRYPTED_B64_DATA = 'AkFPZ26w1cinXt2On+slFVU5GVN9WWtmeVkzMzI2IANXQIq+b19qaxJfMjM0MW4GVkBPYWxca2Z8XDY1MyQwAFRAXnZvX28qeF8yMDI2IwFVQU9nbF1vcdq02vV8s/rKyJTVsvP0roiGDTZUJZbK5oEPxIO9zorErOii9M7HtOW7zbiYe+6DkhzY+/e/4//U7OOfzKPEpaEdTJHb0/VvhqK307HQic7msZvyeFc='
# XOR 解密密钥(用于解密主 payload)
MAIN_XOR_KEY = bytes([84, 65, 79, 103, 111, 95, 107, 101, 121, 95, 50, 48, 50, 53, 33, 0])
def xor_decrypt(data: bytes, key: bytes) -> bytes:
"""对数据进行 XOR 解密"""
return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
class StringTableParser:
"""
解析加密数据中的字符串表。
数据格式:
- 前 4 字节:字符串表的偏移(小端)
- 从该偏移开始:
1 字节:字符串数量 N
接下来 N 个字符串,每个以 1 字节长度 L 开头,后跟 L 字节加密数据
每个字符串用固定密钥 'StrEncKey_2025!' XOR 解密
"""
def __init__(self, encrypted_payload: bytes):
self.payload = encrypted_payload
self.strings = []
if len(encrypted_payload) < 4:
return
# 读取字符串表偏移(小端序)
string_table_offset = int.from_bytes(encrypted_payload[:4], 'little')
if string_table_offset >= len(encrypted_payload):
return
offset = string_table_offset
string_count = encrypted_payload[offset]
offset += 1
# 解密每个字符串
for _ in range(string_count):
if offset >= len(encrypted_payload):
break
length = encrypted_payload[offset]
offset += 1
if offset + length > len(encrypted_payload):
self.strings.append('') # 数据损坏,添加空串
offset += 1
continue
encrypted_str = encrypted_payload[offset:offset + length]
offset += length
# 使用固定密钥解密字符串
str_key = b'StrEncKey_2025!'
decrypted = bytes(
encrypted_str[i] ^ str_key[i % len(str_key)]
for i in range(len(encrypted_str))
)
try:
decoded_str = decrypted.decode('utf-8')
except UnicodeDecodeError:
decoded_str = decrypted.decode('utf-8', errors='replace')
self.strings.append(decoded_str)
def run(self):
"""当前为空,无实际执行逻辑"""
pass
# 主流程
if __name__ == '__main__':
# 1. Base64 解码
decoded_data = base64.b64decode(ENCRYPTED_B64_DATA)
# 2. 用 MAIN_XOR_KEY 解密主 payload
decrypted_payload = xor_decrypt(decoded_data, MAIN_XOR_KEY)
# 3. 解析字符串表
parser = StringTableParser(decrypted_payload)
parser.run()
7. hash函数
找到最终的hash函数实现方式
一个自定义的非加密哈希函数,其设计融合了多种常见哈希构造技巧,但是输出空间小(32位)
这就给我们可以进行hash碰撞的机会
[Python] 纯文本查看 复制代码 def custom_hash_simple(data: bytes) -> int:
hash_value = 2596069104
for i, b in enumerate(data):
hash_value = (hash_value * 2654435761 + b + (i ^ 21930)) & 0xFFFFFFFF
hash_value = (hash_value ^ (hash_value >> 11)) & 0xFFFFFFFF
hash_value = ((hash_value << 7) | (hash_value >> 25)) & 0xFFFFFFFF
for const in [305419896, 2596069104, 4275878552]:
hash_value = ((hash_value ^ const) * 16777619) & 0xFFFFFFFF
return hash_value
8. 进行hash碰撞
代码中的这部分是最终的判断逻辑,如果我们输入555,计算的hash结果是1131975512,而代码中的期望值是3653593870
编写C++继续hash碰撞
[C++] 纯文本查看 复制代码 #include <iostream>
#include <vector>
#include <string>
#include <cstdint>
#include <atomic>
#include <thread>
#include <cstring>
#include <mutex>
const uint32_t MOD_INVERSE = 899433627U;
const int TARGET_FIND_COUNT = 5; // 目标找到 5 个
std::atomic<int> found_count(0); // 全局计数器
std::mutex cout_mutex; // 用于保护标准输出的锁
// === 哈希函数保持不变 ===
inline uint32_t fast_hash_core(const char* data, size_t len) {
uint32_t hash_value = 2596069104U;
for (size_t i = 0; i < len; ++i) {
hash_value = (hash_value * 2654435761U + (uint8_t)data[i] + (static_cast<uint32_t>(i) ^ 21930U)) & 0xFFFFFFFFU;
hash_value = (hash_value ^ (hash_value >> 11)) & 0xFFFFFFFFU;
hash_value = ((hash_value << 7) | (hash_value >> 25)) & 0xFFFFFFFFU;
}
return hash_value;
}
uint32_t pre_process_target(uint32_t target) {
uint32_t v = target;
const uint32_t constants[] = { 4275878552U, 2596069104U, 305419896U };
for (uint32_t const_val : constants) {
v = (v * MOD_INVERSE) & 0xFFFFFFFFU;
v ^= const_val;
}
return v;
}
// === 搜索逻辑修改 ===
const std::string charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
void search_worker(uint32_t processed_target, std::string prefix, int max_len) {
char buffer[16];
int prefix_len = prefix.length();
std::memcpy(buffer, prefix.c_str(), prefix_len);
// 使用递归 DFS 搜索
auto solve = [&](auto self, int depth) -> void {
// 如果已经找到足够数量,立即退出递归
if (found_count.load() >= TARGET_FIND_COUNT || depth >= max_len) return;
for (char c : charset) {
if (found_count.load() >= TARGET_FIND_COUNT) return;
buffer[depth] = c;
uint32_t h = fast_hash_core(buffer, depth + 1);
if (h == processed_target) {
// 加锁保证输出完整性并安全增加计数器
std::lock_guard<std::mutex> lock(cout_mutex);
if (found_count.load() < TARGET_FIND_COUNT) {
found_count++;
std::cout << "[!] 找到碰撞 #" << found_count << ": "
<< std::string(buffer, depth + 1) << std::endl;
}
if (found_count.load() >= TARGET_FIND_COUNT) return;
}
// 继续向下递归
self(self, depth + 1);
}
};
solve(solve, prefix_len);
}
int main() {
// 目标哈希值(示例)
uint32_t original_target = 3653593870U;
uint32_t processed_target = pre_process_target(original_target);
std::cout << "--- 开始破解 ---" << std::endl;
std::cout << "目标结果数: " << TARGET_FIND_COUNT << std::endl;
std::cout << "正在启动多线程扫描...\n" << std::endl;
int max_length = 6;
std::vector<std::thread> threads;
// 为字符集中的每个首字母开一个线程(或者根据 CPU 核心数分组)
// 这里我们启动更多的线程以充分利用多核
for (size_t i = 0; i < charset.size(); ++i) {
threads.emplace_back(search_worker, processed_target, std::string(1, charset[i]), max_length);
}
for (auto& t : threads) {
if (t.joinable()) t.join();
}
if (found_count.load() >= TARGET_FIND_COUNT) {
std::cout << "\n任务完成:已找到 " << TARGET_FIND_COUNT << " 个结果。" << std::endl;
}
else {
std::cout << "\n扫描结束,共找到 " << found_count.load() << " 个结果。" << std::endl;
}
return 0;
}
很快啊找到了合适的结果
验证两个,正确
|