python爬虫之破解javascript-obfuscator的混淆加密

python爬虫之破解javascript-obfuscator的混淆加密

2023年6月29日发(作者:)

python爬⾍之破解javascript-obfuscator的混淆加密接上⼀篇有关前端加密达到反爬的⽂章,是不是觉得⽤了javascript-obfuscator 就很安全了,那还真不⼀定啊,还是那句,反爬与反反爬⼀直在⽃争,没有谁能绝对的压制另⼀⽅,只有使⽤者技术的⾼低。以下就是⼀个⼤神的针对javascript-obfuscator库的破解。

本篇⽂章转载于 :

死代码与花指令

在开始之前,我们先了解⼀下这种「在代码中插⼊⼤量⽆⽤代码以混淆视听」的混淆⽅式吧。这种混淆⽅式有两种叫法,或者说是两种做法,它们分别是「死代码」和「花指令」。

>

死代码死代码⼀开始是被⽤来描述⼀些⼈写代码时写出的没有⽤到的代码的,为了编译后的⽂件尽可能地⼩,编译器通常会对死代码进⾏移除处理。⽽在不知道什么时候开始,死代码被安全⼯作者们⽤来作为⼀种混淆机制,以将代码量变得极为庞⼤,使进⾏逆向⼯程的⼈难以找到主要逻辑。但死代码有个很明显的特征:它虽然看着代码量很⼤,但实际却完全不会在程序的正常代码中被调⽤。如果你有兴趣的话,可以对⼀些包含了死代码的代码进⾏聚类分析,你会发现死代码和正常代码之间泾渭分明,正常代码都是互相关联着的,⽽死代码却是孤零零的⼀块或者多块,并且正常代码还完全不会与死代码产⽣关联。>

花指令花指令是以前被⼤量运⽤在⽊马、病毒的免杀上的⼀种反反汇编⼿段,花指令中的“指令”通常指的是汇编中的 jmp、call 之类的调⽤、跳转指令,⽽攻击者们会将这些指令巧妙地插⼊到恶意代码的执⾏逻辑中,使得静态分析⼯具在分析到这个位置时⽆法正常反汇编。花指令曾经的⽬的主要有两个,⼀个是使杀毒软件⽆法⾃动分析出恶意代码,达到瞒天过海的效果;⼀个是给安全⼯作者在分析恶意软件时设下层层阻拦,使安全⼯作者需要花费更多的时间才能理清代码逻辑,达到拖延时间的效果。同样是不知道什么时候开始,花指令也被安全⼯作者们⽤来作为⼀种混淆机制。在这种应⽤场景下,花指令和死代码其实很类似,它们都是⽤了⼤量⽆⽤代码来混淆视听,但花指令和死代码最⼤的区别就是,花指令的⽆⽤代码是会被混在正常代码中进⾏执⾏的。相⽐于死代码⽽⾔,花指令会造成⼀些性能损失,但同时也会让进⾏逆向⼯程的⼈更加难以分析。但花指令也不是⽆懈可击的,为了不影响程序正常的执⾏,花指令不能⼲扰到程序的原有逻辑,举个例⼦:a = 1b = 2# 花指令开始,对变量进⾏了⼀通操作a += 1a += b# 花指令结束,⼜把变量的值给变回去了a -= ba -= 1c(a, b)

所以其实只要你能看出这⼀通操作没有任何意义,花指令也⾃然就没法影响到你了。>

⼩结不管是死代码还是花指令,其实都只需要我们仔细观察就能将其剔除,它们并不是什么很难搞的东西,见得多了之后你甚⾄都不需要细看就能快速排除掉⼀些明显不是正常代码的部分,毕竟常见的混淆器中⽤到的代码其实重合度是很⾼的,同样的套路见多了之后⾃然很容易分辨。更何况,代码混淆是需要考虑性能损耗的,对⽅不可能为了防你逆向⼯程⽽⽆⽌尽地对代码进⾏混淆,要不然⼈家正常业务也没办法进⾏了。

实战

基础知识了解完了,我们来进⼊实战环节。⾸先,我们打开 ,这是 Obfuscator 的⽹页版本,可以快速在⽹页上进⾏混淆参数的配置,并且⼀键⽣成并导出混淆后的代码。顺带⼀提,Obfuscator 是⼀款⾮常优秀的 JavaScript 代码混淆⼯具,但代码结构都是固定的,如果想要更好的混淆效果,可将混淆后的代码进⾏修改,从⽽让别⼈更难分析和调试。现在,我们⽤它给出的样例代码来进⾏混淆。样例代码如下: // Paste your JavaScript code herefunction hi() { ("Hello World!");}hi();

注意,我们需要勾选以下选项:

这三个选项的效果分别是:•Compact code将代码中的换⾏符全部去掉,使得代码看起来毫⽆结构性。也就是所谓的代码压缩。•Self Defending在代码中插⼊⾃检代码,⽤来⼲扰逆向⼯程的⼈对代码进⾏格式化、变量重命名操作,如果代码被格式化了就会⽆法正常运⾏。•Dead Code Injection在代码中插⼊死代码,也就是本⽂的重点。配置好参数后点击 Obfuscate 按钮,即可⽣成按配置混淆后的代码,我⽣成的代码是这样的(长图警告):

可以看到,原本短短的⼏⾏代码,在经过混淆后变成了这么多。⽽且这个代码还是经过压缩的,完全看不出层级。当然,这个代码是可以正常运⾏的,我们⽤NodeJS跑⼀遍看看:

看起来混淆并没有影响到正常的代码逻辑,我们再把这⼀坨代码给格式化⼀下看看:

果不其然,格式化后的代码直接就没法运⾏了。在平时我们遇到这种情况时要记住,原代码可以正常运⾏但格式化之后不⾏,那么这个报错肯定是跟格式化代码有关系的,⾄于它报错的内容具体是啥意思其实并不重要。那么怎么办呢?我们来静态分析⼀下它的代码就知道了。先来看看第⼀段代码:

定义了⼀个数组并初始化,显然不可能造成什么问题。接着看看第⼆段代码(长图警告):

这是⼀个⾃执⾏的函数,没有返回值。但是注意,它的第⼀个实参是 _0x2831,也就是之前定义的那个数组,对应的形参是 _0x528cba。我们可以根据这个来判断它对 _0x2831 做了些什么。现在我们来⼀段⼀段地分析这第⼆⼤段代码中的每⼀段代码,⾸先是第⼀段代码:var _0x1b0e99 = function(_0x5beb46) { while (--_0x5beb46) { _0x528cba['push'](_0x528cba['shift']()); } };

这么短的代码相信⼤家都应该能看懂,是对 _0x528cba 进⾏ shift 操作,⽽ _0x528cba 是⾃执⾏函数的形参,实参是 _0x2831。换句话说,它就是对实参进⾏ shift 操作。不过这⾥它只是声明,并没有调⽤,所以还不会去改变实参。然后是第⼆段代码和第三段代码,这⾥因为代码量太⼤就不整个贴出来了,之前已经贴过完整代码了。第⼆段代码是定义了⼀个函数,⽽第三段代码则是调⽤这个函数,因此我们主要分析这第⼆段代码即可。如果你不会分析,可以跳过它声明的语句,它真正开始执⾏的是这⾏代码:

var _0x53c9b6 = _0x1d1bc5['updateCookie']();

不要看它这段代码⾥⾯既有 setCookie,⼜有 getCookie,其实它跟 cookie 没有半⽑钱关系,它只是⼀个 object 的 key 值,仅此⽽已。因此,我们只需要关注它有没有改变实参,有没有改变全局变量。整个代码全局变量只有⼀个 _0x2831,它也是实参,也就是说只需要关⼼这个 _0x2831 即可。通过上⾯的分析我们可以知道,它的第⼀段代码定义了⼀个函数,确实改变了实参,但是没有调⽤。因此,我们得找找看它在哪⾥被调⽤的,直接搜函数名 _0x1b0e99,定位到这⾥:_0x4c51d1(_0x1b0e99, _0x283138);

这时,_0x1b0e99是第⼀个实参,第⼆个实参 _0x283138 则是⾃执⾏函数的形参,它对应的实参是 0x1bf,是⼀个整形的数值。这下,我们只需要看看 _0x4c51d1 的函数声明即可:

var _0x4c51d1 = function(_0x3d5743, _0x3c21e0) { _0x3d5743(++_0x3c21e0);};

这么⼤⼀段代码,其实真正改变实参的只有这⾥。我们将这两⾏代码结合⼀下,就会变成这样:

_0x1b0e99(++_0x283138);

所以,第⼆⼤段代码这个⾃执⾏函数中的第⼆段代码只有这⼀句是真正改变实参的地⽅,其他的全部是垃圾代码,直接删除即可。删除后,这个⾃执⾏函数就变成了这样:(function(_0x528cba, _0x283138) { var _0x1b0e99 = function(_0x5beb46) { while (--_0x5beb46) { _0x528cba['push'](_0x528cba['shift']()); } }; _0x1b0e99(++_0x283138);}(_0x2831, 0x1bf));

这样看起来就清爽多了,运⾏试试看:

还是报同样的错误,接着往下分析第三段代码(长图警告):

这是⼀个函数,可以看到,引⽤全局变量 _0x2831 的只有这⼀⾏:var _0x1b0e99 = _0x2831[_0x528cba];

这是⼀个赋值语句,但是不会改变 _0x2831 这个变量,因此我们只需要重点关注它的返回值 _0x1b0e99 就好。再来看看它最后赋值的地⽅,有两处,⼀处在 if 语句⾥⾯:_0x1b0e99 = _0x1b0e['SmClCt'](_0x1b0e99, _0x283138);

另外⼀处在 else 语句⾥⾯:_0x1b0e99 = _0x309846;

那它到底是执⾏的那⾏代码呢,来看看 if 语句的条件:_0x309846 === undefined

继续分析上⾯的代码:var _0x309846 = _0x1b0e['jZzRvK'][_0x528cba];

以及 _0x1b0e['jZzRvK'] 最近的定义的地⽅:_0x1b0e['jZzRvK'] = {};

这样就清楚了,_0x309846 === undefined 这个条件是成⽴的,所以 _0x1b0e99 最后赋值的地⽅是这⾥:_0x1b0e99 = _0x1b0e['SmClCt'](_0x1b0e99, _0x283138);

函数 _0x1b0e['SmClCt'] 赋值在这⾥:

_0x1b0e['SmClCt'] = _0x5beb46;

⽽实参,则是在 _0x5beb46 函数之前定义过,因此只需要这两⾏代码提到 _0x5beb46 函数之后即可。注意,这⾥为了清楚⼀点,可以这样操作:if (_0x1b0e['DVdkAf'] === undefined) {............................ _0x1b0e['SmClCt'] = _0x5beb46; _0x1b0e['jZzRvK'] = {}; _0x1b0e['DVdkAf'] = !![];}_0x1b0e99 = _0x1b0e['SmClCt'](_0x1b0e99, _0x283138);return _0x1b0e99;

根据上⾯的思路继续分析 hi 函数,同样也注⼊了⼀些不会改变全局变量 _0x2831 的垃圾代码,真正有效的代码只有这⼀⾏:console[_0x1b0e('0xc', '^G6o')](_0x1b0e('0xd', 'Bi36'));

删除掉这些垃圾代码后,我们就可以知道这份代码原来长什么样了。

BOOM!结果就是这么三⾏代码:

function hi() { console[_0x1b0e('0xc', '^G6o')](_0x1b0e('0xd', 'Bi36'));}

最后我们再运⾏⼀下试试看吧:

没有报错,代码成功运⾏了~总结碰到⼤段代码时不要慌,先试着分析⼀下,把⽆⽤代码剔除掉之后其实最后剩下的可能就只有⼏⾏⽽已。当然实际情况中你往往会碰到混淆参数更复杂的代码,你需要让程序来帮助你进⾏分析,⽽想要写出这种程序⼜会使⽤到 AST 操作,所以说掌握 AST 操作还是很有必要的,建议学习⼀下。

以上为转载的内容全⽂,怎么样,⼀样可以解出来,傻眼了吧,但是,也不是说javascript-obfuscator就完全没⽤了,当然有⽤,必须有⽤,只是看怎么⽤,你想⼀下,如果让这个混淆加密定期换⼀套呢?我换的速度难道还能⽐你解密的速度慢吗?然后就算你能解密出来,我再加⼏道关卡呢?让你解的费劲,恶⼼你,击垮你的信⼼,等等的,所以⽤处是很⼤的。只是看你怎么⽤了

发布者:admin,转转请注明出处:http://www.yc00.com/web/1687982680a63540.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信