JS代码混淆

JS代码混淆

就像XSS那样,JS代码不经过过滤,直接呈现在浏览器中会被直接执行;因此现在的输入框都对JS代码进行了转码过滤,避免恶意代码的执行

得益于JS代码本身的特性,所有的JS代码能够由最初的几个字符构造而成,这几个字符在《离散数学》中类似全功能集的定义

混淆方式

JSFuck (JSF*ck)

日本安全研究人员长谷川阳介制作了名为JSF*ck的编码器,能够将所有JS代码用6个字符(分别是[]()!+)来表示(称之为用6个字符来混淆JavaScript

  1. 构造数字

    数字0使用+[]来构造,[]代表空数组、而+是一元加运算符

    数字1使用+!![]+!+[]来构造,其中!![]!+[]代表布尔值true,前置一元加运算符将真值true转换为1

    数字2通过真值的相加获得,true+true这一表达式在JS中输出结果为2,而true可以写作!![]!+[],所以2可以表示为!![]+!![]!+[]+!+[]

    数字39方法相同

    多位数字通过字符串串接得到,如10,不采用10个true相加,而是先得到字符串"10";字符串"10"可以表达为两个数组的串接形式[1]+[0],而将其中的数字替换为JSFuck,就有了[+!+[]]+[+[]];有了字符串"10"后,在JS中将数字型字符串转换为数值,只需在字符串括在"括号"或"方括号"中,并加上一个+运算符,最终10的JSFuck表达为+([+!+[]]+[+[]])

    JSFuck的实现得益于JS本身的坑爹性,如:

    • 空数组[]前置一元加运算符+变成0,前置一元布尔运算符!变成false!![] = true
    • 布尔值truefalse前置一元加运算符+能变成对应的10结合上一条,就有+!![] = 1
    • 两个数组相加,得到由元素组成的字符串;[1,2]+[3] = "1,23",你没看错,连分隔符,都在;还可以利用[]+[] = ""得到空字符串,然后用空字符串与布尔值false相加,得到"false"

    你也能理解为什么长谷川阳介先生将它命名为JSFuck而不是JSPraise了

  2. 构造字母

    • 索引构造——常见字符串

      []+[]+[![]] = "false"能够得到字符串"false"一样,通过下标"false"[0]就能够得到字母"f",将下标的数字0换成JSFuck,那么"f"就能表示成([]+[]+[![]])[+![]]

      除了![] = false外,内置的可以利用的字符串还有!![] = true+[![]] = NaN[][[]] = undefined,以及+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]) = Infinity

      ↑ 上面的+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])是将字符串1e1000转换成数字得到的

      将上面的内置显示单词转换为字符串,再用索引提取,能够构造部分字母

      补充

      将Boolean类型转换为String,可以通过:

      []+[]+false,这是利用[]+[]得到的空字符串,后面的强制转化为字符串

      []+false,这是利用JS神奇的强制转换:Array + Boolean = String

  3. 构造函数

    通过JSFuck构造字母,得到了字符串"alert(1)",却发现它并不能像普通的JS代码那样执行——因为这里的"alert(1)"是字符串,而不是语句

    怎样才能变成alert()函数执行呢?

    这里需要对JavaScript有比较深入的了解,由于鄙人没时间,所以直接贴出一个答案:[]["filter"]["constructor"]("alert(1)")()等价于语句alert(1)

    上面的例子通过三个字符串"filter""constructor""alert",将"alert()"从字符串变为执行语句alert()

    解释

    语句alert(1)等价于Function("alert(1)")()Function是类似其它语言中的eval函数,用于触发执行包含在字符串中的JavaScript代码

    Function是JS中任何常用函数constructor属性,这里的常用函数是指[]["filter"](即Array.prototype.filter)之类的函数;访问一个空数组的filter属性下的constructor属性,就能够构造出一个Function

    这也就是为什么语句alert(1)能被表示成[]["filter"]["constructor"]("alert(1)")()的原因

  4. 构造字母(2)

    • 索引构造——其它字符串

      前面的索引构造是利用内置的显示字符串,如falsetrueNaNundefinedInfinity,除了这几个比较容易得到的字符串之外,还有其它"冷门"的方法

      • []["find"] + []会得到字符串"function find() { [native code] }"

        原理是空数组[]拥有许多方法和属性,像访问Python的字典元素那样访问[]find方法,虽然不能直接执行,但加上一个[]find强制转换为字符串,就会显示出数组find()方法的一些信息

        "find"能够从"undefined"中提取,执行[]["find"] + []后就多出了几个字符,如o{

      • []["entries"]() + []会得到字符串"[object Array Iterator]"

        能够得到新的字符jA

      • ......

以上就是JSFuck的大致实现思路,了解学习一下混淆的概念

正是因为混淆,JSFuck代码能够被浏览器直接运行,相当于等价的JavaScript代码

如果想深入了解JSFuck的全部实现过程,可以去作者的Github上看,有十分完整的过程,作者的jsfuck.js文件就罗列出了所有字符的表示法

【本部分参考】

JSFuck Wiki https://zh.wikipedia.org/wiki/JSFuck

JSFuck作者Github https://github.com/aemkei/jsfuck


aaencode

aaencode也是JS的一种混淆方式,又称为颜文字编码

经过aaencode混淆的JS代码十分显眼,通篇代码呈现(゚Д゚)(゚o゚)(゚Θ゚)之类的颜文字,相当可爱

举个例子:

o=(˙_˙) =_=3; 是某一段aaencode得到的代码,表面上看这里只有两个颜文字:o=(˙_˙)=_=3,但其实这里用JS语法定义了三个变量

将颜文字以=为分隔符,拆分开,得到 o = (˙_˙) = _ = 3;

其实就是

1
2
3
_ = 3;
(˙_˙) = _;
o = (˙_˙);

三个赋值语句的连写形式

赋值语句连写在弱类型语言中都可以实现,比如Python

人眼由于对颜文字的敏感,下意识将 o=(˙_˙) =_=3; 拆分解读为 o=(˙_˙)=_=3; ;而对浏览器而言,它会尝试以JS的语法去分析它,这时它就以=为分隔符,将 o=(˙_˙) =_=3; 分析成一条三赋值的JS语句

语句 o=(˙_˙) =_=3; 让变量 o(˙_˙)_ 同时赋值为3;随后就能够在后续代码中用这三个变量替代3

类似的代码有 c=(゚Θ゚) =(゚ー゚)-(゚ー゚); 暗藏了一个减运算,使得变量 (゚Θ゚) 值为0(前提是变量 (゚ー゚) 已定义)

混淆JS代码是有代价的,代价之一是冗余,像上式的 (˙_˙),其实就是一个用()包起来的 ˙_˙ 变量,这对括号()事实上是没什么意义的,只是为了构成颜文字额外添加的

冗余的一个体现是不必要的部分细小的代码,另一个体现就是前置条件

前置条件

哪怕是空的JS代码,扔到某个aaencode编码器中也会得到很长的一串编码:

゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

将JS代码alert(1)经过aaencode混淆,得到:

゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

对比一下这两段aaencode代码,你会发现两者的前面部分完全一样,变的只是后面的代码

前面一样的aaencode代码就是所谓的前置条件,无论对什么JavaScript代码进行aaencode混淆,混淆后的代码中,前面部分的前置条件都是相同的

原因是因为,想要用颜文字混淆JS代码,哪怕只是简单的一句alert(1)也是很难实现的

还记得JSFuck得到语句 alert(1) 的步骤吗?先通过索引的方法从 "false" 中取出 "a""l""e";从 "true" 中取出 "r""t";用某个方法得到 "1""("")"

有了字符串"alert(1)"还不够,还有将字符串转换为语句:[]["filter"]["constructor"]("alert(1)")(),那么这时又得想办法得到"filter""constructor" 了...

冗余是混淆的通病,JSFuck是,aaencode也不例外

用aaencode混淆的 alert(1),需要经过提前的赋值,然后就是与JSFuck类似的操作:拼凑出字符串、字符串转语句、为了转语句需要额外的字符串...

可以将前置条件理解成JSFuck中的字符串转语句代码:[]["filter"]["constructor"]("你的代码")(),正是因为字符串转语句的重要性,所以前置条件才会作为"统一"的前缀,出现在任意JS代码的混淆中

这也是为什么明明是空的JS代码,还能给你混淆出一大段aaencode代码的原因...

前置条件取决于具体的混淆方式,方式不同前置条件也不一样,如果自己写一个aaencode颜文字编码,那么前置条件就取决于你怎么编

想深入追踪一下aaencode的编码过程,可以访问这篇博文

【本部分参考】

TechBridge文章 https://blog.techbridge.cc/2016/07/16/javascript-jsfuck-and-aaencode/

aaencode在线编码器 http://utf-8.jp/public/aaencode.html

CSDN yudhui文章 https://blog.csdn.net/github_36788573/article/details/102866435


jjencode

jjencode同样又长谷川阳介先生创建,只是与JSFuck的创建时间不同

jjencode创建于2009年7月,JSFuck则是2010年底;jjencode先于JSFuck,而在混淆能力上,jjencode弱于JSFuck——jjencode将JS代码混淆为[]()!+,\"$.:;_{}~= 这18个字符的排列组合

可以说jjencode是JSFuck的早期弱化版本

Perl语言有ppencode混淆、Ruby语言有rrencode,所以从jjencode的命名也能看出这是最初的JS混淆

详细解析jjencode的混淆方式可以查看文章,很棒的文章。我没那耐心慢慢看jjencode源码,也对JavaScript无感: )

自定义

JSFuck混淆的灵活性很差,因为它是基本固定的,拿 1 举个例子,或许除了 +!![]+!+[] 外,或许还能通过其它办法得到 1 ,但终究是有限的,因为JSFuck困死在 []()!+ 这六个字符

JSFuck给你的发挥余地不多

但是jjencode不同,尽管它也困死在18个字符中,但它能够实现自定义jjencode

所谓"自定义"是指变量名自定义,而代码逻辑是一样的,混淆本身就追求的是"看上去"不一样

  • 全局变量gv(默认的jjencode全局变量为$

    空的JavaScript代码经过jjencode混淆为:

    $=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"("+$.__$+")"+"\"")())();

    这是之前提到的"前置条件"

    我们来稍微解析一下这段jjencode代码。首先看第一条语句:

    1
    $ = ~[];

    这里将变量 $ 赋值为 -1:空数组[]在被当作数使用时,表示0;而~是按位取反操作,有~0 = -1

    然后是一个比较长的Hash表语句:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    $ = {
    ___ : ++$,
    $$$$ : (![]+"")[$],
    __$ : ++$,
    $_$_ : (![]+"")[$],
    _$_ : ++$,
    $_$$ : ({}+"")[$],
    $$_$ : ($[$]+"")[$],
    _$$ : ++$,
    $$$_ : (!""+"")[$],
    $__ : ++$,
    $_$ : ++$,
    $$__ : ({}+"")[$],
    $$_ : ++$,
    $$$ : ++$,
    $___ : ++$,
    $__$ : ++$
    };

    这段代码通过已有的 $ = -1 ,直接设置了16个新变量:___$$$$__$$_$__$_$_$$$$_$_$$$$$_$__$_$$$__$$_$$$$___$__$

    在浏览器中运行下这段代码,查看这些变量分别被赋予了什么值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ___ : 0
    $$$$ : "f"
    __$ : 1
    $_$_ : "a"
    _$_ : 2
    $_$$ : "b"
    $$_$ : "d"
    _$$ : 3
    $$$_ : "e"
    $__ : 4
    $_$ : 5
    $$__ : "c"
    $$_ : 6
    $$$ : 7
    $___ : 8
    $__$ : 9

    $ 之所以在jjencode中被称为全局变量,就是因为所有变量以及后续操作都源于最初的$ = ~[];

    如果把全局变量改成,就有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    帅 = ~[];
    帅 = {
    ___ : ++帅,
    $$$$ : (![]+"")[帅],
    __$ : ++帅,
    $_$_ : (![]+"")[帅],
    _$_ : ++帅,
    $_$$ : ({}+"")[帅],
    $$_$ : (帅[帅]+"")[帅],
    _$$ : ++帅,
    $$$_ : (!""+"")[帅],
    $__ : ++帅,
    $_$ : ++帅,
    $$__ : ({}+"")[帅],
    $$_ : ++帅,
    $$$ : ++帅,
    $___ : ++帅,
    $__$ : ++帅
    };
  • 内置的16个初始变量

    你留意到了吗?

    第二条长长的Hash表语句中定义的16个变量也是可以更改的,把这16个变量改成你喜欢的变量名,并且在源代码的其它对应地方也进行更改,那么你就能够实现自定义的jjencode

比如,语句 alert(1);http://utf-8.jp/public/jjencode.html 上的默认混淆是:

$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"("+$.__$+");"+"\"")())();

将全局变量从 $ 替换成 风伯,然后修改jjencode源码,依次替换掉16个变量名为:盘古女娲伏羲精卫夸父蚩尤黄帝炎帝刑天共工祝融大禹后羿嫦娥东皇太一女魃

再利用这段修改过后的jjencode对 alert(1) 进行编码,得到

风伯=~[];风伯={盘古:++风伯,女魃:(![]+"")[风伯],女娲:++风伯,祝融:(![]+"")[风伯],伏羲:++风伯,大禹:({}+"")[风伯],嫦娥:(风伯[风伯]+"")[风伯],精卫:++风伯,东皇太一:(!""+"")[风伯],夸父:++风伯,蚩尤:++风伯,后羿:({}+"")[风伯],黄帝:++风伯,炎帝:++风伯,刑天:++风伯,共工:++风伯};风伯.$_=(风伯.$_=风伯+"")[风伯.蚩尤]+(风伯._$=风伯.$_[风伯.女娲])+(风伯.$$=(风伯.$+"")[风伯.女娲])+((!风伯)+"")[风伯.精卫]+(风伯.__=风伯.$_[风伯.黄帝])+(风伯.$=(!""+"")[风伯.女娲])+(风伯._=(!""+"")[风伯.伏羲])+风伯.$_[风伯.蚩尤]+风伯.__+风伯._$+风伯.$;风伯.$$=风伯.$+(!""+"")[风伯.精卫]+风伯.__+风伯._+风伯.$+风伯.$$;风伯.$=(风伯.盘古)[风伯.$_][风伯.$_];风伯.$(风伯.$(风伯.$$+"\""+风伯.祝融+(![]+"")[风伯.伏羲]+风伯.东皇太一+"\\"+风伯.女娲+风伯.黄帝+风伯.伏羲+风伯.__+"("+风伯.女娲+")"+"\"")())();

(还能继续改,因为还有一些变量如 $_$__ 都未更改)

把上面这段代码复制到浏览器的Console中执行,是会有消息框出现显示"1",也就是意味着执行了语句 alert(1)

碰巧最近热议着"完全自主开发的"木兰编程语言,我觉得我上我也行,看我上面的代码,拿去唬人不成问题

【本部分参考】

Eller博客文章 https://eller.tech/post/33

JSFuck Wiki https://zh.wikipedia.org/zh-hans/JSFuck


Decode

  • .toString()

    事实上许多JS混淆的基本思路都与JSFuck一样,即[]['filter']['constructor'][你的代码]()

    注意到最后面的 () ,它表示调用函数,那么将它替换为 .toString(),就能阻止前面函数的执行、并且把函数转换成字符串输出出来

    就目前介绍到的3中混淆方式而言,JSFuck和jjencode都能通过置换最后的 ().toString(),还原JS代码;而aaencode不行,它本身最后面就不是以 () 结尾

    举例

    alert(1); 进行JSFuck加密后,把密文放在浏览器的Console中,将最后的 () 替换为 .toString(),执行

    结果会输出

    1
    2
    3
    4
    >"function anonymous(
    >) {
    >alert(1);
    >}"
  • hook function constructor

    1
    2
    3
    4
    5
    6
    Function.prototype.__defineGetter__('constructor', function() {
    return function(...args) {
    console.log('code:', ...args);
    return Function(...args);
    };
    });

    这个方法直接修改了深层的JS功能,需要对JS有较深入的了解

    原本函数调用只是单纯地调用该函数,但是修改后的JS能够在执行该函数的同时,把函数的执行语句通过 console.log() 输出出来

    使用方法就是复制上面的代码,在浏览器的Console中执行,随后就可以执行一些JS混淆代码

    以无法通过 .toString() 还原的aaencode为例,当浏览器的Console执行上面的代码后,再执行以下aaencode代码:

    ゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (o^_^o))+ (o^_^o)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

    除了弹出消息框显示"1"外,还会在Console中出现语句:code: alert(1);

  • 在线帮助

    大部分JS混淆都已经广为人知,有许多人找寻对应的decode方法,比如:

    考验你的信息检索能力了

  • 手工计算

    手工将JS混淆还原的确是可行的,权当在练习JavaScript的类型转换

    网络上还有人承接此类工作呢:


混淆的意义

  • 正面的

    这篇文章只是在某道CTF题目之后延申出来学习用的,就我而言混淆JS代码能给解CTF题目的人增添一些困惑

    除此之外似乎没有什么用了,just interesting

  • 负面的

    • 代码量增多

    • 非加密

      混淆不是加密,完全可以在仅知道混淆后代码的情况下,还原出原代码

    • 有可能失效

      经过混淆后的JS代码有可能无法使用,这样的原因有以下几种:

      1. JS代码不规范,iffor 语句没有 {};代码没有以 ; 结束等
      2. JS代码中有大量复杂的正则表达式

因此往往建议对JS代码进行加密后再混淆,效果倍增,别人根本看不懂


总结

  • JSFuck:仅由 []()+! 六个字符组成

  • aaencode:颜文字编码

  • jjencode:由[]()!+,\"$.:;_{}~= 18个字符组成,自定义能力强

    明显特征为开头的 全局变量=~[];,默认的全局变量为 $

解码方法:.toString()hook function constructor;在线网站解码


end