《JavaScript AST其实很简单》二、Step1-函数调用还原

2023-10-12 15:30

本文主要是介绍《JavaScript AST其实很简单》二、Step1-函数调用还原,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

本系列所有反混淆内容都是基于开源项目JavaScript Obfuscator Tool进行的。
打开网站后,使用如下配置对js源码进行混淆
在这里插入图片描述
下载混淆后的js文件,进行格式化后大概是这样的
在这里插入图片描述
备注:源文件见最下方附件内的ob.txt

可以看到第一个节点定义了一个大数组_0x101c,第二和第三是一个立即执行函数和一个函数,这两个顺序不一定。从上图中可以看到,被混淆后的js代码中,存在非常多的相同的函数调用,那么第一步就是要将这个函数调用的结果还原回去。

1.语法分析

要反混淆第一步就是对现有的代码做分析,找到其加密的规律,然后按照这个规律进行还原
首先将混淆后的代码复制到AST explorer,其中使用的解析器是esprima,我用用node的模块也是这个
在这里插入图片描述
可以看到很快就可以将源代码转换成抽象语法树,然后随便点击一个_0x166e函数调用的地方
在这里插入图片描述
可以看到,其中的类型为CallExpression,那么现在就可以找所有的CallExpression了,但是并不是所有的函数调用都是需要还原的,只有名称为_0x166e的才需要。

但是并不能将这个函数名写死,因为这个函数名是随机的,所以要先确定函数名。由前面可知,函数的定义一定会出现在第二或者第三个节点,那么只要查找第二和第三个节点,看看哪个是函数定义,就可以知道函数名了。只知道函数名还不够,还需要里面的参数。

在这里插入图片描述
在CallExpression的子节点在有一个arguments的节点,里面就有函数调用的参数了。此时就已经获取了函数名和所有调用的参数

2.函数调用计算

我们首先编写一个node的命令行文件,用于将js代码转换为json,保存为文件:js2jsonyuge.js

const fs = require('fs');
const esprima = require('esprima')
const escodegen = require('escodegen')var inputtext = process.argv[2];
var outputtext = process.argv[3];var data = fs.readFileSync(inputtext);
var ast = esprima.parseScript(data.toString());
var ast_to_json = JSON.stringify(ast);
fs.writeFileSync(outputtext, ast_to_json);

再编写一个相反的,将json转换为js代码,保存为文件:json2jsyuge.js

const fs = require('fs');
const esprima = require('esprima')
const escodegen = require('escodegen')var inputtext = process.argv[2];
var outputtext = process.argv[3];var data = fs.readFileSync(inputtext);
var ast = JSON.parse(data.toString());
var code = escodegen.generate(ast, {format: {compact: true,escapeless: true}
});
fs.writeFileSync(outputtext, code);

先读取转换的json,并将前3个节点输出,用于后面计算结果

    inputfile = 'ob'os.system('node js2jsonyuge '+inputfile+'.js '+inputfile+'.json')with open(inputfile+'.json', 'r', encoding='utf-8') as f:data = f.read()# 删除缓存os.remove(inputfile+'.json')data = json.loads(data)# 定义替换函数的jsontempstep1 = {'type': 'Program','body': data['body'][:3],'sourceType': 'script'}# 写出第一步替换的函数体with open(inputfile+'_step1.json', 'w', encoding='utf-8') as f:f.write(json.dumps(tempstep1, ensure_ascii=False, separators=(',', ':')))os.system('node json2jsyuge '+inputfile+'_step1.json '+inputfile+'_step1.js')

运行后会得到一个ob_step1.json和ob_step1.js,打开ob_step1.js并在第二行输入

console.log(_0x166e('0x305'));

保存后在命令行中运行

node ob_step1.js

如果可以显示【return (function()】,说明正常计算。
而在python中需要用到execjs模块

with open('ob_step1.js', 'r', encoding='utf-8') as f:ctx = execjs.compile(f.read())
resul = ctx.call('_0x166e', '0x305', '')
print(resul)

此时一样可以得到【return (function()】

3.递归还原

此时就可以递归获取所有名称为_0x166e的CallExpression节点,然后计算结果,基本的递归格式我是如下编写的。
填写核心逻辑后,就可以递归获取所有参数,并进行调用还原,还原的结果要怎么塞回去呢?继续进行分析,可以看到所有的返回值都是字符串,那么字符串的类型就是Literal,那么就可以自己构建一个Literal节点,然后将源节点替换掉即可

def diguiyangli(node, Functionname, ctx):if type(node) == list:if node:for i in range(len(node)):diguiyangli(node[i], Functionname, ctx)elif type(node) == dict:for key in node.keys():if node[key]:if not type(node[key]) in [str, bool, int]:for eachkey in node[key].keys():if type(node[key][eachkey]) == dict:if 'type' in node[key][eachkey].keys():if node[key][eachkey]['type'] == 'CallExpression':  # 获取类型为CallExpression的节点if 'name' in node[key][eachkey]['callee'].keys():if node[key][eachkey]['callee']['name'] == Functionname:  # 获取指定函数调用名的节点if len(node[key][eachkey]['arguments']) == 2:  # 获取函数调用的参数arg1, arg2 = node[key][eachkey]['arguments']arg1 = arg1['value']arg2 = arg2['value']else:arg1 = node[key][eachkey]['arguments'][0]['value']arg2 = ''value = ctx.call(Functionname, arg1, arg2)# 创建一个Literal节点returnobject = {'type': 'Literal', 'value': value}# 替换原来节点node[key][eachkey] = returnobjectdiguiyangli(node[key], Functionname, ctx)

经过一系列的调用还原后,输出js代码,并将其格式化如下图
在这里插入图片描述
可以看到,原来的

'VlwGE': _0x166e('0x305')

已经被替换为

'VlwGE': 'return (function() '

此时第一步已经完成,那么前三个节点已经没有用了,将前三个节点删除后,就是第一步反混淆的最终结果.
备注:源文件见最下方附件内的ob_step1.txt

附件地址:https://www.lanzoux.com/b0101ok4b

这篇关于《JavaScript AST其实很简单》二、Step1-函数调用还原的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/196759

相关文章

vite搭建vue3项目的搭建步骤

《vite搭建vue3项目的搭建步骤》本文主要介绍了vite搭建vue3项目的搭建步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1.确保Nodejs环境2.使用vite-cli工具3.进入项目安装依赖1.确保Nodejs环境

Nginx搭建前端本地预览环境的完整步骤教学

《Nginx搭建前端本地预览环境的完整步骤教学》这篇文章主要为大家详细介绍了Nginx搭建前端本地预览环境的完整步骤教学,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录项目目录结构核心配置文件:nginx.conf脚本化操作:nginx.shnpm 脚本集成总结:对前端的意义很多

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

vue监听属性watch的用法及使用场景详解

《vue监听属性watch的用法及使用场景详解》watch是vue中常用的监听器,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作,:本文主要介绍vue监听属性watch的用法及使用场景... 目录1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景附Watch 的

前端导出Excel文件出现乱码或文件损坏问题的解决办法

《前端导出Excel文件出现乱码或文件损坏问题的解决办法》在现代网页应用程序中,前端有时需要与后端进行数据交互,包括下载文件,:本文主要介绍前端导出Excel文件出现乱码或文件损坏问题的解决办法,... 目录1. 检查后端返回的数据格式2. 前端正确处理二进制数据方案 1:直接下载(推荐)方案 2:手动构造

Vue实现路由守卫的示例代码

《Vue实现路由守卫的示例代码》Vue路由守卫是控制页面导航的钩子函数,主要用于鉴权、数据预加载等场景,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、概念二、类型三、实战一、概念路由守卫(Navigation Guards)本质上就是 在路

uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)

《uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)》在uni-app开发中,文件上传和图片处理是很常见的需求,但也经常会遇到各种问题,下面:本文主要介绍uni-app小程序项目中实... 目录方式一:使用<canvas>实现图片压缩(推荐,兼容性好)示例代码(小程序平台):方式二:使用uni