如何只用颜文字搞出一堆可执行代码?

1,218 阅读6分钟

最近在知乎看到一段有趣的代码。代码基本由颜文字组成,但是复制到chrome控制台确是可执行的,会弹出"hello world"的警示框。完整代码如下,你可以先自己复制到控制台试一下。

゚ω゚ノ= /`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) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚Θ゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

顺便贴上原答主链接:

怎么把 Hello World 写的高端大气上档次? - 我哟的回答 - 知乎

这段代码引起了我的兴趣,于是用工具将其格式化了,然后逐段分析了一下,虽然并未涉及到太多高深的知识,但感觉里面还是有一些有趣的小技巧值得分享一下。顺便一提,如果你在知乎看到了同样的答案,那也是我回答的,并非抄袭。

代码分析

掘金的注释配色可能看得不是很清楚,可以自己复制到其他环境查看。如果你对js不熟悉,可以先跳到下一节看关键知识点。

// 这里可以分为三段
// /`m´)ノ ~┻━┻   / 一个正则
// /*´∇`*/ 这里是一串注释,可以当作不存在
// ['_'] 方括号里面加个字符串,用来取得对象里面与该字符串相同属性的值,
// 因为正则表达式也算一个对象,所以这里的格式是合法的
// 不过正则里面也没有"_"属性,所以゚ω゚ノ值为undefined
゚ω゚ノ = /`m´)ノ ~┻━┻   / /*´∇`*/['_']; 

// 下面大多数的小括号都是无意义的,别看花眼了
o = (゚ー゚) = _ = 3; // 定义了变量 o 和变量 ゚ー゚和变量_, 值为3
// 定义了变量 c 和 ゚Θ゚,值为 ゚ー゚ 减去 ゚ー゚, ゚ー゚上面定义了时3,所以这里为0
c = (゚Θ゚) = (゚ー゚) - (゚ー゚); 
// 定义了变量(゚Д゚)和(゚Θ゚),
// (o ^ _ ^ o) 注意到^这个符号了吗,^为异或运算符,
// 而o 和 _ 上面已经定义过了,值为3,所以这里其实是 3 ^ 3 ^ 3, 进行运算后还是3,
// 所以(゚Д゚)和(゚Θ゚)的值为 3/3 = 1
(゚Д゚) = (゚Θ゚) = (o ^ _ ^ o) / (o ^ _ ^ o); 

// 给(゚Д゚) 重新赋值,所以上面的内容都是没用的……
// 定义了一个对象 (゚Д゚)
(゚Д゚) = {
    ゚Θ゚: '_',
    // ゚ω゚ノ为undefined,所以 == 3 的结果为布尔值false, 
    // false + “_”,根据js自动转型机制,false被转为字符串"false",再跟"_"相加,结果为“false_”
    // ゚Θ゚上面定义了这个变量值为1;
    // 所以这里是取“false_“这个字符串下标为1的字母,结果为a
    ゚ω゚ノ: ((゚ω゚ノ == 3) + '_')[゚Θ゚], 
    // 同上,゚ω゚ノ值为undefined, 再跟字符串相加自动转型,结果为”undefined_“
    // 方括号内,o ^ _ ^ o 异或运算值为3, 上面已经说过了,゚Θ゚为1,结果2
    // 取得”undefined_“中下标为2的字母d
    ゚ー゚ノ: (゚ω゚ノ + '_')[o ^ _ ^ o - (゚Θ゚)],
    // 这里不分析了,原理跟上面是一样的,结果为e
    ゚Д゚ノ: ((゚ー゚ == 3) + '_')[゚ー゚]
}; 

// 又是给゚Д゚的属性重新赋值,所以上面分析了那么多的都是没用代码的吗?(゚Д゚)
// 下面的套路都是上面用过的,我就直接写结果了
(゚Д゚)[゚Θ゚] = ((゚ω゚ノ == 3) + '_')[c ^ _ ^ o];  // ”f“
// 这里有个小知识点,对象自动转型会调用内部的toString()方法,
// 而object默认的toString方法会返回"[object Object]"
// 所以((゚Д゚) + '_')的结果为"[object Object]_"
(゚Д゚)['c'] = ((゚Д゚) + '_')[(゚ー゚) + (゚ー゚) - (゚Θ゚)]; // c
(゚Д゚)['o'] = ((゚Д゚) + '_')[゚Θ゚]; // o 

// 一行代表一个字母,合起来是constructor
(゚o゚) = (゚Д゚)['c'] 
  + (゚Д゚)['o'] 
  + (゚ω゚ノ + '_')[゚Θ゚] 
  + ((゚ω゚ノ == 3) + '_')[゚ー゚] 
  + ((゚Д゚) + '_')[(゚ー゚) + (゚ー゚)] 
  + ((゚ー゚ == 3) + '_')[゚Θ゚] 
  + ((゚ー゚ == 3) + '_')[(゚ー゚) - (゚Θ゚)] 
  + (゚Д゚)['c'] 
  + ((゚Д゚) + '_')[(゚ー゚) + (゚ー゚)] 
  + (゚Д゚)['o'] 
  + ((゚ー゚ == 3) + '_')[゚Θ゚]; 

// (o ^ _ ^ o)结果为3,゚o゚是字符串constructor
// 所以这里是3["constructor"]["constructor"]
// 因为js的自动转型(装箱机制),数字类型3会被转型为Number对象,所以这里取的是Number的构造器的构造器
// 可以通过 Number.constructor.constructor === 3 .constructor.constructor 验证
(゚Д゚)['_'] = (o ^ _ ^ o)[゚o゚][゚o゚]; 

// 一行代表一个字母,合起来是return
(゚ε゚) = ((゚ー゚ == 3) + '_')[゚Θ゚] 
  + (゚Д゚).゚Д゚ノ + ((゚Д゚) + '_')[(゚ー゚) 
  + (゚ー゚)] + ((゚ー゚ == 3) + '_')[o ^ _ ^ o - ゚Θ゚] 
  + ((゚ー゚ == 3) + '_')[゚Θ゚] + (゚ω゚ノ + '_')[゚Θ゚]; 

(゚ー゚) += (゚Θ゚);  // 4
(゚Д゚)[゚ε゚] = '\\'; 
(゚Д゚).゚Θ゚ノ = (゚Д゚ + ゚ー゚)[o ^ _ ^ o - (゚Θ゚)]; // b
(o゚ー゚o) = (゚ω゚ノ + '_')[c ^ _ ^ o]; // u
(゚Д゚)[゚o゚] = '\"'; 

// (゚Д゚)['_']上面已经赋值过,就是Number.constructor.constructor
// 引用类型的构造函数指向Function类,Function的构造函数指向自身
// Function直接调用等效于new Function(),其实就是一种不常见的创建函数的方式
// 例如:const f = new Function("num1","num2","return num1 + num2")
// 等效于const f = function (num1,num2) { return num1 + num2};
(゚Д゚)['_'](
  (゚Д゚)['_'](
    // 下面这么长一串,其实都是用加号连接单个字符串进行拼接,最终输出
    // "return"\141\154\145\162\164\50\42\110\145\154\154\157\54\40\167\157\162\154\144\42\51""
    // 这里是的数字表示八进制ASCII码,例如141八进制转成10进制就是97,对应ASCII码中的字母"a”
    // 所以这里其实是字符串 "return"alert("Hello, world")"
    ゚ε゚ + (゚Д゚)[゚o゚] + (゚Д゚)[゚ε゚] + (゚Θ゚) + (゚ー゚) + (゚Θ゚) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((゚ー゚) + (゚Θ゚)) + (゚ー゚) + (゚Д゚)[゚ε゚] + (゚Θ゚) + (゚ー゚) + ((゚ー゚) + (゚Θ゚)) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + ((o ^ _ ^ o) - (゚Θ゚)) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + (゚ー゚) + (゚Д゚)[゚ε゚] + ((゚ー゚) + (゚Θ゚)) + (c ^ _ ^ o) + (゚Д゚)[゚ε゚] + (゚ー゚) + ((o ^ _ ^ o) - (゚Θ゚)) + (゚Д゚)[゚ε゚] + (゚Θ゚) + (゚Θ゚) + (c ^ _ ^ o) + (゚Д゚)[゚ε゚] + (゚Θ゚) + (゚ー゚) + ((゚ー゚) + (゚Θ゚)) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((゚ー゚) + (゚Θ゚)) + (゚ー゚) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((゚ー゚) + (゚Θ゚)) + (゚ー゚) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((゚ー゚) + (゚Θ゚)) + ((゚ー゚) + (o ^ _ ^ o)) + (゚Д゚)[゚ε゚] + ((゚ー゚) + (゚Θ゚)) + (゚ー゚) + (゚Д゚)[゚ε゚] + (゚ー゚) + (c ^ _ ^ o) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + ((゚ー゚) + (o ^ _ ^ o)) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((゚ー゚) + (゚Θ゚)) + ((゚ー゚) + (o ^ _ ^ o)) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + ((o ^ _ ^ o) - (゚Θ゚)) + (゚Д゚)[゚ε゚] + (゚Θ゚) + ((゚ー゚) + (゚Θ゚)) + (゚ー゚) + (゚Д゚)[゚ε゚] + (゚Θ゚) + (゚ー゚) + (゚ー゚) + (゚Д゚)[゚ε゚] + (゚ー゚) + ((o ^ _ ^ o) - (゚Θ゚)) + (゚Д゚)[゚ε゚] + ((゚ー゚) + (゚Θ゚)) + (゚Θ゚) + (゚Д゚)[゚o゚]
  )(゚Θ゚)
)('_');
// 上面那一堆东西等同于为下面的形式
/**
  Function(
    Function(
      "return'alert(\"Hello, world\")'"
    )() // Function返回一个匿名函数,后面接小括号表示立即执行
  )()
**/

核心知识点分析

本质上这东西就是利用颜文字取变量名,还有js神奇的自动转型机制搞出来的。

众所周知js是一个动态语言,会在运行时帮你自动推断变量的类型。例如将一个字符串与数字相加,js会先尝试将数字转为字符串,再进行字符串拼接;如果参与运算的是对象,则会调用对象内部的toString()方法,再参与运算,如下:

const a = { 
  toString: function() { 
    return 123 
  }
}
a + 1 // 结果为124

利用这个机制,我们可以搞出一大堆匪夷所思的操作:

[] + {} // "[object Object]"
[] + 123 // "123"
[] == 0 // true
true + "123" // "true123"
(function () {}) + 123  //"function () {}123" 

理解了这个知识点,上面的内容就好理解了。

还有一个知识点是ASCII码。在js里面可以用字符串形式的 "/" + ASCII码表对应数字来取得相应字符,例如 "\141" 就能在js里面自动识别为字符串"a"。如果是中文,则可以用"\u" + unicode码来表示。

如何把任意代码转化成颜文字

上面代码真正有用的还是最后一段,你可以把最后一段的颜文字变量替换成其他任意颜文字,至于关键字的获取方式,利用js自动转型机制,你也可以自己搞出各种花样来,这里就不多说了。

我们还可以在上面代码的基础上拓展一下。把任意代码拆分成单个字符,然后把字符转化成对应unicode码。再把unicode码也一个个拆开,转成对应颜文字。把生成的颜文字替换掉上述代码中最后一段的关键内容(就是Funtion里面的内容),就可以完成任意颜文字代码的生成了。

下面给出了一个示例代码,复制下面的代码到chrome控制台,再输入任意文字,就可以转化成颜文字代码了。该颜文字代码的功能是弹出你刚才输入的中文。

function generateEmojiCode(text) {
    // 为了方便操作,维护一个unicode关键字符和对应颜文字的映射
    const emojiStrMap = {
        0:"(゚ー゚) - (゚ー゚)", 
        1:"(゚Θ゚)", 
        2:"(゚Θ゚) + (゚Θ゚)", 
        3:"(o ^ _ ^ o)", 
        4:"(o ^ _ ^ o) + (゚Θ゚)", 
        5:"(゚ー゚) + (゚Θ゚)", 
        6:"(o ^ _ ^ o) + (o ^ _ ^ o)", 
        7:"(゚ー゚) + (o ^ _ ^ o)",
        8:"(゚ー゚) + (゚ー゚)",
        9:"(゚ー゚) + (゚Θ゚) + (゚ー゚)",
        a: "((゚Д゚).゚Θ゚ノ / (゚Θ゚) + '_')[(゚Θ゚)]",
        b: "(゚Д゚).゚Θ゚ノ",
        c: "(゚Д゚)['c']",
        d: "(゚Д゚).゚ー゚ノ",
        e: "(゚Д゚).゚Д゚ノ",
        f: "((o ^ _ ^ o)[゚o゚] + '_')[(゚Θ゚) ^ (゚Θ゚)]",
        u: "(o゚ー゚o)",
        "\\": "(゚Д゚)[゚ε゚]",
    }

    let str = "''";
    
    // 这里理论上可以替换成任意代码
    const alertText = `alert("${text}")`
    for(let t of alertText) {
        const uniNum = t.charCodeAt();
        const numstr = uniNum.toString(uniNum < 128 ? 8 : 16);
        let uniStr = uniNum < 128 ? emojiStrMap['\\'] : emojiStrMap['\\'] + "+" + emojiStrMap["u"];
        for(let s of numstr) {
            uniStr += "+(" + emojiStrMap[s] + ")";
        }
        str += "+" + uniStr;
    }
    return str;
}

function template(text) {
    return  `゚ω゚ノ = /`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゚] + ${text} + (゚Д゚)[゚o゚]
      )(゚Θ゚)
    )('_');
    `
};

const text = window.prompt("请输入你要转换的字符");
const res = template(generateEmojiCode(text));
console.log(res.replace(/[\r\n\s]/g,""))