乐趣区

关于python:0x4c9738-怎么还原嘿还真可以还原

_0x4c9738 变量名还原,噂嘟假嘟?

代码混同(obfuscation)和代码反混同(deobfuscation)在爬虫、逆向当中能够说是十分常见的状况了,初学者常常问一个问题,相似 _0x4c9738 的变量名怎么还原?从失常角度来说,这个货色没方法还原,就好比一个人以前的名字叫张三,起初改名叫张四了,除了张四自己和他爸妈,他人基本不晓得他以前叫啥,相似 _0x4c9738 的变量名也一样,除了编写原始代码的人晓得它原来的名称是啥以外,其他人是没方法晓得的。

然而,_0x4c9738 就真的没方法还原吗?时代在提高,这几年人工智能蓬勃发展,在机器学习的加持下,让变量名的还原也成为了一种可能。本文将介绍三种还原的办法:ChatGPT、JSNice 和 JSNaughty

这里筹备了一小段局部变量名通过混同了的代码:

function chunkData(a, b) {var _0xdc97x4 = [];
    var _0xdc97x5 = a.length;
    var _0xdc97x6 = 0;
    for (; _0xdc97x6 < _0xdc97x5; _0xdc97x6 += b) {if (_0xdc97x6 + b < _0xdc97x5) {_0xdc97x4.push(a.substring(_0xdc97x6, _0xdc97x6 + b))
        } else {_0xdc97x4.push(a.substring(_0xdc97x6, _0xdc97x5))
        }
    }
    return _0xdc97x4;
}

var inputString = "abcdefghijklmnopqrstuvwxyz";
var chunkedArray = chunkData(inputString, 5);
console.log(chunkedArray);

ChatGPT

ChatGPT 的弱小就不须要解释了,去年 11 月由美国人工智能钻研实验室 OpenAI 公布 GPT-3.5,应用了 Transformer 神经网络架构,领有语言了解和文本生成能力,能够依据用户的输出生成各种各样的文本,包含代码。往年 3 月推出多模态大模型 GPT-4,反对图像和文本输出以及文本输入,咱们让 ChatGPT 还原一下以上代码并解释这段代码的作用:

能够看到所有变量都被还原成了更容易了解的命名形式,每行代码都有具体的正文,精确的剖析出了这段代码是在模仿字符串宰割的性能,事实上 ChatGPT 还能够剖析更加简单的代码,不仅限于变量名的还原,其余简略的混同也能够解决,比方 OB 混同和 sojson 的加密,这里咱们拿一个简略的抉择排序算法,通过 sojson 最新的 jsjiami v7 进行混同:

而后让 ChatGPT 进行反混同,革除无用代码,并解释其含意:

能够看到最终后果和解释都十分精确、易读,以前咱们面对这种代码还须要本人利用 AST 语法树来编写代码还原,而有了 ChatGPT 的加持就大大降低了难度和工夫老本,然而 ChatGPT 也不是万能的,有时候还原的也不是很彻底,要多试几次,发问形式上也要加以疏导,通过测试 ChatGPT 对于较为简单的混同是无奈做到齐全解混同的,最次要的是 ChatGPT 有字数限度,而理论场景中,混同的代码广泛几千上万行,无奈齐全应用 ChatGPT 来解决,不过正当应用 ChatGPT 还是能对逆向有不少帮忙的,比方剖析一段代码的作用,基于贝塞尔曲线写一个滑块轨迹等等,都是能给咱们提供不少思路的。

JSNice

JSNice(jsnice.org)次要用于对 JS 进行反混同,它还有一个兄弟我的项目 apk-deguard(apk-deguard.com)次要用于 APK 反混同,由苏黎世联邦理工学院(ETH Zürich)的几位博士生在 2015 年 实现的,本文仅介绍 JSNice,对 APK 反混同感兴趣的能够自行去官网体验一下,先间接应用官网的示例看看成果:

官网的示例代码,是由 UglifyJS 解决后的代码,UglifyJS 是一个 JS 解析器、最小化器、压缩器和丑化器的工具集,能够将变量名用很简略的字母如 abn 来示意,JSNice 也次要是针对 UglifyJS 而呈现的,它能够将相似 abn 这种没有显著含意的变量名还原成相似 dockeyresults 这种具备显著含意的名称,实测如果变量名是相似 _0x4c9738 的混同名称,也是能够还原的。比拟遗憾的是 JSNice 没有齐全开源,最初一次更新是在 2018 年 3 月,JSNice 实质上是一种机器学习,五年没有更新了,对于很多变量名混同做不到完满还原,不过其原理还是十分值得学习的,以下就简略介绍一下其核心思想。

JSNice 提出了一种从大量代码库预测程序属性的新办法,他们称之为 Big Code,其中用到了概率图模型(Probabilistic Graphical Models,机器学习的一个分支,用图来示意变量概率依赖关系的实践),首先从现有数据中学习一个概率模型,而后应用这个模型来预测新的程序的属性。总的来说,JSNice 分为预测阶段 + 训练阶段,如下图所示:

想让程序可能还原混同的变量名,天经地义的要具备推理和联想的能力,JSNice 能够从相似 GitHub 等平台获取很多的未混同的 JS 脚本供程序学习,但在反混同 JS 代码时,程序是无奈了解简单的 JS 代码的,所以须要将 JS 代码先示意成一种能够利用已知属性推理出的未知属性的构造,而概率图模型就比拟适宜,下图展现了 JSNice 的推理过程,(a)、(b)、(c)、(d)、(e) 代表了 JSNice 预测变量名的五个阶段:

(a) => (b): 确定已知和未知属性 ,给定上图 (a) 中的程序,首先要拆散出两个元素集,即已知属性的元素和未知属性的元素,元素的属性即带有语义的名称,有语义的天然就不须要推理了,没有语义的、属性未知的天然须要推理,对于上图 (a) 中的程序来讲,很显著未知属性的元素有:变量 e、t、n、r 和 i,已知属性的元素有:常量 []、0、办法 push 等。

(b) => (c): 构建依赖网络 ,依赖网络是捕获程序元素之间各种关系的要害,它直观地展现了待预测属性之间的相互影响。依赖关系是模式为 (n,m,rel) 的三元组,其中 n 和 m 是程序元素,rel 是两个元素之间的特定关系。在上图 (c) 中展现了元素之间的三个依赖关系,例如语句 i += t 生成了一个依赖关系 (i,t,L+=R),因为 i 和 t 别离位于 += 表达式的左侧和右侧,相似地,语句 i < r 生成了一个依赖关系 (i,t,L<R),语句 var r = e.length 生成了一个依赖关系 (r,length,L=_.R)

(c) => (d):MAP 推理 ,即推理网络节点的最可能值,在上图 (d) 中,对于图 (c) 的网络结构,零碎预测出新的变量名称 step 和 len,图中的一些表格是学习阶段的输入,每个表格都是一个函数,用于为连贯的节点的属性赋分,最终取最高分即可,但对于节点 r 和 length,抉择的是 0.4 评分的 len,而不是最高评分 0.5 的 length,这是因为前者的综合 score 是 0.5(step)+0.8(len)+0.4(len)=1.7,而后者的综合 score 只有 0.5(step)+0.6(length)+0.5(length)=1.6,换句话说,MAP 推理必须思考到节点之间的构造和依赖关系,不能简略地抉择每个函数的最大分数而后进行。

(d) => (e): 程序输入 ,最初,零碎将会对原始程序进行转换,转换后的程序会应用这些预测出的新的变量名称,如上图 (e) 所示。

在以上过程中,JSNice 还会对类型、正文进行预测,其外围步骤都和变量名的推理是一样的,这里就不再赘述。

程序属性的预测

首先定义整个训练集 D,蕴含 t 个程序,每个程序 x(j) 都有一个标签为 y 的向量:

$$
D=\{\langle x^{(j)},\mathbf{y}^{(j)}\rangle\}_{j=1}^{t}
$$

$$
\mathbf{y}=(y_{1},…,y_{n(x)})
$$

给定要预测的程序 x,返回具备最大概率的标签向量:

$$
\mathbf{y}=\mathrm{argmax}_{\mathbf{y}^{\prime}\in\Omega_x}Pr(\mathbf{y}^{\prime}\mid x)
$$

接下来最要害的思维是将推断程序属性的问题形式化为基于条件随机场 (Conditional Random Fields, CRFs) 的结构化预测,JSNice 定义了一个条件随机场,score 是一个返回实数的函数,该实数示意属性 y 和程序 x 的匹配水平的分数,该分数越大示意越匹配:

$$
Pr(\mathbf{y}\mid x)=\frac{1}{Z(x)}\exp(score(\mathbf{y},x))
$$

Z(x) 称为配分函数,保障所有可能调配 y 的概率之和为 1。

$$
Z(x)=\sum_{\mathbf{y}\in\Omega_x}\exp(score(\mathbf{y},x))
$$

将 score 示意为 k 个特征函数 $f_i$ 的加权均匀,其中 f 是函数 $f_i$ 的向量,w 是权重 $w_i$ 的向量:

$$
score(\mathbf{y},x)=\sum_{i=1}^kw_if_i(\mathbf{y},x)=\mathbf{w}^T\mathbf{f}(\mathbf{y},x)
$$

那么最终条件随机场 CRFs 的示意模式为:

$$
Pr(\mathbf{y}\mid x)=\frac{1}{Z(x)}\exp(\mathbf{w}^{T}\mathbf{f}(\mathbf{y},x))
$$

用以下公式来定义特征函数 $f_i$,$E^x$ 示意程序 x 的依赖网络中所有边的汇合,如果该对呈现在训练集中,则函数 $\psi_{i}$ 返回 1,否则返回 0

$$
f_i(\mathbf{y},x)=\sum_{(a,b,rel)\in E^x}\psi_i\big((\mathbf{y},\mathbf{z})_a,(\mathbf{y},\mathbf{z})_b,rel\big)
$$

基于上述特征函数的定义,当初就能够定义如何取得预测 y 的总分了:

$$
score(\mathbf{y},x)=\sum_{(a,b,rel)\in E^x}\sum_{i=1}^kw_i\psi_i((\mathbf{y},\mathbf{z})_a,(\mathbf{y},\mathbf{z})_b,rel)
$$

下图以一个简略的示例来阐明上述步骤以及一些关键点,有 6 个程序元素,其属性未知,另外 4 个程序元素,其属性已知,每个程序元素都是一个节点,其索引显示在圆圈之外,边示意节点之间的关系,节点内的标签是预测的程序属性或者已知的属性,如前所述,已知属性 z 在预测过程开始之前是固定的,在结构化预测问题中,属性 y1,…,y6,程序元素 1,…,6,被预测为 $Pr(\mathbf{y}\mid x)$ 是最大的。

依赖网络的构建

程序元素之间的关系定义了如何构建程序 x 的边集 $E^x$,元素之间的关系是十分复杂的,这里次要思考三类:相干表达式 (Relating Expressions)、别名关系 (Aliasing Relations)、函数名称关系 (Function name relationships)。这里次要介绍一下第一个关系,相干表达式,它实质上是语法关系,它依据程序形象语法树 (Abstract Syntax Tree, AST) 中的语法关系将两个程序元素关联起来,例如语句 i + j < k,先构建如下图 (a) 所示的 AST,而后构建如下图 (b) 所示的依赖网络,以表明对这三个元素的预测是相互依赖的:

这类关系的形式化定义如下:

$$
\begin{aligned}
&rel_{ast}::=rel_{L}(rel_{R})|rel_{L}\otimes rel_{R} \\
&rel_{L}:::=\mathbf{L}|rel_{L}(\_)|\_(rel_{L})|rel_{L}\otimes_{\_}|\_\otimes rel_{L} \\
&rel_{R}::=\mathbf{R}\mid rel_R(\_)\mid\_(rel_R)\mid rel_R\circledast\_\mid\_\circledast rel_R
\end{aligned}
$$

MAP 推理预测算法

在后面预测程序 x 的属性 y 中,须要找到这样一个 y:

$$
\mathbf{y}=\mathop{\mathrm{argmax}}_{\mathbf{y’}\in\Omega_x}Pr(\mathbf{y’}|x)=\mathop{\mathrm{argmax}}_{\mathbf{y’}\in\Omega_x}score(\mathbf{y’},x)=\mathop{\mathrm{argmax}}_{\mathbf{y’}\in\Omega_x}\mathbf{w}^T\mathbf{f}(\mathbf{y’},x)
$$

JSNice 在设计推理算法时,重点关注了性能,优化预测的速度,因而采纳了贪心算法,也称为迭代条件模式,下图演示了贪心算法的推理过程:

推理算法有四个输出:从程序 x 取得的依赖网络 $G^x$、未知元素 y 的初始属性赋值、取得的已知属性 z 和配对特征函数及其权重,该算法的输入是一个近似的预测 y,它也合乎冀望的束缚 $\Omega_{x}$,该算法还应用了一个名为 scoreEdges 的辅助函数,定义如下:

$$
scoreEdges(E,A)=\sum_{(a,b,rel)\in E}\sum_{i=1}^{k}w_i\psi_i(A_a,A_b,rel)
$$

scoreEdges 函数与后面定义的 score 雷同,只是 scoreEdges 作用于网络边 E 的一个子集 $\begin{aligned}E\subseteq&E^x\end{aligned}$,给定一组边 E 和属性 a 的元素赋值,scoreEdges 在 E 上迭代,对每个边利用适当的特征函数并总结后果。

而对于获取最终的候选对象,算法不会去尝试一个节点的所有可能的变量名,而是定义了一个函数 candidates(v,A,E),在给定节点 v、赋值 A 和一组边 E 的状况下来获取候选标签,定义辅助函数:

$$
\begin{array}{l}topL_s(lbl,rel)=top_s(\{t\mid t_l=lbl\wedge t_{rel}=rel\wedge t\in F\})\\topR_s(lbl,rel)=top_s(\{t\mid t_r=lbl\wedge t_{rel}=rel\wedge t\in F\})\end{array}
$$

对于固定光束参数大小 s (fixed beam size s) 和训练数据 F 中的所有三元组,能够轻松地事后计算上述函数:

$$
\begin{gathered}
candidates(v,A,E)= \\
=\bigcup_{\langle a,v,rel\rangle\in E}\{l^2\mid\langle l^1,l^2,r\rangle\in topL_s(A_a,rel)\}\cup \\
\bigcup_{\langle v,b,rel\rangle\in E}\{l^{1}\mid\langle l^{1},l^{2},r\rangle\in topR_{s}(A_{b},rel)\}
\end{gathered}
$$

上述公式的含意是,对于与 v 相邻的每条边,算法最多思考 s 个得分最高的三元组(依据学习到的权重),这将产生一组用于驱动推理算法的 v 的可能赋值,光束参数 s 管制着精度和运行工夫之间的衡量。

预测后果

从 GitHub 上抓取了 10517 个 JavaScript 我的项目,选取其中提交次数最多的 50 个我的项目作为样本,训练了 324501 个文件,预测了 3710 个文件(由 UglifyJS 解决后的)。

下图展现了还原的变量名称的准确性、类型的精度:

下图展现了光束参数 (beam parameter) 的大小与预测精度和工夫的关系:

JSNaughty

JSNaughty (https://github.com/bvasiles/jsNaughty) 则是在 2017 年由美国卡内基梅隆大学(Carnegie Mellon University)、加利福尼亚大学戴维斯分校(University of California, Davis)的几位传授研发的,其外围是应用了 Moses 统计机器翻译框架 (http://www2.statmt.org/moses/),与 JSNice 相似,能够将混同过的变量名进行还原,次要侧重于解决通过 UglifyJS 压缩后的代码,JSNaughty 还退出了 JSNice 的逻辑,二者互补能够达到更好的成果,下图是 JSNaughty 的架构图:

同样比拟遗憾的是这个我的项目也进行更新很多年了,不过其核心思想依然值得学习,其官网曾经打不开了,但 GitHub 上开源了代码,还提供了 docker 镜像,咱们还是应用文章结尾的那段混同 JS 来看看成果,运行 renameFile.py 来还原变量名:

能够看到成果和 JSNice 是相似的,上面分享一下其核心思想。

统计机器翻译 SMT

JSNaughty 的外围,是其内置了一个被称为 Autonym 的工具,该工具基于统计机器翻译模型(Statistical Machine Translation,SMT),从放大的 JS 程序中复原一些原始名称,SMT 是一种数据驱动的机器翻译办法,基于从(大型)双语文本语料库预计的统计模型,被宽泛使用于谷歌翻译等服务中,在 SMT 中,文档依据一个概率分布 $p(e\mid f)$ 进行翻译,即目标语言(例如英语)中的字符串 e 是源语言(例如芬兰语)中的字符串 f 的翻译,依据贝叶斯定理,概率分布 $p(e\mid f)$ 能够从新表述为:

$$
p(e\mid f)=\frac{p(f\mid e)p(e)}{p(f)}
$$

在给定输出字符串 f 的状况下,最优的预测输入字符串 $e_{\mathrm{best}}$ 为:

$$
e_{\mathrm{best}}=\mathrm{argmax}p(e\mid f)
$$

$$
=\underset{e}{\operatorname*{argmax}}\frac{p(f\mid e)p(e)}{p(f)}
$$

$$
=\operatorname{argmax}p(f\mid e)p(e)
$$

这种翻译问题被称为“噪声信道(“noisy channel)”模型,直觉上,咱们认为源语言(例如芬兰语)是目标语言(例如英语)的“噪声扭曲(noisy distortion)”,而后试图复原最有可能导致转为芬兰语句子的英语句子,SMT 模型有两个局部:一个翻译模型,它捕捉英语句子如何被“噪声扭曲(noisily distorted)”成芬兰语($p(f\mid e)$);另一个是语言模型($p(e)$),它捕捉不同类型英语句子的可能事件类型。因而,预测 $p(e\mid f)$ 的问题能够合成为两个子问题,即预测翻译模型 $p(f\mid e)$ 和预测语言模型 $p(e)$。

与混同名还原的分割是不言而喻的:正如 SMT 用于将芬兰语“去噪声(de-noisify)”和“去扭曲(“de-distort)”后翻译回英语一样,对于通过混同后的变量名,也能够应用 SMT 去除“噪声(noise)”并复原其原始模式。

Autonym 与 JSNice 的交融

Autonym 和 JSnice 各有长处,选取了从 GitHub 下载下来的 300000 个文件用于翻译模型的训练,1000 个文件用于 Moses 的参数调优,10000 个文件用于逻辑回归估计,随机抽取 2150 个待还原的文件,三个工具:Autonym、JSNice 和二者的联合 JSNaughty 对变量名还原的精度比照箱形图如下:

能够看到 Autonym 与 JSNice 的交融(JSNaughty),其预测的精准度漫步范畴要更高,准确度更好,下图以一个实在的 JS 脚本来演示阐明了 Autonym 和 JSNice 的互补劣势,这种劣势促使了 JSnaughty 的产生:

从图中咱们能够看出:一些被压缩的变量名被 Autonym 完满还原(例如第 1 行的参数 e 和 r 别离还原为 req 和 res),而 JSNice 无奈做到;而另一些被压缩的变量名被 JSNice 完满还原(例如第 6 行的参数 r 还原为 index),但 Autonym 又无奈做到。与此同时,两种办法有时候都能精确还原一些变量名(例如第 2 行的 t 还原为 headers),此外,也有两种办法都不能还原的状况(例如第 3 行的 i)。

结语

本文分享了三种还原混同变量名的办法:ChatGPT、JSNice 和 JSNaughty,ChatGPT 更加智能,甚至能解决一些简略的、通过加密压缩等形式混同后的代码,JSNaughty 能够看做是 JSNice 的降级优化版,但二者都进行更新比拟长时间了,不足最新的训练,且二者的出发点都是为了还原通过 UglifyJS 压缩后的变量名,因而还原变量名也是十分无限的,三者的共同点就是解决不了通过简单加密、压缩、混同后的代码,然而实践曾经存在且被验证过,基于 AST、Big Code 概念、概率图模型和统计机器翻译(SMT)等技术,针对 JS 代码混同还原畛域加以训练和优化,说不定当前真就实现了一键还原混同代码呢?

而后再瞎扯一句,人工智能在反爬、数字业务平安等畛域利用越来越广了,比方最近各大验证码厂商推出的 AIGC 每天主动生成海量验证码图片等,兴许在不久的未来逆向的反抗将倒退成 AI 之间的反抗,用魔法战胜魔法!

参考资料

  • Predicting Program Properties from“Big Code”
  • Statistical Deobfuscation of Android Applications
  • Recovering Clear, Natural Identifiers from Obfuscated JS Names
退出移动版