SystemFileB

C++混淆器

Feb 24th, 2026 (edited)
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.29 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. 注意:此代码完全由deepseek-v3.2模型编写,SystemFileB负责本地化
  4.  
  5. C++ 源代码混淆器(基于 Clang AST)
  6. 收集所有出现在普通代码中的标识符(包括关键字和 main),
  7. 为每个标识符生成随机宏名,并在源文件开头插入 #define,
  8. 同时将源文件中所有出现的位置替换为宏名。
  9. 预处理指令(如 #include)中的标识符会被跳过,避免破坏编译。
  10. """
  11.  
  12. import sys
  13. import os
  14. import argparse
  15. import random
  16. import string
  17. import clang.cindex
  18.  
  19. # 常见预定义宏(避免混淆,因为它们有特殊含义)
  20. PREDEFINED_MACROS = {
  21.     '__LINE__', '__FILE__', '__DATE__', '__TIME__', '__STDC__',
  22.     '__cplusplus', '__ASSEMBLER__', '__INCLUDE_LEVEL__', '__BASE_FILE__',
  23.     '__TIMESTAMP__', '__COUNTER__', '__FUNCTION__', '__PRETTY_FUNCTION__',
  24.     '__func__'
  25. }
  26.  
  27. # 需要排除的特殊关键字(混淆后会破坏语法)
  28. SPECIAL_KEYWORDS = {'operator'}   # operator 必须与后面的运算符连用,不能单独替换
  29.  
  30. # 最终黑名单
  31. BLACKLIST = PREDEFINED_MACROS | SPECIAL_KEYWORDS
  32.  
  33. def generate_obfuscated_name(existing_names):
  34.     """
  35.    生成一个唯一的随机混淆名。
  36.    - 由大小写字母、数字、_ 和 $ 组成
  37.    - 首字符不能为数字
  38.    - 不与 existing_names 中的任何名称重复
  39.    """
  40.     chars = string.ascii_letters + string.digits + '_'
  41.     while True:
  42.         length = random.randint(8, 20)
  43.         first = random.choice(string.ascii_letters + '_')
  44.         rest = ''.join(random.choices(chars, k=length-1))
  45.         name = first + rest
  46.         if name not in existing_names:
  47.             return name
  48.  
  49. def collect_identifiers_from_cursor(cursor, main_file_path, identifiers):
  50.     """递归遍历 AST,收集主文件中所有标识符和关键字"""
  51.     loc = cursor.location
  52.     if loc.file and os.path.abspath(loc.file.name) == main_file_path:
  53.         # 获取该节点覆盖的所有 token
  54.         for token in cursor.get_tokens():
  55.             if token.kind in (clang.cindex.TokenKind.IDENTIFIER,
  56.                               clang.cindex.TokenKind.KEYWORD):
  57.                 identifiers.add(token.spelling)
  58.     # 递归子节点
  59.     for child in cursor.get_children():
  60.         collect_identifiers_from_cursor(child, main_file_path, identifiers)
  61.  
  62. def main():
  63.     parser = argparse.ArgumentParser(description='使用 Clang AST 来混淆C++代码')
  64.     parser.add_argument('-i', '--input', help='输入的 C++ 源代码文件')
  65.     parser.add_argument('-o', '--output', help='输出文件 (默认: input.obfuscated.cpp)')
  66.     parser.add_argument('-c', dest='compiler_args', nargs=argparse.REMAINDER,
  67.                         help='给 clang 的编译参数 (比如: -std=c++17 -I...)')
  68.     args = parser.parse_args()
  69.  
  70.     if args.input==None:
  71.         print("错误: 无输入文件\n请使用--help参数查看用法")
  72.         sys.exit(1)
  73.  
  74.     # 如果需要,可在此设置 libclang 路径(例如:/usr/lib/llvm-14/lib)
  75.     # clang.cindex.Config.set_library_path('/usr/lib/llvm-14/lib')
  76.  
  77.     # 解析翻译单元
  78.     index = clang.cindex.Index.create()
  79.     tu = index.parse(args.input, args.compiler_args or [])
  80.     if not tu:
  81.         print('错误: 无法解析翻译单元')
  82.         sys.exit(1)
  83.  
  84.     # 输出诊断信息(警告/错误)
  85.     for diag in tu.diagnostics:
  86.         print(diag, file=sys.stderr)
  87.         if diag.severity >= clang.cindex.Diagnostic.Error:
  88.             print('出现编译错误, 将继续...', file=sys.stderr)
  89.  
  90.     # 获取主文件对象及其绝对路径
  91.     main_cursor = tu.cursor
  92.     # 直接使用输入文件的绝对路径作为主文件(避免依赖 location.file)
  93.     main_file_path = os.path.abspath(args.input)
  94.     if not os.path.isfile(main_file_path):
  95.         print(f'错误: 输入文件 {main_file_path} 不存在')
  96.         sys.exit(1)
  97.  
  98.     # 收集主文件中所有出现在 AST 中的标识符(包括关键字)
  99.     identifiers = set()
  100.     collect_identifiers_from_cursor(main_cursor, main_file_path, identifiers)
  101.  
  102.     # 从收集中移除黑名单中的名称(它们将保持原样)
  103.     original_names = identifiers - BLACKLIST
  104.     if not original_names:
  105.         print('没有符号用于混淆')
  106.         sys.exit(0)
  107.  
  108.     # 生成原始名称到混淆名的映射
  109.     obfuscated_map = {}
  110.     # 已使用的名称包括原始名称、黑名单、以及新生成的混淆名
  111.     used_names = set(original_names) | BLACKLIST
  112.     for name in sorted(original_names):   # 排序以保证确定性
  113.         obf_name = generate_obfuscated_name(used_names)
  114.         obfuscated_map[name] = obf_name
  115.         used_names.add(obf_name)
  116.  
  117.     # 获取整个源文件的预处理 token(包含所有预处理指令)
  118.     tokens = list(tu.get_tokens(extent=tu.cursor.extent))
  119.  
  120.     # 标记需要跳过的 token 索引(所有预处理行)
  121.     skip_indices = set()
  122.     i = 0
  123.     while i < len(tokens):
  124.         tok = tokens[i]
  125.         # 检测预处理指令起始:单独的 '#' token
  126.         if tok.kind == clang.cindex.TokenKind.PUNCTUATION and tok.spelling == '#':
  127.             # 跳过从当前 token 开始直到该行结束的所有 token
  128.             line = tok.location.line
  129.             j = i
  130.             while j < len(tokens) and tokens[j].location.line == line:
  131.                 skip_indices.add(j)
  132.                 j += 1
  133.             i = j  # 跳过已处理的行
  134.             continue
  135.         i += 1
  136.  
  137.     # 收集需要替换的 token 位置
  138.     replacements = []
  139.     for idx, token in enumerate(tokens):
  140.         if idx in skip_indices:
  141.             continue  # 跳过预处理行内的 token
  142.  
  143.         loc = token.location
  144.         # 仅处理主文件中的 token
  145.         if not loc.file or os.path.abspath(loc.file.name) != main_file_path:
  146.             continue
  147.         if token.kind in (clang.cindex.TokenKind.IDENTIFIER,
  148.                           clang.cindex.TokenKind.KEYWORD):
  149.             name = token.spelling
  150.             if name in obfuscated_map:
  151.                 start = token.location.offset
  152.                 end = token.extent.end.offset
  153.                 if start is not None and end is not None and start < end:
  154.                     replacements.append((start, end, obfuscated_map[name]))
  155.  
  156.     # 收集需要替换的 token 位置
  157.     replacements = []
  158.     for idx, token in enumerate(tokens):
  159.         if idx in skip_indices:
  160.             continue  # 跳过已标记的 token
  161.  
  162.         loc = token.location
  163.         # 仅处理主文件中的 token
  164.         if not loc.file or os.path.abspath(loc.file.name) != main_file_path:
  165.             continue
  166.         if token.kind in (clang.cindex.TokenKind.IDENTIFIER,
  167.                           clang.cindex.TokenKind.KEYWORD):
  168.             name = token.spelling
  169.             if name in obfuscated_map:
  170.                 start = token.location.offset
  171.                 end = token.extent.end.offset
  172.                 if start is not None and end is not None and start < end:
  173.                     replacements.append((start, end, obfuscated_map[name]))
  174.  
  175.     # 按起始偏移排序
  176.     replacements.sort(key=lambda x: x[0])
  177.  
  178.     # 读取源文件(二进制模式)
  179.     with open(args.input, 'rb') as f:
  180.         source_bytes = f.read()
  181.  
  182.     # 构建新源代码(逐段拼接字节串)
  183.     new_parts = []
  184.     last_pos = 0
  185.     for start, end, obf_name in replacements:
  186.         if last_pos < start:
  187.             new_parts.append(source_bytes[last_pos:start])
  188.         new_parts.append(obf_name.encode('utf-8'))
  189.         last_pos = end
  190.     if last_pos < len(source_bytes):
  191.         new_parts.append(source_bytes[last_pos:])
  192.     obfuscated_bytes = b''.join(new_parts)
  193.  
  194.     # 生成宏定义头(编码为 UTF-8 字节)
  195.     header_lines = [f'#define {obf} {orig}' for orig, obf in obfuscated_map.items()]
  196.     header = '\n'.join(header_lines) + '\n\n'
  197.     header_bytes = header.encode('utf-8')
  198.  
  199.     final_bytes = header_bytes + obfuscated_bytes
  200.  
  201.     # 确定输出文件并以二进制写入
  202.     if args.output:
  203.         output_path = args.output
  204.     else:
  205.         base, ext = os.path.splitext(args.input)
  206.         output_path = base + '.obfuscated' + ext
  207.  
  208.     with open(output_path, 'wb') as f:
  209.         f.write(final_bytes)
  210.  
  211.     print(f'混淆后的代码将写到 {output_path} 中')
  212.  
  213. if __name__ == '__main__':
  214.     main()
  215.  
Add Comment
Please, Sign In to add comment