好友
阅读权限30
听众
最后登录1970-1-1
|
本帖最后由 神奇的人鱼 于 2026-3-4 10:14 编辑
1. 欢迎来到windows中级,先看什么编写的
这不是巧了吗,之前做过nuitka的逆向分析,参考:破解 [CrackMe] 我又来了,这次用py的nuitka,我打包工具的另一个分支 思路 - 吾爱破解 - 52pojie.cn
2. 既然知道了nuitka,直接提取相关文件,可以通过github上的一个库提取内置的二进制文件,可以通过监控程序查看释放路径地址:https://github.com/extremecoders-re/nuitka-extractor
得到关键dll
3. 提取资源使用Resource Hacker提取id为3的资源
4. 内容解析
首先先尝试使用上个帖子中的代码尝试解析
[Python] 纯文本查看 复制代码 import io
import sys
import struct
def read_uint8(bio):
return struct.unpack("<B", bio.read(1))[0]
def read_uint16(bio):
return struct.unpack("<H", bio.read(2))[0]
def read_uint32(bio):
return struct.unpack("<I", bio.read(4))[0]
def read_utf8_size_1(bio):
return bio.read(1).decode("utf-8")
def read_utf8(bio):
bs = b""
while True:
c = bio.read(1)
if c == b"\x00" or not c: # 读取到空字节或文件末尾时停止
break
bs += c
return bs.decode("utf-8")
def read_bytearray(bio):
bs = b""
while True:
c = bio.read(1)
if c == b"\x00" or not c:
break
bs += c
return bs
def decode_blob(bio, count):
container = []
for i in range(count):
type_ = chr(read_uint8(bio))
if type_ in ('a', 'u'):
o = read_utf8(bio)
elif type_ == 'l':
o = read_uint32(bio)
elif type_ == 'w':
o = read_utf8_size_1(bio)
elif type_ == 'T':
sub_count = read_uint32(bio)
o = tuple(decode_blob(bio, sub_count))
elif type_ == 'c':
o = read_bytearray(bio)
elif type_ == 'b':
size = read_uint32(bio)
o = bio.read(size)
else:
raise ValueError(f"unhandled type {type_}")
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)
# print(f"Header Hash: {hex(hash_)}")
# print(f"Total Size: {hex(size)}")
while bio.tell() < size:
blob_name = read_utf8(bio)
blob_size = read_uint32(bio)
blob_count = read_uint16(bio)
# print(f"Found Blob - Name: '{blob_name}', Size: {hex(blob_size)}, Count: {hex(blob_count)}")
# 检查是否是我们要找的 '__main__' 数据块
if blob_name == "__main__":
print("Decoding '__main__' blob...")
# 解码 '__main__' 数据块中的所有对象
decoded = decode_blob(bio, blob_count)
# 打印出每个对象的索引和值
for idx, obj in enumerate(decoded):
print(f"{idx}: {obj}")
break # 如果只需要 __main__,找到后就可以退出
else:
# 跳过当前数据块的剩余部分
bio.seek(bio.tell() + (blob_size - 2))
if __name__ == "__main__":
main()
不出所料解析失败,看来使用的nuitka版本不同
5. IDA分析
直接定位到解析函数
和之前帖子中的函数对比还是差距比较大的
6. 编写新的解析代码
借助AI的帮助也是很快的编写出来的新的解析代码
[Python] 纯文本查看 复制代码 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Nuitka 新版 Blob 解析器
适配 sub_31340ECE0 反编译逻辑
"""
import io
import struct
import sys
from typing import Any, List, Dict, Optional
def read_uint8(bio: io.BytesIO) -> int:
return struct.unpack("<B", bio.read(1))[0]
def read_uint16(bio: io.BytesIO) -> int:
return struct.unpack("<H", bio.read(2))[0]
def read_uint32(bio: io.BytesIO) -> int:
return struct.unpack("<I", bio.read(4))[0]
def read_leb128(bio: io.BytesIO) -> int:
"""读取无符号 LEB128 编码整数"""
result = 0
shift = 0
while True:
byte_val = read_uint8(bio)
result |= (byte_val & 0x7F) << shift
if not (byte_val & 0x80):
break
shift += 7
return result
def _read_leb128_signed(bio: io.BytesIO) -> int:
"""读取有符号 LEB128(Nuitka 内部使用无符号编码+符号位)"""
return read_leb128(bio)
def _decode_utf8_safe(data: bytes) -> str:
"""安全解码 UTF-8,使用 surrogatepass 处理代理对"""
return data.decode("utf-8", errors="surrogatepass")
class BlobParser:
"""Nuitka blob 解析器类,维护解析状态"""
# 预定义浮点数索引映射 (根据 C 代码 Z 类型推断)
PREDEFINED_DOUBLES = [
0.0, # index 0
-0.0, # index 1
float('inf'), # index 2
-float('inf'), # index 3
float('nan'), # index 4
1.0, # index 5
]
# M 类型匿名对象映射
ANON_TYPES = {
0: None,
1: Ellipsis,
2: NotImplemented,
3: "<PyFunction_Type>",
4: "<PyGen_Type>",
5: "<PyCFunction_Type>",
6: "<PyCode_Type>",
7: "<PyModule_Type>",
10: "<Unknown_M_10>",
}
# Q 类型特殊值映射
SPECIAL_VALUES = {
0: Ellipsis,
1: NotImplemented,
2: "<SELF_REFERENCE>",
}
def __init__(self):
self.stack: List[Any] = [] # 对象栈,用于 'p' 引用
self.ctx: Dict[str, Any] = {} # 上下文
def decode_one(self, bio: io.BytesIO) -> Any:
"""解析单个 blob 对象"""
type_byte = read_uint8(bio)
type_char = chr(type_byte)
result = self._decode_type(bio, type_char, type_byte)
# 将结果压栈(供 'p' 引用使用)
if result is not None or type_char == 'p':
self.stack.append(result)
return result
def _decode_type(self, bio: io.BytesIO, tc: str, tb: int) -> Any:
"""根据类型字符分发解析逻辑"""
# ========== 基础类型 ==========
if tc == 'n': # None
return None
if tc == 't': # True
return True
if tc == 'F': # False
return False
# ========== 字符串类型 ==========
if tc in ('a', 'u'): # null-terminated UTF-8 string
bs = b""
while True:
c = bio.read(1)
if not c or c == b"\x00":
break
bs += c
result = _decode_utf8_safe(bs)
# 'a' 会 intern,这里简化处理
return result
if tc == 'w': # single char interned
return _decode_utf8_safe(bio.read(1))
if tc == 'v': # length-prefixed UTF-8 string
length = read_leb128(bio)
return _decode_utf8_safe(bio.read(length))
if tc == 's': # interned empty string (new version)
# C 代码: PyUnicode_DecodeUTF8(v7, 0, ...) 长度为 0
return ""
# ========== 整数类型 ==========
if tc in ('l', 'q'): # small int
value = read_leb128(bio)
return -value if tc == 'q' else value
if tc in ('G', 'g'): # big int (LEB128 chunked)
byte_count = read_leb128(bio)
if byte_count == 0:
return 0
# 读取 byte_count 个 LEB128 编码的 7-bit chunks
result = 0
shift = 0
for _ in range(byte_count):
chunk = read_leb128(bio)
result |= chunk << shift
shift += 7
return -result if tc == 'g' else result
# ========== 容器类型 ==========
if tc == 'T': # tuple
count = read_leb128(bio)
return tuple(self.decode_one(bio) for _ in range(count))
if tc == 'L': # list
count = read_leb128(bio)
return [self.decode_one(bio) for _ in range(count)]
if tc == 'D': # dict
count = read_leb128(bio)
result = {}
for _ in range(count):
k = self.decode_one(bio)
v = self.decode_one(bio)
result[k] = v
return result
if tc in ('S', 'P'): # set / frozenset
count = read_leb128(bio)
items = [self.decode_one(bio) for _ in range(count)]
return set(items) if tc == 'S' else frozenset(items)
# ========== 字节类型 ==========
if tc == 'B': # bytearray
length = read_leb128(bio)
return bytearray(bio.read(length))
if tc == 'b': # bytes
length = read_leb128(bio)
return bio.read(length)
if tc == 'c': # interned bytes (null-terminated)
bs = b""
while True:
c = bio.read(1)
if not c or c == b"\x00":
break
bs += c
return bs
# ========== 浮点/复数 ==========
if tc == 'd': # predefined float index
idx = read_uint8(bio)
return f"<PREDEFINED_FLOAT_{idx}>"
if tc == 'f': # float - new version skips 8 bytes
# 实际值需要查 PyRuntime 表,这里跳过并返回占位符
bio.seek(8, io.SEEK_CUR)
return f"<FLOAT_PLACEHOLDER>"
if tc == 'j': # complex - new version skips 16 bytes
bio.seek(16, io.SEEK_CUR)
return f"<COMPLEX_PLACEHOLDER>"
if tc == 'Z': # predefined double index
idx = read_uint8(bio)
if idx < len(self.PREDEFINED_DOUBLES):
return self.PREDEFINED_DOUBLES[idx]
return f"<PREDEFINED_DOUBLE_{idx}>"
# ========== 特殊对象 ==========
if tc == 'M': # anonymous types
idx = read_uint8(bio)
return self.ANON_TYPES.get(idx, f"<ANON_{idx}>")
if tc == 'Q': # special values
idx = read_uint8(bio)
return self.SPECIAL_VALUES.get(idx, f"<SPECIAL_{idx}>")
if tc == 'O': # getattr dynamic
name = b""
while True:
c = bio.read(1)
if not c or c == b"\x00":
break
name += c
return f"<GETATTR:{_decode_utf8_safe(name)}>"
# ========== 控制/引用类型 ==========
if tc == 'X': # skip bytes
skip_len = read_leb128(bio)
bio.seek(skip_len, io.SEEK_CUR)
return None # 不产生对象
if tc == 'p': # stack reference (previous object)
return self.stack[-1] if self.stack else "<UNDEF_REF>"
if tc == 'E': # skip to null
while True:
c = bio.read(1)
if not c or c == b"\x00":
break
return "<SKIP_E>"
if tc == '.': # error
raise ValueError("Missing blob values")
# ========== 复杂类型 ==========
if tc == 'C': # Code Object (complex flags)
return self._decode_code_object(bio)
if tc == 'A': # GenericAlias
origin = self.decode_one(bio)
args = self.decode_one(bio)
return f"<GenericAlias[{origin}, {args}]>"
if tc == ';': # range/lambda-like (4 params)
items = [self.decode_one(bio) for _ in range(4)]
return f"<RANGE_LIKE:{items}>"
if tc == ':': # slice
start = self.decode_one(bio)
stop = self.decode_one(bio)
step = self.decode_one(bio)
return slice(start, stop, step)
if tc == 'H': # bitwise OR reduction
count = read_leb128(bio)
if count == 0:
return None
result = self.decode_one(bio)
for _ in range(count - 1):
item = self.decode_one(bio)
try:
if result is not None and item is not None:
result = result | item
except TypeError:
result = f"<OR[{result}, {item}]>"
return result
# ========== 未处理类型 ==========
raise ValueError(f"Unhandled type: '{tc}' (0x{tb:02X})")
def _decode_code_object(self, bio: io.BytesIO) -> Any:
"""
解析 Code Object (类型 'C')
根据 C 代码的位掩码逻辑解析可选字段
"""
# 读取 flags byte 和可能的扩展
flags_byte = read_uint8(bio)
base_val = flags_byte & 0x7F
has_extended = flags_byte < 0 # sign bit indicates LEB128 extension
if has_extended:
# 扩展 LEB128 解码
shift = 7
while True:
b = read_uint8(bio)
base_val |= (b & 0x7F) << shift
if not (b & 0x80):
break
shift += 7
# 解析基础参数
argcount = read_leb128(bio) # 参数数量
kwonlyargcount = read_leb128(bio) # 仅关键字参数数量
posonlyargcount = kwonlyargcount + 1 # C 代码逻辑
# 可选字段标志 (base_val 的位掩码)
has_defaults = bool(base_val & 0x01) # bit 0
has_kwdefaults = bool(base_val & 0x02) # bit 1
has_cellvars_count = bool(base_val & 0x04) # bit 2
has_freevars_count = bool(base_val & 0x08) # bit 3
has_name = bool(base_val & 0x100) # bit 8
has_qualname = bool(base_val & 0x200) # bit 9
has_doc = bool(base_val & 0x400) # bit 10
has_annotations = bool(base_val & 0x10) # bit 4 (inferred)
code_info = {
'type': 'code_object',
'argcount': argcount,
'kwonlyargcount': kwonlyargcount,
'posonlyargcount': posonlyargcount,
}
# 解析可选字段
if has_defaults:
code_info['defaults'] = self.decode_one(bio)
if has_kwdefaults:
code_info['kwdefaults'] = self.decode_one(bio)
if has_annotations:
code_info['annotations'] = self.decode_one(bio)
if has_cellvars_count:
count = read_leb128(bio)
code_info['cellvars'] = [self.decode_one(bio) for _ in range(count)]
if has_freevars_count:
count = read_leb128(bio)
code_info['freevars'] = [self.decode_one(bio) for _ in range(count)]
if has_name:
code_info['name'] = self.decode_one(bio)
if has_qualname:
code_info['qualname'] = self.decode_one(bio)
if has_doc:
code_info['doc'] = self.decode_one(bio)
# CO flags 计算 (简化)
co_flags = 0x400000 # CO_NEWLOCALS
if base_val & 0x40:
co_flags |= 0x200000 # CO_OPTIMIZED
if has_extended:
co_flags |= 0x02
code_info['co_flags'] = co_flags
return f"<CODE_OBJECT:{code_info}>"
def decode_blob_list(bio: io.BytesIO, count: int) -> List[Any]:
"""解析多个 blob 对象"""
parser = BlobParser()
return [parser.decode_one(bio) for _ in range(count)]
def parse_blob_file(filepath: str, target_name: str = "__main__") -> Optional[List[Any]]:
"""
解析 Nuitka blob 文件
:param filepath: blob 文件路径
:param target_name: 要解析的 blob 名称,默认 "__main__"
:return: 解析结果列表,或 None
"""
with open(filepath, "rb") as f:
data = f.read()
bio = io.BytesIO(data)
# 文件头: hash(4) + total_size(4)
hash_val = read_uint32(bio)
total_size = read_uint32(bio)
while bio.tell() < total_size:
# 读取 blob 名称 (null-terminated)
name_bytes = b""
while True:
c = bio.read(1)
if not c or c == b"\x00":
break
name_bytes += c
blob_name = name_bytes.decode("utf-8", errors="replace")
# 读取 blob 元数据
blob_size = read_uint32(bio)
blob_count = read_uint16(bio)
if blob_name == target_name:
print(f"[+] Decoding '{blob_name}': size={blob_size}, count={blob_count}")
return decode_blob_list(bio, blob_count)
else:
# 跳过非目标 blob
bio.seek(blob_size - 2, io.SEEK_CUR)
print(f"[-] Blob '{target_name}' not found")
return None
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <blob_file> [blob_name]")
print(f" blob_name: default is '__main__'")
return
filepath = sys.argv[1]
target = sys.argv[2] if len(sys.argv) > 2 else "__main__"
try:
results = parse_blob_file(filepath, target)
if results:
print(f"\n[+] Decoded {len(results)} objects:")
for i, obj in enumerate(results):
# 限制长对象输出
obj_repr = repr(obj)
if len(obj_repr) > 200:
obj_repr = obj_repr[:197] + "..."
print(f" [{i:3d}] {obj_repr}")
except Exception as e:
print(f"[!] Error: {e}", file=sys.stderr)
if '--debug' in sys.argv:
raise
if __name__ == "__main__":
main()
代码运行后结果:
[Python] 纯文本查看 复制代码 [+] Decoding '__main__': size=1579, count=83
[+] Decoded 83 objects:
[ 0] [b'dc!a;`b', '<PREDEFINED_FLOAT_17>', b'cacg', '<PREDEFINED_FLOAT_47>', b'\x19e!!(', '<PREDEFINED_FLOAT_14>', b'\x1fb&', '<PREDEFINED_FLOAT_14>', b'\x08be#', b'ppp']
[ 1] '_parts'
[ 2] 81
[ 3] '_key'
[ 4] 30
[ 5] '_total_len'
[ 6] '解密单个字符'
[ 7] 'current'
[ 8] '_decrypt_char'
[ 9] '获取指定位置的字符'
[ 10] 'self'
[ 11] '_get_char_at_position'
[ 12] '验证用户输入'
[ 13] 'total'
[ 14] '计算校验和'
[ 15] ''
[ 16] 'flag'
[ 17] 'checksum'
[ 18] '获取目标校验和'
[ 19] 'hashlib'
[ 20] 'sha256'
[ 21] 'encode'
[ 22] 'hexdigest'
[ 23] slice(None, 8, None)
[ 24] 16
[ 25] '哈希函数'
[ 26] 305419896
[ 27] -94069293185
[ 28] 1380994890
[ 29] 'hash_input'
[ 30] '假检查'
[ 31] 'print'
[ 32] ('==================================================',)
[ 33] (' CrackMe Challenge - Binary Edition',)
[ 34] ('Keywords: 52pojie, 2026, Happy New Year',)
[ 35] ('Hint: 1337 5p34k & 5ymb0l5!',)
[ 36] (' Try to decompile this in IDA!',)
[ 37] ('--------------------------------------------------',)
[ 38] 'CrackMeCore'
[ 39] '\n[?] Enter the password: '
[ 40] 'fake_check'
[ 41] ('\n[!] Close, but not quite there...',)
[ 42] '\nPress Enter to exit...'
[ 43] 'verify'
[ 44] 'get_target_checksum'
[ 45] ('\n==================================================',)
[ 46] (' *** SUCCESS! ***',)
[ 47] ('[+] L33T H4X0R!',)
[ 48] '[+] Your answer: '
[ 49] '\n[!] Checksum mismatch: '
[ 50] ' != '
[ 51] ('\n[X] Access Denied!',)
[ 52] ('[X] Wrong password!',)
[ 53] '主函数'
[ 54] '__doc__'
[ 55] '__file__'
[ 56] '__cached__'
[ 57] '__annotations__'
[ 58] 'sys'
[ 59] '__main__'
[ 60] '__module__'
[ 61] '核心验证类 - 将被编译成二进制'
[ 62] '__qualname__'
[ 63] '__init__'
[ 64] 'CrackMeCore.__init__'
[ 65] 'CrackMeCore._decrypt_char'
[ 66] 'CrackMeCore._get_char_at_position'
[ 67] 'CrackMeCore.verify'
[ 68] 'CrackMeCore.checksum'
[ 69] 'CrackMeCore.get_target_checksum'
[ 70] 'main'
[ 71] ('\n\n[!] Interrupted',)
[ 72] 'crackme_hard.py'
[ 73] '<module>'
[ 74] ('self',)
[ 75] ('self', 'part_idx', 'char_idx', 'encrypted_byte')
[ 76] ('self', 'pos', 'current', 'part_idx', 'part')
[ 77] ('self', 's', 'total', 'i', 'c')
[ 78] ('user_input', 'fake_hashes', 'user_hash')
[ 79] ('self', 'flag', 'i')
[ 80] ('s',)
[ 81] ('core', 'user_input', 'cs', 'target')
[ 82] ('self', 'user_input', 'i', 'expected')
看起来像那么回事了,需要我们猜测解密的方式,我们可以参照初级题目中的python代码来进行合理猜测
下面是初级题目的python代码
[Python] 纯文本查看 复制代码 # Decompiled with PyLingual ([url=https://pylingual.io]https://pylingual.io[/url])
# Internal filename: 'crackme_easy.py'
# Bytecode version: 3.14rc3 (3627)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
import hashlib
import base64
import sys
def xor_decrypt(data, key):
"""XOR解密"""
result = bytearray()
for i, byte in enumerate(data):
result.append(byte ^ key ^ i & 255)
return result.decode('utf-8', errors='ignore')
def get_encrypted_flag():
"""获取加密的flag"""
enc_data = 'e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O'
return base64.b64decode(enc_data)
def generate_flag():
"""动态生成flag"""
encrypted = get_encrypted_flag()
key = 78
result = bytearray()
for i, byte in enumerate(encrypted):
result.append(byte ^ key)
return result.decode('utf-8')
def calculate_checksum(s):
"""计算校验和"""
total = 0
for i, c in enumerate(s):
total += ord(c) * (i + 1)
return total
def hash_string(s):
"""计算字符串哈希"""
return hashlib.sha256(s.encode()).hexdigest()
def verify_flag(user_input):
"""验证flag"""
correct_flag = generate_flag()
if len(user_input)!= len(correct_flag):
return False
else:
for i in range(len(correct_flag)):
if user_input[i]!= correct_flag[i]:
return False
return True
def fake_check_1(user_input):
"""假检查1"""
fake_hash = 'a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890'
return hash_string(user_input) == fake_hash
def fake_check_2(user_input):
"""假检查2"""
fake_hash = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
return hash_string(user_input) == fake_hash
def main():
"""主函数"""
print('==================================================')
print(' CrackMe Challenge - Python Edition')
print('==================================================')
print('Keywords: 52pojie, 2026, Happy New Year')
print('Hint: Decompile me if you can!')
print('--------------------------------------------------')
user_input = input('\n[?] Enter the password: ').strip()
if fake_check_1(user_input):
print('\n[!] Nice try, but not quite right...')
input('\nPress Enter to exit...')
return None
else:
if fake_check_2(user_input):
print('\n[!] You\'re getting closer...')
input('\nPress Enter to exit...')
else:
if verify_flag(user_input):
checksum = calculate_checksum(user_input)
expected_checksum = calculate_checksum(generate_flag())
if checksum == expected_checksum:
print('\n==================================================')
print(' *** SUCCESS! ***')
print('==================================================')
print('[+] Congratulations! You cracked it!')
print(f'[+] Correct flag: {user_input}')
else:
print('\n[!] Checksum failed!')
else:
print('\n[X] Access Denied!')
print('[X] Wrong password. Keep trying!')
input('\nPress Enter to exit...')
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('\n\n[!] Interrupted by user')
sys.exit(0)
结合分析得到解密代码
[Python] 纯文本查看 复制代码
def decrypt_flag():
"""解密 crackme_hard.py 中的正确 flag"""
# ========== 原始加密数据(从 Nuitka Blob 提取) ==========
# 格式:bytes 数据 和 标记数字交替出现
parts = [
b'dc!a;`b', # [0] → 52p0j13
17, # [1] → @ (17 ^ 81 = 64)
b'cacg', # [2] → 2026
47, # [3] → ~ (47 ^ 81 = 126)
b'\x19e!!(', # [4] → H4ppy
14, # [5] → _ (14 ^ 81 = 95)
b'\x1fb&', # [6] → N3w
14, # [7] → _ (14 ^ 81 = 95)
b'\x08be#', # [8] → Y34r
b'ppp', # [9] → !!!
]
key = 81 # XOR 密钥
total_len = 30 # flag 总长度
flag_chars = []
# ========== 逐部分解密 ==========
for part in parts:
if isinstance(part, bytes):
# bytes 类型:每个字节 XOR key
for byte in part:
decrypted_char = chr(byte ^ key)
flag_chars.append(decrypted_char)
elif isinstance(part, int):
# int 类型(标记数字):直接 XOR key
decrypted_char = chr(part ^ key)
flag_chars.append(decrypted_char)
else:
# 字符串类型(如果有)
for char in part:
decrypted_char = chr(ord(char) ^ key)
flag_chars.append(decrypted_char)
# 拼接得到完整 flag
correct_flag = ''.join(flag_chars)
return correct_fla
最终flag是52p0j13@2026~H4ppy_N3w_Y34r!!!
|
免费评分
-
| 参与人数 3 | 威望 +2 |
吾爱币 +101 |
热心值 +3 |
收起
理由
|
杨辣子
| |
+ 1 |
+ 1 |
谢谢@Thanks! |
Hmily
| + 2 |
+ 100 |
+ 1 |
感谢发布原创作品,吾爱破解论坛因你更精彩! |
Coxxs
| |
|
+ 1 |
用心讨论,共获提升! |
查看全部评分
|