初试js反混淆

一、目标

最近js玩的花样越来越多了,本来简洁方便的一门开发语言,现在混淆的一塌糊涂。今天我们就介绍几种常见的反混淆方案。

混淆的本质就是等价替换,把 a = 12 ,替换成 a = 100 - 8 + 5 - 15 - 70。 把 "push" 替换成 "\u0070\u0075\u0073\u0068", 基本上就让你调试起来很难受了。

反混淆就是一个逆过程,上古时期用的是最简单的正则匹配,然后再替换。现在时代进步了,我们有了一个更强大的工具 抽象语法树 (Abstract Syntax Tree),简称 AST ,它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。这样可以更方便的实现对源代码的处理。

二、步骤

先搭个架子

最近比较喜欢用JetBrains全家桶,先用WebStorm创建一个Node js项目,然后在工程目录下面执行

1 2npm install @babel/core --save-dev 3

来安装js的@babel库,用于将JS源码转换成语法树AST

Tip:

我这边执行的时候,卡了半天,一直没反应,我第一个反应就是给他换个国内镜像

1 2# 更换成淘宝镜像 3 4npm config set registry https://registry.npmmirror.com 5 6# 查看当前镜像 7 8npm config get registry 9

结果不好使,问下了谷哥,哥说,可能是使用了代理,被乱七八糟的代理影响了npm,可以尝试禁用代理:

1 2npm config delete proxy 3 4npm config delete https-proxy 5

哥是对的,现在好使了

Show me the code

1 2var parser = require("@babel/parser"); // 将JS源码转换成语法树AST 3 4var template = require("@babel/template").default; // 为parser提供模板引擎 5 6var traverse = require("@babel/traverse").default; // 遍历AST树节点操作 7 8var types = require("@babel/types"); // 操作节点,比如判断节点类型,生成新的节点等 9 10var generator = require("@babel/generator").default; // 将语法树AST转换为js代码 11 12 13 14 15var fsrw = require("fs"); // 文件读取 16 17 18 19 20var jscode = fsrw.readFileSync("./input/demo2.js", {encoding: "utf-8"}); // 读取原始混淆文件 21 22var ast_parse = parser.parse(jscode); //将JS源码转换成语法树AST 23 24 25 26 27 28// 做处理 29 30var traverses_2 = { 31 32...... 33 34} 35 36 37 38 39console.log(" traverses 处理 \\u00 ======== "); 40 41traverse(ast_parse, traverses_2) 42 43 44 45 46// 生成还原后的js文件 47 48var ast_js_code = generator(ast_parse); 49 50fsrw.writeFileSync('./output/demo2_out.js', ast_js_code.code) 51

这就是一个基础的AST反混淆处理框架。

先把待处理的文件读取出来,转成语法树AST,然后针对性的做处理,最后生成还原后的js文件

Demo1 \uxxx 转换

第一个例子是

1 2var e = "\u002e\u0068\u006f\u006c\u0064\u0065\u0072"; 3 4... 5

它把字符串做了一次unicode编码,我们还原的时候只需要做个正则匹配,把 '\uxxx' 的字符串的extra 属性移除,就会重新解析成正常的字符串了

1 2var traverses_2 = { 3 4// B. 处理Unicode字符串,针对"\u0068\u0065\u006c\u006c" 》》'hell' 5 6StringLiteral(path) { 7 8cur_node = path.node; 9 10if (cur_node.extra && /\\[ux]/gi.test(cur_node.extra.raw)) { 11 12cur_node.extra = undefined; 13 14} 15 16}, 17 18} 19

得到的结果就是

1 2var e = ".holder" 3

Demo2 a = 100 - 8 + 5 - 15 - 70 表达式替换

1 2var a = 100 - 8 + 5 - 15 - 70; 3 4var b = 1; 5 6console.log(a+b); 7

这个js就有点小复杂了,我们需要一个分析工具 https://astexplorer.net/

ast1

1:ast1

从这个解析里面看一看出,a的值是一个二元表达式(BinaryExpression) 套着一个二元表达式,我们需要把每个二元表达式的结果计算出来,然后一层一层往上吐结果,这不就是妥妥的递归嘛。

1 2function evaluateBinaryExpression(path) { 3 4let left = path.node.left ; 5 6let right = path.node.right; 7 8let operator = path.node.operator; 9 10 11 12 13console.log(left); 14 15console.log(right); 16 17console.log(operator); 18 19 20 21 22// 递归处理左、右两边 23 24if (types.isBinaryExpression(left)) { 25 26left = evaluateBinaryExpression(path.get('left')); 27 28} 29 30if (types.isBinaryExpression(right)) { 31 32right = evaluateBinaryExpression(path.get('right')); 33 34} 35 36 37 38 39console.log(" =================== "); 40 41console.log(left); 42 43console.log(right); 44 45 46 47 48// 如果左右两边都是数字类型,计算结果并替换 49 50if (( isNumericLiteral(left)|| types.isUnaryExpression(left) ) && (isNumericLiteral(right) || types.isUnaryExpression(right) ) ) { 51 52try { 53 54const leftValue = getNodeValue(left); 55 56const rightValue = getNodeValue(right); 57 58const evalResult = eval(`${leftValue} ${operator} ${rightValue}`); 59 60 61 62 63console.log( `${leftValue} ${operator} ${rightValue}` + " = " + evalResult); 64 65// if(evalResult >=0) 66 67path.replaceWith(types.numericLiteral(evalResult)); 68 69return path.node; 70 71// else 72 73// path.replaceWith(types.unaryExpression('-', types.numericLiteral(Math.abs(evalResult)))); 74 75} catch (e) { 76 77console.log("Failed to evaluate numeric expression:", e); 78 79} 80 81} 82 83 84 85 86// 如果左右两边都是字符串类型,拼接结果并替换 87 88else if (isStringLiteral(left) && isStringLiteral(right) && operator === "+") { 89 90const leftValue = getNodeValue(left); 91 92const rightValue = getNodeValue(right); 93 94const evalResult = leftValue + rightValue; 95 96path.replaceWith(types.stringLiteral(evalResult)); 97 98return path.node; 99 100} 101 102 103 104 105return null; 106 107} 108 109 110 111 112 113var traverses_3= { 114 115BinaryExpression(path) { 116 117evaluateBinaryExpression(path); // 递归处理 118 119} 120 121} 122

跑一下,搞定

1 2var a = 12; 3 4var b = 1; 5 6console.log(a + b); 7

三、总结

js反混淆基本上就是一次模版批量替换的套路,以前我们仅仅依赖查找替换,然后高级一点就是正则替换,现在有了AST解析,就更加灵活了。后面咱们在继续介绍一些高阶的用法。

ffshow

1:ffshow

角弓玉剑,桃花马上春衫,犹忆少年侠气

Tip:

: 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我wx: fenfei331 讨论下。

关注微信公众号: 奋飞安全,最新技术干货实时推送

点赞
收藏

评论区

加载中...

相关推荐

寻找消失的类名

一、目标前几天有个朋友问了我一个新手问题,frida提示:找不到 p009cn.com.chinatelecom.gateway.lib.C1402a 这个类1:main原因是,jadx反编译的时候,为了反混淆,会把类名做个处理,加上几个数字,这样好让你分辨,不会满眼都是变量a。实际上他已经提示你了,这个类的真实类名是 cn.com.chinateleco

彻底搞懂 JS 中的 prototype、__proto__与constructor

1\.前言作为一名前端工程师,必须搞懂JS中的prototype、proto与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞懂它们。这里说明一点,proto属性的两边是各由两个下划线构成(这里为了方便大家看清,在两下划线之间加入了一个空格:proto,读作“dunderpr

如何保护你的代码 - Ollvm(一)

一、目标李老板:奋飞呀,最近的so都混淆的很厉害呀,也没有啥通用的反混淆方法。一点都不好玩,之前你说的也不是很好使。奋飞:我传你六字真言,打不过就加入。搞不定反混淆,我们搞搞代码混淆,好处多多。可以很好的保护自己的代码搞明白混淆的原理,可以增强反混淆的思路二、步骤下载代码先OLLVM(ObfuscatorLLVM的老家在这里,只不过官方仅更新

拒绝反爬虫!教你搞定爬虫验证码

导读:目前,许多网站采取各种各样的措施来反爬虫,其中一个措施便是使用验证码。随着技术的发展,验证码的花样越来越多。验证码最初是几个数字组合的简单的图形验证码,后来加入了英文字母和混淆曲线。有的网站还可能看到中文字符的验证码,这使得识别越发困难。使用验证码可以防止应用或者网站被恶意注册、攻击,对于网站、APP而言,大量的无效注册、重复注册甚至是恶意攻击很令

(使用通过混淆+自己第三方保留成功混淆)AndroidStudio 混淆打包

原文:https://blog.csdn.net/mazhidong/article/details/64820838(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fblog.csdn.net%2Fmazhidong%2Farticle%2Fdetails%2F64820838)A

Java代码混淆器Allatori Java obfuscator流程混淆

AllatoriJavaobfuscator(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.evget.com%2Fproduct%2F3325)是第二代Java代码混淆器,为您的知识产权提供全方位的保护。大部分第二代混淆器都具有保护功能。Allatori除了保护功能之外,