MIS.W 公式ブログ

早稲田大学公認、情報系創作サークル「早稲田大学経営情報学会」(MIS.W)の公式ブログです!

C言語とプロセスメモリエディタ【カウントダウンカレンダー2016冬9日目】

こんにちは、51代のしらす(@hty)です。先日SECCONに参加しました。結果は400点と、なんとも寂しい結果に終わってしまいましたが、バイナリ解析で色んなツールに触れて、こういうアンダーグラウンドなこと楽しーなってちょっとモチベーションあがってます。

さて、SECCONの話に触れたのは、そのとき使用したプロセスメモリエディタを使ってプログラミングを授業でもいいから少しでも触ったことのある皆さんに、面白い話題を提供できるのではと参加中に考えたからです。大半の人が、プロセスメモリエディタってなんだよ!って状態だと思うのでそこから説明しながら、C言語のプログラムで遊んでみたいと思います。C言語を少しでも知っていることが前提です。申し訳ありません。

プロセスメモリエディタって?

大容量記憶装置に保存されたプログラムのデータはプログラムを実行したとき、メモリ上に展開されます。メモリはプロセス(exeの実行単位)ごとに分割されています。プロセスメモリエディタではこの分割されたプロセスごとのメモリの様子を見たり、編集したりすることができます。

日本製のプロセスメモリエディタではうさみみハリケーンというツールがあります。なんとも可愛らしい名前です。フリーゲームの改造とかによく使われますね。以下サイトからダウンロードできます。

http://www.vector.co.jp/soft/win95/prog/se375830.html

C言語コンパイラ ~Visual C++

あまりコンパイラというものを意識したことがないかもしれません。コンパイラとは文字列としてのソースコードを解釈して実行ファイルを作るものという程度の理解でいいと思います。コンパイラごとに動作が解釈が異なったり、記法が微妙に異なったりなんてこともあります。早稲田の理工Linuxにはgccが入っていますね。WindowsでのC言語(C++)のコンパイラにはVC++があります。Visual studioをインストールしたときに一緒についてきます。Visual Studioでのプログラムのコンパイルや実行方法についてはここでは特に述べませんが、調べてみて下さい。

プロセスのメモリの様子を知る

さて、こんなVC++向けのC言語のプログラムを用意しました。

はじめにint型の変数aとchar型の配列sを用意しました。この「用意する」とはつまりメモリ上に「変数の中身を格納する領域を確保する」ということです。プログラムはこれを自動的に行なっており、領域が確保されるとその値を保存することと、その値が保存されているメモリ上のアドレスが取得できるようになります。アドレスとはメモリ上の場所を表す住所のようなものです。次に値の入力が求められ、その後Enterを押すと変数の中身が表示されるという流れです。何もしなければ入力した値がそのまま表示されるはずです。これをよく覚えておいて下さい。

Visual Studioでコンソールアプリケーションとして実行してみます。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-18-%e6%99%82%e5%88%bb-21-09-58

こんな感じで入力してEnterを送る手前まで来ました。Enterを押せば入力した内容がそのまま表示されるはずです。ここでうさみみハリケーンの登場です。ダウンロードしてきたzipを解凍して、UsaMimi.exeを実行して下さい。するとこのようなプロセス選択画面が出てくるはずです。%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-18-%e6%99%82%e5%88%bb-20-59-34

現在実行されている管理者権限なしでアクセスできるプロセスの一覧が出てきます。testProject.exeが今実行しているプログラムの名前です。これはVisual Studioのプロジェクトの名前に依存するので人によって違うと思います。選択してOKを押すと...

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-18-%e6%99%82%e5%88%bb-21-00-53

うわあ、おぞましい文字列の海が出てきましたね。これを見て「良い...」と感じる心があれば大変立派です(笑)アドレスとその中身、中身をShift_JISで解釈した文字列が出てきています。これがプログラムが展開されているメモリの様子です!どうですか!ワクワクしますね!(個人の感想です)

では、ここでプログラムを実行したときに変数が確保されているアドレスを表示するようにしていたことを思い出します。

  • int型(4byte)の変数a
    • アドレス・・・0x7bfaa4
    • その値・・・97253(10進) 17BE5(16進)
  • char型(1byte)の配列s
    • アドレス(先頭)・・・0xa67eb8
    • その値・・・BOON!BOON!HELLO,YOUTUBE

ではそのアドレスを見に行ってみましょう。右クリック→表示アドレスを指定→アドレスを入力→アドレスを変更で移動できます。まずは変数aのアドレスを見に行ってみます。%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-18-%e6%99%82%e5%88%bb-21-04-25

4byteなので青く選択しているところが変数aの領域となっているはずです。格納データ97253が、下のバイトから格納されていることが分かります。この最下位のバイトからメモリへの格納を行う方式を「リトルエンディアン」と呼びます。Windowsが動作しているパソコンはx64系、x86系プロセッサのCPUを使用しており、このCPUではリトルエンディアン方式が採用されているされているためです。確かに入力したデータがメモリ上に保存されているのが分かりますね。ではこれを書き換えてしまいましょう。%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-18-%e6%99%82%e5%88%bb-21-07-28

「E57B0100」を「52BF0100」に書き換えました。カーソルを合わせて上から入力するだけで大丈夫です。「1BF52」は10進では「114514」です。他意はありません。

次に配列sの先頭アドレスを見に行ってみます。

aaa

青く選択したところがちょうど100byte分なので、確保した領域となっているはずです。確かに入力した文字列が格納されていることが分かります。確保したけど使っていない領域には意味のない数字が入っていますね。文字と数値の変換についてはASCIIコード表を参照して下さい。

ASCIIコード表

では、これも書き換えてしまいましょう。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-18-%e6%99%82%e5%88%bb-21-20-50

では、元のプログラムに戻ります。Enterの入力待機中にしてあったはずです。変数の中身をプロセスメモリエディタを使って書き換えてしまったので、中身が変わっているはずです。こんな感じになります。%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-18-%e6%99%82%e5%88%bb-21-20-12

おお〜確かに変わってますね。でも配列sの中身は意図しない形になってしまいました。どうしてでしょう。考えても見て下さい。printfには文字列の先頭アドレスしか渡していないのに、どうやって文字列の終端を検知しているのでしょう。

答えは「null文字」です。

C言語では文字列の終端には必ずnull文字(数値としてはゼロに対応、ASCIIコード表を参照のこと)が挿入されるようになっています。ソースコード上の文字リテラルや、scanfで文字を入力したときは、実は文字列の終端(配列の一番最後)にnull文字が付加されるようになっているのです。実際、書き換える前の配列sのプロセスメモリエディタの様子を見ても、0xa67ec0+fの位置に「00」が格納されていることが分かります。printfはこのnull文字を検知して文字列の終端を検知していたのです。配列sの中身を書き換えたときこのnull文字も書き換えてしまいました。だからprintfはこちらが意図した文字列の終端を検知できずに必要のないところまで表示してしまったのです。配列sのプロセスメモリエディタの様子とprintfの表示内容を比較しても分かるとおり、printfは配列sに用意したメモリ領域を超えてデータを表示してしまっています。printfはとにかく値がゼロになるまで進むので、0xa67f20+6まで進んでいます。

どうだったでしょうか。普段授業で習っているだけではなかなか動いてる様子までは見られませんが、このようなツールを使うと確かに動いてるんだなってことを実感できると思います。このうさみみハリケーンはまだまだ色んな機能がある(僕もよく分かっていない)のでぜひ色々遊んでみて下さい。

よく分からんかった、そんな人

多分、読んだけどよく分からん、なんかスゴイで終わってしまった人もいると思います。でもまあこういう世界の一端を知って興味を持ってくれたなら嬉しいです。どうでもいいって思った人も、はっきり言って普通のプログラミングをするならこんなこと知らなくていいですし、もはや趣味の領域です。プログラミング自体はこんなに変なものじゃないのでぜひプログラミングには挑戦してみてほしいです。

まだまだ僕もにわかだけど、いつか強い人になれるといいね。