文章涉及 repr() 和 literal_eval() 的用法
今天客户给了一个小程序源码,不过很可惜,是混淆后的,压根无法肉眼分析。。。。
看得我头皮发麻,但是硬着头皮也要进行审计,那么就先想着,先把这些 16 进制格式的字符串给转换为字符串吧。然后再进行 js 的格式化。
然后就不出意外的出了意外。
在一开始我发现,在 python 中,如果是 \x6a
格式的字符串,那么是可以直接输出为字符串的。
a = "\x6a"
print(a)
输出结果是 j
但是如果你从文件里读取字符,然后是没有办法进行这样的操作的。
a = r"\x6a"
print(a)
输出结果是 \x6a
就多了一个 r
, 结果完全不同。
这里就涉及到一个问题,当 python 读取到一个字符串的时候,底层是会理解为 bytes 来进行处理的。
普通字符串 | 带 r 的字符串 | |
---|---|---|
代码中 | "\x6a" | r"\x6a" |
bytes 表示 | 6a | 5c 78 36 61 |
可以看到,带 r
和不带 r
的差别非常大,带 r
的意思是 4 个字符,不带 r
的意思是 1 个字符。
如果我们想要将 r"\x6a
转换成 "\x6a"
那么就要非常复杂了,你要截取后两位,然后转十进制,然后转 chr。
那么如果是中文 unicode 编码呢, 比如 \x61\x74\x74\x72
,一个字符可不止占一个字节,还要涉及到编码转换。而且一个 js 中,还有其他那么多干扰字符呢。 光听起来低血压就要被治好了。
还好让我找到了一个简便的方法:
from ast import literal_eval
s = r"一些语句; attr = ['\x61\x74\x74\x72','\x0a', '\x61\x74\x74\x72']; 一些语句"
s = repr(s)
s = s.replace(r'\\', '\\')
# 这一句是我的特殊需求,我不要换行,因为我后面要格式化代码
s = s.replace(r'\x0a', r'\n')
s = literal_eval(s)
print(s)
repr()
的意思是将字符串格式化成机器可理解的字符:将字符串用单引号包裹,然后将其中影响理解的字符(主要就是单斜杠 )给转移一下。
literal_eval()
就是 repr
的逆方法。
那么正常情况下:literal_eval(repr(r"\x0a"))
和 literal_eval(repr("\x0a"))
会发生什么呢?
literal_eval(repr(r"\x0a")) | literal_eval(repr("\x0a")) | |
---|---|---|
repr() | "'\\x0a'" | "'\x0a'" |
literal_eval() | '\x0a' | \n |
repr()
输出的原本是机器理解的结果,但是为了大家的脑细胞考虑我给改写为人正常理解的。如果我这里不改写的话:也就是"'\\x0a'" <=人的视角====机器视角=> "'\\\\x0a'"
和"'\x0a'" <=人的视角====机器视角=> "'\\x0a'"
repr()
函数的功能是转义转义符号 \(也就是多加 \ 呗) ,然后两头添加了单引号而已。
正常情况下, 上述过程的示意图如下:
可以看到中间的两个字符串只相差一个斜杠而已,如果我们通过字符变换,将左边的字符串变成右边的字符串不久好了吗
而且这个字符串的操作也非常简单,就是替换一下就好。这样就简单实现了 4 个字符 "\" + "x" + "0" + "a"
到一个字符 换行
的变换。过程如下:
s = r'\x0a'
s = repr(s)
s = s.replace("\\\\", "\\")
s = literal_eval(s)
print(s)
当然了,如果你改造原始字符串也是可以的,在原始字符外边加单引号或者双引号都行。
from ast import literal_eval
s = r'\x0a'
s = '"' + s + '"'
# s = ' "\\x0a" '
s = literal_eval(s)
print(s)
# 或者
s = r'\x0a'
s = "'" + s + "'"
# s = " '\\x0a' "
s = literal_eval(s)
print(s)
# 或者
s = r"\x0a"
s = "'" + s + "'"
# s = " '\\x0a' "
s = literal_eval(s)
print(s)
# 或者
s = r"\x0a"
s = '"' + s + '"'
# s = ' "\\x0a" '
s = literal_eval(s)
print(s)
真有趣啊,没想到字符串外边加单双引号还有这么多结果。
序号 | 相加 | 结果 |
---|---|---|
1 | 单 + 双 | 单双 |
2 | 双 + 单 | 双单 |
3 | 单 + 单 | 双单 |
4 | 双 + 双 | 单双 |
搞定了,就是这么简单,而且不受干扰字符的限制,就算你在 \x0a
前面或者后面添加别的字符,或者一段字符串中有多个这样的字符都不影响代码(如果有特殊的情况还是会报错的,我没有仔细研究到底是啥字符导致的。估计是触发了 python 库代码的 bug 吧留给有缘人研究了。)
下面就给他出题目的解决代码吧,可以输入一个文件夹的路径,然后遍历文件夹及其子文件夹中所有的 js 文件,然后读取文件内容,将其中的 16 进制格式的文本(其实是 unicode_escape 格式)转化为字符串,然后将 js 代码格式化一下。
import codecs
import os
from ast import literal_eval
import jsbeautifier
"""
小程序混淆过的源码里有很多的十六进制的数据,肉眼完全没法看,那么只能转换一下看看了
"""
def transform(target_file_path):
"""
将代码中的 16 进制表示的字符串转换为字符串
Parameters:
target_file_path (string): 要进行转换的文件的路径
Returns:
boole: 转换是否成功,true 成功,false 失败
Raises:
Unknown: 我也不知道。
Example:
>> transform("/abc/aa.js")
5
"""
target_file = codecs.open(target_file_path, mode='r', encoding="UTF-8")
content = target_file.read()
target_file.close()
content = repr(content).replace(r'\\', '\\')
content = content.replace(r'\x0a', r'\\n')
try:
content = literal_eval(content)
except SyntaxError:
print("[E] 发生错误:" + target_file_path)
return False
# 如果先进行代码格式化的话,
beautified_code = jsbeautifier.beautify(content)
target_file = open(target_file_path, "w")
target_file.write(beautified_code)
target_file.close()
return True
def beautify(target_file_path):
target_file = codecs.open(target_file_path, mode='r', encoding="UTF-8")
content = target_file.read()
target_file.close()
beautified_code = jsbeautifier.beautify(content)
target_file = open(target_file_path, "w")
target_file.write(beautified_code)
target_file.close()
pass
if __name__ == "__main__":
# 设置要遍历的文件夹路径
# folder_path = '/Users/sanqiushu/Code_Space/PyCharm_CE_Space/APPandMiniAPP/将小程序混淆代码中的十六进制转化为字符串/test'
folder_path = '/Users/sanqiushu/Downloads/dist-pro'
errFile = []
# 使用os.walk()遍历文件夹
for root, dirs, files in os.walk(folder_path):
print("当前文件夹:" + root)
# 遍历当前文件夹中的所有文件
for file in files:
file_path = os.path.join(root, file)
if file_path.endswith(".js"):
# 对文件内的 16 进制进行转换
print("开始处理:" + file_path)
if not transform(file_path):
errFile.append(file_path)
else:
print(file_path + ": 非 js 文件,跳过")
for i in errFile:
beautify(i)
完全不需要自己去正则匹配 16 进制的格式,然后自己转化什么的,方便了很多。