業務などでC/C++言語で作ったプログラム(実行可能ファイル)をリリースしたり他の人に渡すことがあると思います。
その時にシンボル情報というものに気をつけていますでしょうか?
シンボル情報というのは関数名とか変数名などの情報です。
例えばデバッガなんかで変数の値をのぞいたり、特定の関数まで実行したりできますよね。
それは実行可能ファイルにシンボル情報があるからできるわけです。どこにどんな変数や関数があるかラベルづけされているわけですね。
このように実行可能ファイルにはシンボル情報が含まれているんですが、場合によってはこれが情報漏洩の原因になりかねません。
デバッグ用の関数や変数や隠し機能などが漏れてしまう可能性があります。また、ビルドした時のパス情報なども含まれるためにユーザ名なんかもわかってしまうかもです。
そこで今回は、実行可能ファイルからシンボル情報をみる方法を示したうえで、それを削除する方法を示します。
リンクできなくなってしまうので要注意です!
リンカはシンボル情報を参照してそれらを結合するので消してはダメです!
シンボル情報をのぞいてみよう
シンボル情報を見るには nm コマンドを使用します。今回の例はWindows MINGW 上で実行していますが、Linuxでも同様です。
ためしに自前でビルドした実行可能ファイルに対してnmを実行したところ、下記のようになりました。
左の数字はアドレス、真ん中はデータの種類、右はシンボル名です。なんとなくたくさんのデータや関数があるんだなぁ、というのがわかるかなと思います。
このように、nmでシンボル情報を表示することで、関数名やファイル名が簡単に見ることができてしまいます。
$ 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文字が表示されていきます。
$ 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 のところでユーザ名が判別できてしまいます。
$ 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というコマンドがそれを実現してくれます。
使い方も簡単。単に実行可能ファイルを渡せばいいだけです。
$ strip ./ccache.exe
こうすると、nmコマンドを実行しても下記のように「シンボルが見つかりません」という旨のメッセージが表示されます。シンボル情報の削除に成功していますね。
$ nm ./ccache.exe C:\Programs\msys64\mingw64\bin\nm.exe: ./ccache.exe: no symbols
stringsについては、さすがに全てのASCII文字を削除するわけにはいかないので、多少は残ります。例えばprintfで表示するメッセージなんか削除されたら困りますし。ですが、先ほどみたようなビルドパスは削除されます。
$ strings ./ccache.exe | grep -i "C:[/\\]" | sort | uniq
このとうり。
これで余計なシンボル情報が削除できていることが確認できましたね。
まとめ
今回は実行可能ファイルに含まれているシンボル情報による漏洩の危険性と、その削除の仕方について述べました。
上記は基本的な使い方のみの紹介ですので、興味のある方はGNUのマニュアルなどを参照して色々と遊んでみてください!
コメント
>ライブラリ(.a, .so)やオブジェクトファイル(.o)からシンボル情報を削除すると、
リンクできなくなってしまうので要注意です!
リンカはシンボル情報を参照してそれらを結合するので消してはダメです!
Linuxだと別なのかもしれませんが、stripされているライブラリは普通にあります。手元のUbuntu18.04内にある標準ライブラリもstripされていましたよ。
“`
$ file /lib/x86_64-linux-gnu/libdl-2.27.so
/lib/x86_64-linux-gnu/libdl-2.27.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=25ad56e902e23b490a9ccdb08a9744d89cb95bcc, for GNU/Linux 3.2.0, stripped
“`
情報ありがとうございます。
システム標準のライブラリは、ちゃんと不要なシンボルだけ削除してるんでしょうね。
>システム標準のライブラリは、ちゃんと不要なシンボルだけ削除してるんでしょうね。
一応自作ライブラリや自分でビルドした標準ライブラリに対してstripコマンドを利用しても同様の結果になります(strippedになるけどリンク出来る状態)。
なので最近のLinux stripコマンド実装が不要なシンボルだけ削除してくれるようになっているって話かもしれませんね。(詳細把握してなくてすいません)
なるほど。。リンクできる環境とできない環境とがあるんですね。
下記のオプションをつけるのが確実そうですね。
ご指摘ありがとうございました!!
–strip-unneeded
リロケーション処理に不必要なシンボルを全て削除する。