EXE/DLLファイルを分析・解析する
EXE/DLLファイルを分析・解析するサンプルプログラムです。内容としてはファイルを読み込みファイルフォーマットの状態を表示します。次に「Import」でそのファイルで使用するDLLとAPIの一覧を取得します。DLLファイルの場合は「Export」で外部から使用可能なAPI名と序数を表示します。基本的な読込は出来ていますので、発展させればリソースの書き換えや逆アセンブルなども可能です。
2016年7月15日
実行ファイルのリソースを編集できる「リソースエディタ」をオープンソースで公開しましたので、そちらをご利用ください。
iResEditor.js
http://www.petitmonte.com/labo/iResEditor/
※言語はJavaScriptですので他の言語に移植しやすいと思います。
実行ファイルのリソースを編集できる「リソースエディタ」をオープンソースで公開しましたので、そちらをご利用ください。
iResEditor.js
http://www.petitmonte.com/labo/iResEditor/
※言語はJavaScriptですので他の言語に移植しやすいと思います。
以下のDelphiコードは32bitのみ対応ですが、iResEditor.jsは32bit/64bitの両方に対応しています。
サンプルの実行画面
サンプルコード
[Forms_ExeViewr.pas (一部)]
unit Forms_EXEViewr; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ToolWin, ImgList, Menus, ExtCtrls,ShellAPI; { [ EXEヘッダ ] MS_DOSスタブ(64byte) . . . CoffHeader(24byte) OptionalHeader(224byte) (オブジェクトファイルの場合はなし) Section Header (40byte * CoffHeader.NumberOfSections) [ リソースヘッダ] ResourceDirectoryTables(16byte) ResourceDirectoryEntries(8byte * RDT.NumberofNameEntries+RDT.NumberofIDEntries) ResourceDirectoryTables(16byte)+ResourceDirectoryEntries(8BYte) } type // MS_DOSスタブ(64byte) TIMAGE_DOS_HEADER =packed record e_magic : array [0..1] of char; // シグネチャ 'MZ' e_cblp : WORD; // 最終ページのバイト数 e_cp : WORD; // ページ数 e_crlc : WORD; // リロケーションテーブルの項目数 e_cparhdr : WORD; // リロケーションテーブルのサイズ(16byte単位) e_minalloc: WORD; // 最小必要メモリー(16byte単位) e_maxalloc: WORD; // 最大必要メモリー(16byte単位) e_ss : WORD; // スタックセグメントの位置(16byte単位) e_sp : WORD; // スタックポインタ初期値 e_csum : WORD; // チェックサム e_ip : WORD; // イニシャル IP値 e_cs : WORD; // イニシャル CS値 e_lfarlc : WORD; // リロケーションテーブルの位置 e_ovno : WORD; // オーバレイ // ListViewではココから下は表示しない e_res : array [0..3] of WORD; // 予約値 e_oemid : WORD; // OEM identifier (for e_oeminfo) e_oeminfo : WORD; // OEM information; e_oemid specific e_res2 : array [0..9] of WORD; // Reserved words NextOffset : Dword; // 'PEヘッダ位置 end; type // CoffHeader(24byte) TIMAGE_Coff_HEADER =packed record PEMagic : array [0..3] of char ; // シグネチャ'PE' Machine : WORD; // マシンタイプ NumberOfSections : WORD; // この次の次に続くSection Headerの数 TimeDateStamp : DWORD; // 作成日時 PointerToSymbolTable : DWORD; // COFFシンボルテーブル位置 (常に0) NumberOfSymbols : DWORD; // シンボルテーブルエントリ数 (常に0) SizeOfOptionalHeader : WORD; // この次に続くOptionalHeaderのサイズ (常に224byte) Characteristics : WORD; // 特徴(下記参照) end; type TIMAGE_DATA_DIRECTORY =packed record VirtualAddress: Dword; //アドレス Size: Dword; //サイズ end; type // OptionalHeader(224byte) TIMAGE_Optional_HEADER =packed record Magic : WORD; //イメージファイルの状態を識別 (実行ファイル 0x10b Romイメージ 0x107) MajorLinkerVersion : BYTE; //リンカのメジャー バージョン番号 MinorLinkerVersion : BYTE; //リンカのマイナー バージョン番号。 SizeOfCode : DWORD; //コードセクションのサイズ SizeOfInitializedData: DWORD; //初期化データセクションのサイズ SizeOfUninitializedData: DWORD; //未初期化セクションのサイズ AddressOfEntryPoint: DWORD; //開始アドレス BaseOfCode: DWORD; //コード セクションの先頭アドレス BaseOfData: DWORD; //データ セクションの先頭アドレス // 以下、Windows NT固有フィールド ImageBase: DWORD; //イメージファイルの先頭アドレス SectionAlignment: DWORD; //メモリロード時のセクションのアラインメント FileAlignment: DWORD; //ファイルのセクションのアラインメント MajorOperatingSystemVersion: WORD; //必要なOSのメジャーバージョン MinorOperatingSystemVersion: WORD; //必要なOSのマイナーバージョン MajorImageVersion: WORD; //イメージのメジャーバージョン (常に0) MinorImageVersion: WORD; //イメージのマイナーバージョン (常に0) MajorSubsystemVersion: WORD; //サブシステムのメジャーバージョン(4で良い) MinorSubsystemVersion: WORD; //サブシステムのマイナーバージョン(0で良い) Win32VersionValue : Dword; //予約値 SizeOfImage: DWORD; //イメージのサイズ SizeOfHeaders: DWORD; //MS-DOSスタブ+PEヘッダ+セクションヘッダのサイズ (常に1024) CheckSum: DWORD; //イメージ ファイルのチェックサム Subsystem: WORD; //イメージを実行するために必要なサブシステム(フラグあり) DllCharacteristics: WORD; //DLLの特性 SizeOfStackReserve: DWORD; //保存するスタックのサイズ SizeOfStackCommit: DWORD; //コミットするスタックのサイズ SizeOfHeapReserve: DWORD; //保存するローカル ヒープ スペースのサイズ SizeOfHeapCommit: DWORD; //コミットするローカル ヒープ スペースのサイズ。 LoaderFlags: DWORD; //ローダフラグ(無効) NumberOfRvaAndSizes: DWORD; //データディクショナリエントリ数 DataDirectory : array [0..15] of TIMAGE_DATA_DIRECTORY; //上記のディレクトリ構造を参照 end; type //Section Header (40byte) TIMAGE_Section_HEADER =packed record Name : array [0..7] of char; //セクション名 PhysicalAddress : DWORD; //メモリ上でのセクションサイズ VirtualAddress : DWORD; //メモリ上でのセクション仮想アドレス SizeOfRawData : DWORD; //セクションのサイズまたはディスク上の初期化されたデータ PointerToRawData : DWORD; //セクションの最初のページへのポインタ PointerToRelocations : DWORD; //セクションの再配置エントリへのファイルポインタ PointerToLinenumbers : DWORD; //行番号エントリ NumberOfRelocations : WORD; //セクション内の再配置エントリの数 NumberOfLinenumbers : WORD; //セクションの行番号エントリの数 Characteristics : DWORD; //セクションの特性 (IMAGE_SCNxxx参照) end; // リソース type //ResourceDirectoryTables(16byte) TResourceDirectoryTables =Packed record Characteristics : Dword; // リソース フラグ。将来の使用のために予約(現在はゼロ) TimeDateStamp : Dword; // リソース データが作成された時刻。 MajorVersion : Word; // メジャー バージョン番号 MinorVersion : Word; // マイナー バージョン番号 NumberofNameEntries : word; // ディレクトリ(リソース) エントリの数 文字列バージョン NumberofIDEntries : word; // ディレクトリ(リソース) エントリの数 数値識別子バージョン end; type //ResourceDirectoryEntries(8byte) TResourceDirectoryEntries=Packed record ID_RVA : Dword ; // リソースのタイプ Data_SubEntry : Dword ; // 最上位ビットが0の場合 リソースデータエントリのアドレス // 最上位ビットが1の場合 下位31ビットは他のリソースディレクトリテーブルのアドレスです(1つ下のレベル)。 end; type //ResourceDataEntries(16byte) TResourceDataEntries=Packed record DataRVA : Dword ; // リソース データ領域のリソース データの単位のアドレス。 Size : Dword ; // Data RVAフィールドによって指し示されているリソースデータのサイズ(バイト単位)。 Codepage : Dword ; // リソース データ内部のコードポイント値をでコードするために使われるコードページ Reserved : Dword ; // 予約値 end; // インポート type // ImportDirectoryTable(20byte) TImportDirectoryTable =packed record ImportLookupTableRVA : Dword; // インポート ルックアップ テーブルの相対仮想アドレス。 TimeDateStamp : Dword; // 結合されるまではゼロがセットされています。結合後はこのフィールドにはDLLのタイムスタンプがセットされます。 FowarderChain : Dword; // 最初のフォワーダ参照のインデックス。 NameRVA : Dword; // DLL名を含んでいるASCII文字列のアドレス。このアドレスはイメージ ベースに対する相対アドレスです ImportAddressTableRVA : Dword; // インポート アドレス テーブルの相対仮想アドレス。 end; // エキスポート type // ExportDirectoryTable(40byte) TExportDirectoryTable =packed record ExportFlags : Dword; // 予約済みフィールド。現在は0にセットされています。 TimeDateStamp : Dword; // エクスポート データの作成日時。 MajorVersion : word; // メジャー バージョン番号。 MinorVersion : word; // マイナー バージョン番号。 NameRVA : Dword; // DLLの名前を含んでいるASCII文字列のアドレス。イメージベース相対。 OrdinalBase : Dword; // エクスポートのための最初の序数(通常は1) AddressTableEntries : Dword; // エクスポート アドレス テーブルのエントリ数。 NumberofNamePointers : Dword; // 名前ポインタ テーブル内のエントリ数 ExportAddressTableRVA : Dword ; // エクスポート アドレス テーブルのアドレス。イメージ ベースに対する相対アドレスです。 NamePointerRVA : Dword; //エクスポート名ポインタ テーブルのアドレス。イメージ ベースに対する相対アドレス。テーブル サイズはNumber of Name Pointersによって与えられます。 OrdinalTableRVA : Dword; //序数テーブルのアドレス。イメージ ベースに対する相対アドレスです。 end; type TForm_EXEViewr = class(TForm) MainMenu1: TMainMenu; File1: TMenuItem; N1: TMenuItem; N2: TMenuItem; Exit1: TMenuItem; ToolBar1: TToolBar; ToolButton2: TToolButton; ToolButton5: TToolButton; ToolButton3: TToolButton; ToolButton4: TToolButton; TreeView1: TTreeView; Splitter1: TSplitter; ListView1: TListView; ImageList1: TImageList; procedure ToolButton4Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure TreeView1Changing(Sender: TObject; Node: TTreeNode; var AllowChange: Boolean); procedure FormCreate(Sender: TObject); procedure ToolButton5Click(Sender: TObject); procedure ListView1ColumnClick(Sender: TObject; Column: TListColumn); procedure ListView1Compare(Sender: TObject; Item1, Item2: TListItem; Data: Integer; var Compare: Integer); private { Private 宣言 } OpenDialog : TOpenDialog; LstID : LongInt; LstBool : Boolean; ExportPos,ImportPos,ImportSize,ImportVirtualAddress{,ResourcePos,ResourceVirtualAddress} :Dword;// 再度、読み込み用 IMAGE_DOS_HEADER : TIMAGE_DOS_HEADER; // MS_DOSスタブ IMAGE_Coff_HEADER : TIMAGE_Coff_HEADER; // COFFヘッダ IMAGE_Optional_HEADER : TIMAGE_Optional_HEADER; // Optionalヘッダ IMAGE_Section_HEADER : array [0..15] of TIMAGE_Section_HEADER; //Sectionヘッダ procedure ListItemsClear(); function ShowImport(MemoryStream: TMemorySTream; VirtualAddress:Dword;Index : Byte):String; procedure ShowExport(MemoryStream: TMemorySTream); procedure Run(FileName : String); procedure WMDropFiles(Var Msg: TMsg); Message WM_DROPFILES ; public { Public 宣言 } OPenFileName :String; end; var Section_Index :byte; // セクションのインデックスを格納 var Form_EXEViewr: TForm_EXEViewr; implementation uses Tokikaze_Link; {$R *.DFM} /////////////////////////////// 汎用関数 ///////////////////////////////////// // // //////////////////////////////////////////////////////////////////////////////// // 仮想アドレスを手掛かりにセクションサイズとファイルオフセットを検索する function Find_SectionFileOffset(ISH : array of TIMAGE_Section_HEADER; NumberOfSections : Integer;VirtualAddress : Dword;var SectionSize : dword) : Integer; Var i,offset : Integer; begin for i:=0 to NumberOfSections -1 do begin offset := VirtualAddress - ISH[i].VirtualAddress; //仮想アドレスの挟み打ち if((offset >= 0) and ( Dword(offset)< ISH[i].SizeOfRawData )) then begin SectionSize := ISH[i].SizeOfRawData - Dword(offset); // セクションのバイト数. Result := ISH[i].PointerToRawData + Dword(offset); // 発見した仮想アドレスに対するRVA(セクションのファイルオフセット) Section_Index := i; // セクションのインデックスを格納(大域変数) Exit; end; end; Result :=0;//与えられた仮想アドレスを含んでいるセクションを発見できなかった. end; // Exprotセクションからエキスポート名のみを取得 function GetExportFileName(MemoryStream: TMemorySTream;IOH :TIMAGE_Optional_HEADER): string; Var Line : Pchar ; LineMem : Pointer; Buffer : String; ExportDirectoryTable : TExportDirectoryTable; begin Result:=''; ZeroMemory(@ExportDirectoryTable,sizeof(TExportDirectoryTable)); MemoryStream.ReadBUffer(ExportDirectoryTable,sizeof(TExportDirectoryTable)); // エクスポートアドレステーブルを無視 MemoryStream.Position :=Dword(MemoryStream.Position) +Dword(ExportDirectoryTable.AddressTableEntries*4); // 名前ポインタテーブルを無視 MemoryStream.Position:=Dword(MemoryStream.Position)+Dword(ExportDirectoryTable.NumberofNamePointers)*4; // エクスポート序数テーブルを無視( MemoryStream.Position:=Dword(MemoryStream.Position)+Dword(ExportDirectoryTable.NumberofNamePointers)*2; // 関数の名前を取得 // if ExportDirectoryTable.NumberofNamePointers<>0 then // begin GetMem(LineMem,MemoryStream.size- MemoryStream.Position); MemoryStream.ReadBUffer(LineMem^,MemoryStream.size- MemoryStream.Position); Line :=Pchar(LineMem); Try // DLLの名前を取得 while True do begin if Line^='' then break ; Buffer:=Buffer+Line^; inc(Line); end; Result:=BUffer; finally FreeMem(LineMem); end; // end; end; // Importセクションからインポート名のみを取得 function GetImportFileName(MemoryStream: TMemorySTream; VirtualAddress:Dword;StringList:TStringList):String; Var Line : char; OffSet : Dword; Buffer : String; ImportDirectoryTable : TImportDirectoryTable; begin ZeroMemory(@ImportDirectoryTable,sizeof(TImportDirectoryTable)); // インポートディレクトリテーブルの取得 while True do begin MemoryStream.ReadBUffer(ImportDirectoryTable,sizeof(TImportDirectoryTable)); // 最後には必ず空の構造が入っている if (ImportDirectoryTable.TimeDateStamp=0) and (ImportDirectoryTable.NameRVA=0) then Break; // 元のポジション OffSet :=MemoryStream.Position; if Integer(ImportDirectoryTable.NameRVA - VirtualAddress) <0 then begin StringList.Add('(Error)'); Continue; end else MemoryStream.Position :=ImportDirectoryTable.NameRVA - VirtualAddress; // DLLの名前を取得 Buffer:=''; while True do begin MemoryStream.ReadBUffer(line,1); if Line='' then break ; Buffer:=Buffer+Line; end; StringList.Add(Buffer); // 元のポジションの復帰 MemoryStream.Position:=OffSet; end; end;
サンプルプログラム一式のダウンロード
exe_analysis.zip 16.4 KB (16,861 バイト)
このサンプルは...
このサンプルは私が2000年頃に作成した「疾風 -tokikaze-」というソフトウェアの隠し機能だったものをサンプルプログラムとしています。(疾風ではEXEファイルをドロップすればばこれと同様の画面が開きます。)
ソースコードは当時のままで粗雑です。あと、リソース辺りを対応しようとして途中で挫折しているみたいです :-)
スポンサーリンク
関連記事
公開日:2015年02月27日 最終更新日:2016年07月19日
記事NO:00309