利用AST对抗js混淆(一) 基础知识
准备工作:
1.网站及工具
JavaScript Obfuscator Tool 主要研究对象,主要是研究此网站的各种混淆方法及破解办法。
JS NICE: Statistical renaming, Type inference and Deobfuscation 用来格式化代码,方便调试。
notepad++ 编辑代码。
https://astexplorer.net/ 用来查看各种代码的AST结构。
2.nodejs的安装
官方网站 https://nodejs.org/zh-cn/
win10 可以直接下载最新版进行安装
win7 最高似乎只能安装nodejs 13.14.0版本 ,需要去页面https://nodejs.org/zh-cn/download/releases/ 下载安装。
安装过程略。
3. vscode的安装
下载地址:https://code.visualstudio.com/ 安装过程略
主要用于调试代码。
babel安装及相关文档
1. Babel的安装
新建一个文件夹,cmd窗口转到此路径下,首先初始化项目:
npm init
然后执行以下命令:
# babel遍历器
npm install @babel/traverse --save# code解析为ast
npm install @babel/parser --save# ast操作相关工具类
npm install @babel/types --save# ast还原为代码
npm install @babel/generator --save
接着可参考参考:https://blog.csdn.net/weixin_40352715/article/details/109809754
可将此文章中的例子在vscode中调试以下,一来试一试环境是否已配置好,二来也先看一看效果。
2. babel官方文档
接着学习基础知识:
babel-handbook/plugin-handbook.md at master · jamiebuilds/babel-handbook · GitHub
babel-handbook/user-handbook.md at master · jamiebuilds/babel-handbook · GitHub
PS:Pandoc 可以将md文件转换为word或者pdf文件。
3.babel插件常用api
此部分来源:https://www.jianshu.com/p/7c8c5ae1e4be
行文至此,顺便梳理下babel中的一些api,市面上很少有这方面的内容,一般都是讲解插件配置和一些demo插件的编写。以下的内容都是参考源码和注释得来的。
[一]path相关api
有关path
相关的源码都在@babel/traverse
这个目录下。想要查找到符合条件的节点并进行各种各样的操作,都要依赖这部分的api,官方文档基本等于没有,相关api的用法只有自己扒源码,源码中的api功能大致通过名字可以猜出来,大家可以先有个印象,以后有对应的需求再去查找具体用法。
[二]ancestry.js相关api
这一部分的api主要是查找当前节点的祖先节点和有关判断,具体使用规则只有看源码自行体会。
// 从当前节点上溯,传入回调函数,通过函数来判断返回什么节点,从自己开始
exports.findParent = findParent;
// 从当前节点上溯,传入回调函数,通过函数来判断返回什么节点,从自己的父节点开始找
exports.find = find;
// 查找第一个函数式父组件
exports.getFunctionParent = getFunctionParent;
// 查找其声明的父组件(感觉指的是react中继承的父组件)
exports.getStatementParent = getStatementParent;
// 传入一个path,获取最上层的常规祖先节点
exports.getEarliestCommonAncestorFrom = getEarliestCommonAncestorFrom;
// 传入一个path,获取最底层的常规祖先节点
exports.getDeepestCommonAncestorFrom = getDeepestCommonAncestorFrom;
// 返回一个包含所有祖先的数组
exports.getAncestry = getAncestry;
// 传入一个节点,判断当前的节点是否是传入节点的祖先
exports.isAncestor = isAncestor;
// 传入一个节点,判断当前节点是否是出入节点的子节点
exports.isDescendant = isDescendant;
// 上溯,传入一个类型数组,判断所有节点中是否是数组中的类型
exports.inType = inType;
[三]comments.js相关api
这一部分的api主要是查找跟注释相关的节点
// 和兄弟元素共享注释
exports.shareCommentsWithSiblings = shareCommentsWithSiblings;
// 添加单条注释
exports.addComment = addComment;
// 添加多行注释
exports.addComments = addComments;
[四]context.js相关api
主要是跟当前访问上下文相关的api
// 调用一系列函数,返回布尔值
exports.call = call;
// 内部方法,配合call使用
exports._call = _call;
// 当前节点的类型是否在黑名单中
exports.isBlacklisted = isBlacklisted;
// 访问一个节点,返回布尔值,是否应该停止访问
exports.visit = visit;
// 标记跳过
exports.skip = skip;
// 置skipkey
exports.skipKey = skipKey;
// 停止
exports.stop = stop;
// 设置scope
exports.setScope = setScope;
// 设置上下文
exports.setContext = setContext;
// 再同步相关
exports.resync = resync;
exports._resyncParent = _resyncParent;
exports._resyncKey = _resyncKey;
exports._resyncList = _resyncList;
exports._resyncRemoved = _resyncRemoved;、
// 上下文出栈
exports.popContext = popContext;
// 上下文入栈
exports.pushContext = pushContext;
// 设置
exports.setup = setup;
exports.setKey = setKey;
// 重新入队
exports.requeue = requeue;
// 获取队列上下文
exports._getQueueContexts = _getQueueContexts;
[五]conversion.js相关api
// 获取节点的key
exports.toComputedKey = toComputedKey;
// 讲一个节点变成语句块
exports.ensureBlock = ensureBlock;
// 将箭头函数表达式变成普通函数
exports.arrowFunctionToShadowed = arrowFunctionToShadowed;
// 去掉函数环境的封装?
exports.unwrapFunctionEnvironment = unwrapFunctionEnvironment;
// 类似arrowFunctionToShadowed
exports.arrowFunctionToExpression = arrowFunctionToExpression;
[六]evaluation.js相关api
这里是进入输入节点并且做静态分析,看返回的值是true或者false,如果不能确定,返回undefined
// 返回true、false或者undefined
exports.evaluateTruthy = evaluateTruthy;
// 返回一个对象,里面有详细信息
exports.evaluate = evaluate;
[七]family.js相关api
这个文件主要处理子元素和兄弟元素
// 获得对位的兄弟元素
exports.getOpposite = getOpposite;
// 获得完整路径记录
exports.getCompletionRecords = getCompletionRecords;
// 传入key,获得兄弟节点
exports.getSibling = getSibling;
// 获得上一个兄弟节点
exports.getPrevSibling = getPrevSibling;
// 获得下一个兄弟节点
exports.getNextSibling = getNextSibling;
// 获得所有的下方的兄弟节点
exports.getAllNextSiblings = getAllNextSiblings;
// 获得所有上方的兄弟节点
exports.getAllPrevSiblings = getAllPrevSiblings;
// 根据key和上下文,传入节点
exports.get = get;
// 配合get使用
exports._getKey = _getKey;
// 配合get使用
exports._getPattern = _getPattern;
// 获得绑定的标识符
exports.getBindingIdentifiers = getBindingIdentifiers;
// 获得外部绑定的标识符
exports.getOuterBindingIdentifiers = getOuterBindingIdentifiers;
// 获得绑定的标识符路径
exports.getBindingIdentifierPaths = getBindingIdentifierPaths;
// 获得外部绑定的标识符路径
exports.getOuterBindingIdentifierPaths = getOuterBindingIdentifierPaths;
[八]introspection.js相关api
此文件包含负责为某些值内省当前路径的方法。
// 输入一个pattern,返回符合条件的节点
exports.matchesPattern = matchesPattern;
// 输入一个key,判断当前节点是否含有这个属性
exports.has = has;
// 判断是否是静态节点
exports.isStatic = isStatic;
// 节点是否不含有某个输入的key,与has相反
exports.isnt = isnt;
// 传入key和value,判断当前节点上key对应的值是否等于value
exports.equals = equals;
// 输入类型字符串,判断当前节点的类型是否和出入的类型相等
exports.isNodeType = isNodeType;
// 判断当前路径是合处在for循环中。因为for循环中允许变量声明和普通的表达式,我们需要告诉path的replactment相关方法
// 在这里替换掉表达式是ok的
exports.canHaveVariableDeclarationOrExpression = canHaveVariableDeclarationOrExpression;
// 这个方法减产我们是否在将箭头函数转换为表达式或者代码块(反之亦然),这是因为
// 箭头函数会隐式地返回表达式,这和块语句类似
exports.canSwapBetweenExpressionAndStatement = canSwapBetweenExpressionAndStatement;
// 判断当前路径是否指向一个完成的记录(是否是一个容器的最后的节点)
exports.isCompletionRecord = isCompletionRecord;
// 判断当前的节点是否允许单独的声明或者块声明,以便我们在必要的时候展开
exports.isStatementOrBlock = isStatementOrBlock;
// 判断当前的指定路径引用了moduleSource的importName
exports.referencesImport = referencesImport;
// 获取当前节点对应的源码
exports.getSource = getSource;
// 有可能会提前执行
exports.willIMaybeExecuteBefore = willIMaybeExecuteBefore;
// 传入一个节点,判断其执行状态是否和当前的节点相关
exports._guessExecutionStatusRelativeTo = _guessExecutionStatusRelativeTo;
exports._guessExecutionStatusRelativeToDifferentFunctions = _guessExecutionStatusRelativeToDifferentFunctions;
// 将一个指针node节点指向绝对路径
exports.resolve = resolve;
exports._resolve = _resolve;
// 是否是固定表达式
exports.isConstantExpression = isConstantExpression;
// 是否处于严格模式
exports.isInStrictMode = isInStrictMode;
// has的别名
exports.is = void 0;
[九]modification.js相关api
// 在当前节点之前插入目标节点
exports.insertBefore = insertBefore;
// 传入位置和节点,在对应的位置插入节点
exports._containerInsert = _containerInsert;
// 在目标节点之前插入
exports._containerInsertBefore = _containerInsertBefore;
// 在目标节点之后插入
exports._containerInsertAfter = _containerInsertAfter;
// 在当前的节点之后插入,但在一个表达式之后插入的时候,确保完成记录是正确的
exports.insertAfter = insertAfter;
// 传入两个参数(起点,终点),更新期间所有兄弟节点的路径
exports.updateSiblingKeys = updateSiblingKeys;
// 校验节点列表
exports._verifyNodeList = _verifyNodeList;
// 容器列表头部新增一个元素
exports.unshiftContainer = unshiftContainer;
// 容器列表尾部新增一个元素
exports.pushContainer = pushContainer;
// 尽可能提升当前节点的作用域,并且返回一个可以引用的uid
exports.hoist = hoist;
[十]removal.js相关api
移除节点相关的api
// 移除当前节点
exports.remove = remove;
// 从作用域中移除
exports._removeFromScope = _removeFromScope;
// 是否调用移除钩子
exports._callRemovalHooks = _callRemovalHooks;
// 内部的remove实现
exports._remove = _remove;
// 标记移除
exports._markRemoved = _markRemoved;
// 声明某个节点不可以出
exports._assertUnremoved = _assertUnremoved;
[十一]replacement.js相关api
跟节点替换相关api
// 将当前节点替换为一系列节点(出入的是一个节点数组),该方法按照以下步骤执行
// 1、继承传入的第一个节点的注释 2、在当前的节点后插入传入的节点 3、删除当前节点
exports.replaceWithMultiple = replaceWithMultiple;
// 将传入字符串作为表达式解析,并且将当前的节点替换为解析的结果
// 这个方法很方便,但是是反模式的,不建议使用,这会使你的应用很脆弱
exports.replaceWithSourceString = replaceWithSourceString;
// 将当前节点替换为另一个
exports.replaceWith = replaceWith;
// 内部实现
exports._replaceWith = _replaceWith;
// 输入一个声明数组并且将他们在表达式中展开。这个方法将会保持完整的记录,这对于维护原始的语义非常重要
exports.replaceExpressionWithStatements = replaceExpressionWithStatements;
// 替换行内内容
exports.replaceInline = replaceInline;
找到一个各种函数详细解释的网站:
Document
4.AST 节点类型对照表
序号 | 类型原名称 | 中文名称 | 描述 |
---|---|---|---|
1 | Program | 程序主体 | 整段代码的主体 |
2 | VariableDeclaration | 变量声明 | 声明一个变量,例如 var let const |
3 | FunctionDeclaration | 函数声明 | 声明一个函数,例如 function |
4 | ExpressionStatement | 表达式语句 | 通常是调用一个函数,例如 console.log() |
5 | BlockStatement | 块语句 | 包裹在 {} 块内的代码,例如 if (condition){var a = 1;} |
6 | BreakStatement | 中断语句 | 通常指 break |
7 | ContinueStatement | 持续语句 | 通常指 continue |
8 | ReturnStatement | 返回语句 | 通常指 return |
9 | SwitchStatement | Switch 语句 | 通常指 Switch Case 语句中的 Switch |
10 | IfStatement | If 控制流语句 | 控制流语句,通常指 if(condition){}else{} |
11 | Identifier | 标识符 | 标识,例如声明变量时 var identi = 5 中的 identi |
12 | CallExpression | 调用表达式 | 通常指调用一个函数,例如 console.log() |
13 | BinaryExpression | 二进制表达式 | 通常指运算,例如 1+2 |
14 | MemberExpression | 成员表达式 | 通常指调用对象的成员,例如 console 对象的 log 成员 |
15 | ArrayExpression | 数组表达式 | 通常指一个数组,例如 [1, 3, 5] |
16 | NewExpression | New 表达式 | 通常指使用 New 关键词 |
17 | AssignmentExpression | 赋值表达式 | 通常指将函数的返回值赋值给变量 |
18 | UpdateExpression | 更新表达式 | 通常指更新成员值,例如 i++ |
19 | Literal | 字面量 | 字面量 |
20 | BooleanLiteral | 布尔型字面量 | 布尔值,例如 true false |
21 | NumericLiteral | 数字型字面量 | 数字,例如 100 |
22 | StringLiteral | 字符型字面量 | 字符串,例如 vansenb |
23 | SwitchCase | Case 语句 | 通常指 Switch 语句中的 Case |
以下内容来源:GitHub - coder-gao/Spider: 爬虫, 反爬虫, JS 逆向, 安卓逆向, AST
节点属性及方法
enter(默认)与 exit 的区别
5.一些结构及函数
内容来自:带你玩转babel工具链(二)@babel/traverse - 掘金
pathNode {// 属性:node // 当前 AST 节点parent // 父 AST 节点parentPath // 父 AST 节点的 pathscope // 作用域hub // 可以通过 path.hub.file 拿到最外层 File 对象, path.hub.getScope 拿到最外层作用域,path.hub.getCode 拿到源码字符串container // 当前 AST 节点所在的父节点属性的属性值key // 当前 AST 节点所在父节点属性的属性名或所在数组的下标listKey // 当前 AST 节点所在父节点属性的属性值为数组时 listkey 为该属性名,否则为 undefined
}
那么scope有哪些属性呢?
scope.bindings
当前作用域内声明的所有变量
scope.block
生成作用域的 block,例如FunctionDeclaration
,Program
等AST节点
scope.path
生成作用域的节点对应的 path,例如FunctionDeclaration
,Program
等AST节点的path
scope.references
所有 binding 的引用对应的 path,详见下文
scope.dump()
打印作用域链的所有 binding 到控制台
scope.parentBlock()
父级作用域的 block
getAllBindings()
从当前作用域到根作用域的所有 binding 的合并
getBinding(name)
查找某个 binding,从当前作用域一直查找到根作用域
getOwnBinding(name)
从当前作用域查找 binding
parentHasBinding(name, noGlobals)
查找某个 binding,从父作用域查到根作用域,不包括当前作用域。可以通过 noGlobals 参数指定是否算上全局变量(比如console,不需要声明就可用),默认是 false
removeBinding(name)
删除某个 binding
hasBinding(name, noGlobals)
从当前作用域查找 binding,可以指定是否算上全局变量,默认是 false
moveBindingTo(name, scope)
把当前作用域中的某个 binding 移动到其他作用域
generateUid(name)
生成作用域内唯一的名字,根据 name 添加下划线,比如 name 为 a,会尝试生成 _a,如果被占用就会生成 __a,直到生成没有被使用的名字。上面的方法和属性有很多,就不一个个说明了,我们能通过scope很方便的操作作用域中的某个变量。完全不需要我们手动去获取对应的AST了!
其他值得学习知识:
AST deObfuscator AST 反混淆 js逆向
js逆向技巧分享 - 知乎
https://www.codenong.com/cs106567825/
ob混淆AST | 一只橘子的思考
https://www.mdeditor.tw/pl/puBb
https://www.jianshu.com/p/7c8c5ae1e4be
利用AST对抗js混淆(一) 基础知识相关推荐
- 利用AST对抗js混淆(三) 控制流平坦化(Control Flow Flattening)的处理
控制流平坦化 参考:https://security.tencent.com/index.php/blog/msg/112 控制流平坦化,简单来讲就是将代码块之间的关系打断,由一个分发器来控制代码块的 ...
- js打印,基础知识,数据类型
打印,基础知识,数据类型 <!DOCTYPE html> <html><head><meta charset="utf-8" />& ...
- 云笔记项目-补充JS面向对象编程基础知识
简单介绍: 此部分知识为在做云笔记项目中补充,因为云笔记项目中涉及到前端js,里面写了很多js脚本,用到了创建js属性和方法,在js中直接声明的属性和方法最终都会变成window的对象,即其成为了全局 ...
- Python操作AST解JS混淆
通过生成语法树(AST),可快速修改代码中的一些混淆处理,从而简化代码,便于后续分析. 本文通过Python来把JS转为AST并进行简单的操作,内容很简单. 比如我们下图中的JS代码,有sum和min ...
- 爬虫 JavaScript 逆向进阶!利用 AST 技术还原混淆代码
这是「进击的Coder」的第 617 篇技术分享 作者:K 小哥 来源:K 哥爬虫 " 阅读本文大概需要 47 分钟. " 目录 文章较长,可作为 AST Babel 入门手册,强 ...
- [Vue.js 1] 入门基础知识与开发
最近接触的几个项目框架全部用到了Vue.js技术,没办法只能对vuejs进行深入学习,不过可喜的是Vue.js学习路线非常快,上手也是非常快的,所以对于前端开发也是主流的开发框架了.不过其中的js部分 ...
- JS学习笔记——入门基础知识总结
JS入门基础知识总结1 前言 基础背景知识 一.产生历史: 二.特点: 三.应用方向: 四.Javascript组成: JavaScript书写使用方式 一.行内式(了解即可,项目中不使用,日常练习尽 ...
- JavaScript基础知识与脚本语言总结
1 Aptana插件安装 1.Aptana插件安装 <1>Aptana是一个非常强大,开源,JavaScript-focused的AJAX开发IDE. <2>它的特点包括: J ...
- [Python图像识别] 四十九.图像生成之什么是生成对抗网络GAN?基础原理和代码普及
该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...
最新文章
- Yahoo为啥赚不到钱
- JAVA中protected的作用
- iview table 自定义列_基于VueJS的render渲染函数打造一款非常强大的IView 的Table组件...
- 日记 [2007年03月10日]重回blog,开始2007
- Visio—如何画矩形虚线边框?
- EXCEL如何新建自定义单元格以及样式怎么设置
- Programming WCF Services中文翻译(3)-契约
- SQL server2019安装教程
- Java已死?一眼就能看懂的Java自学手册,挑战大厂重燃激情!
- avr单片机流水灯程序c语言,动手学AVR单片机流水灯实验电路和程序实现.doc
- 防关联超级浏览器(超级防关联浏览器)的应用场景和技术原理
- python人机交互界面
- 【C语言】【结构体】复数类型加减乘除的实现
- 用C语言编辑一光年相当于多少米,一光年到底有多远?是光速跑了365天的距离,这样说你就少算了...
- 实验一 Java编程基础
- 【Modbus 】Modbus 协议
- 听说软件测试工程师们都在考ISTQB?
- 按照日期:蓝桥杯真题、洛谷题单、力扣题单汇总
- Oracle not in 范围超过 1000 报错问题及解决方案
- java websocket即时通讯+layui实现移动端一对一聊天客服功能