LoginSignup
55
48

その透明な文字に混じらず、見つけ出すんだ。

Last updated at Posted at 2015-03-21

経緯

日常で最もよく遭遇する透明な文字 (Whitespace など) は、いわゆる (全角|半角) スペースそして改行 (CR, LF) タブなどがある。

Unicode にはたくさんの透明な文字が存在する。以下の文字列は "AB" ではない。
なんと、 A と B の間に WORD JOINER というゼロ幅の文字が潜んでいる!

どうみてもAB
A⁠B
実際のところ
A{U+2060}B

他にも、左右のテキストの順序を変えることができる文字 RLM など、
色々と複雑な制御文字がある。


このような特性を利用してファイル名やパス名などを偽装する攻撃 が存在する。

Unicode の透明な文字を知っておくことは、セキュリティにおいても重要だと思う。

このような攻撃に使われそうな(使えそうな)、透明な文字一覧を探してみた。
The Unicode Consortium の配布する資料、 Wikipedia 、自分が Python で書いた簡易的な探し方などを用いて探した。

まず結果

tsv
U+9	
U+A	
U+B	
U+C	
U+D	
U+20	SPACE
U+85	
U+A0	NO-BREAK SPACE
U+2000	EN QUAD
U+2001	EM QUAD
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+2028	LINE SEPARATOR
U+2029	PARAGRAPH SEPARATOR
U+202F	NARROW NO-BREAK SPACE
U+205F	MEDIUM MATHEMATICAL SPACE
U+3000	IDEOGRAPHIC SPACE
U+180E	MONGOLIAN VOWEL SEPARATOR
U+200B	ZERO WIDTH SPACE
U+200C	ZERO WIDTH NON-JOINER
U+200D	ZERO WIDTH JOINER
U+2060	WORD JOINER
U+2061	FUNCTION APPLICATION
U+2062	INVISIBLE TIMES
U+2063	INVISIBLE SEPARATOR
U+2064	INVISIBLE PLUS
U+3164	HANGUL FILLER
U+FEFF	ZERO WIDTH NO-BREAK SPACE
U+FFA0	HALFWIDTH HANGUL FILLER

釈明・免責

私は Unicode のプロフェッショナルではありません。この記事の内容は無保証です。

以下は、延々と経緯

まずは困った時の Wikipedia

ここに一覧がある。 http://en.wikipedia.org/wiki/Whitespace_character

このページは Unicode 6.3.0 の http://www.unicode.org/Public/6.3.0/ucd/PropList.txt
を元に作られているので、ほぼ網羅されている。

レンダリングして調べる

Python の Pillow を使い、画像に文字を実際にレンダリングしてみた。
簡単に説明すると、元の画像と文字をレンダリングした画像とに変化がなければ、透明であると定義した。
それを Unicode の全文字に対して試した。
フォントは最大のグリフ数を誇ると思われる、 GNU Unifont を使用した。
Unicode の PUA (Private Use Area) は除外した。

この方式では、 Unicode 上で制御文字として定義されている文字は、むしろ列挙されない。
以下の画像のように、制御文字用のグリフがそのまま描画されるので透明でなくなる。
http://unifoundry.com/pub/unifont-7.0.06/unifont-7.0.06.bmp

find_invisible.py
import unicodedata

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont


FONT = ImageFont.truetype('./unifont-7.0.06.ttf', 32)


def main():
    assert_principles()

    for char in invisible_characters():
        try:
            name = unicodedata.name(char)
        except ValueError:
            name = ''

        line = 'U+{:X}\t{}'.format(ord(char), name)
        print(line)


def invisible_characters():
    '''空白として描画された文字のみを返却する'''
    empty_draw_bytes = letter_draw_bytes(' ')
    for char in unicode_characters():
        draw_bytes = letter_draw_bytes(char)
        if draw_bytes == empty_draw_bytes:
            yield char


def unicode_characters():
    '''Unicode コードポイントに含まれる文字全てを返すジェネレータ
ただし、 PUA (Private Use Area) は含めない。
    '''

    for codepoint in range(0, 0xE000):
        yield chr(codepoint)

    for codepoint in range(0xF8FF + 1, 0xF0000):
        yield chr(codepoint)

    for codepoint in range(0xFFFFD + 1, 0x100000):
        yield chr(codepoint)

    for codepoint in range(0x10FFFD + 1, 0x10FFFF):
        yield chr(codepoint)


def letter_draw_bytes(char):
    '''空白の画像に文字を描画した際の bytes を取得する'''

    image = Image.new('RGB', (32, 32), (255, 255, 255))
    draw = ImageDraw.Draw(image)
    draw.text((0, 0), char, font=FONT, fill='#000')
    return image.tobytes()


def assert_principles():
    '''以下の原則が成立することをいくつかの例でチェック'''
    f = letter_draw_bytes

    assert f('') == f('')  # 同じ文字の bytes は同じ

    assert f('') == f(' ') == f(' ')  # スペースは空文字列と同じ bytes になる

    assert f('') != f('')  # 見た目が異なる文字の bytes は異なる


if __name__ == '__main__':
    main()

結果

U+20	SPACE
U+A0	NO-BREAK SPACE
U+2000	EN QUAD
U+2001	EM QUAD
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+205F	MEDIUM MATHEMATICAL SPACE
U+3000	IDEOGRAPHIC SPACE
U+FFA0	HALFWIDTH HANGUL FILLER

このうち

U+FFA0	HALFWIDTH HANGUL FILLER

だけが未知だった。 HALFWIDTH だけが透明として検知できたのだが、 HALFWIDTH でないものも、 GNU Unifont 以外では透明であったので、一応追加した。

U+3164	HANGUL FILLER

The Unicode Consortium の配布物

The Unicode Consortium の配布物を延々と人力で調べることにした。
例えば DerivedCoreProperties など

その結果 U+2061 ... U+2064 などを発見した。
おそらく数学用の制御文字(?)。詳細は不明。

U+2060 WORD JOINER のすぐそばにあるのに、ほとんど言及されてない。

55
48
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
48