沒想到 Unicode 字符還能這樣玩?


上周的時候,朋友圈的直升飛機不知道為什么就火了,很多朋友開着各種花式飛機帶着起飛。

 

 

還沒來得及了解咋回事來着,這個直升飛機就🔥到了微博熱搜。

后面越來越多人開來他們的直升飛機,盤旋在朋友圈上方。於是很多朋友開來他們的坦克,專打直升飛機,一轟一個准。

 

 

程序員朋友應該都很熟悉 Unicode (萬國碼),它幾乎包含世界上所有符號,比如組成直升飛機這幾個特殊符號對應的 Unicode 碼分別為:

 

 

 

 

ps:推薦一個網站,可以根據符號搜對應的 Unicode 碼:https://unicode.yunser.com/unicode

除了這些正常字符以外,Unicode 還包含着各種各樣的奇葩字符。

奇葩字符

除了我們熟知的這些正常的文字以外,Unicode 中還有一些奇怪的文字,比如下面這些文字:

 

 

除了這些奇怪文字以外,Unicode 還有一些奇葩的符號。

例如下面一整套的麻將牌:

 

 

三缺一?

一整套的撲克牌:

 

 

對三?要不起!

一整套的國際象棋:

 

 

不會玩--!

除了這些,通過組合符合,我們還可以造出各種各樣的顏文字(๑•̀ㅂ•́)و✧、

 

 

另外 Unicode 還收錄着我們常用的 Emoji 。

 

 

除此之外,Unicode 中還有一些特殊字符的,利用這些字符,我們還可以玩出很多有趣的騷操作。

組合字符

Unicode 有一類字符稱為組合字符,它可以附加在前一個非組合字符上,從而使整體看起來像是一個字符。

組合字符原來目的是為了解決一些地區語言、文字特殊的需要,比如說泰文聲調符號與母音符號。

 

 

正常使用的情況下,這些組合字符數量都會有一些限制。但是在 Unicode 組合字符設計上,並沒有加這種限制,這樣使我們可以無限加這類組合字符。

利用這個特性,可以達到一些惡搞效果,比如「擊穿天花板」與「鑿穿地板」的效果。

 

 

上面實現原理其實是利用以下兩個組合字符:

 

 

上翻字符

 

 

下翻字符

只要復制這兩個字符相應的 HTML 代碼,跟在正常的字符后面,就可以使這兩個字符附加在普通字符上,比如下面實現效果為

黑̮̑

Unicode 碼值通常使用 U+N(16 進制N 代表碼值)表示,比如 A 的碼值為 U+0041。

在 HTML 中 Unicode 可以使用 &#N;(十進制,N 代表碼值)表示。

在 JS 中 Unicode 中需要使用] \uN(16 進制N 代表碼值)表示。

只要我們在普通字符多復制幾個這類附加字符,就可以形成上述「擊穿」效果。

還記得上面說的泰文嗎,曾經有一段時間,貼吧很流行一種噴射文,比如下面的效果。

 

 

這種噴射文實際原理就是利用泰文中聲調符號附加在其他正常符號上。

不過現在這個效果貌似已經沒辦法再復現了,現在我們只能看到這樣的效果:

在一些老版本的系統/瀏覽器可能還能看到這種效果,知道的小伙伴留言區可以告知一下。

零寬字符

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 : 用於在混合文字方向的多種語言文本中,規定排版文字書寫方向為右至左。

利用零寬字符不可見的特性,我們也可以玩出一些騷效果。

空白微博

發布微博的時候,如果內容都是空格,將沒辦法發布。

 

 

但是如果我們將零寬字符,比如說「零寬度空格符 U+200B」復制到微博,這樣我們就可以發布空白微博。

我們可以利用 Chrome 瀏覽器的控制台復制零寬字符,操作方式如下:

發布效果如下:

 

 

隱形水印

對於一些內部論壇或者說小說網站來說,可以通過零寬字符在帖子或小說內容中嵌入隱形水印。

當這些內容被一些爬蟲復制到其他網站時,我們就可以通過隱形水印,輕松查找是哪位用戶泄漏內容。

隱形水印主要原理就是將用戶信息比如用戶名,通過一定算法轉成零寬字符,這樣普通用戶瀏覽時完全看不到這個水印。

如果內容被復制到其他網站,隱形水印也被復制,只要找到這個水印,將這些零寬字符反轉成用戶名即可。

下面展示一種轉換方法,JS 代碼主要參考以下 Github 項目:

https://github.com/umpox/zero-width-detection

隱形水印生成方法

第一步我們需要將明文字符串中每個字符都轉成二進制串。

    // 每個字符轉為二進制,用空格分隔
    const textToBinary = username => (
      username
      .split('')
      // charCodeAt 將字符轉成相應的 Unicode 碼值
      .map(char => char.charCodeAt(0).toString(2))
      .join(' ')
    );
 

示例如下:

第二步,將二進制串轉為零度字符串,轉換規則如下:

  • 1 轉換為 \u200b 零寬度字符(zero-width space)
  • 0 轉換為 \u200c 零寬度斷字符(zero-width non-joiner)
  • 其他(剩余就是空格) 轉換為 \u200d 零寬度連字符 (zero-width joiner)
  • 最后使用 \ufeff 零寬度非斷空格符 (zero width no-break space) 作為分隔符
const binaryToZeroWidth = binary => (
  binary.split('').map((binaryNum) => {
    const num = parseInt(binaryNum, 10);
    if (num === 1) {
      return '\u200b'; // \u200b 零寬度字符(zero-width space)
    } else if(num===0) {
      return '\u200c'; // \u200c 零寬度斷字符(zero-width non-joiner)
    }
    return '\u200d'; // \u200d 零寬度連字符 (zero-width joiner)

  }).join('\ufeff') // \ufeff 零寬度非斷空格符 (zero width no-break space)
);
 

最終加密方法如下:

const encode = username => {
  const binaryUsername = textToBinary(username);
  const zeroWidthUsername = binaryToZeroWidth(binaryUsername);
  return zeroWidthUsername;
};
 

使用加密方法將明文字符串加密之后,加密字符串肉眼是看不到了,但是實際還是存在的。

 

 

實際上,如果我們將加密之后字符串復制到 👉BEJSON 網站,就可以看到字符。

 

 


另外你還可以把加密字符串復制到 IDEA 中,可以看到相應的 Unicode 編碼值。

 

 

解密隱形水印

知道了加密的方式,解密其實就很簡單,我們只要按照相反的步驟來就可以了。

第一步,將隱形水印按照以下規則轉換為二進制串。轉換規則如下:

  • 使用 \ufeff 分隔字符串
  • \u200b 轉為 1
  • \u200c 轉為 0
  • 其他字符使用空格
const zeroWidthToBinary = string => (
  string.split('\ufeff').map((char) => { // \ufeff 零寬度非斷空格符 (zero width no-break space)
    if (char === '\u200b') { // \u200b 零寬度字符(zero-width space)
      return '1';
    } else if(char === '\u200c') { // \u200c 零寬度斷字符(zero-width non-joiner)
      return '0';
    }
    return ' ';
  }).join('')
);

 

 

調用該方法,隱形水印轉成二進制串。

 

 

第二步,將二進制再轉為相應的字符。

const binaryToText = string => (
  // fromCharCode 二進制轉化
  string.split(' ').map(num => String.fromCharCode(parseInt(num, 2))).join('')
);
 

最終解密方法如下:

const decode = zeroWidthUsername => {
  const binaryUsername = zeroWidthToBinary(zeroWidthUsername);
  const textUsername = binaryToText(binaryUsername);
  return textUsername;
};
 

解密示例如下:

 

 

短網址

我們常用的短網址,域名后面會跟上一串隨機串,從而實現短網址到長網址的映射。比如以下網址:

https://sourl.cn/iLyn9S

我們利用零寬字符也可以實現短網址的效果,比如下面這個網站,就可以生成這類短網址。

https://zws.im/

 

 

可以看到這個短網址后面看不到任何字符,實際上這后面跟着一串零寬字符。當瀏覽器訪問該短網址時,后端程序只要反解密后面的零寬字符,拿到相應的網址,然后再做跳轉就可以到指定的網站。

反解密的原理可以參考上面隱形水印的代碼。

小心零寬字符

日常開發過程中,我們有時需要從一些文件中讀取文本內容,然后做相應的處理。

有時候我們可能會碰到一些詭異的現象,比如我們之前碰到的例子。

后台程序從 Excel 讀取文本內容,然后程序中判斷讀取的文本內容是否與指定的字符串相等。

然后當我們讀取一份 Excel 內容后,發現這段比較邏輯怎么也通過不了。本來以為是 Excel 內容存在空格什么的,但是打開 Excel 仔細一看,跟指定字符串一模一樣,並沒有什么其他字符。

第一次碰到這種例子,沒有什么經驗,真的排查了很久,到最后都有點懷疑人生了。最后無意間將文本內容復制到了 IDEA 中,才發現這里混雜着零寬字符!

如果各位小伙伴也碰到這類問題,不妨復制文本內容,然后到 IDEA 中查看是否存在某些看不見的字符~

參考鏈接

  1. https://juejin.im/post/5d3f01e7f265da03c23ead69
  2. http://zero.rovelast.com/
  3. https://zws.im/
  4. https://imweb.io/topic/5a08a5c7ef79bc941c30d8dd

轉自菜鳥教程

喜歡這篇文章?歡迎打賞~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM