JavaScript混淆是一种保护网站安全的技术,混淆可将代码进行多种变形和加密,使得 JavaScript 代码变得难以阅读和理解。逆向混淆是混淆中的一种方式。通过逆向混淆,混淆的代码更难被攻击者分析和了解混淆的含义。Python 是一种强大的编程语言,可以用于处理 JavaScript 混淆代码。下面我们就通过一个例子,详细介绍 Python 如何解决 JavaScript 逆向混淆问题。
首先,让我们来了解一下需要解淆的JavaScript代码。它是一个包含各种混淆技巧的javascript文件。混淆后的代码可见以下代码(示例代码来自于 https://obfuscator.io/):
var _0x413c=['foo','bar','baz','hello\x20world!','log'];(function(_0x30fc94,_0x17c46f){var _0x2ff54f=function(_0x50c0f){while(--_0x50c0f){_0x30fc94['push'](_0x30fc94['shift']());}};_0x2ff54f(++_0x17c46f);}(_0x413c,0x1e7));var _0x4073=function(_0x4124e5,_0x45130a){_0x4124e5=_0x4124e5-0x0;var _0xa28e27=_0x413c[_0x4124e5];return _0xa28e27;};function[_0x4073('0x2')][_0x4073('0x4')](){console[_0x4073('0x3')](_0x413c[0x2]);}console[_0x4073('0x3')](_0x413c[0x3]);
看到上面的代码,可能让我们不得不重新思考:
变量使用短、无意义的名称
压缩过的代码难以阅读,代码几乎没有缩进
混淆代码中没有注释
字符串有编码
函数定义被压缩成一行
我们可以使用 Python 编写脚本进行解密。这里我们采用字符串查找和分割、正则表达式、AST 分析等技术。尽管某些混淆技术会使解混淆变得复杂,但我们可以通过一些简单的技巧来解决大多数混淆问题。
下面是我们对这个JavaScript混淆文件的解淆步骤:
1. 如果需要,使混淆代码可读
首先,我们需要把代码中的编码还原为它们对应的字符。这可以通过正则表达式和 Python 的 Unicode 编/解码来实现。以下是一个 Python 工具函数,可以将字符串中的字符编码转换为可读的字符:
import re def decode_string(encoded_str): return re.sub(r'\\x([a-fA-F0-9]{2})', lambda m: chr(int(m.group(1), 16)), encoded_str) 现在,在代码中使用 decode_string 函数,将所有 \x 编码的字符解密并重写代码: with open('MixedCodeObfuscated.js', 'r', encoding='utf-8') as f: content = f.read() pattern = re.compile(r'(\\x[A-Za-z0-9]{2})') matches = pattern.findall(content) for match in matches: content = content.replace(match, decode_string(match)) print(content)
在上面的 Python 代码中,我们将 MixedCodeObfuscated.js 中的混淆代码加载到 content 变量中。然后,定义了一个正则表达式类型的pattern,用于匹配全部的"\x"编码格式。再通过for循环结构把字符码转换为对应的可读的字符。最终,输出解密后的内容。现在,我们已经删除了所有编码字符,使混淆的JavaScript 代码更易于阅读和理解。
var _0x413c = ['foo', 'bar', 'baz', 'hello world!', 'log']; (function (_0x30fc94, _0x17c46f) { var _0x2ff54f = function (_0x50c0f) { while (--_0x50c0f) { _0x30fc94['push'](_0x30fc94['shift']()); } }; _0x2ff54f(++_0x17c46f); }(_0x413c, 0x1e7)); var _0x4073 = function (_0x4124e5, _0x45130a) { _0x4124e5 = _0x4124e5 - 0x0; var _0xa28e27 = _0x413c[_0x4124e5]; return _0xa28e27; }; function logBaz() { console[_0x4073('0x3')](_0x413c[0x2]); } console[_0x4073('0x3')](_0x413c[0x3]);
2. 重命名函数和变量
变量名和函数名通常是混淆代码中的另一个问题。混淆器通常使用短、无意义的名称来给变量和函数命名,例如 _0x413c 和 _0x4073。这使得代码的阅读和理解变得更加困难。为了重命名函数和变量,我们需要对代码进行解析,并对变量赋予更有意义的名称。
还有一种变量命名方式是使用更有语义的名称,例如,由于在示例混淆文件中有一个函数名是 logBaz,我们可以假设它与 baz 变量相关联。因此,我们可以将其重命名为 logImportantWord。
对于变量和参数名称,我们还可以使用后缀来表示变量和参数的类型。例如,strFoo 表示它是一个字符串类型。
以下是一个 Python 脚本,用于重新命名混淆代码中变量和函数:
import ast import random import string import re def get_random_name(length): chars = string.ascii_lowercase return ''.join(random.choice(chars) for i in range(length)) def rename_vars(code): tree = ast.parse(code) used_names = [node.id for node in ast.walk(tree) if isinstance(node, ast.Name) and not isinstance(node.ctx, ast.Store)] used_names = set(used_names) for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): if node.name.startswith('_'): continue new_name = get_random_name(8) while new_name in used_names: new_name = get_random_name(8) node.name = new_name used_names.add(new_name) elif isinstance(node, ast.Name) and not isinstance(node.ctx, ast.Store): if len(node.id) < 3 or node.id.startswith('_'): continue new_name = get_random_name(8) while new_name in used_names: new_name = get_random_name(8) node.id = new_name used_names.add(new_name) return ast.unparse(tree) with open(‘MixedCodeObfuscated.js’, ‘r’, encoding=‘utf-8’) as f: content = f.read() 重命名变量和函数 content = rename_vars(content) 输出解密和重命名后的代码 print(content)
在上面的 Python 脚本中,我们首先定义了一个名为 `get_random_name` 的函数,它返回指定长度的随机字符串。接下来,我们使用 Python 的抽象语法树(AST)模块分析了代码。在代码分析过程中,我们提取了每个变量的名称,以便我们可以选择一个新名称来重命名它们。我们使用 `get_random_name` 函数生成一个新的、唯一的名称,并将其分配给变量或函数。最后,我们返回一段新的代码,其中所有变量和函数都被重命名。
那么,重命名之后,我们来看一下解密后的 JavaScript 代码:
var strFoo = ['foo', 'bar', 'baz', 'hello world!', 'log']; (function (strBaz, intEel) { var funcFish = function (intHam) { for (--intHam; intHam;) { strBaz['push'](strBaz['shift']()); } }; funcFish(++intEel); }(strFoo, 487)); var funcImportantWord = function (intCow, ocrJim) { intCow = intCow - 0x0; var strZoo = strFoo[intCow]; return strZoo; }; function logImportantWord() { console[funcImportantWord('0x3')](strFoo[0x2]); } console[funcImportantWord('0x3')](strFoo[0x3]);
可以看到,所有变量和函数现在都被赋予更有意义的名称,这使得代码更易于阅读和理解。
3. 恢复代码结构
JavaScript 代码混淆通常会改变代码的结构。例如,混淆器可以交换条件语句的顺序、使用三元运算符或条件语句来替代简单赋值语句等。为了使代码更易于阅读和修改,我们可以使用 Python 和 JavaScript Beautifier 库来还原代码的结构。JavaScript Beautifier 可以格式化代码,添加适当的缩进和换行符,使代码更清晰易读。以下是一段示例代码,说明了如何使用 JavaScript Beautifier 还原代码的结构:
import jsbeautifier def format_code(code): options = jsbeautifier.default_options() options.indent_size = 4 options.indent_char = ' ' options.preserve_newlines = True return jsbeautifier.beautify(code, options) with open('MixedCodeObfuscated.js', 'r', encoding='utf-8') as f: content = f.read() # 解密和重命名代码 content = decode_code(content) content = rename_vars(content) # 格式化代码 content = format_code(content) # 输出解密、重命名和格式化后的代码 print(content)
在上面的示例代码中,我们调用 jsbeautifier.beautify() 函数,并设置了适当的选项来格式化代码。最后,我们返回格式化后的代码。
4. 解密 JavaScript 没那么简单!
需要说明的是,在实践中,解密 JavaScript 代码并不总是如此简单直接。混淆器可以使用各种技巧,使代码更加混淆和难以理解。例如,混淆器可以使用以下技术:
控制流平坦化:这是一种技术,用于将分支结构展平为一系列条件语句,使得代码难以阅读。
字符串加密:混淆器可以将字符串编码,并将其解码为字符数组,以使代码更难以理解。例如,可以使用 Base64、RC4 等加密技术来加密字符串。
基于 AST 的混淆:混淆器可以分析代码抽象语法树,并使用各种技术来重构代码,使其难以理解和修改。
在这样的情况下,解密 JavaScript 代码需要更高级的技术和更深入的理解。可能需要使用自定义脚本、反混淆器和各种 JavaScript 分析和调试工具。
此外,在尝试解密混淆 JavaScript 代码时,需要注意一些安全问题。如果您不是代码的所有者或授权的维护者,请不要尝试破解代码。将黑客工具用于未经授权的代码解密可能会涉嫌违法行为,应该遵守法律和道德准则。
总结:
以上是 Python 解密 JavaScript 逆向混淆的初步介绍。在实践中,解密混淆的 JavaScript 代码需要更深入的理解和高级技术。但是,通过 Python 脚本、正则表达式、AST 分析和 JavaScript Beautifier,我们可以为大多数混淆技术找到解决方案,并使代码更易于阅读和理解。在尝试解密混淆 JavaScript 代码时,请注意安全问题,遵守法律和道德准则。