Table des matières

⚠‍͏ cette page utilise beaucoup de caractères que vous ne voyez potentiellement pas, n'hésitez pas à copier&coller dans un terminal du texte echo -n 'texte' | hd pour constater la présence d'octets invisibles ⚠‍͏

Qu'est-ce qu'Unicode ?

Unicode est le système utilisé pour numériser du texte aujourd'hui. C'est le consortium unicode http://www.unicode.org/ qui s'en occupe.

Ce système permet d'associer des codes, des symboles, des significations et des numérisations.

Les procédés de numérisation d'unicode sont actuellement utf-8 (rétrocompatible avec ASCII) et utf-16 (pas rétrocompatible et rempli de \x00).

Par exemple, le symbole « tête de chat qui rigole avec des yeux souriants »
U+1F638 GRINNING CAT FACE WITH SMILING EYES Est représenté de cette manière : 😸 Et est stocké sur 4 octets en utf-8

  $ echo -n '😸' | hd
  00000000  f0 9f 98 b8                                       |....|

En théorie, le caractère U+45484 n'a pas encore été affecté, aussi vous devriez voir un carré (avec le code dedans pour les polices les plus sympa) ici : 񅒄.

La différence entre utf-8 et utf-16

UTF-8 a été conçu pour être rétrocompatible. Prenons trois exemples simples pour illustrer , « ↹ », « A » et «䕅» :

  U+21B9 LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR
  UTF-8  : 0xE2 0x86 0xB9
  UTF-16 : 0x21B9
  
  U+0041 LATIN CAPITAL LETTER A
  UTF-8  : 0x41
  UTF-16 : 0x0041
  
  U+4545 CJK UNIFIED IDEOGRAPH-4545
  UTF-8 : 0xE4 0x95 0x85
  UTF-16 : 0x4545

On remarque plusieurs choses : - En UTF-16, on recopie le code comme des brutes, même s'il contient des \x45 == 'E' en ascii - En UTF-8 pas de problèmes de ce genre, on échappe les bits d'information en utilisant les octets du plan supérieur ASCII. (Comme on dit, retrouver la doc est left as an exercise to the reader) - En UTF-16, on mange des \x00 qui posent problème dans beaucoup de langages (par exemple avec la stdlib de C et son strlen)

Mais, me direz-vous, le chat de tout à l'heure il avait 5 nibbles dans son code unicode U+1F638, ça ne tient pas dans deux octets comme \x45\x45 ?

Eh oui, c'est pourquoi en UTF-8 il est échappé sur 4 octets et qu'en UTF-16 il est échappé en deux blocs UTF-16 :

  UTF-8 : 0xF0 0x9F 0x98 0xB8
  UTF-16 : 0xD83D 0xDE38

Si vous vous posez des questions, la vraie documentation est ici : http://www.unicode.org/reports/tr17/#CharactersVsGlyphs

Unicode en tant que vecteur stéganographique

Il existe plein de caractères unicode (installez donc toutes les polices possibles sur votre ordinateur et scrollez dans gucharmap pour vérifier), et certains ne sont pas visibles.

Le but du jeu ici est d'utiliser les vecteurs de transport de texte comme d'habitude, et d'y cacher des données à l'insu d'un lecteur humain.

Il existe des homoglyphes, c'est-à-dire des caractères de code (et numérisation) différents dont l'affichage par une police sera identique.

Par exemple, « CС », on pourrait croire que ces deux lettres sont identiques. Eh bien non :

  $ echo -n 'CС'|hd
  00000000  43 d0 a1                                          |C..|

Les espaces

Un autre vecteur que les homoglyphes, moins fiable : les espaces :

  $ unicode space --max 100 | grep ^U+ | grep -v MONO
  U+0008 <control>
  U+0020 SPACE
  U+00A0 NO-BREAK SPACE
  U+1361 ETHIOPIC WORDSPACE
  U+1680 OGHAM SPACE MARK
  U+2002 EN SPACE
  U+2003 EM SPACE
  U+2004 THREE-PER-EM SPACE
  U+2005 FOUR-PER-EM SPACE
  U+2006 SIX-PER-EM SPACE
  U+2007 FIGURE SPACE
  U+2008 PUNCTUATION SPACE
  U+2009 THIN SPACE
  U+200A HAIR SPACE
  U+200B ZERO WIDTH SPACE
  U+202F NARROW NO-BREAK SPACE
  U+205F MEDIUM MATHEMATICAL SPACE
  U+2408 SYMBOL FOR BACKSPACE
  U+2420 SYMBOL FOR SPACE
  U+3000 IDEOGRAPHIC SPACE
  U+303F IDEOGRAPHIC HALF FILL SPACE
  U+FEFF ZERO WIDTH NO-BREAK SPACE
  U+E0020 TAG SPACE

Comme vous pouvez le constater, il en existe beaucoup (23), en ne considérant que les 16 les plus stables, on peut stocker un nibble d'information dans un seul espace !

Le souci avec les espaces, c'est qu'ils ont des tailles variables, et qu'à moins d'être affichés dans un terminal, ils sont carrément visibles. (Sauf le discret U+00A0 « »).

Les caractères invisibles de combinaison

Si les espaces peuvent poser problème parce qu'ils se mesurent, il existe des caractères unicode qui ne se voient pas par nature :

  $ python -c 'print "a".join([unichr(i) for i in [0x034f,0x200c,0x200d]+range(0xfe00,0xfe10)])'
  a‌a‍a︀a︁a︂a︃a︄a︅a︆a︇a︈a︉a︊a︋a︌a︍a︎a️

a‌a‍a︀a︁a︂a︃a︄a︅a︆a︇a︈a︉a︊a︋a︌a︍a︎a️

Il s'agit des caractères suivants :

  U+034F COMBINING GRAPHEME JOINER
  U+200C ZERO WIDTH NON-JOINER
  U+200D ZERO WIDTH JOINER
  U+FE00 VARIATION SELECTOR-1
  ...
  U+FE0F VARIATION SELECTOR-16
  

Les 16 premiers sont plutôt invisibles, car présents dans Unicode depuis sa version 3.2.

Les suivants ne son présents que depuis la 4.0, et ils sont rangés bien plus loin, de 0xE0100 à 0xE01EF :

  $ python -c 'print "a".join([unichr(i) for i in range(0xe0100,0xe01f0)])'
  a󠄁a󠄂a󠄃a󠄄a󠄅a󠄆a󠄇a󠄈a󠄉a󠄊a󠄋a󠄌a󠄍a󠄎a󠄏a󠄐a󠄑a󠄒a󠄓a󠄔a󠄕a󠄖a󠄗a󠄘a󠄙a󠄚a󠄛a󠄜a󠄝a󠄞a󠄟a󠄠a󠄡a󠄢a󠄣a󠄤a󠄥a󠄦a󠄧a󠄨a󠄩a󠄪a󠄫a󠄬a󠄭a󠄮a󠄯a󠄰a󠄱a󠄲a󠄳a󠄴a󠄵a󠄶a󠄷a󠄸a󠄹a󠄺a󠄻a󠄼a󠄽a󠄾a󠄿a󠅀a󠅁a󠅂a󠅃a󠅄a󠅅a󠅆a󠅇a󠅈a󠅉a󠅊a󠅋a󠅌a󠅍a󠅎a󠅏a󠅐a󠅑a󠅒a󠅓a󠅔a󠅕a󠅖a󠅗a󠅘a󠅙a󠅚a󠅛a󠅜a󠅝a󠅞a󠅟a󠅠a󠅡a󠅢a󠅣a󠅤a󠅥a󠅦a󠅧a󠅨a󠅩a󠅪a󠅫a󠅬a󠅭a󠅮a󠅯a󠅰a󠅱a󠅲a󠅳a󠅴a󠅵a󠅶a󠅷a󠅸a󠅹a󠅺a󠅻a󠅼a󠅽a󠅾a󠅿a󠆀a󠆁a󠆂a󠆃a󠆄a󠆅a󠆆a󠆇a󠆈a󠆉a󠆊a󠆋a󠆌a󠆍a󠆎a󠆏a󠆐a󠆑a󠆒a󠆓a󠆔a󠆕a󠆖a󠆗a󠆘a󠆙a󠆚a󠆛a󠆜a󠆝a󠆞a󠆟a󠆠a󠆡a󠆢a󠆣a󠆤a󠆥a󠆦a󠆧a󠆨a󠆩a󠆪a󠆫a󠆬a󠆭a󠆮a󠆯a󠆰a󠆱a󠆲a󠆳a󠆴a󠆵a󠆶a󠆷a󠆸a󠆹a󠆺a󠆻a󠆼a󠆽a󠆾a󠆿a󠇀a󠇁a󠇂a󠇃a󠇄a󠇅a󠇆a󠇇a󠇈a󠇉a󠇊a󠇋a󠇌a󠇍a󠇎a󠇏a󠇐a󠇑a󠇒a󠇓a󠇔a󠇕a󠇖a󠇗a󠇘a󠇙a󠇚a󠇛a󠇜a󠇝a󠇞a󠇟a󠇠a󠇡a󠇢a󠇣a󠇤a󠇥a󠇦a󠇧a󠇨a󠇩a󠇪a󠇫a󠇬a󠇭a󠇮a󠇯

a󠄁a󠄂a󠄃a󠄄a󠄅a󠄆a󠄇a󠄈a󠄉a󠄊a󠄋a󠄌a󠄍a󠄎a󠄏a󠄐a󠄑a󠄒a󠄓a󠄔a󠄕a󠄖a󠄗a󠄘a󠄙a󠄚a󠄛a󠄜a󠄝a󠄞a󠄟a󠄠a󠄡a󠄢a󠄣a󠄤a󠄥a󠄦a󠄧a󠄨a󠄩a󠄪a󠄫a󠄬a󠄭a󠄮a󠄯a󠄰a󠄱a󠄲a󠄳a󠄴a󠄵a󠄶a󠄷a󠄸a󠄹a󠄺a󠄻a󠄼a󠄽a󠄾a󠄿a󠅀a󠅁a󠅂a󠅃a󠅄a󠅅a󠅆a󠅇a󠅈a󠅉a󠅊a󠅋a󠅌a󠅍a󠅎a󠅏a󠅐a󠅑a󠅒a󠅓a󠅔a󠅕a󠅖a󠅗a󠅘a󠅙a󠅚a󠅛a󠅜a󠅝a󠅞a󠅟a󠅠a󠅡a󠅢a󠅣a󠅤a󠅥a󠅦a󠅧a󠅨a󠅩a󠅪a󠅫a󠅬a󠅭a󠅮a󠅯a󠅰a󠅱a󠅲a󠅳a󠅴a󠅵a󠅶a󠅷a󠅸a󠅹a󠅺a󠅻a󠅼a󠅽a󠅾a󠅿a󠆀a󠆁a󠆂a󠆃a󠆄a󠆅a󠆆a󠆇a󠆈a󠆉a󠆊a󠆋a󠆌a󠆍a󠆎a󠆏a󠆐a󠆑a󠆒a󠆓a󠆔a󠆕a󠆖a󠆗a󠆘a󠆙a󠆚a󠆛a󠆜a󠆝a󠆞a󠆟a󠆠a󠆡a󠆢a󠆣a󠆤a󠆥a󠆦a󠆧a󠆨a󠆩a󠆪a󠆫a󠆬a󠆭a󠆮a󠆯a󠆰a󠆱a󠆲a󠆳a󠆴a󠆵a󠆶a󠆷a󠆸a󠆹a󠆺a󠆻a󠆼a󠆽a󠆾a󠆿a󠇀a󠇁a󠇂a󠇃a󠇄a󠇅a󠇆a󠇇a󠇈a󠇉a󠇊a󠇋a󠇌a󠇍a󠇎a󠇏a󠇐a󠇑a󠇒a󠇓a󠇔a󠇕a󠇖a󠇗a󠇘a󠇙a󠇚a󠇛a󠇜a󠇝a󠇞a󠇟a󠇠a󠇡a󠇢a󠇣a󠇤a󠇥a󠇦a󠇧a󠇨a󠇩a󠇪a󠇫a󠇬a󠇭a󠇮a󠇯

Un autre vecteur, les tags unicode

Comme le fait habilement remarquer la page : http://www.irongeek.com/i.php?page=security/unicode-steganography-homoglyph-encoder, le bloc 0xE0000 - 0xE007F contient des «Tags», plutôt invisibles, voyez-vous même :

  $ python3 -c 'print("A".join([chr(i) for i in range(0xe0000,0xe0080)]))'
 󠀀 A󠀁A󠀂A󠀃A󠀄A󠀅A󠀆A󠀇A󠀈A󠀉A󠀊A󠀋A󠀌A󠀍A󠀎A󠀏A󠀐A󠀑A󠀒A󠀓A󠀔A󠀕A󠀖A󠀗A󠀘A󠀙A󠀚A󠀛A󠀜A󠀝A󠀞A󠀟A󠀠A󠀡A󠀢A󠀣A󠀤A󠀥A󠀦A󠀧A󠀨A󠀩A󠀪A󠀫A󠀬A󠀭A󠀮A󠀯A󠀰A󠀱A󠀲A󠀳A󠀴A󠀵A󠀶A󠀷A󠀸A󠀹A󠀺A󠀻A󠀼A󠀽A󠀾A󠀿A󠁀A󠁁A󠁂A󠁃A󠁄A󠁅A󠁆A󠁇A󠁈A󠁉A󠁊A󠁋A󠁌A󠁍A󠁎A󠁏A󠁐A󠁑A󠁒A󠁓A󠁔A󠁕A󠁖A󠁗A󠁘A󠁙A󠁚A󠁛A󠁜A󠁝A󠁞A󠁟A󠁠A󠁡A󠁢A󠁣A󠁤A󠁥A󠁦A󠁧A󠁨A󠁩A󠁪A󠁫A󠁬A󠁭A󠁮A󠁯A󠁰A󠁱A󠁲A󠁳A󠁴A󠁵A󠁶A󠁷A󠁸A󠁹A󠁺A󠁻A󠁼A󠁽A󠁾A󠁿
  

Enfin, ça dépend de la police, voyez vous même :

󠀀A󠀁A󠀂A󠀃A󠀄A󠀅A󠀆A󠀇A󠀈A󠀉A󠀊A󠀋A󠀌A󠀍A󠀎A󠀏A󠀐A󠀑A󠀒A󠀓A󠀔A󠀕A󠀖A󠀗A󠀘A󠀙A󠀚A󠀛A󠀜A󠀝A󠀞A󠀟A󠀠A󠀡A󠀢A󠀣A󠀤A󠀥A󠀦A󠀧A󠀨A󠀩A󠀪A󠀫A󠀬A󠀭A󠀮A󠀯A󠀰A󠀱A󠀲A󠀳A󠀴A󠀵A󠀶A󠀷A󠀸A󠀹A󠀺A󠀻A󠀼A󠀽A󠀾A󠀿A󠁀A󠁁A󠁂A󠁃A󠁄A󠁅A󠁆A󠁇A󠁈A󠁉A󠁊A󠁋A󠁌A󠁍A󠁎A󠁏A󠁐A󠁑A󠁒A󠁓A󠁔A󠁕A󠁖A󠁗A󠁘A󠁙A󠁚A󠁛A󠁜A󠁝A󠁞A󠁟A󠁠A󠁡A󠁢A󠁣A󠁤A󠁥A󠁦A󠁧A󠁨A󠁩A󠁪A󠁫A󠁬A󠁭A󠁮A󠁯A󠁰A󠁱A󠁲A󠁳A󠁴A󠁵A󠁶A󠁷A󠁸A󠁹A󠁺A󠁻A󠁼A󠁽A󠁾A󠁿.

Ceux-ci ne sont pas forcément affichés de la même manière en fonction des navigateurs. On peut stocker 6 bits par tag avec le range [ 0xE0040 : 0xE0080 ].

Les caractères coréens Hangûl

Vous l'aurez peut-être remarqué, les caractères Hangûl sont organisés par morceaux : 쟪푫폁킟쵑즩왚쌄쁳뷛몥뛇담깘. Il existe des méthodes d'organisation des caractères élémentaires pour en faire des gros (décrites ici : https://en.wikipedia.org/wiki/Hangul#Morpho-syllabic_blocks ), et certains caractères invisibles permettent de «remplir» la place manquante :

  U+115F HANGUL CHOSEONG FILLER
  U+1160 HANGUL JUNGSEONG FILLER
  U+3164 HANGUL FILLER
  U+FFA0 HALFWIDTH HANGUL FILLER

Cela dit, l'utilisation de ceux-ci peut déclencher un affichage plus large que d'ordinaire des caractères, aussi ils ne sont pas très utiles.

Les caractères invisibles

Invisibles par nature :

  $ unicode invisible | grep ^U+
  U+2062 INVISIBLE TIMES
  U+2063 INVISIBLE SEPARATOR
  U+2064 INVISIBLE PLUS
  $ python -c 'print ">"+"a".join([unichr(i) for i in [0x2062,0x2063,0x2064]])+"<"'
  >⁢a⁣a⁤<

Dans une autre police : >⁢a⁣a⁤<

Unicode comme transport de données

Des gros caractères unicode ranges 0x100000:0x1FFFFF et 0x10000:0xFFFFF permettent de stocker des bits (jusqu'à 5 nibbles par caractère). Ils passent très bien sur twitter par exemple.

Plus de détails ici : https://github.com/qolund/u.brony.fr

  $ python -c 'import sys;sys.stdout.write("".join([chr(i) for i in xrange(256)]))' | unipack.py 
  𐀁𐈃𐐅𐘇𐠉𐨋𐰍𐸏𑀑𑈓𑐕𑘗𑠙𑨛𑰝𑸟𒀡𒈣𒐥𒘧𒠩𒨫𒰭𒸯𓀱𓈳𓐵𓘷𓠹𓨻𓰽𓸿𔁁𔉃𔑅𔙇𔡉𔩋𔱍𔹏𕁑𕉓𕑕𕙗𕡙𕩛𕱝𕹟𖁡𖉣𖑥𖙧𖡩𖩫𖱭𖹯𗁱𗉳𗑵𗙷𗡹𗩻𗱽𗹿𘂁𘊃𘒅𘚇𘢉𘪋𘲍𘺏𙂑𙊓𙒕𙚗𙢙𙪛𙲝𙺟𚂡𚊣𚒥𚚧𚢩𚪫𚲭𚺯𛂱𛊳𛒵𛚷𛢹𛪻𛲽𛺿𜃁𜋃𜓅𜛇𜣉𜫋𜳍𜻏𝃑𝋓𝓕𝛗𝣙𝫛𝳝𝻟𞃡𞋣𞓥𞛧𞣩𞫫𞳭𞻯🃱🋳📵🛷🣹🫻🳽🻿$