首页 > 编程知识 正文

逻辑链条分析法,jvm可达性分析算法

时间:2023-05-05 17:39:04 阅读:22763 作者:2061

今天,我们来研究一下某个标题的jsvmp逻辑层加密算法。 其主要目的是在接触这类算法时提供实质性的建议和思路。

0x01 分析加密

访问目标站点分析请求时,动态_signature加密参数0x02 定位加密

与以往不同,这次使用ajax请求监听方式监听此参数

根据堆栈的调用信息,可以得到以下结果

加入断点实现最终定位

以其他形式调用

0x03 window被赋予的属性

分析表明,_signature参数通过window.byted_arawler.sign方法实现,但该方法指向acrawler.js文件中的一个令人费解的位置

因此,我们的目的也大致明确了。 如果window对象具有byted_acrawler属性,则整个算法将被完全破坏

0x04 惯用的方法

在本地创建新的HTML文件,复制整个代码,然后在浏览器中打开

令人惊讶的是,该byted_acrawler属性已经存在于窗口对象中,但如果借用节点运行,秒会在现实中击中面部

0x05 如何分析

让我们先看看整个代码。 除了if-else以外,都是else-if活生生的无限人偶。 而且,里面的逻辑是用单一的文字表示的,没有任何可读性

通篇都是if-else,但那也一定符合一定的逻辑规律。 否则,报告错误。 但是,政府这样做的目的是让它失去可读性,让我们无法分析。 想想看。 我们能用别的形式表达它,恢复可读性吗?0x06 平坦化流程

这是一个全新的概念,也是对抗jsvmp算法的必要概念。 方式很简单,但工程量很大。 代码中的所有if-else都必须替换为switch-case,但问题是与switch中的哪个变量case类似?

让我们来看看这个代码:

varj=parseint('b[o]b[o1],16 );

验证一下,整体流程会不会因j变量的动态变化而进入不同的逻辑分支?

在开头提到的条件断点处,尝试每j=16次是否进入相同的逻辑分支。 如果可能,请说明j的所有if-else的动态参数。 如果不能,则验证其他参数。

实证上,在j=16时可以确认进入上述分支,因此也许可以这样表达

开关(j ) { case 0://todosomethindbreak; case 16: q=S[R--],w=S[R--],a=s[r--].x===g? A.y=1? s[r]=k(b,A.c,A.l,q,A.z,w,null,1 ) : ) s[r]=k ) b,A.c,A.l,q,A.z,w,null,w d fault ://todosomethingreturn; 最终我们整理的结果是这样的

在此注意:x 会有一个偏移量计算,每次运行都会 x4 并且 x 和 A 的值相等

0x07 环境对比

在考虑代码平面化过程后,必须考虑以前的问题。 为什么浏览器环境可以有byted_acrawler,而节点环境是undefined? 可能的原因是存在某种环境检测,节点环境被禁用

首先从运行的开始进行分析吧。 (这也是很多人容易忽略的细节) ) )。

glb='undefined'==typeof window? 名为global : window的代码在浏览器中运行,表示窗口对象

如果将其放入node环境中运行,它将成为全局对象

所以,我们需要改变节点环境。 这里采用jsdom来伪装环境。 这样,我们就改变了初期的环境

0x08 逻辑对比

修改环境后再次运行本地文件时,将抛出错误

因为这是case 26过程投出的,所以我们直接在官方打条件断点进入

行比对


这是官方的结果

经过对比,官方 S 中拥有Object 函数而本地却是 undefined 所以我们要给它添加这种属性

window.Object = function (){ return ["native code"];}

添加完之后自然有了这种属性

0x09 环境补全

鉴于这个对比过程太过漫长,我这里主要列出几个重要的属性,其他的请自行校验:

1.补齐 canvas 属性

window.HTMLCanvasElement.prototype.getContext = function () { return { fillText(text, x, y, maxWidth) { return ["native code"]; }, arc(x, y, radius, startAngle, endAngle, anticlockwise) { return ["native code"]; }, stroke() { return ["native code"] }, getExtension() { return ["native code"] }, getParameter() { return ["native code"] } }}

2.补齐 localStorage(这个会影响 _signature 生成的长度)

(function () { let localStorage = { "__tea_cache_tokens_2018": "{"user_unique_id":"verify_kut5p98i_BUC0aRbH_QZ5B_4lu4_BQzz_F22NI2z6snKh","web_id":"7018918857491351074","timestamp":1634353853331}", "__tea_cache_tokens_24": "{"web_id":"7018898163830441479","ssid":"522d279c-ba4b-4c85-a61c-98b7f1a05b71","user_unique_id":"7018898163830441479","timestamp":1634219409505}", "__tea_cache_first_24": "1", "tt_scid": "z1OscwP3.P6dJQt3WyFHyZ65WZCoGUYARVlW9x0SAYAXAN.mmr9m1v6FlmpA1M98f94d", "__tea_cache_first_2018": "1", "_byted_param_sw": "lmr6SespEoX9rv9+V/8=" } for (var p in localStorage) { window.localStorage.setItem(p, localStorage[p]) }})() 修改 jsdom User-Agent (必须跟请求中保持一致) resourceLoader = new jsdom.ResourceLoader({ strictSSL: false, userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",});const jsdomConfig = { resources: resourceLoader, url: "https://www.toutiao.com/",}

0x10 简洁化处理

如果细心的话会发现,每个分支的代码,只运行了一小部分就跳出了所在的分支,剩下的代码形同虚设般存在。之所以会这样,是因为分支内部的 A 值会影响代码的整体走势,所以我们只需要判断满足 A的代码就可以,其他的都是在尸位素餐

精简之前

case 25: (A = x) > 8 ? (C = S[R--], S[R] = typeof C) : A > 6 ? S[R] = --S[R] : A > 4 ? S[R -= 1] = S[R][S[R + 1]] : A > 2 && (q = S[R--], (A = S[R]).x === G ? A.y >= 1 ? S[R] = K(b, A.c, A.l, [q], A.z, w, null, 1) : (S[R] = K(b, A.c, A.l, [q], A.z, w, null, 0), A.y++) : S[R] = A(q)); break;

精简之后

case 25: S[R -= 1] = S[R][S[R + 1]] break;

0x11 总结

1.对于这种混淆的代码而言,流程平坦化是首选,但如何进行才是关键
2.代码不仅想知道你的运行环境,它更想知道这种环境是否真的适合它

最终效果


最后声明:

✧✧ 本片文章只用于学习交流,切勿用于商业用途,如果有侵权行为,请与作者联系,本人会在第一时间将其删除。✧✧

欢迎关注:

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。