実行可能ファイルのシンボル情報を削除して情報漏えいのリスクを下げる方法を説明します。


業務などでC/C++言語で作ったプログラム(実行可能ファイル)をリリースしたり他の人に渡すことがあると思います。 そのときにシンボル情報というものに気をつけていますでしょうか? シンボル情報というのは関数名とか変数名などの情報です。

例えばデバッガなんかで変数の値をのぞいたり、特定の関数まで実行したりできますよね。 それは実行可能ファイルにシンボル情報があるからできるわけです。どこにどんな変数や関数があるかラベルづけされているわけですね。 このように実行可能ファイルにはシンボル情報が含まれているんですが、場合によってはこれが情報漏洩の原因になりかねません。

デバッグ用の関数や変数や隠し機能などが漏れてしまう可能性があります。また、ビルドしたときのパス情報なども含まれるためにユーザ名なんかもわかってしまうかもです。 そこで今回は、実行可能ファイルからシンボル情報をみる方法を示したうえで、それを削除する方法を示します。

Caution

ライブラリ(.a, .so)やオブジェクトファイル(.o)からシンボル情報を削除すると、 リンクできなくなってしまうので要注意です! リンカはシンボル情報を参照してそれらを結合するので消してはダメです!

シンボル情報をのぞいてみよう

シンボル情報を見るには nm コマンドを使用します。今回の例はWindows MINGW上で実行していますが、Linuxでも同様です。

ためしに自前でビルドした実行可能ファイルに対してnmを実行したところ、下記のようになりました。 左の数字はアドレス、真ん中はデータの種類、右はシンボル名です。なんとなくたくさんのデータや関数があるんだなぁ、というのがわかるかなと思います。 このように、nmでシンボル情報を表示することで、関数名やファイル名が簡単に見ることができてしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ nm ./ccache.exe

0000000000436000 d .CRT$XCA
0000000000436008 d .CRT$XCAA
0000000000436010 d .CRT$XCZ
0000000000411560 T dirname
00000000004029c0 t do_copy_or_move_file_to_cache
000000000040c030 t do_debug_text.isra.0.part.1
000000000040c090 t do_hash_buffer
000000000040c060 t do_hash_buffer.part.2
00000000004128d0 t do_x_unlink
000000000042e2a8 b done.97297
0000000000433790 b dtoa_CS_init
00000000004337a0 b dtoa_CritSec
000000000041dbb0 t dtoa_lock
000000000041dc80 t dtoa_lock_cleanup
0000000000403100 t dump_log_buffer_exitfn
000000000041f1b8 T dup
0000000000432c20 b emu_pdata
0000000000432b20 b emu_xdata
000000000042e018 b envp
000000000041f0a0 T exit
000000000042e230 b exit_functions
000000000040bf80 T exitfn_add
000000000040bfc0 T exitfn_add_last
000000000040bf30 T exitfn_add_nullary
000000000040bea0 T exitfn_call
000000000040bef0 T exitfn_init
000000000040ce60 T extension_for_language
0000000000425ba0 r extensions
0000000000402790 t failed
0000000000411060 T fatal
000000000041f098 T fclose

さらに実行可能ファイルの中身をのぞいてみよう

nmでもいろいろな情報が見ることができますが、さらにもっと情報が見れないか試してみましょう。 そういう時はstringsというコマンドが便利です。これは指定したファイルから4文字以上の繋がったASCII文字を抽出するコマンドです。

つまりバイナリファイルから4文字以上のASCII文字を片っ端から表示するわけですね。 こんな感じにひたすらにASCII文字が表示されていきます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ strings  ./ccache.exe

!This program cannot be run in DOS mode.
.text
P`.data
.rdata
`@.pdata
0@.xdata
0@.bss
.idata
.CRT
.tls
.rsrc
B/19
B/31
B/45
B/57

例えばビルドした人を知りたければ、stringsの結果をgrepに渡してあげればわかる可能性があります。 下記の例はMINGW(Windows)なのでパスの形式がC:で始まっています。Linuxの場合は適宜そこを置き換えてください。 C:\Users\username\work\tmp\ccache-3.5というようにビルドしたときのパスがあります。usernameのところでユーザ名が判別できてしまいます。

1
2
3
4
5
6
7
$ strings ./ccache.exe | grep -i "C:[/\\]" | sort | uniq

C:/Programs/msys64/mingw64/etc
C:/Programs/msys64/mingw64/include
C:/Programs/msys64/mingw64/x86_64-w64-mingw32/include
C:\Users\username\work\tmp\ccache-3.5
C:\repo\mingw-w64-crt-git\src\crt-x86_64-w64-mingw32

このようにして、割と簡単にいろんな情報を抜き出せてしまいます。悪用できてしまいそうですね。

シンボル情報を削除する

ではどう対処すればいいか。不要なシンボル情報を削除すればOKですね。stripというコマンドがそれを実現してくれます。 使い方も簡単。単に実行可能ファイルを渡せばいいだけです。

1
strip ./ccache.exe

こうするとnmコマンドを実行しても下記のように「シンボルが見つかりません」という旨のメッセージが表示されます。シンボル情報の削除に成功していますね。

1
2
$ nm ./ccache.exe
C:\Programs\msys64\mingw64\bin\nm.exe: ./ccache.exe: no symbols

stringsについては、さすがにすべてのASCII文字を削除するわけにはいかないので、多少は残ります。 例えばprintfで表示するメッセージなんか削除されたら困りますし。ですが、先ほどみたようなビルドパスは削除されます。

1
strings ./ccache.exe | grep -i "C:[/\\]" | sort | uniq

これで余計なシンボル情報が削除できていることが確認できましたね。

まとめ

今回は実行可能ファイルに含まれているシンボル情報による漏洩の危険性と、その削除の仕方について述べました。 上記は基本的な使い方のみの紹介ですので、興味のある方はGNUのマニュアルなどを参照していろいろと遊んでみてください!