一例js混淆分析
总阅读次
拿到代码找思路
第一次处理混淆,找了半天找到了这个
,于是接下来要做的事就是针对下面三个点做逆向分析
- 字符串字面量混淆:首先提取全部的字符串,在全局作用域创建一个字符串数组,同时转义字符增大阅读难度,然后将字符串出现的地方替换成为数组元素的引用。
- 变量名混淆:不同于压缩器的缩短命名,此处使用了下划线加数字的格式,变量之间区分度很低,相比单个字母更难以阅读。
- 成员运算符混淆:在 Javascript 中,window[‘top’] 和 window.top 是等价的。混淆器便利用这一特性,将成员访问复杂化,首先替换成字符串,然后对字符串进行混淆。
分析语法逆向
使用工具 JavascriptObfuscator ,以 esprima 作为 javascript parser 工具,代码在这
1 | var _0x180a=['random','charCodeAt','fromCharCode','parse','substr','\x5cw+','replace','(3(){(3\x20a(){7{(3\x20b(2){9((\x27\x27+(2/2)).5!==1||2%g===0){(3(){}).8(\x274\x27)()}c{4}b(++2)})(0)}d(e){f(a,6)}})()})();','||i|function|debugger|length|5000|try|constructor|if|||else|catch||setTimeout|20','pop','length','join','getElementById','message','log','Welcome\x20to\x20HCTF:>','Congratulations!\x20you\x20got\x20it!','Sorry,\x20you\x20are\x20wrong...','window.console.clear();window.console.log(\x27Welcome\x20to\x20HCTF\x20:>\x27)','version','error','download','substring','push','Function','charAt','idle','pyW5F1U43VI','init','https://the-extension.com','local','storage','eval','then','get','getTime','setUTCHours','origin','set','GET','loading','status','removeListener','onUpdated','callee','addListener','onMessage','runtime','executeScript','data','test','http://','Url\x20error','query','filter','active','floor'];(function(_0xd4b7d6,_0xad25ab){var _0x5e3956=function(_0x1661d3){while(--_0x1661d3){_0xd4b7d6['push'](_0xd4b7d6['shift']());}};_0x5e3956(++_0xad25ab);}(_0x180a,0x1a2));var _0xa180=function(_0x5c351c,_0x2046d8){_0x5c351c=_0x5c351c-0x0;var _0x26f3b3=_0x180a[_0x5c351c];return _0x26f3b3;};function check(_0x5b7c0c){try{var _0x2e2f8d=['code',_0xa180('0x0'),_0xa180('0x1'),_0xa180('0x2'),'invalidMonetizationCode',_0xa180('0x3'),_0xa180('0x4'),_0xa180('0x5'),_0xa180('0x6'),_0xa180('0x7'),_0xa180('0x8'),_0xa180('0x9'),_0xa180('0xa'),_0xa180('0xb'),_0xa180('0xc'),_0xa180('0xd'),_0xa180('0xe'),_0xa180('0xf'),_0xa180('0x10'),_0xa180('0x11'),'url',_0xa180('0x12'),_0xa180('0x13'),_0xa180('0x14'),_0xa180('0x15'),_0xa180('0x16'),_0xa180('0x17'),_0xa180('0x18'),'tabs',_0xa180('0x19'),_0xa180('0x1a'),_0xa180('0x1b'),_0xa180('0x1c'),_0xa180('0x1d'),'replace',_0xa180('0x1e'),_0xa180('0x1f'),'includes',_0xa180('0x20'),'length',_0xa180('0x21'),_0xa180('0x22'),_0xa180('0x23'),_0xa180('0x24'),_0xa180('0x25'),_0xa180('0x26'),_0xa180('0x27'),_0xa180('0x28'),_0xa180('0x29'),'toString',_0xa180('0x2a'),'split'];var _0x50559f=_0x5b7c0c[_0x2e2f8d[0x5]](0x0,0x4);var _0x5cea12=parseInt(btoa(_0x50559f),0x20);eval(function(_0x200db2,_0x177f13,_0x46da6f,_0x802d91,_0x2d59cf,_0x2829f2){_0x2d59cf=function(_0x4be75f){return _0x4be75f['toString'](_0x177f13);};if(!''['replace'](/^/,String)){while(_0x46da6f--)_0x2829f2[_0x2d59cf(_0x46da6f)]=_0x802d91[_0x46da6f]||_0x2d59cf(_0x46da6f);_0x802d91=[function(_0x5e8f1a){return _0x2829f2[_0x5e8f1a];}];_0x2d59cf=function(){return _0xa180('0x2b');};_0x46da6f=0x1;};while(_0x46da6f--)if(_0x802d91[_0x46da6f])_0x200db2=_0x200db2[_0xa180('0x2c')](new RegExp('\x5cb'+_0x2d59cf(_0x46da6f)+'\x5cb','g'),_0x802d91[_0x46da6f]);return _0x200db2;}(_0xa180('0x2d'),0x11,0x11,_0xa180('0x2e')['split']('|'),0x0,{}));(function(_0x3291b7,_0xced890){var _0xaed809=function(_0x3aba26){while(--_0x3aba26){_0x3291b7[_0xa180('0x4')](_0x3291b7['shift']());}};_0xaed809(++_0xced890);}(_0x2e2f8d,_0x5cea12%0x7b));var _0x43c8d1=function(_0x3120e0){var _0x3120e0=parseInt(_0x3120e0,0x10);var _0x3a882f=_0x2e2f8d[_0x3120e0];return _0x3a882f;};var _0x1c3854=function(_0x52ba71){var _0x52b956='0x';for(var _0x59c050=0x0;_0x59c050<_0x52ba71[_0x43c8d1(0x8)];_0x59c050++){_0x52b956+=_0x52ba71[_0x43c8d1('f')](_0x59c050)[_0x43c8d1(0xc)](0x10);}return _0x52b956;};var _0x76e1e8=_0x5b7c0c[_0x43c8d1(0xe)]('_');var _0x34f55b=(_0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)](-0x2,0x2))^_0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)](0x4,0x1)))%_0x76e1e8[0x0][_0x43c8d1(0x8)]==0x5;if(!_0x34f55b){return![];}b2c=function(_0x3f9bc5){var _0x3c3bd8='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';var _0x4dc510=[];var _0x4a199f=Math[_0xa180('0x25')](_0x3f9bc5[_0x43c8d1(0x8)]/0x5);var _0x4ee491=_0x3f9bc5[_0x43c8d1(0x8)]%0x5;if(_0x4ee491!=0x0){for(var _0x1e1753=0x0;_0x1e1753<0x5-_0x4ee491;_0x1e1753++){_0x3f9bc5+='';}_0x4a199f+=0x1;}for(_0x1e1753=0x0;_0x1e1753<_0x4a199f;_0x1e1753++){_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5)>>0x3));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5)&0x7)<<0x2|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x1)>>0x6));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x1)&0x3f)>>0x1));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x1)&0x1)<<0x4|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x2)>>0x4));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x2)&0xf)<<0x1|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x3)>>0x7));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x3)&0x7f)>>0x2));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x3)&0x3)<<0x3|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x4)>>0x5));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x4)&0x1f));}var _0x545c12=0x0;if(_0x4ee491==0x1)_0x545c12=0x6;else if(_0x4ee491==0x2)_0x545c12=0x4;else if(_0x4ee491==0x3)_0x545c12=0x3;else if(_0x4ee491==0x4)_0x545c12=0x1;for(_0x1e1753=0x0;_0x1e1753<_0x545c12;_0x1e1753++)_0x4dc510[_0xa180('0x2f')]();for(_0x1e1753=0x0;_0x1e1753<_0x545c12;_0x1e1753++)_0x4dc510[_0x43c8d1('1b')]('=');(function(){(function _0x3c3bd8(){try{(function _0x4dc510(_0x460a91){if((''+_0x460a91/_0x460a91)[_0xa180('0x30')]!==0x1||_0x460a91%0x14===0x0){(function(){}['constructor']('debugger')());}else{debugger;}_0x4dc510(++_0x460a91);}(0x0));}catch(_0x30f185){setTimeout(_0x3c3bd8,0x1388);}}());}());return _0x4dc510[_0xa180('0x31')]('');};e=_0x1c3854(b2c(_0x76e1e8[0x2])[_0x43c8d1(0xe)]('=')[0x0])^0x53a3f32;if(e!=0x4b7c0a73){return![];}f=_0x1c3854(b2c(_0x76e1e8[0x3])[_0x43c8d1(0xe)]('=')[0x0])^e;if(f!=0x4315332){return![];}n=f*e*_0x76e1e8[0x0][_0x43c8d1(0x8)];h=function(_0x4c466e,_0x28871){var _0x3ea581='';for(var _0x2fbf7a=0x0;_0x2fbf7a<_0x4c466e[_0x43c8d1(0x8)];_0x2fbf7a++){_0x3ea581+=_0x28871(_0x4c466e[_0x2fbf7a]);}return _0x3ea581;};j=_0x76e1e8[0x1][_0x43c8d1(0xe)]('3');if(j[0x0][_0x43c8d1(0x8)]!=j[0x1][_0x43c8d1(0x8)]||(_0x1c3854(j[0x0])^_0x1c3854(j[0x1]))!=0x1613){return![];}k=_0xffcc52=>_0xffcc52[_0x43c8d1('f')]()*_0x76e1e8[0x1][_0x43c8d1(0x8)];l=h(j[0x0],k);if(l!=0x2f9b5072){return![];}m=_0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x0,0x4))-0x48a05362==n%l;function _0x5a6d56(_0x5a25ab,_0x4a4483){var _0x55b09f='';for(var _0x508ace=0x0;_0x508ace<_0x4a4483;_0x508ace++){_0x55b09f+=_0x5a25ab;}return _0x55b09f;}if(!m||_0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x5,0x1),0x2)==_0x76e1e8[0x4][_0x43c8d1(0xd)](-0x5,0x4)||_0x76e1e8[0x4][_0x43c8d1(0xd)](-0x2,0x1)-_0x76e1e8[0x4][_0x43c8d1(0xd)](0x4,0x1)!=0x1){return![];}o=_0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6,0x2))[_0x43c8d1(0xd)](0x2)==_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6,0x1)[_0x43c8d1('f')]()*_0x76e1e8[0x4][_0x43c8d1(0x8)]*0x5;return o&&_0x76e1e8[0x4][_0x43c8d1(0xd)](0x4,0x1)==0x2&&_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6,0x2)==_0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x7,0x1),0x2);}catch(_0x4cbb89){console['log']('gg');return![];}}function test(){var _0x5bf136=document[_0xa180('0x32')](_0xa180('0x33'))['value'];if(_0x5bf136==''){console[_0xa180('0x34')](_0xa180('0x35'));return![];}var _0x4d0e29=check(_0x5bf136);if(_0x4d0e29){alert(_0xa180('0x36'));}else{alert(_0xa180('0x37'));}}window['onload']=function(){setInterval(_0xa180('0x38'),0x32);test();}; |
代码瞬间变成了
1 | var _0x180a = [ |
分析语法,注释掉其中与分析无关的debug等操作,发现是直接前端读取标签input内容调用check函数进行判断,返回值得为1。
接着对字符串字面量混淆做逆向,发现是对某个字符串利用某个变换函数从已有的数组里做映射取值。使用浏览器把脚本直接放进去就可以拿到混淆前的真正字符串。
1 | var _0x180a = [ |
check函数里的字符串类似于_0xa180(‘0x19’),扔到浏览器就行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17a = ["window.console.clear();window.console.log('Welcome to HCTF :>')","Sorry, you are wrong...","Congratulations! you got it!","Welcome to HCTF:>","log","message","getElementById","join","length","pop","||i|function|debugger|length|5000|try|constructor|if|||else|catch||setTimeout|20","(3(){(3 a(){7{(3 b(2){9((''+(2/2)).5!==1||2%g===0){(3(){}).8('4')()}c{4}b(++2)})(0)}d(e){f(a,6)}})()})();","replace","\w+","substr","parse","fromCharCode","charCodeAt","random","floor","active","filter","query","Url error","http://","test","data","executeScript","runtime","onMessage","addListener","callee","onUpdated","removeListener","status","loading","GET","set","origin","setUTCHours","getTime","get","then","eval","storage","local","https://the-extension.com","init","pyW5F1U43VI","idle","charAt","Function","push","substring","download","error","version"]
a = a[::-1]
# print len(a)
# for i in range(57):
# print i
for i in range(57):
s = "_0xa180('"+hex(i)+"')"
t = a[i]
# print s
# print t
with open("hun.js" , "r+")as f :
lines=f.readlines()
f.seek(0)
f.truncate()
for l in lines:
f.write(l.replace(s , "'"+t+"'"))
f.close()
通过一段小py替换js里混淆的数组得到check函数里用到的数组
分析check函数1
2
3
4
5
6
7
8
9
10
11var shuru_1 = shuru.substring(0, 4);
var shuru_2 = parseInt(btoa(shuru_1), 32); // 31
(function (shuzu, shuru) {
var shift = function (i) {
while (--i) {
shuzu.push(shuzu.shift());
}
};
shift(++shuru);
}(shuzu_2, shuru_2 % 123));
首先是取输入前五位base64编码以32进制解析base64结果得到一个数字,以该数字对数组2移位变换
1 | var shuzu_2_yingshe = function (shuzi) { |
然后会对数组2做映射
1 | for (var i = 0; i < shuzu_2[shuzu_2_yingshe(8)]; i++) { |
看到这样的结构,猜想shuzu_2_yingshe(8)应该对应shuzu_2中的length,shuzu_2_yingshe(‘12’)对应toString,结合一开始会以输入对shuzu_2移位,带入hctf{进行测试,结果吻合,说明flag前5位应该就是hctf{
接下来对所有shuzu_2_yingshe函数映射的地方进行替换
1 | function check(shuru) { |
接着分析check内的判断条件
1 | var shuru_3 = shuru.split('_'); |
输入以_分割分为5部分
1 | var res = (toHex(shuru_3[0].substr(-2, 2)) ^ toHex(shuru_3[0].substr(4, 1))) % shuru_3[0].length == 5; |
可知res不能为0,而shuru_3[0].length无法得知,继续往下看
遇到了b2c函数,变换十分复杂,其中出现了
1 | var _0x3c3bd8 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; |
还是师兄见多识广,看出了它是base32变换
其实不管它是何种变换,根据后面的判断提示直接调用函数,只需要函数结果
1 | j = shuru_3[1].split('3'); |
可知输入第二部分以3分为两半,两边长度相等,toHex(j[0]) ^ toHex(j[1])) == 5651 ==> 经过测试确定两边长度为2或3,分为以此为条件带入下面的等式“爆破”
1 | h = function (input, func) { |
丢进浏览器跑,得到输入第二部分为rev3rse
1 | e = toHex(b2c(shuru_3[2]).split('=')[0]) ^ 87703346; // e=1266420339 1313224001 |
第三四部分更简单了,同样直接爆破
1 | toHex(b2c(shuru_3[2]).split('=')[0]) == 1313224001 |
1 | n = f * e * shuru_3[0].length; //f=70341426;e=1266420339; |
存在两个未知变量,输入第一部分长度和输入第五部分前四位,不多说
1 | var toHex = function (stri) { |
拿到输入第五部分前四位h4rd和输入第一部分长度7
1 | o = toHex(shuru_3[4].substr(6, 2)).substr(2) == shuru_3[4].substr(6, 1).charCodeAt() * shuru_3[4].length * 5; |
得到第五部分第5位为2;return需要为1 ==> o为1,两个变量,跑
1 | var toHex = function (stri) { |
得到第五部分长度13和第五部分7、8位为ee
至此,flag雏形为hctf{_rev3rse_iz_s0_h4rd2ee*},*为未知
本地条件差不多用光了,用官方提示
1 | flag.substr(-5,3)=="333" |
得到hctf{**_rev3rse_iz_s0_h4rd23ee3333}
还差两位,根据提示用sha256爆破,首先,由
1 | var res = (toHex(shuru_3[0].substr(-2, 2)) ^ toHex(shuru_3[0].substr(4, 1))) % shuru_3[0].length == 5; |
根据条件跑出最后两位的所有可能
1 | var toHex = function (stri) { |
然后对所有可能的sha256与预期验证
1 | <script src='http://www.bichlmeier.info/sha256.js'></script> |
拿到flag ==> hctf{j5_rev3rse_iz_s0_h4rd23ee3333}
参考链接:
http://www.freebuf.com/articles/web/97945.html
https://github.com/javascript-obfuscator/javascript-obfuscator