« 2009年9月 | トップページ | 2009年11月 »

2009年10月

2009年10月26日 (月)

CFF/Type2 Charstring

データベース絡みの事をやっていたのであるが、行き詰まったdashので、気分転換にちょっと本格的にT2FAnalyzerで要望のあったCFF(Compact Font Format)/Type2 Charstringの解析に向けて、仕様書を読み始めた。

Type2 Charstringというものがグリフのアウトラインを記述してるようであるが、TrueTypeアウトラインのglyfテーブルと違って、CFFアウトラインは一種のプログラムじゃんbearing。このプログラムの実行環境であるType2 Charstringインタプリタもどきみたいなの作らんとアウトラインの形状をラスタライズできそうにもないな(TrueTypeインストラクションほど複雑そうではないが・・)wobbly

うーん。

漠然とCFF/Type2の全体のイメージが掴めたので、CFF/Type2の解析部分のプログラミングを開始しましたhappy02

Cfftype2_2

2009年10月 7日 (水)

Win32デバッグ(12)・・・SEH(Structured Exception Handling)

まぁ、どうしようか悩んだが、とりあえず、進める所まで進んでみますsad

ということで今回のお題目は例外。結論から言うとDelphiではtry-except文による例外処理にしろ、try-finally文の終了処理にしろ、WindowsのSEH(Structured Exception Handling)を使って実装されているのだが、そのSEHについて。

SEHはWindowsによって提供されている構造化例外処理のためのメカニズムのことであるが、その流れについて説明する。

まず、0による除算や不正なメモリへのアクセスなどのハードウェア例外にしろ、Win32 APIのRaiseException関数によるソフトウェア例外にしろ、スレッド内で例外が発生するとOSに制御が移る。そして、OSは例外が発生したスレッドのCPUレジスタなどのコンテキスト情報の保存など必要な処理を行った後、例外をハンドルするための例外ハンドラを検索し、呼び出す。

具体的には、まず、OSは例外が発生したスレッドのスレッド環境ブロック(TEB:Thread Environment Block)またはスレッド情報ブロック(TIB:Thread Information Block)と呼ばれる領域の先頭4バイト(at FS:[0])の値を取得する。この4バイトの値は例外ハンドラのリンクリストの先頭をポイントしているので、この値から先頭の例外ハンドラから順に呼び出していくのである(つまり、ここに例外ハンドラを登録すれば例外発生時に例外ハンドラが呼び出されるようになるのだが、通常は、Delphiにしろ、C++にしろ、言語提供の例外処理などを利用すれば、コンパイラによって登録するコードが挿入されるので、普通は自前で登録しないが・・)。

リンクリストのノードは次のようなEXCEPTION_REGISTRATION構造体になっている。

prevメンバは次のノードへのアドレス、handlerメンバーには例外ハンドラのアドレスつまりOSから呼び出されるコールバック関数のアドレスを表す。呼び出されるコールバック関数のプロトタイプは次のようになる。

第1引数はEXCEPTION_RECORD構造体のアドレスである。EXCEPTION_RECORD構造体は例外の種類を表すコードや例外発生時のアドレスなどの発生した例外に関する様々な情報が格納されているので、例外ハンドラはこれらの情報を見て例外を処理するかを決定する。Delphiのtry-except文では例外オブジェクトのクラスとexceptのon句に指定したクラス(on E: ExceptionClass doのExceptionClassの部分)を比較して判定している。

呼び出された例外ハンドラで例外を処理せず、OSに次の例外ハンドラを呼び出させる場合はExceptionSearchException(1)を返して例外ハンドラからOSにリターンする。

また、例外が発生したアドレスから例外の原因を修正(0による除算が発生した場合、除数を0以外の値に修正)するなどして、再実行する場合はExceptionContinueExecute(0)を返してOSにリターンする。Delphiではこれは行われない。

例外ハンドラで例外を処理する場合は、例外ハンドラからOSにリターンせず、通常は、Win32APIのRtlUnwindなどでアンワインド(巻き戻し)したり、スタックフレームを適切に設定し直して、処理を続行する。Delphiではexceptのon句の後に指定した処理から実行が再開される。

RtlUnwindによるアンワインドはMSDNのドキュメントみても何の事だかさっぱり理解できないと思うが、簡単に言うと、例外ハンドラのリンクリストから不必要になった例外ハンドラのノードを削除することである。また、このとき、例外ハンドラの2回目の呼び出しが行われる。この2回目の呼び出しのときに、通常は終了処理を行う。Delphiではtry-finally文のfinally句の後に指定した終了処理がこの2回目の呼び出しのときに、実行される。

とまぁ、おおざっぱに書くとこんなところであろうが、詳細は

すべて、英語で書かれているが、特に一番上の記事にはSEHについて分かりやすく、詳細に書かれているのでお勧めです。自分は頑張って読みましたdash

2009年10月 6日 (火)

Win32デバッグ(11)・・・番外編

今回は寄り道。というより、前回までに示したコードに少し問題があった。

第1回で

  • ReadProcessMemory
  • WriteProcessMemory

を使えば、プロセスハンドルからそのプロセスのアドレス空間を読み書きできるし、また、

  • GetThreadContext
  • SetThreadContext

を使えば、スレッドハンドルからそのスレッドのコンテキスト(レジスタの値)を読み書きできる。

と書いたが、大切な事が抜けていたshock

ということで、例えば、あるスレッドが次に実行する命令を読み込むプログラムを上記のAPIを使って書いてみる。x86アキーテクチャではインストラクションポインタ(EIP)レジスタが次に実行される命令をポイントするので、例えば、次のようになる(繰り返すが、対象スレッドは停止してないとまずいと思う)。

x86命令は可変長の命令で何バイト読みこめばよいか?という問題があるが、上記のプログラムではとりあえず4バイト読み込んでいる。上記のプログラムを実際に動かすと正しく動いているように見えるが、問題があるのである。

何が問題かと言うと、ReadProcessMemory/WriteProcessMemoryの第2引数には読み書きするメモリの仮想アドレスを渡すのであり、上記のプログラムでは、EIPレジスタの値を渡しているのであるが、EIPレジスタの値は仮想アドレスの値ではないのである。EIPレジスタに格納されているのは確かにアドレスであるが、CSレジスタに格納されているセグメントセレクタによってポイントされるセグメント(コードセグメント)内の相対アドレス(segment-relative address)なのである。

ということで、仮想アドレスに変換する必要があるのであるが、そのために使うAPIがGetThreadSelectorEntryである。GetThreadSelectorEntryによって引数で指定したセグメントの仮想アドレス空間内のセグメントのベースアドレスが求まるので、それを使って正しく先ほどのプログラムを書き換えると次のようになる。

と、肝心な事を忘れたdash

ちなみに、最初に示したプログラムが動いてしまうのは、どうも、NT環境ではコードセグメントのベースアドレスが常に0になるからのようである。特にESPレジスタによってポイントされるスタックトップを読み書きするような場合、スレッド毎に異なるスタックセグメント(SSレジスタ)が割り当てられるので、なお更まずい。

« 2009年9月 | トップページ | 2009年11月 »

自作ソフトウェア

無料ブログはココログ

メモ