用Babel解析AST处理OB混淆JS代码(一):搭环境(含IDEA配置eslint踩坑记录)

依赖

  • Windows10、IDEA、yarn
yarn add shelljs -D# 如果有现成的package.json,只运行这个就行yarn

package.json指定的依赖如下:

  "devDependencies": {    "@babel/preset-typescript": "^7.18.6",    "@types/babel__core": "^7.1.19",    "@types/babel__traverse": "^7.18.0",    "@types/node": "^18.7.13",    "@typescript-eslint/eslint-plugin": "^5.35.1",    "@typescript-eslint/parser": "^5.35.1",    "eslint": "8.22.0",    "shelljs": "^0.8.5"  },  "dependencies": {    "@babel/core": "^7.18.13",    "@babel/parser": "^7.18.13",    "@babel/preset-env": "^7.18.10",    "@babel/traverse": "^7.18.13",    "typescript": "^4.7.4"  }

【52pojie】用Babel解析AST处理OB混淆JS代码(一):https://www.52pojie.cn/thread-1700036-1-1.html

【52pojie】用Babel解析AST处理OB混淆JS代码(二):https://www.52pojie.cn/thread-1700038-1-1.html

【52pojie】用Babel解析AST处理OB混淆JS代码(三):https://www.52pojie.cn/thread-1700050-1-1.html

【52pojie】用Babel解析AST处理OB混淆JS代码(四):https://www.52pojie.cn/thread-1700068-1-1.html

本系列所有代码都基于GitHub仓库:https://github.com/Hans774882968/control-flow-flattening-remove-public

作者:hans774882968以及hans774882968以及hans774882968

引言

AST(Abstract Syntax Tree,抽象语法树),简称语法树(Syntax Tree),是源代码的抽象语法结构的树状表现形式,树上的每个节点都表示源代码中的一种结构。语法树不是某一种编程语言独有的,JavaScript、Python、Java、Golang等几乎所有编程语言都有语法树。

AST 的用途很广:IDE 的语法高亮、代码检查、格式化、压缩、转译等,使用AST来处理源代码都是最方便的。又比如ES5ES6语法有不少差异,为了向后兼容,在实际应用中需要进行转换,这个场景用AST也是最方便的。AST并不是为了逆向而生,但做逆向学会了AST,在解混淆时会更方便。

原本只打算用AST来去除JS代码的控制流平坦化,但发现只有先熟悉AST的相关操作,才能更好地完成这个目标。索性我把一篇blog拆成一个系列,来讲清楚所有相关知识。相信在看到这个系列以后,大家都会感慨AST真简单!如果和我一样鶸,就把这些代码跑一遍,很快就能学会!

技术选型

  1. shelljs:在nodejs中执行cmd命令。
  2. Babel:解析AST,修改AST并重新生成代码。在 astexplorer 中还展示了recast等包,它们都能做这件事。
  3. eslint:检查代码格式是否符合规范,并尝试自动format代码。
  4. TypeScript

这里只有Babel是必须的,但为了在提升开发效率的同时保证代码的健壮,还是建议把TS、eslint配好。

为什么要用TypeScript

  1. Babel的官方文档语焉不详,TypeScript的类型提示结合IDE是更好的文档。
  2. 写类型守卫的过程是在倒逼自己去思考各种边界情况,写出更健壮的代码。

IDEA配置eslint踩坑记录

每次配置eslint,eslint都有新的方式来折磨我,我愿称之为yyds。

eslint报错:TypeError: this.options.parse is not a function

IDEA配置的eslint不要大于等于8.23.0,否则你会遇到这个错误:

TypeError: this.options.parse is not a function

那么我们的package.json得这么写:"eslint": "8.22.0"

eslint报cliEngine的错

打开<IDEA安装目录>\plugins\JavaScriptLanguage\languageService\eslint\bin\eslint-plugin.js

// 旧版用this.cliEngine = require(this.basicPath + "lib/cli-engine");// 新版用this.cliEngine = require(this.basicPath + "lib/cli-engine").CLIEngine;

当然有兼容的写法:?.即可,但es2020很可能不支持,自己polyfill一下就行。

IDEA配置自动format

yarn add之后,要根据参考链接3来配置:

  1. Languages & Frameworks -> JavaScript -> Code Quality Tools -> ESLint,勾选Enable,然后填相关的字段。
  2. 设置 -> ESLint Settings,勾选Enable,然后填Path to eslint bin,勾选Auto fix errors等字段。
  3. 打开设置 -> Keymap搜索Fix ESLint Problems,配置快捷键。

看到JS / TS代码标红,并且能按快捷键format代码就成功了。总之能配置的都配置一下,免得它老不生效……

后续:呵呵呵这次IDEA叕不能显示TypeScript的eslint错误了,明明啥都装了……幸好还能通过npm run lint来format。不得不说eslint永远得神……

动态指定执行命令:用npm scripts+nodejs脚本解决

希望实现:在项目根目录输入命令npm run cff <fname>,自动执行tsc && node src/<fname>.js

这方面资料少得可怜,参考链接1已经是能找到的里面最好的了。

根据参考链接1,尝试过在package.json里加fname属性,然后读取%npm_package_fname%,但发现读不到值,因为必须放到package.jsonconfig属性里;也尝试过在package.jsonconfig对象里加自定义属性fname,这次%npm_package_fname%能读到值但无法修改。于是我们只能用最麻烦但最灵活的方案了:

用nodejs写个脚本,然后用npm scripts包装一下。

放在项目根目录下的cff.js

const process = require('process');const shell = require('shelljs');const args = process.argv.slice(2);if (!args.length) {  console.log('Usage: npm run cff <file_name>');  process.exit(0);}const fname = args[0];shell.exec(`tsc && node src/${fname}.js`);

依赖:

yarn add shelljs -D

给一个demo(src/check_pass_demo.ts)最简单的代码:

import fs from 'fs';function getFile (path: string) {  return fs.readFileSync(path, 'utf-8');}const jsCode = getFile('src/inputs/check_pass_demo.js'); // 运行者不是自己,所以要相对于项目根目录console.log(jsCode.substring(0, 60));

运行命令:

npm run cff check_pass_demo