速さこそ正義

調べたことを書いていきます。

stringsでなぜprintfが見えるのか

Linuxへの理解を深めるため、最近CTFの問題を解いています。

CTFの回答を見ていると実行ファイル(バイナリ)に対してstringsコマンドを実行して、その中にpritnfがあるから書式文字列攻撃を試して・・・と書いてありました。
全く意味がわからなかったので、調べたことのメモになります。

そもそもstringsとは

Linuxのコマンド 。 指定したファイル中の表示可能な文字列を表示します。 バイナリファイルやデータファイルの内容を判断するために利用します。

strings実行例

実行するとHello world!と表示されるファイルを用意します。

$ ./main
Hello world!

この実行ファイルをstirngsで見てみます。
※stringsはデフォルトではファイルの先頭しか見ないので、オプションの-をつけることでファイル全体から文字を検索します。

$ strings - main
__PAGEZERO
__TEXT
__text
__TEXT
__stubs
__TEXT
__stub_helper
__TEXT
__cstring
__TEXT
__unwind_info
__TEXT
__DATA
__nl_symbol_ptr
__DATA
__la_symbol_ptr
__DATA
__LINKEDIT
/usr/lib/dyld
/usr/lib/libSystem.B.dylib
Hello world!
@dyld_stub_binder
@_printf
_mh_execute_header
!main
__mh_execute_header
_main
_printf
dyld_stub_binder

stringsは実行ファイル中に存在する文字だけを抽出してくれるのがメリットです。
※lessやcatで実行ファイルを見てもノイズが多くて見にくい

実行ファイル(バイナリ)って変数名とか見れないんじゃないの?

私の認識では高級言語のソースコードをコンパイルしたら変数名や関数は機械語に変換されるため、stringsコマンドでバイナリを見てももちろんソースコードは出てこないし、関数名・変数も機械の都合の良い名前に変換されていると思っていました。
なので例えばint hoge=1111なんてコードを書いても、コンパイル後はintはアセンブリ言語のMOVかなにかに変換されて、変数名hogeも別の何かに変換されて跡形もなくなると思っていました。

なのでstringsコマンドで実行ファイルを覗いても、ソースコードの関数は見えないはず(=printfは出てこない)では?と思い混乱しました。

実際にコンパイルしてみた

やってみないとわからないので、
試しに以下のコードを書いてコンパイルしてみました。

C言語でテスト

/* test.c */
#include <stdio.h>
int test(){
  int hensu = 1111;
  printf("変数の中身は%dです\n",hensu);
  return 0;
} 

int main(){
  printf("Hello world!\n");
  test();
  return 0;
}

上記コードを保存(test.cなど)して以下コマンドでオブジェクトファイルの作成をします。
gcc -o test.c

するとtest.oが生成されるので、今回はこれをstringsで覗きます。

strings結果

MacBook-Pro:Desktop takanori$ strings - test2.o
__text
__TEXT
__cstring
__TEXT
__compact_unwind__LD
__eh_frame
__TEXT
Hello world!
_test
_main
_printf

たしかにprintfが拾えてる!

まとめ

上記の検証から 変数名hensuはコンパイル後stringsで見えない。
関数名main,test,printfは見えることがわかった。

どうやら変数名(hensu)や既存の関数(intなど)はコンパイルされると跡形もなくなりますが、自分で作った関数main,testや外部の関数printfはそのまま変換されずに残るようです。
ただし残るといっても関数名が残っているだけで、関数内部の処理はコンパイル済みであり、あくまでも関数の名前の部分だけがそのまま残るみたいです。

ここまで調べるのに結構時間がかかりましたが、最後まとめているときに逆コンパイルについてまとめているサイトを見つけました。これを初めから見ていえればこんなに時間がかからなかったのに・・・ とはいえ、今までわかっていなかったコンパイルの流れや実行ファイルの仕組みを理解できたのは非常に有益でした。
特にコンパイルには広義・狭義の意味があったり、アセンブラ・アセンブリ・アセンブルの違いなども調べていくうちに理解できたので、興味がある人は調べてみてはいかがでしょうか。

参考書籍

セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方-

セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方-

  • 作者: 碓井利宣,竹迫良範,廣田一貴,保要隆明,前田優人,美濃圭佑,三村聡志,八木橋優,SECCON実行委員会
  • 出版社/メーカー: マイナビ出版
  • 発売日: 2015/09/30
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (4件) を見る

セキュリティコンテストのためのCTF問題集

セキュリティコンテストのためのCTF問題集

  • 作者: 清水祐太郎,竹迫良範,新穂隼人,長谷川千広,廣田一貴,保要隆明,美濃圭佑,三村聡志,森田浩平,八木橋優,渡部裕,SECCON実行委員会
  • 出版社/メーカー: マイナビ出版
  • 発売日: 2017/07/28
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (1件) を見る

参考サイト

C言語がコンパイルされて実行可能になるまでの流れ - にょきにょきブログ

Cの逆コンパイラはどこまで実現可能か,Javaはなぜ逆コンパイルされやすいのか?