プロセスのベースアドレスの取得のしかた

解決


もんちきTwin Turbo  2018-03-13 03:05:55  No: 49069

先日、以下のスレッドで、プロセスの列挙についてアドバイスをいただきましたが、
そこで列挙されたプロセスをファイルとして保存するプログラムを作成したいと思っています。
https://www.petitmonte.com/bbs/answers?question_id=8828

先ずは、先日のスレッドの中でもご紹介いただいた、Mr.XRAY様の以下のサンプルを参考に、
ListViewのOnDoubleClicに以下のコードを追加しました。

[06_Toolhelp32Snapshot を使用したプロセスの列挙でフルパスを取得] 
http://mrxray.on.coocan.jp/Delphi/plSamples/330_AppProcessList.htm#06 

procedure TForm1.ListView1DblClick(Sender: TObject);
var
  str         : string;
  int         : integer;
  pnt         : pointer;
  pmc         : PPROCESS_MEMORY_COUNTERS;
  cb          : Integer;
  hProcHandle : THandle;
begin
  int      := listview1.ItemIndex;
  selected := strtoint(ListView1.Items[int].SubItems[0]);
  str      := ListView1.Items[int].Caption;
  str      := ExtractFileName(str);

  cb := SizeOf(_PROCESS_MEMORY_COUNTERS);
  GetMem(pmc, cb);
  pmc^.cb := cb;

  hProcHandle := OpenProcess(PROCESS_ALL_ACCESS, FALSE, selected);

  GetProcessMemoryInfo(hProcHandle, pmc, cb);
  FreeMem(pmc);

  pnt := GetModuleBaseAddress(hProcHandle ,str);
  if pnt = nil then
    showmessage('unko');
end;

そして、そこから呼び出されるベースアドレスの取得関数GetModuleBaseAddressを、
以下の通り追加しました。

function GetModuleBaseAddress(PHandle: Thandle; MName: String): Pointer;
var
  Modules         : Array of HMODULE;
  cbNeeded, i     : Cardinal;
  ModuleInfo      : TModuleInfo;
  ModuleName      : Array[0..MAX_PATH] of Char;
  Pandle          : THandle;
begin
  Result := nil;
  SetLength(Modules, 1024);
  cbNeeded := 0;
  if (PHandle <> 0) then
  begin
    try
      EnumProcessModules(PHandle, @Modules[0], 1024 * SizeOf(HMODULE),cbNeeded);
    except
      Exit;
    end;

    SetLength(Modules, cbNeeded div SizeOf(HMODULE));

  if (Length(Modules) > 0) then
  begin
    for i := 0 to Length(Modules) - 1 do
    begin
      try
        GetModuleBaseName(PHandle, Modules[i], ModuleName, SizeOf(ModuleName));
        if (Pos(MName, ModuleName) > 0) then
        begin
          GetModuleInformation(PHandle, Modules[i], @MoDuleInfo, SizeOf(ModuleInfo));
          Result := ModuleInfo.lpBaseOfDll;
          Exit;
        end;
      Except
      end;
    end;
   end;
  end;
end;

しかし、Length(Modules)が常に0になるためループに入らず、結果Resultは毎回nilを返します。
EnumProcessModulesのPHandleの値が変化しても結果が変わらないので、呼び方が間違っている様なのですが、
MSDNのヘルプを読んでもどこが違うのか良く分かりません。どこが間違っているのでしょうか?

OSは64bit Windows7 professional Delphiは7です。
Delphi  10.2でも試しましたが、結果は同じでした。

よろしくおねがいします。


Mr.XRAY  2018-03-13 05:07:10  No: 49070

メモ帳を起動して確認してみてください.
メモ帳は以下のコマンドで起動ですます.

%SystemRoot%\System32\notepad.exe   // 64 ビット版のメモ帳
%SystemRoot%\SysWow64\notepad.exe   // 32 ビット版のメモ帳


Mr.XRAY  2018-03-13 05:10:05  No: 49071

>メモ帳は以下のコマンドで起動ですます.

Delphi のプログラム (Shellexecute 等を使用して ) からは起動しないでください.
[ ファイル名を指定して実行 ] のダイアログから起動してください.


もんちきTwin Turbo  2018-03-13 10:10:59  No: 49072

Mr.XRAY様、ありがとうございます。

コマンドラインからメモ帳を起動してみました。
その結果、32ビット版については、上記と同じ状況でしたが、
64ビット版ではLength(Modules)が0より大きく(35)なり、
nil以外の値($6C000)が戻りました。

32ビットと64ビットのプロセスで、動作が変わるのですね。
勉強になります。

EnumProcessModules 32bitで検索したところ、
Vista以降にはEnumProcessModulesExというのがあるのが分かりましたが、
単純に書き変えても「未定義の識別子」になってしまいました。

以下を読む限り、ヘッダーは同じ様ですが

https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms682633(v=vs.85).aspx


Mr.XRAY  2018-03-13 15:47:34  No: 49073

EnumProcessModulesEx 関数は 64 ビットのアプリ ( EXE ) 用です.
32 ビットのアプリ ( EXE ) でも使用できますが,( 末尾に Ex がない ) EnumProcessModules 関数と同じ動作仕様になります.

>単純に書き変えても「未定義の識別子」になってしまいました。 

以下を参考にしてください.

[VCL で未定義の Windows API 関数の使用 - DLL の使用方法]
http://mrxray.on.coocan.jp/Delphi/Others/Usage_Win32API.htm


Mr.XRAY  2018-03-13 18:27:26  No: 49074

>EnumProcessModulesEx 関数は 64 ビットのアプリ ( EXE ) 用です.
>32 ビットのアプリ ( EXE ) でも使用できますが,( 末尾に Ex がない ) EnumProcessModules 関数と同じ動作機能になります.

EnumProcessModulesEx 関数本来の機能を使用するには,64 ビットの Kernel32.dll 内の関数を使用する必要があります.
一般的に 32 ビットの EXE からは 64 ビットの DLL は使用てきません.その逆も同じです.
ただし,以下の様な方法もあります.私はやったことがありませんが.

[プロセスの相互運用性]
https://ht-deko.com/tech070.html#0108

32 ビットの EXE からシステムフォルダ内の 64 ビットの DLL を呼び出すには工夫が必要です.
リダイレクトの機能を無効にしないと 32 ビットの DLL をロードしてしまいます.


もんちきTwin Turbo  2018-03-13 20:27:07  No: 49075

Mr.XRAY様、ありがとうございます。

>[VCL で未定義の Windows API 関数の使用 - DLL の使用方法]

非常に勉強になります。Mr.XRAY様のサンプルプロジェクトでも出てくるinterface部の関数の定義の意味が
理解できました。サンプルを参考にして、関数の定義を以下の通り追加し、EnumProcessModulesExは
動作する様になりました。

function EnumProcessModulesEx(hProcess: Cardinal; lphModule: PDWORD;cb: Cardinal;
 var lpcbNeeded: Cardinal): pointer; stdcall; external 'PsAPI.dll';

>EnumProcessModulesEx 関数本来の機能を使用するには,64 ビットの Kernel32.dll 内の関数を使用する必要があります.

ということは、追加した定義で動作はするものの、本来の機能は使用できていないのですね。

>一般的に 32 ビットの EXE からは 64 ビットの DLL は使用てきません.その逆も同じです. 

更に私が作成しているのが32ビットのEXEなので、それをするためには、DLLの相互運用が必要なのですね。

ただ、メモ帳で試したところ、正常に動いていないのは32ビット版の方なので、Mr.XRAY様のご指摘にもある様に、
これは問題の解決にならないのですね。

64ビット版のメモ帳に関しては、一応ベースアドレスらしいものを返しているので、ロジック的な間違いではなさそうですが、
何が間違っているのでしょうか…


Mr.XRAY  2018-03-14 00:43:30  No: 49076

>64ビット版のメモ帳に関しては、一応ベースアドレスらしいものを返しているので

これ,もう一度確認してみてください.違うと思しますよ.
起動しているメモ帳が 32 ビット版か 64 ビット版かの判定は,例えば以下を参考にしてください.

[起動中のアプリが 32 ビットか 64 ビットかをタスクマネージャで確認]
http://mrxray.on.coocan.jp/Delphi/Others/32bitOr64bit.htm#02


もんちきTwin Turbo  2018-03-14 02:33:55  No: 49077

Mr.XRAY様

>これ,もう一度確認してみてください.違うと思しますよ. 

ご指摘の通りでした。

32ビット版がベースアドレスらしきものを返しており、64ビット版は駄目でした。
EnumProcessModulesExは追加はできたものの、64ビットKernel32.dll内の関数を使用していないため、
64ビット版のアプリケーションのベースアドレスが取得できないのですね。納得できました。

ところで、もう一つ教えていただいてよろしいでしょうか?

  GetProcessMemoryInfo(hProcHandle, pmc, cb);
  pnt := GetModuleBaseAddress(hProcHandle ,str);

現在、ここでベースアドレスを取得した後、以下の様な感じでプロセスをダンプしようと考えています。
しかしReadProcessMemoryで読み取るサイズが正しく取得できません。現在はPPROCESS_MEMORY_COUNTERSの
WorkingSetSizeをセットしているのですが、保存されたdump.exeをバイナリエディタで見みると、
PEヘッダの途中位でちょん切れています。セットしているサイズが短いのだと思いますが、この値は
どの様にしたら正しく取得できますか?

  if GetInfo(selected, dBaseAddr, pmc^.WorkingSetSize) then
  begin
    hOpen := OpenProcess(PROCESS_VM_READ, FALSE, selected);
    if hOpen <> INVALID_HANDLE_VALUE then
    begin
      dsize := pmc^.WorkingSetSize;
      SetLength(bBuff, dSize);//ここの値が間違っている様なのですが…
      ReadProcessMemory(hOpen, pnt, @bBuff[0], dSize, dRead);
      CloseHandle(hOpen);
      hFile := CreateFile(PChar('dump.exe'), GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_ALWAYS, 0, 0);
      if hFile <> INVALID_HANDLE_VALUE then
      begin
        SetFilePointer(hFile, 0, nil, FILE_BEGIN);
        WriteFile(hFile, bBuff[0], dSize, dWritten, nil);
        CloseHandle(hFile);
      end;
  end;

よろしくお願いします。


もんちきTwin Turbo  2018-03-14 03:26:17  No: 49078

済みません。見間違いでした。

ダンプしたファイルのバイナリを見ると、先頭と終わりの位置は正しそうですが、
途中に0が沢山挿入された様になっていました。もう少しバイナリの中身を調べてみます。


Mr.XRAY  2018-03-14 03:54:29  No: 49079

>よろしくお願いします。 

これは多分どなたにも無理だと思います.

> SetLength(bBuff, dSize);//ここの値が間違っている様なのですが… 

だとすると dSize が問題となると思いますが,dSize は

>dsize := pmc^.WorkingSetSize; 

となっています.これは GetInfo から取得しているようてすが,
肝心の GetInfo がどういう処理をしているか不明です.
最初に提示されたコードもそうですが,他の方が実際に確認可能なコードでない限り無理があります.

基本的に 64 ビットの Windows でシステム的な情報操作には 64 ビットの EXE が必要です.
もちろん,情報の内容によっては可能なものもあります.

では頑張ってくたさい.


もんちきTwin Turbo  2018-03-14 18:48:41  No: 49080

Mr.XRAY様

ご指導ありがとうございました。
問題点もさることながら、未定義のAPIの呼び方とか、とても勉強になりました。
今までDelphi7を使っていましたが、64ビット用のコンパイラも使ってみたくなりました。

色々ありがとうございました。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加