mirror of
https://github.com/smallfawn/decode_action.git
synced 2025-12-20 08:44:59 +08:00
1
This commit is contained in:
@@ -5,7 +5,6 @@ const PluginSojson = require('./plugin/sojson.js')
|
|||||||
const PluginSojsonV7 = require('./plugin/sojsonv7.js')
|
const PluginSojsonV7 = require('./plugin/sojsonv7.js')
|
||||||
const PluginObfuscator = require('./plugin/obfuscator.js')
|
const PluginObfuscator = require('./plugin/obfuscator.js')
|
||||||
const PluginAwsc = require('./plugin/awsc.js')
|
const PluginAwsc = require('./plugin/awsc.js')
|
||||||
const PluginObfuscator2 = require('./plugin/obfuscator2.js')
|
|
||||||
|
|
||||||
// 读取参数
|
// 读取参数
|
||||||
let encodeFile = 'input.js'
|
let encodeFile = 'input.js'
|
||||||
@@ -29,8 +28,7 @@ let pluginUsed = '';
|
|||||||
|
|
||||||
// 循环尝试不同的插件,直到源代码与处理后的代码不一致
|
// 循环尝试不同的插件,直到源代码与处理后的代码不一致
|
||||||
const plugins = [
|
const plugins = [
|
||||||
{ name: 'obfuscator', plugin: PluginObfuscator2 },
|
{ name: 'obfuscator', plugin: PluginObfuscator },
|
||||||
{ name: "ob2", plugin: PluginObfuscator },
|
|
||||||
{ name: 'sojsonv7', plugin: PluginSojsonV7 },
|
{ name: 'sojsonv7', plugin: PluginSojsonV7 },
|
||||||
{ name: 'sojson', plugin: PluginSojson },
|
{ name: 'sojson', plugin: PluginSojson },
|
||||||
{ name: 'common', plugin: PluginCommon },// 最后一次使用通用插件
|
{ name: 'common', plugin: PluginCommon },// 最后一次使用通用插件
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ module.exports = function (code) {
|
|||||||
}
|
}
|
||||||
const deleteExtra = require('../visitor/delete-extra')
|
const deleteExtra = require('../visitor/delete-extra')
|
||||||
traverse(ast, deleteExtra)
|
traverse(ast, deleteExtra)
|
||||||
const calculateBinary = require('../visitor/calculate-binary')
|
const calculateConstantExp = require('../visitor/calculate-constant-exp')
|
||||||
traverse(ast, calculateBinary)
|
traverse(ast, calculateConstantExp)
|
||||||
const calculateRString = require('../visitor/calculate-rstring')
|
const calculateRString = require('../visitor/calculate-rstring')
|
||||||
traverse(ast, calculateRString)
|
traverse(ast, calculateRString)
|
||||||
code = generator(ast).code
|
code = generator(ast).code
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const traverse = require('@babel/traverse').default
|
|||||||
const t = require('@babel/types')
|
const t = require('@babel/types')
|
||||||
const ivm = require('isolated-vm')
|
const ivm = require('isolated-vm')
|
||||||
const PluginEval = require('./eval.js')
|
const PluginEval = require('./eval.js')
|
||||||
|
const calculateConstantExp = require('../visitor/calculate-constant-exp')
|
||||||
|
|
||||||
const isolate = new ivm.Isolate()
|
const isolate = new ivm.Isolate()
|
||||||
const globalContext = isolate.createContextSync()
|
const globalContext = isolate.createContextSync()
|
||||||
@@ -16,6 +17,12 @@ function virtualGlobalEval(jsStr) {
|
|||||||
return globalContext.evalSync(String(jsStr))
|
return globalContext.evalSync(String(jsStr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const optGenMin = {
|
||||||
|
comments: false,
|
||||||
|
minified: true,
|
||||||
|
jsescOption: { minimal: true },
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the literal value of an object, and remove an object if all
|
* Extract the literal value of an object, and remove an object if all
|
||||||
* references to the object are replaced.
|
* references to the object are replaced.
|
||||||
@@ -77,6 +84,110 @@ function decodeObject(ast) {
|
|||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the StringArrayRotateFunction does not exist, we can only verify a
|
||||||
|
* string-array by checking the StringArrayCallsWrapper.
|
||||||
|
*
|
||||||
|
* @param {t.File} ast The ast file
|
||||||
|
* @returns Object
|
||||||
|
*/
|
||||||
|
function stringArrayV0(ast) {
|
||||||
|
console.info('Try v0 mode...')
|
||||||
|
function check_wrapper(ref, array_name) {
|
||||||
|
ref = ref.parentPath
|
||||||
|
const index = ref.node.property?.name
|
||||||
|
if (ref.key !== 'init') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
ref = ref.parentPath
|
||||||
|
const value = ref.node.id?.name
|
||||||
|
ref = ref.getFunctionParent()
|
||||||
|
if (!index || ref.node.params?.[0]?.name !== index) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const container = ref.node.body.body
|
||||||
|
const ret_node = container[container.length - 1]
|
||||||
|
if (!t.isReturnStatement(ret_node) || ret_node.argument?.name !== value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (ref.key !== 'init') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const rm_path = ref.parentPath
|
||||||
|
if (array_name == rm_path.node.id.name) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return rm_path
|
||||||
|
}
|
||||||
|
// check if all the binding references are wrapper
|
||||||
|
function check_other_function(path, array_name) {
|
||||||
|
const binding = path.scope.getBinding(array_name)
|
||||||
|
if (!binding.referencePaths) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ob_func_str = []
|
||||||
|
const ob_dec_call = []
|
||||||
|
for (const ref of binding.referencePaths) {
|
||||||
|
if (ref.findParent((path) => path.removed)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (ref.parentPath.isMemberExpression() && ref.key === 'object') {
|
||||||
|
const rm_path = check_wrapper(ref, array_name)
|
||||||
|
if (!rm_path) {
|
||||||
|
console.error('Unexpected reference')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const code = generator(rm_path.node, optGenMin).code
|
||||||
|
rm_path.get('body').replaceWith(t.blockStatement([]))
|
||||||
|
ob_func_str.push(code)
|
||||||
|
ob_dec_call.push({ name: rm_path.node.id.name, path: rm_path })
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected reference')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ob_func_str.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
ob_func_str.push(generator(path.node, optGenMin).code)
|
||||||
|
path.remove()
|
||||||
|
return {
|
||||||
|
version: 0,
|
||||||
|
stringArrayName: array_name,
|
||||||
|
stringArrayCodes: ob_func_str,
|
||||||
|
stringArrayCalls: ob_dec_call,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ret_obj = {
|
||||||
|
version: 0,
|
||||||
|
stringArrayName: null,
|
||||||
|
stringArrayCodes: [],
|
||||||
|
stringArrayCalls: [],
|
||||||
|
}
|
||||||
|
function check_string_array(path) {
|
||||||
|
if (path.getFunctionParent()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const init = path.get('init')
|
||||||
|
if (!init.isArrayExpression()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!init.node.elements.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const array_name = path.node.id.name
|
||||||
|
const obj = check_other_function(path, array_name)
|
||||||
|
if (obj) {
|
||||||
|
ret_obj = obj
|
||||||
|
path.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traverse(ast, {
|
||||||
|
VariableDeclarator: check_string_array,
|
||||||
|
})
|
||||||
|
return ret_obj
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Before version 2.19.0, the string-array is a single array.
|
* Before version 2.19.0, the string-array is a single array.
|
||||||
* Hence, we have to find StringArrayRotateFunction instead.
|
* Hence, we have to find StringArrayRotateFunction instead.
|
||||||
@@ -119,7 +230,7 @@ function stringArrayV2(ast) {
|
|||||||
// The string array can be found by its binding
|
// The string array can be found by its binding
|
||||||
const bind = path.scope.getBinding(obj.stringArrayName)
|
const bind = path.scope.getBinding(obj.stringArrayName)
|
||||||
const def = t.variableDeclaration('var', [bind.path.node])
|
const def = t.variableDeclaration('var', [bind.path.node])
|
||||||
obj.stringArrayCodes.push(generator(def, { minified: true }).code)
|
obj.stringArrayCodes.push(generator(def, optGenMin).code)
|
||||||
// The calls can be found by its references
|
// The calls can be found by its references
|
||||||
for (let ref of bind.referencePaths) {
|
for (let ref of bind.referencePaths) {
|
||||||
if (ref?.listKey === 'arguments') {
|
if (ref?.listKey === 'arguments') {
|
||||||
@@ -134,19 +245,19 @@ function stringArrayV2(ast) {
|
|||||||
if (up1.node.id) {
|
if (up1.node.id) {
|
||||||
// 2.12.0 <= v < 2.15.4
|
// 2.12.0 <= v < 2.15.4
|
||||||
// The `stringArrayCallsWrapperName` is included in the definition
|
// The `stringArrayCallsWrapperName` is included in the definition
|
||||||
obj.stringArrayCalls.push(up1.node.id.name)
|
obj.stringArrayCodes.push(generator(up1.node, optGenMin).code)
|
||||||
obj.stringArrayCodes.push(generator(up1.node, { minified: true }).code)
|
up1.node.body = t.blockStatement([])
|
||||||
up1.remove()
|
obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (up1.key === 'init') {
|
if (up1.key === 'init') {
|
||||||
// v < 2.12.0
|
// v < 2.12.0
|
||||||
// The `stringArrayCallsWrapperName` is defined by VariableDeclarator
|
// The `stringArrayCallsWrapperName` is defined by VariableDeclarator
|
||||||
up1 = up1.parentPath
|
up1 = up1.parentPath
|
||||||
obj.stringArrayCalls.push(up1.node.id.name)
|
const node = t.variableDeclaration('var', [up1.node])
|
||||||
up1 = up1.parentPath
|
obj.stringArrayCodes.push(generator(node, optGenMin).code)
|
||||||
obj.stringArrayCodes.push(generator(up1.node, { minified: true }).code)
|
up1.node.init = null
|
||||||
up1.remove()
|
obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 2.15.4 <= v < 2.19.0
|
// 2.15.4 <= v < 2.19.0
|
||||||
@@ -158,15 +269,16 @@ function stringArrayV2(ast) {
|
|||||||
console.warn('Unexpected reference!')
|
console.warn('Unexpected reference!')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
obj.stringArrayCalls.push(wrapper)
|
obj.stringArrayCodes.push(generator(up2.node, optGenMin).code)
|
||||||
obj.stringArrayCodes.push(generator(up2.node, { minified: true }).code)
|
up1.remove()
|
||||||
up2.remove()
|
up2.node.body = t.blockStatement([])
|
||||||
|
obj.stringArrayCalls.push({ name: wrapper, path: up2 })
|
||||||
}
|
}
|
||||||
// Remove the string array
|
// Remove the string array
|
||||||
bind.path.remove()
|
bind.path.remove()
|
||||||
// Add the rotate function
|
// Add the rotate function
|
||||||
const node = t.expressionStatement(path.node)
|
const node = t.expressionStatement(path.node)
|
||||||
obj.stringArrayCodes.push(generator(node, { minified: true }).code)
|
obj.stringArrayCodes.push(generator(node, optGenMin).code)
|
||||||
path.stop()
|
path.stop()
|
||||||
if (path.parentPath.isUnaryExpression()) {
|
if (path.parentPath.isUnaryExpression()) {
|
||||||
path.parentPath.remove()
|
path.parentPath.remove()
|
||||||
@@ -232,7 +344,7 @@ function stringArrayV3(ast) {
|
|||||||
}
|
}
|
||||||
const nodes = [...body]
|
const nodes = [...body]
|
||||||
nodes.shift()
|
nodes.shift()
|
||||||
const code = generator(t.BlockStatement(nodes)).code
|
const code = generator(t.BlockStatement(nodes), optGenMin).code
|
||||||
const fp = `${name_func}=function(){return${string_var}}${name_func}()`
|
const fp = `${name_func}=function(){return${string_var}}${name_func}()`
|
||||||
if (!checkPattern(code, fp)) {
|
if (!checkPattern(code, fp)) {
|
||||||
return
|
return
|
||||||
@@ -274,8 +386,9 @@ function stringArrayV3(ast) {
|
|||||||
if (name_func == rm_path.node.id.name) {
|
if (name_func == rm_path.node.id.name) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nodes.push([rm_path.node, 'func3'])
|
const code = generator(rm_path.node, optGenMin).code
|
||||||
rm_path.remove()
|
rm_path.node.body = t.blockStatement([])
|
||||||
|
nodes.push([code, 'func3', rm_path])
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected reference')
|
console.error('Unexpected reference')
|
||||||
}
|
}
|
||||||
@@ -285,16 +398,18 @@ function stringArrayV3(ast) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ob_string_func_name = name_func
|
ob_string_func_name = name_func
|
||||||
ob_func_str.push(generator(path.node, { minified: true }).code)
|
ob_func_str.push(generator(path.node, optGenMin).code)
|
||||||
nodes.map(function (item) {
|
nodes.map(function (item) {
|
||||||
let node = item[0]
|
|
||||||
if (item[1] == 'func3') {
|
if (item[1] == 'func3') {
|
||||||
ob_dec_name.push(node.id.name)
|
ob_func_str.push(item[0])
|
||||||
|
ob_dec_name.push({ name: item[2].node.id.name, path: item[2] })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
let node = item[0]
|
||||||
if (t.isCallExpression(node)) {
|
if (t.isCallExpression(node)) {
|
||||||
node = t.expressionStatement(node)
|
node = t.expressionStatement(node)
|
||||||
}
|
}
|
||||||
ob_func_str.push(generator(node, { minified: true }).code)
|
ob_func_str.push(generator(node, optGenMin).code)
|
||||||
})
|
})
|
||||||
path.stop()
|
path.stop()
|
||||||
path.remove()
|
path.remove()
|
||||||
@@ -312,14 +427,17 @@ function decodeGlobal(ast) {
|
|||||||
let obj = stringArrayV3(ast)
|
let obj = stringArrayV3(ast)
|
||||||
if (!obj.stringArrayName) {
|
if (!obj.stringArrayName) {
|
||||||
obj = stringArrayV2(ast)
|
obj = stringArrayV2(ast)
|
||||||
if (!obj.stringArrayName) {
|
}
|
||||||
console.error('Cannot find string list!')
|
if (!obj.stringArrayName) {
|
||||||
return false
|
obj = stringArrayV0(ast)
|
||||||
}
|
}
|
||||||
|
if (!obj.stringArrayName) {
|
||||||
|
console.error('Cannot find string list!')
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
console.log(`String List Name: ${obj.stringArrayName}`)
|
console.log(`String List Name: ${obj.stringArrayName}`)
|
||||||
let ob_func_str = obj.stringArrayCodes
|
let ob_func_str = obj.stringArrayCodes
|
||||||
let ob_dec_name = obj.stringArrayCalls
|
let ob_dec_call = obj.stringArrayCalls
|
||||||
try {
|
try {
|
||||||
virtualGlobalEval(ob_func_str.join(';'))
|
virtualGlobalEval(ob_func_str.join(';'))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -328,11 +446,11 @@ function decodeGlobal(ast) {
|
|||||||
let lost = e.message.split(' ')[0]
|
let lost = e.message.split(' ')[0]
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
Program(path) {
|
Program(path) {
|
||||||
ob_dec_name.push(lost)
|
|
||||||
let loc = path.scope.getBinding(lost).path
|
let loc = path.scope.getBinding(lost).path
|
||||||
let obj = t.variableDeclaration(loc.parent.kind, [loc.node])
|
let obj = t.variableDeclaration(loc.parent.kind, [loc.node])
|
||||||
ob_func_str.unshift(generator(obj, { minified: true }).code)
|
ob_func_str.unshift(generator(obj, optGenMin).code)
|
||||||
loc.remove()
|
loc.node.init = null
|
||||||
|
ob_dec_call.push({ name: lost, path: loc })
|
||||||
path.stop()
|
path.stop()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -340,125 +458,111 @@ function decodeGlobal(ast) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 循环删除混淆函数
|
// 递归删除混淆函数
|
||||||
let call_dict = {}
|
function getChild(father) {
|
||||||
let exist_names = ob_dec_name
|
if (father.key !== 'argument' || !father.parentPath.isReturnStatement()) {
|
||||||
let collect_codes = []
|
console.error(`Unexpected chained call: ${father}`)
|
||||||
let collect_names = []
|
return null
|
||||||
function do_parse_value(path) {
|
}
|
||||||
let name = path.node.callee.name
|
const func = father.getFunctionParent()
|
||||||
if (path.node.callee && exist_names.indexOf(name) != -1) {
|
let name = func.node.id?.name
|
||||||
let old_call = path + ''
|
let root
|
||||||
try {
|
let code
|
||||||
// 运行成功则说明函数为直接调用并返回字符串
|
if (name) {
|
||||||
let new_str = virtualGlobalEval(old_call)
|
// FunctionDeclaration
|
||||||
console.log(`map: ${old_call} -> ${new_str}`)
|
// function A (...) { return function B (...) }
|
||||||
call_dict[old_call] = new_str
|
root = func
|
||||||
} catch (e) {
|
code = generator(root.node, optGenMin).code
|
||||||
// 运行失败则说明函数为其它混淆函数的子函数
|
} else {
|
||||||
console.log(`sub: ${old_call}`)
|
// FunctionExpression
|
||||||
|
// var A = function (...) { return function B (...) }
|
||||||
|
root = func.parentPath
|
||||||
|
code = generator(t.variableDeclaration('var', [root])).code
|
||||||
|
name = root.node.id.name
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
path: root,
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function dfs(stk, item) {
|
||||||
|
stk.push(item)
|
||||||
|
const cur_val = item.name
|
||||||
|
console.log(`Enter sub ${stk.length}:${cur_val}`)
|
||||||
|
let pfx = ''
|
||||||
|
for (let parent of stk) {
|
||||||
|
pfx += parent.code + ';'
|
||||||
|
}
|
||||||
|
virtualGlobalEval(pfx)
|
||||||
|
let scope = item.path.scope
|
||||||
|
if (item.path.isFunctionDeclaration()) {
|
||||||
|
scope = item.path.parentPath.scope
|
||||||
|
}
|
||||||
|
const binding = scope.getBinding(cur_val)
|
||||||
|
binding.scope.crawl()
|
||||||
|
const refs = binding.scope.bindings[cur_val].referencePaths
|
||||||
|
const refs_next = []
|
||||||
|
// 有4种链式调用情况:
|
||||||
|
// - VariableDeclarator和FunctionDeclaration为原版
|
||||||
|
// - AssignmentExpression 出现于 #50
|
||||||
|
// - FunctionExpression 出现于 #94
|
||||||
|
for (let ref of refs) {
|
||||||
|
const parent = ref.parentPath
|
||||||
|
if (ref.key === 'callee') {
|
||||||
|
// CallExpression
|
||||||
|
let old_call = parent + ''
|
||||||
|
try {
|
||||||
|
// 运行成功则说明函数为直接调用并返回字符串
|
||||||
|
let new_str = virtualGlobalEval(old_call)
|
||||||
|
console.log(`map: ${old_call} -> ${new_str}`)
|
||||||
|
parent.replaceWith(t.StringLiteral(new_str))
|
||||||
|
} catch (e) {
|
||||||
|
// 运行失败则说明函数为其它混淆函数的子函数
|
||||||
|
console.log(`sub: ${old_call}`)
|
||||||
|
const ret = getChild(parent)
|
||||||
|
if (ret) {
|
||||||
|
refs_next.push(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (ref.key === 'init') {
|
||||||
|
// VariableDeclarator
|
||||||
|
refs_next.push({
|
||||||
|
name: ref.parent.id.name,
|
||||||
|
path: ref.parentPath,
|
||||||
|
code: 'var ' + ref.parentPath,
|
||||||
|
})
|
||||||
|
} else if (ref.key === 'right') {
|
||||||
|
// AssignmentExpression
|
||||||
|
// 这种情况尚不完善 可能会产生额外替换
|
||||||
|
refs_next.push({
|
||||||
|
name: ref.parent.left.name,
|
||||||
|
path: ref.parentPath,
|
||||||
|
code: 'var ' + ref.parentPath,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
for (let ref of refs_next) {
|
||||||
function do_collect_remove(path) {
|
dfs(stk, ref)
|
||||||
// 可以删除所有已收集混淆函数的定义
|
|
||||||
// 因为根函数已被删除 即使保留也无法运行
|
|
||||||
let node = path.node?.left
|
|
||||||
if (!node) {
|
|
||||||
node = path.node?.id
|
|
||||||
}
|
}
|
||||||
let name = node?.name
|
binding.scope.crawl()
|
||||||
if (exist_names.indexOf(name) != -1) {
|
console.log(`Exit sub ${stk.length}:${cur_val}`)
|
||||||
// console.log(`del: ${name}`)
|
stk.pop()
|
||||||
if (path.parentPath.isCallExpression()) {
|
if (!item.path.parentPath.isCallExpression()) {
|
||||||
path.replaceWith(node)
|
item.path.remove()
|
||||||
} else {
|
binding.scope.crawl()
|
||||||
path.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function do_collect_func_dec(path) {
|
|
||||||
// function A (...) { return function B (...) }
|
|
||||||
do_collect_func(path, path)
|
|
||||||
}
|
|
||||||
function do_collect_func_var(path) {
|
|
||||||
// var A = function (...) { return function B (...) }
|
|
||||||
let func_path = path.get('init')
|
|
||||||
if (!func_path.isFunctionExpression()) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
do_collect_func(path, func_path)
|
// 只会出现在AssignmentExpression情况下 需要再次运行
|
||||||
|
item.path.replaceWith(t.identifier(cur_val))
|
||||||
|
item.path = binding.path
|
||||||
|
binding.scope.crawl()
|
||||||
|
dfs(stk, item)
|
||||||
}
|
}
|
||||||
function do_collect_func(root, path) {
|
for (let item of ob_dec_call) {
|
||||||
if (
|
item.code = ''
|
||||||
path.node.body.body.length == 1 &&
|
dfs([], item)
|
||||||
path.node.body.body[0].type == 'ReturnStatement' &&
|
|
||||||
path.node.body.body[0].argument?.type == 'CallExpression' &&
|
|
||||||
path.node.body.body[0].argument.callee.type == 'Identifier' &&
|
|
||||||
// path.node.params.length == 5 &&
|
|
||||||
root.node.id
|
|
||||||
) {
|
|
||||||
let call_func = path.node.body.body[0].argument.callee.name
|
|
||||||
if (exist_names.indexOf(call_func) == -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let name = root.node.id.name
|
|
||||||
let t = generator(root.node, { minified: true }).code
|
|
||||||
if (collect_names.indexOf(name) == -1) {
|
|
||||||
collect_codes.push(t)
|
|
||||||
collect_names.push(name)
|
|
||||||
} else {
|
|
||||||
console.log(`err: redef ${name}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function do_collect_var(path) {
|
|
||||||
// var A = B
|
|
||||||
let left, right
|
|
||||||
if (t.isVariableDeclarator(path.node)) {
|
|
||||||
left = path.node.id
|
|
||||||
right = path.node.init
|
|
||||||
} else {
|
|
||||||
left = path.node.left
|
|
||||||
right = path.node.right
|
|
||||||
}
|
|
||||||
if (right?.type == 'Identifier' && exist_names.indexOf(right.name) != -1) {
|
|
||||||
let name = left.name
|
|
||||||
let t = 'var ' + generator(path.node, { minified: true }).code
|
|
||||||
if (collect_names.indexOf(name) == -1) {
|
|
||||||
collect_codes.push(t)
|
|
||||||
collect_names.push(name)
|
|
||||||
} else {
|
|
||||||
console.warning(`redef ${name}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (exist_names.length) {
|
|
||||||
// 查找已收集混淆函数的调用并建立替换关系
|
|
||||||
traverse(ast, { CallExpression: do_parse_value })
|
|
||||||
// 删除被使用过的定义
|
|
||||||
traverse(ast, { FunctionDeclaration: do_collect_remove })
|
|
||||||
traverse(ast, { VariableDeclarator: do_collect_remove })
|
|
||||||
traverse(ast, { AssignmentExpression: do_collect_remove })
|
|
||||||
// 收集所有调用已收集混淆函数的混淆函数
|
|
||||||
collect_codes = []
|
|
||||||
collect_names = []
|
|
||||||
traverse(ast, { FunctionDeclaration: do_collect_func_dec })
|
|
||||||
traverse(ast, { VariableDeclarator: do_collect_func_var })
|
|
||||||
traverse(ast, { VariableDeclarator: do_collect_var })
|
|
||||||
traverse(ast, { AssignmentExpression: do_collect_var })
|
|
||||||
exist_names = collect_names
|
|
||||||
// 执行找到的函数
|
|
||||||
virtualGlobalEval(collect_codes.join(';'))
|
|
||||||
}
|
|
||||||
// 替换混淆函数
|
|
||||||
function do_replace(path) {
|
|
||||||
let old_call = path + ''
|
|
||||||
if (Object.prototype.hasOwnProperty.call(call_dict, old_call)) {
|
|
||||||
path.replaceWith(t.StringLiteral(call_dict[old_call]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
traverse(ast, { CallExpression: do_replace })
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,35 +605,9 @@ function stringArrayLite(ast) {
|
|||||||
traverse(ast, visitor)
|
traverse(ast, visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcBinary(path) {
|
|
||||||
let tps = ['StringLiteral', 'BooleanLiteral', 'NumericLiteral']
|
|
||||||
let nod = path.node
|
|
||||||
function judge(e) {
|
|
||||||
return (
|
|
||||||
tps.indexOf(e.type) != -1 ||
|
|
||||||
(e.type == 'UnaryExpression' && tps.indexOf(e.argument.type) != -1)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
function make_rep(e) {
|
|
||||||
if (typeof e == 'number') {
|
|
||||||
return t.NumericLiteral(e)
|
|
||||||
}
|
|
||||||
if (typeof e == 'string') {
|
|
||||||
return t.StringLiteral(e)
|
|
||||||
}
|
|
||||||
if (typeof e == 'boolean') {
|
|
||||||
return t.BooleanLiteral(e)
|
|
||||||
}
|
|
||||||
throw Error('unknown type' + typeof e)
|
|
||||||
}
|
|
||||||
if (judge(nod.left) && judge(nod.right)) {
|
|
||||||
path.replaceWith(make_rep(eval(path + '')))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeCodeBlock(ast) {
|
function decodeCodeBlock(ast) {
|
||||||
// 合并字面量
|
// 合并字面量
|
||||||
traverse(ast, { BinaryExpression: { exit: calcBinary } })
|
traverse(ast, calculateConstantExp)
|
||||||
// 先合并分离的Object定义
|
// 先合并分离的Object定义
|
||||||
const mergeObject = require('../visitor/merge-object')
|
const mergeObject = require('../visitor/merge-object')
|
||||||
traverse(ast, mergeObject)
|
traverse(ast, mergeObject)
|
||||||
@@ -537,69 +615,10 @@ function decodeCodeBlock(ast) {
|
|||||||
const parseControlFlowStorage = require('../visitor/parse-control-flow-storage')
|
const parseControlFlowStorage = require('../visitor/parse-control-flow-storage')
|
||||||
traverse(ast, parseControlFlowStorage)
|
traverse(ast, parseControlFlowStorage)
|
||||||
// 合并字面量(在解除区域混淆后会出现新的可合并分割)
|
// 合并字面量(在解除区域混淆后会出现新的可合并分割)
|
||||||
traverse(ast, { BinaryExpression: { exit: calcBinary } })
|
traverse(ast, calculateConstantExp)
|
||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
function purifyBoolean(path) {
|
|
||||||
// 简化 ![] 和 !![]
|
|
||||||
const node0 = path.node
|
|
||||||
if (node0.operator !== '!') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const node1 = node0.argument
|
|
||||||
if (t.isArrayExpression(node1) && node1.elements.length === 0) {
|
|
||||||
path.replaceWith(t.booleanLiteral(false))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!t.isUnaryExpression(node1) || node1.operator !== '!') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const node2 = node1.argument
|
|
||||||
if (t.isArrayExpression(node2) && node2.elements.length === 0) {
|
|
||||||
path.replaceWith(t.booleanLiteral(true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanIFCode(path) {
|
|
||||||
function clear(path, toggle) {
|
|
||||||
// 判定成立
|
|
||||||
if (toggle) {
|
|
||||||
if (path.node.consequent.type == 'BlockStatement') {
|
|
||||||
path.replaceWithMultiple(path.node.consequent.body)
|
|
||||||
} else {
|
|
||||||
path.replaceWith(path.node.consequent)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 判定不成立
|
|
||||||
if (!path.node.alternate) {
|
|
||||||
path.remove()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (path.node.alternate.type == 'BlockStatement') {
|
|
||||||
path.replaceWithMultiple(path.node.alternate.body)
|
|
||||||
} else {
|
|
||||||
path.replaceWith(path.node.alternate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 判断判定是否恒定
|
|
||||||
const test = path.node.test
|
|
||||||
const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral']
|
|
||||||
if (test.type === 'BinaryExpression') {
|
|
||||||
if (
|
|
||||||
types.indexOf(test.left.type) !== -1 &&
|
|
||||||
types.indexOf(test.right.type) !== -1
|
|
||||||
) {
|
|
||||||
const left = JSON.stringify(test.left.value)
|
|
||||||
const right = JSON.stringify(test.right.value)
|
|
||||||
clear(path, eval(left + test.operator + right))
|
|
||||||
}
|
|
||||||
} else if (types.indexOf(test.type) !== -1) {
|
|
||||||
clear(path, eval(JSON.stringify(test.value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanSwitchCode(path) {
|
function cleanSwitchCode(path) {
|
||||||
// 扁平控制:
|
// 扁平控制:
|
||||||
// 会使用一个恒为true的while语句包裹一个switch语句
|
// 会使用一个恒为true的while语句包裹一个switch语句
|
||||||
@@ -699,9 +718,9 @@ function cleanSwitchCode(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanDeadCode(ast) {
|
function cleanDeadCode(ast) {
|
||||||
traverse(ast, { UnaryExpression: purifyBoolean })
|
traverse(ast, calculateConstantExp)
|
||||||
traverse(ast, { IfStatement: cleanIFCode })
|
const pruneIfBranch = require('../visitor/prune-if-branch')
|
||||||
traverse(ast, { ConditionalExpression: cleanIFCode })
|
traverse(ast, pruneIfBranch)
|
||||||
traverse(ast, { WhileStatement: { exit: cleanSwitchCode } })
|
traverse(ast, { WhileStatement: { exit: cleanSwitchCode } })
|
||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
@@ -763,29 +782,6 @@ function standardLoop(path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitSequence(path) {
|
|
||||||
let { scope, parentPath, node } = path
|
|
||||||
let expressions = node.expressions
|
|
||||||
if (parentPath.isReturnStatement({ argument: node })) {
|
|
||||||
let lastExpression = expressions.pop()
|
|
||||||
for (let expression of expressions) {
|
|
||||||
parentPath.insertBefore(t.ExpressionStatement(expression))
|
|
||||||
}
|
|
||||||
|
|
||||||
path.replaceInline(lastExpression)
|
|
||||||
} else if (parentPath.isExpressionStatement({ expression: node })) {
|
|
||||||
let body = []
|
|
||||||
expressions.forEach((express) => {
|
|
||||||
body.push(t.ExpressionStatement(express))
|
|
||||||
})
|
|
||||||
path.replaceInline(body)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.crawl()
|
|
||||||
}
|
|
||||||
|
|
||||||
function purifyCode(ast) {
|
function purifyCode(ast) {
|
||||||
// 标准化if语句
|
// 标准化if语句
|
||||||
traverse(ast, { IfStatement: standardIfStatement })
|
traverse(ast, { IfStatement: standardIfStatement })
|
||||||
@@ -848,7 +844,8 @@ function purifyCode(ast) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 拆分语句
|
// 拆分语句
|
||||||
traverse(ast, { SequenceExpression: splitSequence })
|
const splitSequence = require('../visitor/split-sequence')
|
||||||
|
traverse(ast, splitSequence)
|
||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1061,6 +1058,12 @@ module.exports = function (code) {
|
|||||||
// IllegalReturn
|
// IllegalReturn
|
||||||
const deleteIllegalReturn = require('../visitor/delete-illegal-return')
|
const deleteIllegalReturn = require('../visitor/delete-illegal-return')
|
||||||
traverse(ast, deleteIllegalReturn)
|
traverse(ast, deleteIllegalReturn)
|
||||||
|
// Lint before split statements
|
||||||
|
const lintIfStatement = require('../visitor/lint-if-statement')
|
||||||
|
traverse(ast, lintIfStatement)
|
||||||
|
// Split declarations to avoid bugs
|
||||||
|
const splitVarDeclaration = require('../visitor/split-variable-declaration')
|
||||||
|
traverse(ast, splitVarDeclaration)
|
||||||
// 清理二进制显示内容
|
// 清理二进制显示内容
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
StringLiteral: ({ node }) => {
|
StringLiteral: ({ node }) => {
|
||||||
@@ -1086,22 +1089,13 @@ module.exports = function (code) {
|
|||||||
console.log('清理死代码...')
|
console.log('清理死代码...')
|
||||||
ast = cleanDeadCode(ast)
|
ast = cleanDeadCode(ast)
|
||||||
// 刷新代码
|
// 刷新代码
|
||||||
ast = parse(
|
ast = parse(generator(ast, optGenMin).code, { errorRecovery: true })
|
||||||
generator(ast, {
|
|
||||||
comments: false,
|
|
||||||
jsescOption: { minimal: true },
|
|
||||||
}).code,
|
|
||||||
{ errorRecovery: true }
|
|
||||||
)
|
|
||||||
console.log('提高代码可读性...')
|
console.log('提高代码可读性...')
|
||||||
ast = purifyCode(ast)
|
ast = purifyCode(ast)
|
||||||
console.log('解除环境限制...')
|
console.log('解除环境限制...')
|
||||||
ast = unlockEnv(ast)
|
ast = unlockEnv(ast)
|
||||||
console.log('净化完成')
|
console.log('净化完成')
|
||||||
code = generator(ast, {
|
code = generator(ast, { jsescOption: { minimal: true } }).code
|
||||||
comments: false,
|
|
||||||
jsescOption: { minimal: true },
|
|
||||||
}).code
|
|
||||||
if (global_eval) {
|
if (global_eval) {
|
||||||
code = PluginEval.pack(code)
|
code = PluginEval.pack(code)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ const traverse = require('@babel/traverse').default
|
|||||||
const t = require('@babel/types')
|
const t = require('@babel/types')
|
||||||
const ivm = require('isolated-vm')
|
const ivm = require('isolated-vm')
|
||||||
const PluginEval = require('./eval.js')
|
const PluginEval = require('./eval.js')
|
||||||
|
const calculateConstantExp = require('../visitor/calculate-constant-exp')
|
||||||
|
|
||||||
const isolate = new ivm.Isolate()
|
const isolate = new ivm.Isolate()
|
||||||
const globalContext = isolate.createContextSync()
|
const globalContext = isolate.createContextSync()
|
||||||
@@ -85,65 +86,6 @@ function decodeGlobal(ast) {
|
|||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
function purifyBoolean(path) {
|
|
||||||
// 简化 ![] 和 !![]
|
|
||||||
const node0 = path.node
|
|
||||||
if (node0.operator !== '!') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const node1 = node0.argument
|
|
||||||
if (t.isArrayExpression(node1) && node1.elements.length === 0) {
|
|
||||||
path.replaceWith(t.booleanLiteral(false))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!t.isUnaryExpression(node1) || node1.operator !== '!') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const node2 = node1.argument
|
|
||||||
if (t.isArrayExpression(node2) && node2.elements.length === 0) {
|
|
||||||
path.replaceWith(t.booleanLiteral(true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanIFCode(path) {
|
|
||||||
function clear(path, toggle) {
|
|
||||||
// 判定成立
|
|
||||||
if (toggle) {
|
|
||||||
if (path.node.consequent.type == 'BlockStatement') {
|
|
||||||
path.replaceWithMultiple(path.node.consequent.body)
|
|
||||||
} else {
|
|
||||||
path.replaceWith(path.node.consequent)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 判定不成立
|
|
||||||
if (!path.node.alternate) {
|
|
||||||
path.remove()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (path.node.alternate.type == 'BlockStatement') {
|
|
||||||
path.replaceWithMultiple(path.node.alternate.body)
|
|
||||||
} else {
|
|
||||||
path.replaceWith(path.node.alternate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 判断判定是否恒定
|
|
||||||
const test = path.node.test
|
|
||||||
const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral']
|
|
||||||
if (test.type === 'BinaryExpression') {
|
|
||||||
if (
|
|
||||||
types.indexOf(test.left.type) !== -1 &&
|
|
||||||
types.indexOf(test.right.type) !== -1
|
|
||||||
) {
|
|
||||||
const left = JSON.stringify(test.left.value)
|
|
||||||
const right = JSON.stringify(test.right.value)
|
|
||||||
clear(path, eval(left + test.operator + right))
|
|
||||||
}
|
|
||||||
} else if (types.indexOf(test.type) !== -1) {
|
|
||||||
clear(path, eval(JSON.stringify(test.value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanSwitchCode(path) {
|
function cleanSwitchCode(path) {
|
||||||
// 扁平控制:
|
// 扁平控制:
|
||||||
// 会使用一个恒为true的while语句包裹一个switch语句
|
// 会使用一个恒为true的while语句包裹一个switch语句
|
||||||
@@ -223,9 +165,9 @@ function cleanSwitchCode(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanDeadCode(ast) {
|
function cleanDeadCode(ast) {
|
||||||
traverse(ast, { UnaryExpression: purifyBoolean })
|
traverse(ast, calculateConstantExp)
|
||||||
traverse(ast, { IfStatement: cleanIFCode })
|
const pruneIfBranch = require('../visitor/prune-if-branch')
|
||||||
traverse(ast, { ConditionalExpression: cleanIFCode })
|
traverse(ast, pruneIfBranch)
|
||||||
traverse(ast, { WhileStatement: { exit: cleanSwitchCode } })
|
traverse(ast, { WhileStatement: { exit: cleanSwitchCode } })
|
||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
@@ -502,54 +444,8 @@ function purifyFunction(path) {
|
|||||||
function purifyCode(ast) {
|
function purifyCode(ast) {
|
||||||
// 净化拼接字符串的函数
|
// 净化拼接字符串的函数
|
||||||
traverse(ast, { AssignmentExpression: purifyFunction })
|
traverse(ast, { AssignmentExpression: purifyFunction })
|
||||||
// 净化变量定义中的常量数值
|
// 计算常量表达式
|
||||||
function purifyDecl(path) {
|
traverse(ast, calculateConstantExp)
|
||||||
if (t.isNumericLiteral(path.node.init)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const name = path.node.id.name
|
|
||||||
const { code } = generator(
|
|
||||||
{
|
|
||||||
type: 'Program',
|
|
||||||
body: [path.node.init],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
compact: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const valid = /^[-+*/%!<>&|~^ 0-9;]+$/.test(code)
|
|
||||||
if (!valid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (/^[-][0-9]*$/.test(code)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const value = eval(code)
|
|
||||||
const node = t.valueToNode(value)
|
|
||||||
path.replaceWith(t.variableDeclarator(path.node.id, node))
|
|
||||||
console.log(`替换 ${name}: ${code} -> ${value}`)
|
|
||||||
}
|
|
||||||
traverse(ast, { VariableDeclarator: purifyDecl })
|
|
||||||
// 合并字符串
|
|
||||||
let end = false
|
|
||||||
function combineString(path) {
|
|
||||||
const op = path.node.operator
|
|
||||||
if (op !== '+') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const left = path.node.left
|
|
||||||
const right = path.node.right
|
|
||||||
if (!t.isStringLiteral(left) || !t.isStringLiteral(right)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
end = false
|
|
||||||
path.replaceWith(t.StringLiteral(eval(path + '')))
|
|
||||||
console.log(`合并字符串: ${path.node.value}`)
|
|
||||||
}
|
|
||||||
while (!end) {
|
|
||||||
end = true
|
|
||||||
traverse(ast, { BinaryExpression: combineString })
|
|
||||||
}
|
|
||||||
// 替换索引器
|
// 替换索引器
|
||||||
function FormatMember(path) {
|
function FormatMember(path) {
|
||||||
// _0x19882c['removeCookie']['toString']()
|
// _0x19882c['removeCookie']['toString']()
|
||||||
@@ -573,26 +469,8 @@ function purifyCode(ast) {
|
|||||||
}
|
}
|
||||||
traverse(ast, { MemberExpression: FormatMember })
|
traverse(ast, { MemberExpression: FormatMember })
|
||||||
// 分割表达式
|
// 分割表达式
|
||||||
function removeComma(path) {
|
const splitSequence = require('../visitor/split-sequence')
|
||||||
// a = 1, b = ddd(), c = null;
|
traverse(ast, splitSequence)
|
||||||
// |
|
|
||||||
// |
|
|
||||||
// |
|
|
||||||
// v
|
|
||||||
// a = 1;
|
|
||||||
// b = ddd();
|
|
||||||
// c = null;
|
|
||||||
let { expression } = path.node
|
|
||||||
if (!t.isSequenceExpression(expression)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let body = []
|
|
||||||
expression.expressions.forEach((express) => {
|
|
||||||
body.push(t.expressionStatement(express))
|
|
||||||
})
|
|
||||||
path.replaceInline(body)
|
|
||||||
}
|
|
||||||
traverse(ast, { ExpressionStatement: removeComma })
|
|
||||||
// 删除空语句
|
// 删除空语句
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
EmptyStatement: (path) => {
|
EmptyStatement: (path) => {
|
||||||
|
|||||||
@@ -7,12 +7,19 @@ const traverse = require('@babel/traverse').default
|
|||||||
const t = require('@babel/types')
|
const t = require('@babel/types')
|
||||||
const ivm = require('isolated-vm')
|
const ivm = require('isolated-vm')
|
||||||
const PluginEval = require('./eval.js')
|
const PluginEval = require('./eval.js')
|
||||||
|
const calculateConstantExp = require('../visitor/calculate-constant-exp')
|
||||||
|
|
||||||
const isolate = new ivm.Isolate()
|
const isolate = new ivm.Isolate()
|
||||||
const globalContext = isolate.createContextSync()
|
const globalContext = isolate.createContextSync()
|
||||||
function virtualGlobalEval(jsStr) {
|
function virtualGlobalEval(jsStr) {
|
||||||
return globalContext.evalSync(String(jsStr))
|
return globalContext.evalSync(String(jsStr))
|
||||||
}
|
}
|
||||||
|
function evalOneTime(str) {
|
||||||
|
const vm = new ivm.Isolate()
|
||||||
|
const ret = vm.createContextSync().evalSync(String(str))
|
||||||
|
vm.dispose()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
function decodeGlobal(ast) {
|
function decodeGlobal(ast) {
|
||||||
// 清理空语句
|
// 清理空语句
|
||||||
@@ -313,65 +320,6 @@ function decodeGlobal(ast) {
|
|||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
function purifyBoolean(path) {
|
|
||||||
// 简化 ![] 和 !![]
|
|
||||||
const node0 = path.node
|
|
||||||
if (node0.operator !== '!') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const node1 = node0.argument
|
|
||||||
if (t.isArrayExpression(node1) && node1.elements.length === 0) {
|
|
||||||
path.replaceWith(t.booleanLiteral(false))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!t.isUnaryExpression(node1) || node1.operator !== '!') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const node2 = node1.argument
|
|
||||||
if (t.isArrayExpression(node2) && node2.elements.length === 0) {
|
|
||||||
path.replaceWith(t.booleanLiteral(true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanIFCode(path) {
|
|
||||||
function clear(path, toggle) {
|
|
||||||
// 判定成立
|
|
||||||
if (toggle) {
|
|
||||||
if (path.node.consequent.type == 'BlockStatement') {
|
|
||||||
path.replaceWithMultiple(path.node.consequent.body)
|
|
||||||
} else {
|
|
||||||
path.replaceWith(path.node.consequent)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 判定不成立
|
|
||||||
if (!path.node.alternate) {
|
|
||||||
path.remove()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (path.node.alternate.type == 'BlockStatement') {
|
|
||||||
path.replaceWithMultiple(path.node.alternate.body)
|
|
||||||
} else {
|
|
||||||
path.replaceWith(path.node.alternate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 判断判定是否恒定
|
|
||||||
const test = path.node.test
|
|
||||||
const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral']
|
|
||||||
if (test.type === 'BinaryExpression') {
|
|
||||||
if (
|
|
||||||
types.indexOf(test.left.type) !== -1 &&
|
|
||||||
types.indexOf(test.right.type) !== -1
|
|
||||||
) {
|
|
||||||
const left = JSON.stringify(test.left.value)
|
|
||||||
const right = JSON.stringify(test.right.value)
|
|
||||||
clear(path, eval(left + test.operator + right))
|
|
||||||
}
|
|
||||||
} else if (types.indexOf(test.type) !== -1) {
|
|
||||||
clear(path, eval(JSON.stringify(test.value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanSwitchCode1(path) {
|
function cleanSwitchCode1(path) {
|
||||||
// 扁平控制:
|
// 扁平控制:
|
||||||
// 会使用一个恒为true的while语句包裹一个switch语句
|
// 会使用一个恒为true的while语句包裹一个switch语句
|
||||||
@@ -482,12 +430,13 @@ function cleanSwitchCode2(path) {
|
|||||||
}
|
}
|
||||||
let test = '' + pre_path
|
let test = '' + pre_path
|
||||||
try {
|
try {
|
||||||
arr = eval(test + `;${arrName}`)
|
arr = evalOneTime(test + `;${arrName}.join('|')`)
|
||||||
|
arr = arr.split('|')
|
||||||
} catch {
|
} catch {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!arr) {
|
if (!Array.isArray(arr)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log(`扁平化还原: ${arrName}[${argName}]`)
|
console.log(`扁平化还原: ${arrName}[${argName}]`)
|
||||||
@@ -527,9 +476,9 @@ function cleanSwitchCode2(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanDeadCode(ast) {
|
function cleanDeadCode(ast) {
|
||||||
traverse(ast, { UnaryExpression: purifyBoolean })
|
traverse(ast, calculateConstantExp)
|
||||||
traverse(ast, { IfStatement: cleanIFCode })
|
const pruneIfBranch = require('../visitor/prune-if-branch')
|
||||||
traverse(ast, { ConditionalExpression: cleanIFCode })
|
traverse(ast, pruneIfBranch)
|
||||||
traverse(ast, { WhileStatement: { exit: cleanSwitchCode1 } })
|
traverse(ast, { WhileStatement: { exit: cleanSwitchCode1 } })
|
||||||
traverse(ast, { ForStatement: { exit: cleanSwitchCode2 } })
|
traverse(ast, { ForStatement: { exit: cleanSwitchCode2 } })
|
||||||
return ast
|
return ast
|
||||||
@@ -748,54 +697,8 @@ function purifyFunction(path) {
|
|||||||
function purifyCode(ast) {
|
function purifyCode(ast) {
|
||||||
// 净化拼接字符串的函数
|
// 净化拼接字符串的函数
|
||||||
traverse(ast, { AssignmentExpression: purifyFunction })
|
traverse(ast, { AssignmentExpression: purifyFunction })
|
||||||
// 净化变量定义中的常量数值
|
// 计算常量表达式
|
||||||
function purifyDecl(path) {
|
traverse(ast, calculateConstantExp)
|
||||||
if (t.isNumericLiteral(path.node.init)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const name = path.node.id.name
|
|
||||||
const { code } = generator(
|
|
||||||
{
|
|
||||||
type: 'Program',
|
|
||||||
body: [path.node.init],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
compact: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const valid = /^[-+*/%!<>&|~^ 0-9;]+$/.test(code)
|
|
||||||
if (!valid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (/^[-][0-9]*$/.test(code)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const value = eval(code)
|
|
||||||
const node = t.valueToNode(value)
|
|
||||||
path.replaceWith(t.variableDeclarator(path.node.id, node))
|
|
||||||
console.log(`替换 ${name}: ${code} -> ${value}`)
|
|
||||||
}
|
|
||||||
traverse(ast, { VariableDeclarator: purifyDecl })
|
|
||||||
// 合并字符串
|
|
||||||
let end = false
|
|
||||||
function combineString(path) {
|
|
||||||
const op = path.node.operator
|
|
||||||
if (op !== '+') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const left = path.node.left
|
|
||||||
const right = path.node.right
|
|
||||||
if (!t.isStringLiteral(left) || !t.isStringLiteral(right)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
end = false
|
|
||||||
path.replaceWith(t.StringLiteral(eval(path + '')))
|
|
||||||
console.log(`合并字符串: ${path.node.value}`)
|
|
||||||
}
|
|
||||||
while (!end) {
|
|
||||||
end = true
|
|
||||||
traverse(ast, { BinaryExpression: combineString })
|
|
||||||
}
|
|
||||||
// 替换索引器
|
// 替换索引器
|
||||||
function FormatMember(path) {
|
function FormatMember(path) {
|
||||||
// _0x19882c['removeCookie']['toString']()
|
// _0x19882c['removeCookie']['toString']()
|
||||||
@@ -819,28 +722,8 @@ function purifyCode(ast) {
|
|||||||
}
|
}
|
||||||
traverse(ast, { MemberExpression: FormatMember })
|
traverse(ast, { MemberExpression: FormatMember })
|
||||||
// 分割表达式
|
// 分割表达式
|
||||||
function removeComma(path) {
|
const splitSequence = require('../visitor/split-sequence')
|
||||||
// a = 1, b = ddd(), c = null;
|
traverse(ast, splitSequence)
|
||||||
// |
|
|
||||||
// |
|
|
||||||
// |
|
|
||||||
// v
|
|
||||||
// a = 1;
|
|
||||||
// b = ddd();
|
|
||||||
// c = null;
|
|
||||||
if (!t.isExpressionStatement(path.parent)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let replace_path = path.parentPath
|
|
||||||
if (replace_path.listKey !== 'body') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for (const item of path.node.expressions) {
|
|
||||||
replace_path.insertBefore(t.expressionStatement(item))
|
|
||||||
}
|
|
||||||
replace_path.remove()
|
|
||||||
}
|
|
||||||
traverse(ast, { SequenceExpression: { exit: removeComma } })
|
|
||||||
// 删除空语句
|
// 删除空语句
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
EmptyStatement: (path) => {
|
EmptyStatement: (path) => {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
const generator = require('@babel/generator').default
|
|
||||||
const t = require('@babel/types')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate BinaryExpression if left and right are both literals.
|
|
||||||
* Otherwise, the expression can't be simplified.
|
|
||||||
*
|
|
||||||
* For example, `typeof window` can be calculated but it's not constant.
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
BinaryExpression(path) {
|
|
||||||
const { left, right } = path.node
|
|
||||||
if (!t.isLiteral(left) || !t.isLiteral(right)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const code = generator(path.node).code
|
|
||||||
try {
|
|
||||||
const ret = eval(code)
|
|
||||||
path.replaceWithSourceString(ret)
|
|
||||||
} catch {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
107
src/visitor/calculate-constant-exp.js
Normal file
107
src/visitor/calculate-constant-exp.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
function checkLiteral(node) {
|
||||||
|
if (t.isNumericLiteral(node)) {
|
||||||
|
return 'positive'
|
||||||
|
}
|
||||||
|
if (t.isLiteral(node)) {
|
||||||
|
return 'literal'
|
||||||
|
}
|
||||||
|
if (!t.isUnaryExpression(node)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!t.isNumericLiteral(node.argument)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (node.operator === '-') {
|
||||||
|
return 'negative'
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate BinaryExpression if left and right are both literals.
|
||||||
|
* Otherwise, the expression can't be simplified.
|
||||||
|
* Note that negative numbers are UnaryExpressions.
|
||||||
|
*/
|
||||||
|
function calculateBinaryExpression(path) {
|
||||||
|
const { left, right } = path.node
|
||||||
|
if (!checkLiteral(left) || !checkLiteral(right)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const code = generator(path.node).code
|
||||||
|
try {
|
||||||
|
const ret = eval(code)
|
||||||
|
// The strings cannot use replaceWithSourceString
|
||||||
|
// For example, string "ab" will be parsed as identifier ab
|
||||||
|
if (typeof ret === 'string') {
|
||||||
|
path.replaceWith(t.stringLiteral(ret))
|
||||||
|
} else {
|
||||||
|
path.replaceWithSourceString(ret)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate UnaryExpression:
|
||||||
|
* - the operator is `!` and the argument is ArrayExpression or Literal.
|
||||||
|
* - the operator is `-` and the argument is a negative number
|
||||||
|
* - the operator is `+`, or `~`, and the argument is a number
|
||||||
|
* - the operator is 'void' and the argument is Literal.
|
||||||
|
* - the operator is 'typeof' and the argument is Literal.
|
||||||
|
*
|
||||||
|
* Otherwise, the expression can't be simplified.
|
||||||
|
* For example, `typeof window` can be calculated but it's not constant.
|
||||||
|
*/
|
||||||
|
function calculateUnaryExpression(path) {
|
||||||
|
const node0 = path.node
|
||||||
|
const node1 = node0.argument
|
||||||
|
const isLiteral = checkLiteral(node1)
|
||||||
|
if (node0.operator === '!') {
|
||||||
|
if (t.isArrayExpression(node1)) {
|
||||||
|
if (node1.elements.length === 0) {
|
||||||
|
path.replaceWith(t.booleanLiteral(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isLiteral) {
|
||||||
|
const code = generator(node0).code
|
||||||
|
path.replaceWith(t.booleanLiteral(eval(code)))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node0.operator === '-') {
|
||||||
|
if (isLiteral === 'negative') {
|
||||||
|
const code = generator(node0).code
|
||||||
|
path.replaceWithSourceString(eval(code))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node0.operator === '+' || node0.operator === '~') {
|
||||||
|
if (isLiteral === 'negative' || isLiteral === 'positive') {
|
||||||
|
const code = generator(node0).code
|
||||||
|
path.replaceWithSourceString(eval(code))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node0.operator === 'void') {
|
||||||
|
if (isLiteral) {
|
||||||
|
path.replaceWith(t.identifier('undefined'))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node0.operator === 'typeof') {
|
||||||
|
if (isLiteral) {
|
||||||
|
const code = generator(node0).code
|
||||||
|
path.replaceWith(t.stringLiteral(eval(code)))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
BinaryExpression: { exit: calculateBinaryExpression },
|
||||||
|
UnaryExpression: { exit: calculateUnaryExpression },
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ module.exports = {
|
|||||||
if (!binding || binding.referenced || !binding.constant) {
|
if (!binding || binding.referenced || !binding.constant) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!t.isLiteral(node.init)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const up1 = path.parentPath
|
const up1 = path.parentPath
|
||||||
const up2 = up1?.parentPath
|
const up2 = up1?.parentPath
|
||||||
if (t.isForOfStatement(up2)) {
|
if (t.isForOfStatement(up2)) {
|
||||||
|
|||||||
22
src/visitor/lint-if-statement.js
Normal file
22
src/visitor/lint-if-statement.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
function LintIfStatement(path) {
|
||||||
|
let { test, consequent, alternate } = path.node
|
||||||
|
let changed = false
|
||||||
|
if (!t.isBlockStatement(consequent)) {
|
||||||
|
consequent = t.blockStatement([consequent])
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (alternate && !t.isBlockStatement(alternate)) {
|
||||||
|
alternate = t.blockStatement([alternate])
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (!changed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path.replaceWith(t.ifStatement(test, consequent, alternate))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
IfStatement: { exit: LintIfStatement },
|
||||||
|
}
|
||||||
@@ -162,7 +162,7 @@ function mergeObject(path) {
|
|||||||
} else {
|
} else {
|
||||||
path.remove()
|
path.remove()
|
||||||
}
|
}
|
||||||
scope.crawl()
|
binding.scope.crawl()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
40
src/visitor/prune-if-branch.js
Normal file
40
src/visitor/prune-if-branch.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
function pruneIfBranch(path) {
|
||||||
|
function clear(path, toggle) {
|
||||||
|
// 判定成立
|
||||||
|
if (toggle) {
|
||||||
|
path.replaceWith(path.node.consequent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 判定不成立
|
||||||
|
if (!path.node.alternate) {
|
||||||
|
path.remove()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path.replaceWith(path.node.alternate)
|
||||||
|
}
|
||||||
|
// 判断判定是否恒定
|
||||||
|
const test = path.node.test
|
||||||
|
const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral']
|
||||||
|
if (test.type === 'BinaryExpression') {
|
||||||
|
if (
|
||||||
|
types.indexOf(test.left.type) !== -1 &&
|
||||||
|
types.indexOf(test.right.type) !== -1
|
||||||
|
) {
|
||||||
|
const left = JSON.stringify(test.left.value)
|
||||||
|
const right = JSON.stringify(test.right.value)
|
||||||
|
clear(path, eval(left + test.operator + right))
|
||||||
|
}
|
||||||
|
} else if (types.indexOf(test.type) !== -1) {
|
||||||
|
clear(path, eval(JSON.stringify(test.value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prune the branch if the test is constant
|
||||||
|
*
|
||||||
|
* The code must be reloaded to update the references
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
IfStatement: pruneIfBranch,
|
||||||
|
ConditionalExpression: pruneIfBranch,
|
||||||
|
}
|
||||||
38
src/visitor/split-member-object.js
Normal file
38
src/visitor/split-member-object.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
function splitMemberObject(path) {
|
||||||
|
const object = path.get('object')
|
||||||
|
if (!object.isAssignmentExpression()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let insertPath = path
|
||||||
|
while (!insertPath?.listKey) {
|
||||||
|
if (insertPath.parentPath.isAssignmentExpression()) {
|
||||||
|
insertPath = insertPath.parentPath
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (insertPath.parentPath.isExpressionStatement()) {
|
||||||
|
insertPath = insertPath.parentPath
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
insertPath.insertBefore(object.node)
|
||||||
|
object.replaceWith(object.node.left)
|
||||||
|
insertPath.scope.crawl()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split assignment operation in member object
|
||||||
|
*
|
||||||
|
* From:
|
||||||
|
* ```javascript
|
||||||
|
* (a = {})['b'] = c;
|
||||||
|
* ```
|
||||||
|
* To:
|
||||||
|
* ```javascript
|
||||||
|
* a = {}
|
||||||
|
* a['b'] = c;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
MemberExpression: splitMemberObject,
|
||||||
|
}
|
||||||
53
src/visitor/split-sequence.js
Normal file
53
src/visitor/split-sequence.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
function doSplit(insertPath, path) {
|
||||||
|
const expressions = path.node.expressions
|
||||||
|
const lastExpression = expressions.pop()
|
||||||
|
while (expressions.length) {
|
||||||
|
insertPath.insertBefore(t.expressionStatement(expressions.shift()))
|
||||||
|
}
|
||||||
|
path.replaceWith(lastExpression)
|
||||||
|
insertPath.scope.crawl()
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitSequence(path) {
|
||||||
|
let { parentPath } = path
|
||||||
|
if (parentPath.isVariableDeclarator()) {
|
||||||
|
// Skip if it's not the first VariableDeclarator
|
||||||
|
if (parentPath.key !== 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let insertPath = parentPath.parentPath
|
||||||
|
// Skip if the container of the VariableDeclaration is not an array
|
||||||
|
if (!insertPath.listKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doSplit(insertPath, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (parentPath.isReturnStatement()) {
|
||||||
|
if (!parentPath.listKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doSplit(parentPath, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (parentPath.isExpressionStatement()) {
|
||||||
|
if (!parentPath.listKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doSplit(parentPath, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sequenceExpressions inside certain statements are splitted if possible:
|
||||||
|
*
|
||||||
|
* - VariableDeclarator
|
||||||
|
* - ReturnStatement
|
||||||
|
* - ExpressionStatement
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
SequenceExpression: splitSequence,
|
||||||
|
}
|
||||||
31
src/visitor/split-variable-declaration.js
Normal file
31
src/visitor/split-variable-declaration.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
function splitVariableDeclaration(path) {
|
||||||
|
// The scope of a for statement is its body
|
||||||
|
if (path.parentPath.isFor()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// The container must be an array
|
||||||
|
if (!path.listKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const kind = path.node.kind
|
||||||
|
const list = path.node.declarations
|
||||||
|
if (list.length == 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let item of list) {
|
||||||
|
path.insertBefore(t.variableDeclaration(kind, [item]))
|
||||||
|
}
|
||||||
|
path.remove()
|
||||||
|
path.scope.crawl()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split the VariableDeclaration if it has more than one VariableDeclarator
|
||||||
|
*
|
||||||
|
* This operation will only be performed when its container is an array
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
VariableDeclaration: splitVariableDeclaration,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user