0x01 解包
首先对Electron应用进行解包:asar extract app.asar app
,解包文件会释放到当前目录下的app文件夹里。通过package.json
可以看到入口js文件以及相关依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 { "name" : "******" , "version" : "1.2.2" , "author" : "*****" , "description" : "*****" , "main" : "main/index.js" , "dependencies" : { "@babel/generator" : "^7.18.2" , "@babel/parser" : "^7.18.5" , "@babel/traverse" : "^7.18.5" , "@nestjs/common" : "^8.2.4" , "@nestjs/core" : "^8.2.4" , "@nestjs/microservices" : "^8.2.4" , "bytenode" : "^1.3.6" , "electron-log" : "^4.4.6" , "electron-store" : "^8.0.1" , "electron-updater" : "^4.6.5" , "express" : "^4.17.3" , "fs-extra" : "^10.1.0" , "glob" : "^7.1.7" , "iconv-lite" : "^0.6.3" , "lodash" : "^4.17.21" , "md5" : "^2.3.0" , "mkdirp" : "^1.0.4" , "node-fetch" : "^2.6.7" , "protobufjs" : "^6.11.2" , "reflect-metadata" : "^0.1.13" , "rxjs" : "^7.5.1" , "semver" : "^7.3.7" , "systeminformation" : "^5.11.9" , "tcp-port-used" : "^1.0.2" , "url-parse" : "^1.5.10" , "uuid" : "^8.3.2" } }
通过"main":"main/index.js"
可知入口JS文件在main子目录下的index.js
文件。打开文件得到源码:
1 2 (function (_0x2c2b00,_0x266f24 ){function _0x495d9e (_0x5c6e5d,_0x2e5990,_0x92ffbf,_0xd034a8,_0x357357 ){return _0x2428 (_0x357357- -0x87 ,_0x2e5990);}function _0x245656 (_0x3c14a2,_0xf20f43,_0x684d2f,_0xbe1a9a,_0x4c1a31 ){return _0x2428 (_0xbe1a9a-0x39e ,_0x4c1a31);}function _0xf3f8ba (_0x3bb4fe,_0x3abbcb,_0x5b1533,_0x12f9ce,_0x428add ){return _0x2428 (_0x12f9ce-0x38e ,_0x3abbcb);}var _0x17d610=_0x2c2b00 ();function _0x3d68b3 (_0x2076a7,_0x106168,_0x234a88,_0x360ccc,_0x461c10 ){return _0x2428 (_0x234a88- -0x1b4 ,_0x360ccc);}while (!![]){try {var _0x277bc2=parseInt (_0x495d9e (0x1d5 ,'\x5a\x37\x47\x6d' ,0x267 ,0x1b1 ,0x231 ))/0x1 +parseInt (_0x495d9e (0x224 ,'\x24\x6a\x38\x33' ,0x18c ,0x17e ,0x211 ))/0x2 +parseInt (_0x495d9e (0x255 ,'\x40\x77\x78\x28' ,0x201 ,0x279 ,0x1ff ))/0x3 *(-parseInt (_0x495d9e (0x29a ,'\x34\x64\x67\x33' ,0x2fc ,0x2a5 ,0x253 ))/0x4 )+parseInt (_0x3d68b3 (0xcb ,0x2b ,0xb6 ,'\x6c\x40\x4a\x75' ,0x14a ))/0x5 +parseInt (_0x495d9e (0x158 ,'\x67\x52\x72\x4f' ,0xdc ,0x1ee ,0x133 ))/0x6 +parseInt (_0x495d9e (0xe6 ,'\x41\x38\x39\x68' ,0x130 ,0xbc ,0x14d ))/0x7 *(parseInt (_0x245656 (0x52c ,0x52c ,0x62d ,0x5e3 ,'\x79\x41\x4a\x78' ))/0x8 )+-parseInt (_0xf3f8ba (0x5cc ,'\x41\x66\x4d\x28' ,0x60f ,0x609 ,0x575 ))/0x9 *(parseInt (_0x495d9e (0x21e ,'\x6c\x7a\x29\x30' ,0x1e0 ,0xd5 ,0x172 ))/0xa );if (_0x277bc2===_0x266f24)break ;else _0x17d610['push' ](_0x17d610['shift' ]());}catch (_0x546e2a){_0x17d610['push' ](_0x17d610['shift' ]());}}}(_0x4a83,0x213df ));function _0x2428 (_0x222817,_0x44b30d ){var _0x30921e=_0x4a83 ();return _0x2428=function (_0xb30ec8,_0x2748e6 ){_0xb30ec8=_0xb30ec8-0x175 ;var _0x4a83e9=_0x30921e[_0xb30ec8];if (_0x2428['\x6f\x4e\x59\x48\x4d\x4f' ]===undefined ){var _0x2428bb=function (_0xdad3f3 ){var _0x322eb2='\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2b\x2f\x3d' ;var _0x42cc1a='' ,_0x3bd1d2='' ,_0x20b060=_0x42cc1a+_0x2428bb;for (var _0x140781=0x0 ,_0x3b67a1,_0x2ed931,_0x43927d=0x0 ;_0x2ed931=_0xdad3f3['\x63\x68\x61\x72\x41\x74' ](_0x43927d++);~_0x2ed931&&(_0x3b67a1=_0x140781%0x4 ?_0x3b67a1*0x40 +_0x2ed931 :_0x2ed931,_0x140781++%0x4 )?_0x42cc1a+=_0x20b060['\x63\x68\x61\x72\x43\x6f\x64\x65\x41\x74' ](_0x43927d+0xa )-0xa !==0x0 ?String ['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65' ](0xff &_0x3b67a1>>(-0x2 *_0x140781&0x6 )):_0x140781 :0x0 ){_0x2ed931=_0x322eb2['\x69\x6e\x64\x65\x78\x4f\x66' ](_0x2ed931);}for (var _0x4d79cc=0x0 ,_0x149750=_0x42cc1a['\x6c\x65\x6e\x67\x74\x68' ];_0x4d79cc<_0x149750;_0x4d79cc++){_0x3bd1d2+='\x25' +('\x30\x30' +_0x42cc1a['\x63\x68\x61\x72\x43\x6f\x64\x65\x41\x74' ](_0x4d79cc)['\x74\x6f\x53\x74\x72\x69\x6e\x67' ](0x10 ))['\x73\x6c\x69\x63\x65' ](-0x2 );}return decodeURIComponent (_0x3bd1d2);};var _0x3ee60f=function (_0x56923f,_0x4dd766 ){var _0x1c8920=[],_0x3cd08d=0x0 ,_0x1a10c0,_0x43d367='' ;_0x56923f=_0x2428bb (_0x56923f)...
0x02 混淆的简单理解
混淆的原理,按我个人的理解,就是把简单的代码复杂化。比如var a=1
这个表达式,混淆之后可以是var a=99;a=a-98;a=a+2-3;++a;--a;++a
,实际上,变量a的值(或者说a的语义)并没有发生改变。但是,数组 混淆之后,其序列内原本的成员顺序改变了,语义怎么保持和原来一致呢?所以,混淆后的JS代码可能会存在数组排序 的问题。
数组排序
以解混淆后的数组排序代码为例:
1 2 3 4 5 6 7 8 9 10 while (!![]) { try { var _0x277bc2 = parseInt (_0x495d9e (469 , "Z7Gm" , 615 , 433 , 561 )) / 1 + parseInt (_0x495d9e (548 , "$j83" , 396 , 382 , 529 )) / 2 + parseInt (_0x495d9e (597 , "@wx(" , 513 , 633 , 511 )) / 3 * (-parseInt (_0x495d9e (666 , "4dg3" , 764 , 677 , 595 )) / 4 ) + parseInt (_0x3d68b3 (203 , 43 , 182 , "l@Ju" , 330 )) / 5 + parseInt (_0x495d9e (344 , "gRrO" , 220 , 494 , 307 )) / 6 + parseInt (_0x495d9e (230 , "A89h" , 304 , 188 , 333 )) / 7 * (parseInt (_0x245656 (1324 , 1324 , 1581 , 1507 , "yAJx" )) / 8 ) + -parseInt (_0xf3f8ba (1484 , "AfM(" , 1551 , 1545 , 1397 )) / 9 * (parseInt (_0x495d9e (542 , "lz)0" , 480 , 213 , 370 )) / 10 ); if (_0x277bc2 === _0x266f24) break ;else _0x17d610["push" ](_0x17d610["shift" ]()); } catch (_0x546e2a) { _0x17d610["push" ](_0x17d610["shift" ]()); } } })(_0x4a83, 136159 );
可以看到当(_0x277bc2
与_0x266f24
全等 时,循环才会跳出。如果不全等,则会执行push
和shift
操作,在数组末尾添加成员,或移除数组成员。以后在混淆的JS代码中,看到循环里对数组的push
和shift
,可以先尝试理解成数组排序。
0x03 解混淆
在这里,先用JS NICE: Statistical renaming, Type inference and Deobfuscation 工具解混淆。这个工具可以将混淆的代码格式化,来变的更可读。但实际在调试过程中,如果遇到函数码表这类反调试措施可能无法让JS文件正常调试下去,具体细节下文会详细说明。
通过工具得到反混淆后代码。此处省略,因为代码太多了笔记会卡就不贴上了。
AST树解混淆
如果是手动反混淆的话,**抽象语法树(Abstract Syntax Tree,AST)**是一个很好的工具。它把JS文件中的每个语句都拆成类似JSON这种树状结构的数据类型,以树状的形式表示JS文件的语法结构,树上的每个结点都表示源代码的一种结构。具体进行分析时,我们可以使用AST explorer 对语句进行分析。
在使用ASTexplorer之前,需要把Parser设置为@bebel/parser
,这是因为在第一节解包的时候发现该应用依赖于@bebel/parser
,所以要设置成这个格式。
混淆的源码中,存在着大量类似\x63\x64\x65\x66
与0xff
这类十六进制数据,我们可以直接对其进行解析:
同样的,AST树中也是直接把这些十六进制的数据给出来了:
可以观察到,最后的值value
的父节点是StringLiteral
和NumericLiteral
。我们可以使用@babel/generator
生成混淆代码的AST树,通过@babel/traverse
遍历每个节点,找到从@babel/types
引用的数据类型后,提取value
替换即可达到一个简单的反混淆效果。
下面是一个替换十六进制字符串的简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 let fs = require ('fs' ) var parser = require ('@babel/parser' ) var traverse = require ('@babel/traverse' ).default var generator = require ('@babel/generator' ).default const {StringLiteral ,NumericLiteral } = require ('@babel/types' ) let t_code = fs.readFileSync ('index.js' ,{encoding :"utf-8" }) let ast = parser.parse (t_code) traverse (ast,{ StringLiteral (path){ let value = path.node .value path.replaceWith (StringLiteral (value)) path.skip () }, NumericLiteral (path){ let value = path.node .value path.replaceWith (NumericLiteral (value)) path.skip () } })let code = generator (ast).code fs.writeFileSync ('2.js' ,code,{encoding :"utf-8" })
替换之后的2.js
文件内容为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 (function (_0x2c2b00, _0x266f24 ) { function _0x495d9e (_0x5c6e5d, _0x2e5990, _0x92ffbf, _0xd034a8, _0x357357 ) { return _0x2428 (_0x357357 - -135 , _0x2e5990); } function _0x245656 (_0x3c14a2, _0xf20f43, _0x684d2f, _0xbe1a9a, _0x4c1a31 ) { return _0x2428 (_0xbe1a9a - 926 , _0x4c1a31); } function _0xf3f8ba (_0x3bb4fe, _0x3abbcb, _0x5b1533, _0x12f9ce, _0x428add ) { return _0x2428 (_0x12f9ce - 910 , _0x3abbcb); } var _0x17d610 = _0x2c2b00 (); function _0x3d68b3 (_0x2076a7, _0x106168, _0x234a88, _0x360ccc, _0x461c10 ) { return _0x2428 (_0x234a88 - -436 , _0x360ccc); } while (!![]) { try { var _0x277bc2 = parseInt (_0x495d9e (469 , "Z7Gm" , 615 , 433 , 561 )) / 1 + parseInt (_0x495d9e (548 , "$j83" , 396 , 382 , 529 )) / 2 + parseInt (_0x495d9e (597 , "@wx(" , 513 , 633 , 511 )) / 3 * (-parseInt (_0x495d9e (666 , "4dg3" , 764 , 677 , 595 )) / 4 ) + parseInt (_0x3d68b3 (203 , 43 , 182 , "l@Ju" , 330 )) / 5 + parseInt (_0x495d9e (344 , "gRrO" , 220 , 494 , 307 )) / 6 + parseInt (_0x495d9e (230 , "A89h" , 304 , 188 , 333 )) / 7 * (parseInt (_0x245656 (1324 , 1324 , 1581 , 1507 , "yAJx" )) / 8 ) + -parseInt (_0xf3f8ba (1484 , "AfM(" , 1551 , 1545 , 1397 )) / 9 * (parseInt (_0x495d9e (542 , "lz)0" , 480 , 213 , 370 )) / 10 ); if (_0x277bc2 === _0x266f24) break ;else _0x17d610["push" ](_0x17d610["shift" ]()); } catch (_0x546e2a) { _0x17d610["push" ](_0x17d610["shift" ]()); } } })(_0x4a83, 136159 );
0x04 绕过反调试机制
通过上节方法解混淆之后,可以得到一个可读性稍强的代码,这时候就可以开始调试了。但是程序设计者肯定不会让我们这么顺利的调试的,往往在混淆的过程中加一些反调试机制,也就是暗桩。
这时候就需要秉持着一个原则:
大函数之间下断点
函数内,代码多的函数先步过(一口气走完跳过)
直到出现异常情况(程序崩溃、大量循环),开始分析暗桩
第一点还是比较好理解的,因为在程序中,往往是从上向下执行。如果一个JS文件中有四个函数,可以先在AB函数之间写个console.log
,看是否输出,如果程序异常且无输出,说明A函数存在暗桩。
注:JavaScript存在声明提升 机制,更多说明与利用见龙哥博客:【Javascript】声明提升 - SomebodyNoStation
第二、三点,就是常规调试操作,没出现异常就步过,出现异常就步进,直至确认异常出现在哪行代码中。在这里,先从_0x2428
函数之前加个console.log
,投石问路:
看图可知,程序执行完毕跳出且没有打印控制台命令。也就是说程序并没有执行完所有的语句就退出了,可能存在反调试机制……
内存爆破
从var _0x277bc2
处打断点,程序运行到此处暂停,步过后停止。也就是说反调试存在此行子函数中。继续调试,暂停到此行后步入,发现以下代码较为可疑:
首先_0x24ecd3
匹配了两个正则表达式,具体匹配的什么先不管~~(其实是看不懂)~~,看下面的103行:
1 _0x32c41e = _0x24ecd3["test" ](this ["myBoDO" ]["toString" ]()) ? --this ["RfnWOy" ][1 ] : --this ["RfnWOy" ][0 ];
这里对this["myBoDO"]["toString"]()
进行了匹配正则,也就是对function () {return "newState";
的格式化后的字符串进行了正则匹配,如果匹配成功则返回-1,匹配不成功则返回0。最后该函数带参数返回this["CugJBF"](_0x32c41e)
。
this["myBoDO"]["toString"]()
也可以写成this["myBoDO"]+''
的形式,语义不会发生改变。
而在下面的函数中:
1 2 3 4 _0x3b755e["prototype" ]["CugJBF" ] = function (_0x893c54 ) { if (!Boolean (~_0x893c54)) return _0x893c54; return this ["RzzIoo" ](this ["xdQAnv" ]); },
已知传入参数只有-1和0,这样我们可以直接从控制台中打印最后的结果:
当传入值为-1时,也就是正则匹配成功时才会直接返回-1,否则不成功的话会接着运行return this["RzzIoo"](this["xdQAnv"])
。
现在看一下这个函数究竟是个什么东西,这里的代码使用JSNICE反混淆后的代码,更容易阅读。
1 2 3 4 5 6 7 8 9 WMCacheControl ["prototype" ]["RzzIoo" ] = function (saveNotifs ) { var fp = 0 ; var len = this ["RfnWOy" ]["length" ]; for (; fp < len; fp++) { this ["RfnWOy" ]["push" ](Math ["round" ](Math ["random" ]())); len = this ["RfnWOy" ]["length" ]; } return saveNotifs (this ["RfnWOy" ][0 ]); };
这里简单来说就是不断生成随机数,并加入到数组中;虽然fp是每次循环都+1,但是数组的长度也是不断增长的,所以这个循环根本就不会停止 。而储存在内存里的数组元素也是不断增长的,所以就会实现一个内存爆破的效果。
更直观的图片可以直接看下图:
至于绕过方法,有很多种,其实原理都是一样的,不要让他爆破就行了:
105行直接返回值改true;
调换103行-1和0的结果值;
直接删除掉内存爆破函数;
……
函数编码表
解决完内存爆破的问题后,在调试过程中不会异常退出了,但会出现程序在很长时间都一直运行的问题……说明程序陷入了某个循环或调用链中,需要再次进行调试。通过打断点发现,出问题的存在_0x2428(row, value)
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function _0x2428 (row, value ) { var custom_filters = _0x4a83 (); return _0x2428 = function (i, value ) { i = i - 373 ; var text = custom_filters[i]; if (_0x2428["oNYHMO" ] === undefined ) { var getOwnPropertyNames = function (o ) { var listeners = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=" ; var PL $13 = "" ; var urn = "" ; var data = PL $13 + getOwnPropertyNames; var bc = 0 ; var bs; var buffer; var n = 0 ; for (; buffer = o["charAt" ](n++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4 ) ? PL $13 = PL $13 + (data["charCodeAt" ](n + 10 ) - 10 !== 0 ? String ["fromCharCode" ](255 & bs >> (-2 * bc & 6 )) : bc) : 0 ) { buffer = listeners["indexOf" ](buffer); } var PL $19 = 0 ; var PL $15 = PL $13["length" ]; for (; PL $19 < PL $15; PL $19++) { urn = urn + ("%" + ("00" + PL $13["charCodeAt" ](PL $19)["toString" ](16 ))["slice" ](-2 )); } return decodeURIComponent (urn); }; }
需要注意的一点,在var data=PL$13 + getOwnPropertyNames;
这个语句中,实际上就是var data=getOwnPropertyNames+''
。它会把getOwnPropertyNames
这个函数以字符串的形式储存在data
中。而在(data["charCodeAt"](n + 10) - 10 !== 0 ? String["fromCharCode"](255 & bs >> (-2 * bc & 6)) : bc) : 0)
,发现了程序在对data
进行数据操作,可以初步确定该函数是以其字符串化的数据为码表,进行编解码的。
但这里需要注意:经过测试,使用JSNICE
解混淆后的代码是没法过函数码表调试的,因为解码后的函数和变量被重新命名,语义发生了改变。
所以这里需要使用AST树
反混淆后的代码进行绕过:
1 2 3 4 5 6 7 8 9 10 11 12 if (_0x2428["oNYHMO" ] === undefined ) { var _0x2428bb = function (_0xdad3f3 ) {var _0x322eb2="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=" ;var _0x42cc1a="" ,_0x3bd1d2="" ,_0x20b060=_0x42cc1a+_0x2428bb; for (var _0x140781 = 0 , _0x3b67a1, _0x2ed931, _0x43927d = 0 ; _0x2ed931 = _0xdad3f3["charAt" ](_0x43927d++); ~_0x2ed931 && (_0x3b67a1 = _0x140781 % 4 ? _0x3b67a1 * 64 + _0x2ed931 : _0x2ed931, _0x140781++ % 4 ) ? _0x42cc1a += _0x20b060["charCodeAt" ](_0x43927d + 10 ) - 10 !== 0 ? String ["fromCharCode" ](255 & _0x3b67a1 >> (-2 * _0x140781 & 6 )) : _0x140781 : 0 ) { _0x2ed931 = _0x322eb2["indexOf" ](_0x2ed931); } for (var _0x4d79cc = 0 , _0x149750 = _0x42cc1a["length" ]; _0x4d79cc < _0x149750; _0x4d79cc++) { _0x3bd1d2 += "%" + ("00" + _0x42cc1a["charCodeAt" ](_0x4d79cc)["toString" ](16 ))["slice" ](-2 ); } return decodeURIComponent (_0x3bd1d2); };
因为在JS代码未格式化之前,所有的语句都是尽量连在一起的,所以在这里我们需要去掉语句之间的空格。再次调试后会发现已经绕过了函数码表,面对我们的是最后一个障碍……
拒绝服务
绕过函数码表后,还是不断打断点进行调试,发现程序运行到_0x3cd8b0()
后卡死。继续在_0x3cd8b0()
打断点,F11步入到var _0x5a2da4
中。按照调试的原则,直到出异常后重新打断点步入。发现在下面代码中无响应:
1 var _0x223952 = _0x4ad3cf[_0x1b6697 ("Q[uh" , 338 , 440 , 475 , 346 )](_0x425e28, arguments );
打断点,停止程序,重新调试。运行到该步时,对该行三个子函数全打断点后步过,发现前两个顺利步过,由此可知_0x425e28
处存在暗桩:
之后f11步入到var _0x377802
,步过一次,发现到了return
,一堆子函数,依然是全打断点步过:
调试到_0x377802
处卡死,这时候猛地发现:这个_0x3cd8b0()
在return处也有……继续分析_0x3cd8b0()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 _0x3cd8b0 = _0x384e83 (this , function ( ) { function _0x1db718 (_0x5b392e, _0x43b334, _0x2e2186, _0x51608b, _0x1e40ec ) { return _0x2428 (_0x51608b - 418 , _0x2e2186); } function _0x5852f4 (_0x879a90, _0x1557ee, _0xa232dd, _0x533bbc, _0x32f796 ) { return _0x2428 (_0x879a90 - 467 , _0x32f796); } var _0x377802 = { "uawfM" : _0x5bc29b (-299 , -136 , -166 , -154 , "pf7m" ) + _0x5bc29b (-337 , -265 , -385 , -263 , "tR1%" ) + "+$" }; function _0x2129cf (_0xb35e3c, _0x58b231, _0x3ea525, _0x3dfb26, _0x54f224 ) { return _0x2428 (_0x54f224 - -299 , _0x58b231); } function _0x5bc29b (_0x29383b, _0x2783c2, _0xab345a, _0x222ff3, _0x444bd3 ) { return _0x2428 (_0x222ff3 - -662 , _0x444bd3); } return _0x3cd8b0[_0x5bc29b (-310 , -327 , -203 , -271 , "PVAS" ) + _0x2129cf (526 , "IVdS" , 443 , 392 , 439 )]()[_0x5852f4 (989 , 1150 , 955 , 1088 , "l@Ju" ) + "h" ](_0x377802[_0x5bc29b (84 , 1 , -69 , 79 , "u4os" )])[_0x2129cf (279 , "uiYA" , 254 , 247 , 428 ) + _0x5bc29b (-229 , 25 , -135 , -55 , "PVAS" )]()[_0x5852f4 (1087 , 1013 , 946 , 1105 , "lz)0" ) + _0x5bc29b (208 , 101 , 98 , 21 , "lz)0" ) + "r" ](_0x3cd8b0)[_0x2129cf (285 , "b#3e" , 379 , 555 , 433 ) + "h" ](_0x377802[_0x5bc29b (-93 , -192 , -239 , -277 , ")Ua$" )]); });
发现存在四个子函数,但是最后都返回调用了_0x2428
,而_0x2428
恰恰是上步我们研究码表时的函数,猜测主要起解码的作用。这里也可以大致猜到,_0x3cd8b0
不断嵌套调用解码函数,起到了一个拒绝服务的作用。但具体表示什么含义呢?不妨先将方括号里的参数用控制台打印输出一下:
1 2 3 4 5 6 7 _0x5bc29b (-310 , -327 , -203 , -271 , "PVAS" ) + _0x2129cf (526 , "IVdS" , 443 , 392 , 439 ) ==> 'toString' _0x5852f4 (989 , 1150 , 955 , 1088 , "l@Ju" ) + "h" ==> 'search' _0x5bc29b (84 , 1 , -69 , 79 , "u4os" ) ==> 'uawfM' _0x2129cf (279 , "uiYA" , 254 , 247 , 428 ) + _0x5bc29b (-229 , 25 , -135 , -55 , "PVAS" ) ==> 'toString' _0x5852f4 (1087 , 1013 , 946 , 1105 , "lz)0" ) + _0x5bc29b (208 , 101 , 98 , 21 , "lz)0" ) + "r" ==> 'constructor' _0x2129cf (285 , "b#3e" , 379 , 555 , 433 ) + "h" ==> 'search' _0x5bc29b (-93 , -192 , -239 , -277 , ")Ua$" ) ==> 'uawfM'
综合起来就是:
1 _0x3cd8b0['toString' ]()['search' ](_0x377802['uawfM' ])['toString' ]()['constructor' ](_0x3cd8b0)['search' ](_0x377802['uawfM' ]);
因为_0x377802
这个函数在上面有体现,还可以进一步替换:_0x3cd8b0.toString().search('(((.+)+)+)+$').toString()
这个表达式的语义是,_0x377802.toString()
把函数字符串输出,然后正则匹配(((.+)+)+)+$
规则,之后输出字符串。但是通过我们调试可以发现,这个字符串是肯定输出不出来的,因为卡住了……
经过测试,发现这个语句是一个基于正则表达式的拒绝服务 ,如果字符串里有回车,第一行正文长度大于14个字符的情况下,就拒绝响应。
直接注释掉这个函数,就可以看到最后的曙光:
这里错误信息表示我没有electron
模块,是因为我这里没有进行环境配置(使用混淆代码直接进行调试会发现相同的错误)。如果配置好electron环境后,就可以发现应用打开了。