Javascript-AAencode-混淆-分析

本文最后更新于:2021年3月11日 下午

encode混淆

大体思路

这种让你看不懂代码的方式是通过编码实现的

编译器能都识别那些 由 16进制字符 或者 Unicode 字符
一般而言为了 让字符看起来非常难顶,代码作者会设计将字符转换成一些奇怪的字符
但为了不影响代码逻辑,这些奇怪字符的值依然与原本保持一致

代码会被保存为非常长的字符串格式
在多数情况下是类似于这样

  • ()["constructor"]["constructor"](执行代码)()
  • eval(执行代码)

当然,它不会明面的把函数写上,他会绕来绕去,让你看不出

AAencode

分析

既然是一个开源的,那就查看一下源码吧
Github: https://github.com/bprayudha/jquery.aaencode

源码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
(function($) {
var aaencode = function(text) {
var t;
var b = [
"(c^_^o)" ,
"(゚Θ゚)" ,
"((o^_^o) - (゚Θ゚))" ,
"(o^_^o)" ,
"(゚ー゚)" ,
"((゚ー゚) + (゚Θ゚))" ,
"((o^_^o) +(o^_^o))" ,
"((゚ー゚) + (o^_^o))" ,
"((゚ー゚) + (゚ー゚))" ,
"((゚ー゚) + (゚ー゚) + (゚Θ゚))" ,
"(゚Д゚) .゚ω゚ノ" ,
"(゚Д゚) .゚Θ゚ノ" ,
"(゚Д゚) ['c']" ,
"(゚Д゚) .゚ー゚ノ" ,
"(゚Д゚) .゚Д゚ノ" ,
"(゚Д゚) [゚Θ゚]"
];
var r = "゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); ";
r += "(゚Д゚) =(゚Θ゚)= (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゚]+ ";

for( var i = 0; i < text.length; i++ ) {
n = text.charCodeAt( i );
t = "(゚Д゚)[゚ε゚]+";
if( n <= 127 ) {
t += n.toString( 8 ).replace( /[0-7]/g, function(c) {
return b[ c ] + "+ ";
});
}
else {
var m = /[0-9a-f]{4}$/.exec( "000" + n.toString(16 ) )[0];
t += "(o゚ー゚o)+ " + m.replace( /[0-9a-f]/gi, function(c) {
return b[ parseInt( c,16 ) ] + "+ ";
});
}
r += t;

}

r += "(゚Д゚)[゚o゚]) (゚Θ゚)) ('_');";

return r;
}

$.fn.aaencode = function() {
return aaencode(this.val());
}
})(jQuery);

逐一调试观察逻辑并记录
(为了调试方便改写了一些格式,但内容没改)

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
function _aaencode(text) {
var t;
// 定义一些表情字符用作替代目标, 里面的每一个表情都对应着一个真正的值
// 在那些变量被赋值后(如(c^_^o)的c和o),整个标签符号的值才会真正显现出来
// 0-9 a-f
var b = [
"(c^_^o)",
"(゚Θ゚)",
"((o^_^o) - (゚Θ゚))",
"(o^_^o)",
"(゚ー゚)",
"((゚ー゚) + (゚Θ゚))",
"((o^_^o) +(o^_^o))",
"((゚ー゚) + (o^_^o))",
"((゚ー゚) + (゚ー゚))",
"((゚ー゚) + (゚ー゚) + (゚Θ゚))",
"(゚Д゚) .゚ω゚ノ",
"(゚Д゚) .゚Θ゚ノ",
"(゚Д゚) ['c']",
"(゚Д゚) .゚ー゚ノ",
"(゚Д゚) .゚Д゚ノ",
"(゚Д゚) [゚Θ゚]"
];
// r 实际上是最终字符串
// 定义了一些变量
var r =
// "゚ω゚ノ = /`m´)ノ ~┻━┻ //*´∇`*/ ['_']
"゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_'];" +
// o = 3
// _ = 3
" o=(゚ー゚) =_=3;" +
// c = 0
// (゚Θ゚) = 0
" c=(゚Θ゚) =(゚ー゚)-(゚ー゚); ";
r +=
// (゚Д゚) = (゚Θ゚) = 1
"(゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);" +
// (゚Д゚) = {゚Θ゚: "_", ゚ω゚ノ: "a", ゚ー゚ノ: "d", ゚Д゚ノ: "e"}
"(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; " +
// (゚Д゚) [゚Θ゚] = 'f'
"(゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];" +
// (゚Д゚) ['c'] = 'c'
"(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];" +
// (゚Д゚) ['o'] = 'o'
"(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];" +
// constructor
"(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];" +
// (゚Д゚) ['_'] = 3['constructor']['constructor'] 等价于 ƒ Function() { [native code] }
"(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];" +
// (゚ε゚) = 'return'
"(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; " +
// (゚ー゚) = 3
"(゚ー゚)+=(゚Θ゚);",
// (゚Д゚)[゚ε゚] = "\\"
"(゚Д゚)[゚ε゚]='\\\\'; " +
// (゚Д゚).゚Θ゚ノ = "b"
"(゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];" +
// (o゚ー゚o) = 'u'
"(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];" +
// (゚Д゚) [゚o゚] = "\\\""
"(゚Д゚) [゚o゚]='\\\"';" +
// function (function (return object 中间导入的字符串 ))
"(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');";

// 遍历传入字符串的所有字符
for (var i = 0; i < text.length; i++) {
n = text.charCodeAt(i);
// charCodeAt() 方法可返回指定位置的字符的 Unicode 编码数
// 由于实际上是标记的转码字符,这里献给内容加上一个转义字符'\\'
t = "(゚Д゚)[゚ε゚]+";
if (n <= 127) {
// unicode 只处理 C0控制与基本的Latin(拉丁字母),这其实已经包含了绝大部分的js源码符号
// 将 十进制数字符串 转为 为8进制数字符串
n = n.toString(8);
// 对八进制数字符串 里的每一个数字(0-7) 用 预先定义好的表情符合进行取代
t += n.replace(/[0-7]/g, function (c) {
return b[c] + "+ ";
// 取代完以后,在最后添加一个 '+ '用于最后eval函数拼接而不出问题
});
} else {
// 对于非C0控制与基本的Latin(拉丁字母)。即 非js代码部分,诸如中文之类的
// 用 "000"+原字符数字转16进制
n = "000" + n.toString(16);
// 用正则表达式/[0-9a-f]{4}$/ 在 十六进数字字符 中寻找匹配项 并返回最短匹配
var m = /[0-9a-f]{4}$/.exec(n)[0];
// 将这个 十六进数字字符 转为对应的10进制数字(因为记录表情的是一个数组,如果传入a-f字符来获取表情的话会报错,所以转10进制数),再转为表情,在最后添加一个 '+ '
m = m.replace(/[0-9a-f]/gi, function (c) {
return b[parseInt(c, 16)] + "+ ";
});
// 字符结果添加前缀 u以作Unicode字符标记
t += "(o゚ー゚o)+ " + m;
}
// 单个字符汇总到答案
r += t;

}

// 添加结尾字符
r += "(゚Д゚)[゚o゚]) (゚Θ゚)) ('_');";

// 返回整个字符串
return r;
}

在详细分析以后,过程是比较得明朗的

过程

  1. 定义一个 表情符号数组
    为了增加迷惑性,设定一个表情符号数组,用作未来取代javascript代码字符

  2. 定义外围函数包围原本的代码
    最外面包裹着一层 执行解释函数
    通过各种奇怪的符号构造出 ()["constructor"]["constructor"](执行代码)()
    ()["constructor"]["constructor"](执行代码)()等价于Function (执行代码)(),能执行代码

  3. 原本的js代码通过unicode编码后映射到了一个数上,作者将这个数做了区分处理

    • 这个数在0-127之间,说明是数字字母等javascript代码原本需要用到的字符 C0 控制与基本的 Latin(拉丁字母)
      这种字符作者将数转为8进制后,将每一位的数转为表情字符
    • 这个数超出127,说明这个字符并非常规的字母数字等,而是一些较少使用的字符,如中文之类的
      这种字符作者将数转为16进制后,将每一位的数转为表情字符,最后在前面添加一个”(o゚ー゚o)”(实际上是字符 u)用作标识Unicode字符
  • 为什么可以转为表情
    实际上,在一开始的变量声明完毕以后,表情字符本身的值就等于序号(16进制,超出的会对应a-f)
    这是作者设定好的,混淆是让你看不懂,并不会破坏逻辑本身
    你可以尝试着去获取那些值

解法

  1. 删掉最后的('_');";,若是不行,可以在后面加toString()来把函数转为字符串输出
    由于代码是()["constructor"]["constructor"](执行代码)()这种形式的
    如果你把最后的括号删掉,那么源代码函数就会暴露出来,直接得到源码了

  2. 进入虚拟空间VM即可看到源码
    在执行混淆代码之前,打上断点,步进到混淆代码内部,分析堆栈,进入虚拟空间,就能看到源码了
    如果内部代码执行报错,那么也可以通过报错的定位进入虚拟空间

Recluse
2020年8月13日14:53:10


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!