TOP > カテゴリ > フォーマット変換 >

EXE/DLLファイルを分析・解析する

EXE/DLLファイルを分析・解析するサンプルプログラムです。内容としてはファイルを読み込みファイルフォーマットの状態を表示します。次に「Import」でそのファイルで使用するDLLとAPIの一覧を取得します。DLLファイルの場合は「Export」で外部から使用可能なAPI名と序数を表示します。基本的な読込は出来ていますので、発展させればリソースの書き換えや逆アセンブルなども可能です。

2016年7月15日
実行ファイルのリソースを編集できる「リソースエディタ」をオープンソースで公開しましたので、そちらをご利用ください。

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