0x00 前言


Javascript 作为一种运行在客户端的脚本语言,其源代码对用户来说是完全可见的。但不是每一个 js 开发者都希望自己的代码能被直接阅读,比如恶意软件的制造者们。为了增加代码分析的难度,混淆(obfuscate)工具被应用到了许多恶意软件(如 0day 挂马、跨站攻击等)当中。分析人员为了掀开恶意软件的面纱,首先就得对脚本进行反混淆(deobfuscate)处理。

本文将介绍一些常见的混淆手段和 estools 进行静态代码分析的入门。

0x01 常见混淆手段


加密

这类混淆的关键思想在于将需要执行的代码进行一次编码,在执行的时候还原出浏览器可执行的合法的脚本,然后执行之。看上去和可执行文件的加壳有那么点类似。Javascript 提供了将字符串当做代码执行(evaluate)的能力,可以通过Function 构造器、eval、setTimeout、setInterval将字符串传递给 js 引擎进行解析执行。最常见的是base62 编码——其最明显的特征是生成的代码以eval(function(p,a,c,k,e,r))开头。

无论代码如何进行变形,其最终都要调用一次 eval 等函数。解密的方法不需要对其算法做任何分析,只需要简单地找到这个最终的调用,改为 console.log 或者其他方式,将程序解码后的结果按照字符串输出即可。自动化的实现方式已经有许多文章介绍过,此处就不再赘述。

隐写术

严格说这不能称之为混淆,只是将 js 代码隐藏到了特定的介质当中。如通过最低有效位(LSB)算法嵌入到图片的 RGB 通道、隐藏在图片 EXIF 元数据、隐藏在 HTML 空白字符等。

比如这个耸人听闻的议题:[一张图片黑掉你:在图片中嵌入恶意程序],PPT放出来一看,正是使用了最低有效位平面算法。结合 HTML5 的 canvas 或者处理二进制数据的 TypeArray,脚本可以抽取出载体中隐藏的数据(如代码)。

隐写的方式同样需要解码程序和动态执行,所以破解的方式和前者相同,在浏览器上下文中劫持替换关键函数调用的行为,改为文本输出即可得到载体中隐藏的代码。

复杂化表达式

代码混淆不一定会调用 eval,也可以通过在代码中填充无效的指令来增加代码复杂度,极大地降低可读性。Javascript 中存在许多称得上丧心病狂的特性,这些特性组合起来,可以把原本简单的字面量(Literal)、成员访问(MemberExpression)、函数调用(CallExpression)等代码片段变得难以阅读。

Js 中的字面量有字符串、数字、正则表达式

下面简单举一个例子。

  • 访问一个对象的成员有两种方法——点运算符和下标运算符。调用 window 的 eval 方法,既可以写成 window.eval(),也可以 window['eval']

  • 为了让代码更变态一些,混淆器选用第二种写法,然后再在字符串字面量上做文章。先把字符串拆成几个部分:'e' + 'v' + 'al'

  • 这样看上去还是很明显,再利用一个数字进制转换的技巧:14..toString(15) + 31..toString(32) + 0xf1.toString(22)

  • 一不做二不休,把数字也展开:(0b1110).toString(4<<2) + (' '.charCodeAt() - 1).toString(Math.log(0x100000000) / Math.log(2)) + 0xf1.toString(11 << 1)

  • 最后的效果:window[(2*7).toString(4<<2) + (' '.charCodeAt() - 1).toString(Math.log(0x100000000) / Math.log(2)) + 0xf1.toString(11 << 1)]('alert(1)')

在 js 中可以找到许多这样互逆的运算,通过使用随机生成的方式将其组合使用,可以把简单的表达式无限复杂化。

0x02 静态分析实现


解析和变换代码

本文对 Javascript 实现反混淆的思路是模拟执行代码中可预测结果的部分,编写一个简单的脚本执行引擎,只执行符合某些预定规则的代码块,最后将计算结果替换掉原本冗长的代码,实现表达式的简化。

如果对脚本引擎解释器的原理有初步了解的话,可以知道解释器在为了“读懂”代码,会对源代码进行词法分析、语法分析,将代码的字符串转换为抽象语法树(Abstract Syntax Tree, AST)的数据形式。

如这段代码:

var a = 42; var b = 5; function addA(d) { return a + d; } var c = addA(2) + b;

对应的语法树如图:

(由 JointJS的在线工具生成)

不考虑 JIT 技术,解释器可以从语法树的根节点开始,使用深度优先遍历整棵树的所有节点,根据节点上分析出来的指令逐个执行,直到脚本结束返回结果。

通过 js 代码生成抽象语法树的工具很多,如压缩器 UglifyJS 带的 parser,还有本文使用的esprima。

esprima 提供的接口很简单:

​ var ast = require('esprima').parse(code)

另外 Esprima 提供了一个在线工具,可以把任意(合法的)Javascript 代码解析成为 AST 并输出: http://esprima.org/demo/parse.html

再结合 estools 的几个辅助库即可对 js 进行静态代码分析:

  • escope Javascript 作用域分析工具

  • esutil 辅助函数库,检查语法树节点是否满足某些条件

  • estraverse语法树遍历辅助库,接口有一点类似 SAX 方式解析 XML

  • esrecurse 另一个语法树遍历工具,使用递归

  • esquery 使用 css 选择器的语法从语法树中提取符合条件的节点

  • escodegen与 esprima 功能互逆,将语法树还原为代码

项目中使用的遍历工具是 estraverse。其提供了两个静态方法,estraverse.traverse 和estraverse.replace。前者单纯遍历 AST 的节点,通过返回值控制是否继续遍历到叶子节点;而 replace 方法则可以在遍历的过程中直接修改 AST,实现代码重构功能。具体的用法可以参考其官方文档,或者本文附带的示例代码。

规则设计

从实际遇到的代码入手。最近在研究一些 XSS 蠕虫的时候遇到了类似如下代码混淆:

观察其代码风格,发现这个混淆器做了这几件事:

  • 字符串字面量混淆:首先提取全部的字符串,在全局作用域创建一个字符串数组,同时转义字符增大阅读难度,然后将字符串出现的地方替换成为数组元素的引用

  • 变量名混淆:不同于压缩器的缩短命名,此处使用了下划线加数字的格式,变量之间区分度很低,相比单个字母更难以阅读

  • 成员运算符混淆:将点运算符替换为字符串下标形式,然后对字符串进行混淆

  • 删除多余的空白字符:减小文件体积,这是所有压缩器都会做的事

经过搜索,这样的代码很有可能是通过 javascriptobfuscator.com的免费版生成的。其中免费版可以使用的三个选项(Encode Strings / Strings / Replace Names)也印证了前面观察到的现象。

这些变换中,变量名混淆是不可逆的。要是可以智能给变量命名的工具也不错,比如这个 jsnice网站提供了一个在线工具,可以分析变量具体作用自动重命名。就算不能做到十全十美,实在不行就用人工的方式,使用 IDE(如 WebStorm)的代码重构功能,结合代码行为分析进行手工重命名还原。

再看字符串的处理。由于字符串将会被提取到一个全局的数组,在语法树中可以观察到这样的特征: 在全局作用域下,出现一个 VariableDeclarator,其 init 属性为 ArrayExpression,而且所有元素都是 Literal ——这说明这个数组所有元素都是常量。简单地将其求值,与变量名(标识符)关联起来。注意,此处为了简化处理,并没有考虑变量名作用域链的问题。在 js 中,作用域链上存在变量名的优先级,比如全局上的变量名是可以被局部变量重新定义的。如果混淆器再变态一点,在不同的作用域上使用相同的变量名,反混淆器又没有处理作用域的情况,将会导致解出来的代码出错。

在测试程序中我设置了如下的替换规则:

  • 全局变量声明的字符串数组,在代码中直接使用数字下标引用其值

  • 结果确定的一连串二元运算,如 1 * 2 + 3 / 4 - 6 % 5

  • 正则表达式字面量的 source,字符串字面量的 length

  • 完全由字符串常量组成的数组,其join / reverse / slice 等方法的返回值

  • 字符串常量的 substr / charAt 等方法的返回值

  • decodeURIComponent 等全局函数,其所有参数为常量的,替换为其返回值

  • 结果为常数的数学函数调用,如 Math.sin(3.14)

至于缩进的还原,这是 escodegen 自带的功能。在调用 escodegen.generate 方法生成代码的时候使用默认的配置(忽略第二个参数)即可。

DEMO 程序

这个反混淆器的原型放在 GitHub 上:https://github.com/ChiChou/etacsufbo

运行环境和使用方法参考仓库的 README。

从  YOU MIGHT NOT NEED JQUERY上摘抄了一段代码,放入 javascriptobfuscator.com 测试混淆:

将混淆结果https://github.com/ChiChou/etacsufbo/blob/master/tests/cases/jsobfuscator.com.js进行解开,结果如下:

虽然变量名可读性依旧很差,但已经可以大体看出代码的行为了。

演示程序目前存在大量局限性,只能算一个半自动的辅助工具,还有许多没有实现的功能。

一些混淆器会对字符串字面量进行更复杂的保护,将字符串转换为 f(x) 的形式,其中 f 函数为一个解密函数,参数 x 为密文的字符串。也有原地生成一个匿名函数,返回值为字符串的。这种方式通常使用的函数表达式具有上下文无关的特性——其返回值只与函数的输入有关,与当前代码所处的上下文(比如类的成员、DOM 中取到的值)无关。如以下代码片段中的 xor 函数:

var xor = function(str, a, b) {

return String.fromCharCode.apply(null, str.split('').map(function(c, i) { var ascii = c.charCodeAt(0); return ascii ^ (i % 2 ? a : b); })); };

如何判断某个函数是否具有这样的特性呢?首先一些库函数可以确定符合,如 btoa,escape,String.fromCharCode 等,只要输入值是常量,返回值就是固定的。建立一个这样的内置函数白名单,接着遍历函数表达式的 AST,若该函数参与计算的参数均没有来自外部上下文,且其所有 CallExpression 的 callee 在函数白名单内,那么通过递归的方式可以确认一个函数是否满足条件。

还有的混淆器会给变量创建大量的引用实例,也就是给同一个对象使用了多个别名,阅读起来非常具有干扰性。可以派出 escope 工具对变量标识符进行数据流分析,替换为所指向的正确值。还有利用数学的恒等式进行混淆的。如声明一个变量 a,若 a 为 Number,则表达式 a-aa * 0均恒为 0。但如果 a 满足 isNaN(a),则表达式返回 NaN。要清理这类代码,同样需要借助数据流分析的方法。

目前还没有见到使用扁平化流程跳转实现的 js 混淆样本,笔者认为可能跟 js 语言本身的使用场景和特点有关。一般 js 的代都是偏业务型的,不会有太复杂的流程控制或者算法,混淆起来效果不一定理想。

0x03 结束语


Javascript 的确是一门神奇的语言,经常可以遇到一些让人惊讶的奇技淫巧。解密保护过的代码也是有趣的事情。据说几大科技巨头在酝酿给浏览器应用设计一款通用的字节码标准——WebAssembly。一旦这个设想得以实现,代码保护将可以引入真正意义上的“加壳”或者虚拟机保护,对抗技术又将提升到一个新的台阶。

演示项目代码托管在 GitHub:https://github.com/ChiChou/etacsufbo

文章转载自 http://drops.wooyun.org/tips/7713?utm_source=tuicool

反混淆JavaScript相关推荐

  1. Js 的 混淆 与反混淆

    Js 混淆: https://obfuscator.io/ 功能很多,暂举两个: 1.Identifier Names Generator : 把变量.方法的名字变成 16进制(0xabc123) 或 ...

  2. 技术分享:几种常见的JavaScript混淆和反混淆工具分析实战【转】

    信息安全常被描述成一场军备竞赛,白帽与黑帽,渗透测试者与黑客,善与恶,本文将聚焦这场永无止境决斗中的一个小点. HTML5 & JS 应用中充满着对输入进行验证/注入的问题,需要开发人员始终保 ...

  3. 【JavaScript 逆向】AST 技术反混淆

    前言 通过浏览器工具可以清楚的看到网站正在运行的 HTML 和 JavaScript 代码,所以对 JavaScript 代码进行混淆处理是一些网站常用的反爬措施,例如下文介绍到的字符串混淆.控制流平 ...

  4. 技术分享:几种常见的JavaScript混淆和反混淆工具分析实战

    信息安全常被描述成一场军备竞赛,白帽与黑帽,渗透测试者与黑客,善与恶,本文将聚焦这场永无止境决斗中的一个小点. HTML5 & JS 应用中充满着对输入进行验证/注入的问题,需要开发人员始终保 ...

  5. JavaScript 反混淆的一般套路和技巧[起][承][转][结]

    https://www.blackglory.me/javascript-deobfuscate-general-routines-and-tips-chapter-1/ 最近发现网上没有什么专门深入 ...

  6. javaScript 压缩混淆反混淆

    javaScript 压缩混淆反混淆 压缩混淆的意义 1. 压缩 2. 混淆 如何压缩混淆 案例 Unicode转义序列(Unicode Escape Sequence) 压缩代码(Compact c ...

  7. 【JS 逆向百例】W店UA,OB反混淆,抓包替换CORS跨域错误分析

    关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 文章目录 声明 逆向目标 OB 混淆简介 抓包分析 混淆还原与替换 CORS 跨域错误 逆向分析 本地联调 完整代码 JavaS ...

  8. python js反混淆 eval(function(p,a,c,k,e,d){

    前言 在Python爬取资源的时候需要分析网站,有时候关键部分的JS会被混淆,所以不得不去解密这段JS去获取数据,这里推荐两个JS在线解密工具. https://www.sojson.com/jsji ...

  9. js混淆 反混淆 在线

    js反混淆地址:http://www.bm8.com.cn/jsConfusion/ 在线javascript 混淆http://www.moralsoft.com/jso-online/hdojso ...

最新文章

  1. Expression Blend学习动画基础
  2. php常用案例,PHP常用数组处理函数总结,附带运行案例
  3. Metadata GC Threshold导致的full gc分析
  4. 英国将迎来史上第二位女首相
  5. 处理selinux方法
  6. 网易游戏2019暑期实习开发岗笔试题
  7. activiti如何最后一次提交事务_2020最后一次水逆,十二星座如何防水逆?
  8. premature end of file错误
  9. 委托BegionInvoke和窗体BegionInvoke
  10. 自适应粉色可爱棒棒糖倒立404错误页面源码
  11. 自注意力机制_与SENet互补提升,华为诺亚提出自注意力新机制:Weight ExcitationECCV2020...
  12. QT5开发及实例学习之三字符串类
  13. 全向轮机器人直线运动分析
  14. halcon 仿射变换和区域跟随
  15. jsp汽车4S店维修管理系统
  16. 光明顶短信支付:格式详解
  17. Hi,这是一个普通Android开发的2021小结
  18. SpringBoot 3.0最低版本要求的JDK 17,这几个新特性不能不知道
  19. Yolov5检测并生成文本及标签文件
  20. uni-app H5打包上线流程

热门文章

  1. QQ空间点赞...取消点赞
  2. 【Leetcode】天堂硅谷·数字经济算法编程大赛(虚拟)
  3. 探索 nunustudio 开发3D模型 第二弹:根据矢量图制作动画效果
  4. org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction
  5. ebcdic编码与ascII编码互转
  6. Matlab中一球反弹的高度,matlab数学建模2乒乓球的弹跳和罗基斯帝模型.doc
  7. ubuntu18.04安装显卡驱动,Anaconda,CUDA,pytorch全套流程
  8. pocketsphinx 模型库_[转] PocketSphinx语音识别系统声学模型的训练与使用
  9. 阿里云搭建自己的anki服务器
  10. 比“跳一跳”好玩100倍的小游戏