共有メモリー・ダンプを解析するための Python アプリケーションを作成する

struct ユーティリティーを使用して、分析用のシステム・データを抽出する

Linux プラットフォーム上にある、コンピューターで読み取り可能な共有メモリー・ダンプを、Python と struct ユーティリティーを使用して解析し、想定されるフォーマットでデータを抽出する方法を学びましょう。この記事ではまず、ダンプ・ファイルのバイナリー・ファイル・フォーマットを読み取って、データのフォーマットを判断する方法について説明します。この操作はデータの解析、抽出、そして分析のために必要です。次に、そのフォーマットに基づいてファイルを解析する方法と、その解析結果と想定フォーマットとを突き合わせて、検証結果を出力する方法について説明します。

改訂情報: この記事の「ダウンロード」セクションに、実際に動作する Python アプリケーションとダンプ・ファイルが用意されました。そのまま使用することもできますし、皆さんのニーズに合わせて変更することもできます。また、この記事の中のダンプ・ファイルの名前を、ダウンロードで提供するファイルで使用されている名前に合わせて変更しました (編集者より)。

Asha Shivalingaiah, Software Engineer, IBM

Asha Shivalingaiah は、Australia Development Lab for IBM Security Solution, Tivoli でテスターとして勤務しているソフトウェア・エンジニアです。彼女は、2008年に IBM の Rational チームに加わって以来、ソフトウェア開発ライフサイクル管理のための Rational Rhapsody やその他の Rational シリーズのツールを使用して作業してきました。彼女は UML 2 や SysML などのモデリング言語について、またモデル駆動型開発用の Rational Rhapsody の使い方について、熟知しています。



2011年 6月 17日

メモリー・ダンプにより、オペレーションのある時点で記録されたワーキング・メモリーの状態を明らかにすることができます。メモリー・ダンプは、システムの状態の「フォレンジックな」証拠を提供してくれるため、システムを管理する上で重要なツールです。

始める前に

この記事では、命令や、コード・サンプル、ダウンロード・コード (記事の終わりにある「ダウンロード」を参照) に Python バージョン 2.4 を使用しました。このバージョンは Python のサイトからダウンロードすることができます (記事の終わりにある「参考文献」を参照)。他のバージョンを使用する場合は、結果が異なる可能性があります。

この記事を読み進めるには、以下のことを理解している必要があります。

  • 従来からの共有メモリーの実装である /dev/shm
  • Linux システム上で共有メモリー・データのダンプを手動で表示する方法
  • 特定の関連事項 (Linux でのファイルのオープン、読み取り、書き込み、クローズの概念、ファイル記述子とファイルを開く場合のモードの使用方法、そして基本的な Python の構造の概念)
  • GNU/Linux に関する一般的な事項

Linux の共有メモリー・ダンプを理解する

/dev/shm は、従来からの共有メモリーの概念を実装したものであり、プログラム間でデータを受け渡すための手段として広く使用され、受け入れられています。/dev/shm では、(適切なアクセス許可レベルを持つ) 他のプロセスがアクセスできるメモリー部分が、1 つのプログラムまたはデーモンによって作成されます。この方法は、プロセス間でデータを共有する手軽で容易な方法です。

各プログラムは、それぞれ独自のファイルを作成します。この記事の例では devmem というファイル名を使用しますが、このファイルは /dev/shm/devmem にあります。

Linux で共有メモリー・ダンプを手動で表示する

共有メモリー・ファイル (一般に shm ファイルと呼ばれます) を表示する場合、shm ファイルはバイナリー・フォーマットであるため、Linux でファイルの表示に通常使用される cat ユーティリティーを使用することはできません。一般的なファイル表示方法で shm ファイルを表示しようとしても、文字化けしたものが表示されるだけです。メモリー・ファイルを読み取り、人間が読めるフォーマットで表示するために、私は hexdump ユーティリティーを使用していますが、この目的のために使用できるユーティリティーは他にもあります。

この記事では、hexdump の使用パターンは以下のようになります。

hexdump <オプションのスイッチ> /dev/shm/devmem (サポートされている <スイッチ> を使用)

hexdump についての詳細は「参考文献」を参照してください。


シナリオを定義する

この記事で取り上げるシナリオでは、ネットワーク・スニファーが、ホストが受信したパケットを分析し、そのデータを共有メモリー・ファイル /dev/shm/devmem に保存します。保存されるデータには、受信したパケットに関する情報が含まれています。

このファイルの概要は以下のとおりです。

  • メモリー・ファイルの保存先は /dev/shm/devmem です。
  • devmem ファイルのフォーマットに含まれるものを以下に示します。
    • 誰が送信したのかを示す 4 バイトの送信元アドレス
    • 誰に送信するのかを示す 4 バイトの送信先アドレス
    • 2 バイトの送信元ポート (つまり、パケットが使用した送信元のポート)
    • 2 バイトの送信先ポート (同様に、パケットが使用する送信先のポート)
    • 2 バイトで表現したプロトコル (パケットが準拠しているプロトコル)
    • ネットワーク・スニファーがパケットを認識した瞬間のタイム・スタンプを示す 4 バイトで表現された時刻
  • 1 レコード長 = devmem ファイルのフォーマットの各構成要素を合計した長さ (つまり 18 バイト)
  • メモリー・ファイルの最大サイズは 1K バイトなので、このファイルには 1024 バイトを含むことができます (1024 / 18 = 56 レコード)

Linux 端末で、手動で hexdump を実行してファイルを表示すると、以下のように表示されます。

リスト 1. ダンプ・ファイルを表示する
# hexdump /dev/shm/devmem
0000000 0004 0000 0400 0000 fc64 0a00 00fb e000
0000010 14e9 14e9 0011 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0800
0000030 1668 0000 0000 0000 0032 0000 0000 0000
0000040 0000 0000 0001 e000 0000 0000 0002 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
0000060 0000 0000 0000 0800 0100 0000 0000 0000
0000070 0008 0000 0000 0000 fc64 0a00 fd64 0a00
0000080 2328 03ea 0006 0000 0000 0000 0000 0000
0000090 0000 0000 0000 0000 0000 0000 0000 0800
00000a0 7700 0001 0000 0000 0040 0000 0000 0000
00000b0 fd64 0a00 fc64 0a00 03ea 2328 0006 0000
00000c0 0000 0000 0000 0000 0000 0000 0000 0000
00000d0 0000 0000 0000 0800 0a00 0000 0000 0000
00000e0 0040 0000 0000 0000 fc64 0a00 fd64 0a00
00000f0 2328 03ec 0006 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0000 0000 0000 0800
0000110 7700 0001 0000 0000 0040 0000 0000 0000

では、このファイルを解析するステップを調べてみましょう。


ダンプ・ファイルを解析する

メモリー・ダンプ・ファイル内のデータを理解するためのステップ (フォーマットの判定、ファイルの解析、およびファイルの読み取り) は、以下に示すように比較的単純です。

  1. ファイルを開きます。
  2. ファイル記述子を使用し、必要なバイト数だけファイルを読み取ります。
  3. 必要であれば、人間が読み取り可能なストリング・フォーマットにデータを変換します。
  4. 読み取りが行われたバッファーに損傷がないかどうか、およびバッファーに欠落やエラーがないかどうかを検証します。
  5. バッファーからデータをアンパックします。
  6. 情報を抽出します。
  7. データを表示します。
  8. 共有データ・ダンプの各レコードに対して、ステップ 1 からステップ 7 を繰り返すループを作成します (このステップを手動で繰り返したいと思う人はいないはずです)。

では、このプロセスのフローを詳細に調べてみましょう。

ファイルを開く

共有メモリー・ファイルを開くためには、一般的な形式である fd = open(fileName, mode) を使用します。fd はファイル記述子であり、ファイルに対するポインターです。この例では、以下に示すファイル名およびモードを使用します。

  • fileName: /dev/shm/devmem
  • mode: rb (バイナリー・モードで読み取り専用)
リスト 2. 共有メモリー・ファイルを開く
fd = open('/dev/shm/devmem ','rb')

必要なバイト数だけファイルを読み取る

上記の関数を呼び出した結果得られたファイル記述子を使用して、必要なバイト数だけファイルを読み取るためには、以下のコードを使用します。このコードは、渡されたファイル・パラメーターから指定されたバイト数を読み取ります。

リスト 3. 共有メモリー・ファイルを開く
def ReadBytes(self, fd, noBytes):
 '''
 Read file and return the number of bytes as specified
 '''
 data = fd.read(noBytes)
 return data

buffer = ReadBytes('/dev/shm/devmem ', 18)
# Pass the file name and pass the number of bytes
# Number of bytes is 18 since in the example scenario each record
#  is of length 18

ここで、必要な情報を抽出するためには、指定されたバイト数を読み取るだけでは十分ではないので、ストリングが読み取られたらそのバッファーを返します。このバッファーは解析され、人間が理解できるストリング・フォーマットに変換される必要があります。

データを変換する

Python の struct を使用すると、ファイルに保存されていたり、ネットワークと接続して得られたり、またはその他のソース内に存在していたりするバイナリー・データを処理することができます。Python の struct には、packunpack という 2 つの一般的な機能があります。

pack は、指定のフォーマットに従ってパックされた値 v1v2、… を含むストリングを返します。引数は、そのフォーマットで要求される値と完全に合致する必要があります。

unpack は指定のフォーマットに従って、(おそらく pack(fmt, ...) によってパックされた) ストリングをアンパックします。アンパックされた結果は、たとえその結果に 1 つの項目しか含まれていない場合であっても、タプルです。ストリングに含まれるデータの量は、そのフォーマットで要求されるデータの量と完全に一致していなければなりません。つまり、len(string)calcsize(fmt) は一致しなければなりません。

受け入れ可能なフォーマットは以下のとおりです。

  • 1 バイト・フォーマット
    • b は符号付きの文字型
    • B は符号なしの文字型
  • 2 バイト・フォーマット
    • h は短整数型
    • H は符号なしの短整数型
  • 4 バイト・フォーマット
    • l は長整数型
    • L は符号なしの長整数型
  • 8 バイト・フォーマット
    • q は長々整数型
    • Q は符号なしの長々整数型

バッファーのバイトのパックおよびアンパック用にサポートされている他のフォーマットに関しては、「参考文献」に挙げた Python についての資料を参照してください。

バッファーを検証する

読み取られた 18 バイトのバッファーに損傷がなく、欠落やエラーがないかどうかを検証するためには、バッファーを読み取る際に calcsize 関数を使用して、バイト・サイズが想定どおりの 18 バイトであるかどうかをチェックします。このチェックを行うには、Python の assert 関数を使用することができます。

リスト 4. バッファーが適切かどうかを検証する
self.assertEqual(len(buffer), struct.calcsize('llllh'))

# 4 l's is 4*4 bytes = 16 bytes and h is 2 bytes so that is 18 bytes
# we could use QQh which is 2*8 + 2 = 18 bytes as well

データをアンパックする

バッファーは確かに 18 バイトであることが確認できたので、今度はバッファーからデータをアンパックします。struct には、読み取りが必要なバイト数、バッファー名、およびオフセットを指定できる便利な unpack_from 関数があります。

struct.unpack_from(fmt, buffer[, offset=0])

詳細情報を抽出する

この記事のシナリオでは、以下の詳細情報を抽出します。

リスト 5. 抽出される詳細情報
sourceAddress = (struct.unpack_from('B', buffer,0),
                     struct.unpack_from('B', buffer,1),
                     struct.unpack_from('B', buffer,2),
                     struct.unpack_from('B', buffer,3))
destinationAddress = (struct.unpack_from('B', buffer,4),
                     struct.unpack_from('B', buffer,5),
                     struct.unpack_from('B', buffer,6),
                     struct.unpack_from('B', buffer,7))
sourcePort = (struct.unpack_from('B', buffer,8),
                     struct.unpack_from('B', buffer,9))
destinationPort = (struct.unpack_from('B', buffer,10),
                     struct.unpack_from('B', buffer,11))
protocolUsed = (struct.unpack_from('B', buffer,12),
                     struct.unpack_from('B', buffer,13))
timeStamp = (struct.unpack_from('B', buffer,14),
                     struct.unpack_from('B', buffer,15),
                     struct.unpack_from('B', buffer,16),
                     struct.unpack_from('B', buffer,17))

注: プラットフォームによっては、またメモリー構造がビッグ・エンディアンかリトル・エンディアンかによっては、バイトの読み取り順序を逆にする必要があるかもしれません。

出力を表示する

読み取ったバイナリー・バッファーから値をアンパックすることができたので、次に標準的な print コマンドを使用して、必要な出力を得ることができます。

リスト 6. 詳細情報を表示する
print "sourceAddress =" ,  
      (struct.unpack_from('B', buffer,0),struct.unpack_from('B', buffer,1),
      struct.unpack_from('B', buffer,2),struct.unpack_from('B', buffer,3))
print "destinationAddress = " ,
      (struct.unpack_from('B', buffer,4),struct.unpack_from('B', buffer,5),
      struct.unpack_from('B', buffer,6),struct.unpack_from('B', buffer,7))
print "sourcePort = " , (struct.unpack_from('H',buffer,8))
print "destinationPort = " , (struct.unpack_from('H',buffer,10))
print "protocolUsed = " , (struct.unpack_from('H',buffer,12))
print "timeStamp = " ,  
      (struct.unpack_from('B', buffer,14),struct.unpack_from('B', buffer,15),
      struct.unpack_from('B', buffer,16),struct.unpack_from('B', buffer,17))

リスト 6 による出力は、以下のようなフォーマットになるはずです。

リスト 7. print コマンドによる出力
sourceAddress =  ((192,), (168,), (10,), (102,))
destinationAddress =  ((207,), (168,), (1,), (103,))
sourcePort =  (11299,)
destinationPort =  (11555,)
protocolUsed =  (256,)
timeStamp =  ((1,), (12,), (0,), (1,))

すべてのレコードに対し、プロセスを自動化する

今度は共有メモリー・ファイル全体からすべてのレコードを読み取って表示するために、ループを作成します。

リスト 8. すべてのレコードを読み取って表示するためのループを作成する
for element in range (0,56):
#loop 18 since we know the file size and
#the record length: 1024/18 = 56 records
		
      buffer = ReadBytes('/dev/shm/devmem ', 18)
      self.assertEqual(len(buffer), struct.calcsize('llllh'))
        
      sourceAddress = struct.unpack_from('B', buffer,0),
                  struct.unpack_from('B', buffer,1),
                  struct.unpack_from('B', buffer,2),
                  struct.unpack_from('B', buffer,3))
      destinationAddress = struct.unpack_from('B', buffer,4),
                       struct.unpack_from('B', buffer,5),
                       struct.unpack_from('B', buffer,6),
                       struct.unpack_from('B', buffer,7))
      sourcePort = struct.unpack_from('B', buffer,8),
                 struct.unpack_from('B', buffer,9)
      destinationPort = struct.unpack_from('B', buffer,10),
                    struct.unpack_from('B', buffer,11))
      protocolUsed = ,struct.unpack_from('B', buffer,12),
                  struct.unpack_from('B', buffer,13))
      timeStamp = struct.unpack_from('B', buffer,14),
                struct.unpack_from('B', buffer,15),
                struct.unpack_from('B', buffer,16),
                struct.unpack_from('B', buffer,17))
        
      print "sourceAddress = " ,  
            struct.unpack_from('B', buffer,0),
            struct.unpack_from('B', buffer,1),
            struct.unpack_from('B', buffer,2),
            struct.unpack_from('B', buffer,3))
      print "destinationAddress =  " ,
            struct.unpack_from('B', buffer,4),
            struct.unpack_from('B', buffer,5),
            struct.unpack_from('B', buffer,6),
            struct.unpack_from('B', buffer,7))
      print "sourcePort =  " ,
            struct.unpack_from('H',buffer,8))
      print "destinationPort =  " ,
            struct.unpack_from('H',buffer,10))
      print "protocolUsed =  " ,
            struct.unpack_from('H',buffer,12))
      print "timeStamp = " ,  
            struct.unpack_from('B', buffer,14),
            struct.unpack_from('B', buffer,15),
            struct.unpack_from('B', buffer,16),
            struct.unpack_from('B', buffer,17))

これで終わりです。この記事では、Linux 上にある既知のフォーマットのバイナリー・メモリー・ダンプを解析し、Python の struct を使用してバイナリーのデータ・ダンプを読み取り、人間が読み取り可能なフォーマットで表示しました。


ダウンロード

内容ファイル名サイズ
Python app for parsing memory dumpParseBinaryInPython.zip6KB

参考文献

学ぶために

製品や技術を入手するために

  • Python の Web サイトから Python をダウンロードしてください。
  • 皆さんに最適な方法で IBM 製品を評価してください。製品の試用版をダウンロードする方法、オンラインで製品を試す方法、クラウド環境で製品を使う方法、あるいは SOA Sandbox で数時間を費やし、サービス指向アーキテクチャーの効率的な実装方法を学ぶ方法などがあります。

議論するために

  • My developerWorks に参加してください。開発者向けのブログ、フォーラム、グループ、およびウィキを利用しながら、他の developerWorks ユーザーとやり取りしてください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=679201
ArticleTitle=共有メモリー・ダンプを解析するための Python アプリケーションを作成する
publish-date=06172011