none
他プロセス・メモリのRead、Writeについて RRS feed

  • 質問

  •  他プロセス・メモリのRead、Writeがしたく、Visual Studio 2008 VC++にて、
    下【ソース・コード・イメージ】に示すようなコードを作成して試してみましたが、
    他プロセス・メモリのRead、Write(ReadProcessMemory、WriteProcessMemory)が
    期待通りにできません。

     他プロセス・メモリのReadについては、ReadPcocessMemory APIによるものでは
    期待通りの結果にはなりませんでしたが、
    直接の代入等によるネイティブ・コード(例: v = *p : mov命令等によるメモリ読み取り)
    ではできるようです。

     他プロセス・メモリの書き換えについては、直接の代入等によるネイティブ・コード
    (例: *p = v : mov命令等によるメモリ書き込み)では、
    アクセス保護例外が起きてしまいます。
     他プロセス・メモリの書き換えについては、WriteProcessMemory APIによる方法しか
    ないものと思います。

     Visual Studio 2008のデバッグ・ツールでは、他プロセス・メモリの書き換えが
    できています。 ですので、何かを見落としているに違いありません。

     一体何を見落としているのかご指摘頂ければ、あるいは参考になるページを
    ご紹介いただければ幸いに思います。

     実際にやろうとしているのは、他プロセスIMPORTテーブル 特定APIの
    ジャンプ・ベクタの変更です。

    【本件について参考にした資料、URLなど】
     ・ http://codezine.jp/article/detail/426?p=2

     ・ http://www7a.biglobe.ne.jp/~tsuneoka/win32tech/18.html

     ・ Advanced Windows 第5版 ・・・ 日経BPソフトプレス ISBN978-4-89100-593-1
       第22章 DLLインジェクションとAPIフック

     ・ セキュア ソフトウェア  ・・・ 日経BP社      ISBN4-8222-8210-4
       第3章 リバースエンジニアリングとプログラムの読解 / シンプルなx86デバッガ

    【他プロセスのメモリRead、Writeをするために使用するAPI】
     他プロセス・メモリを読み書きするためのチェック・ポイントは、
    そのために使用するAPIの呼び出しパラメータの設定内容になるかと思います。

     下に使用したAPIと設定概要を示します。

     (1) CreateProcess
        ターゲット・プロセスは、dwCreationFlagsをDEBUG_PROCESSで
       生成。
        プロセス・アクセス権の設定変更は、特に行っていません。

     (2) VirtualQueryEx
        ターゲット・プロセスのRead、Writeしたいメモリ・アドレスは、
       取得済みで、デバッガからそれが誤っていないことを確認すみです。
       ちなみに、メモリはコード・ページです。

        ターゲット・メモリのアクセス権を知るため、本APIを使用。

        ターゲット・プロセスのプロセス・ハンドルを第1引数にして、本APIを
       呼び出した。 APIが返すMEMORY_BSIC_INFORMATIONのProtectメンバは、
       0x80(=PAGE_EXECUTE_WRITECOPY)となっていた。

     (3) VirtualProtectEx
        第1引数のhProcessをターゲットのプロセス・ハンドルに、
       flNewProtectは次の値にしてみてそれぞれ確認。
         
         ・ PAGE_EXECUTE_READWRITE
         ・ PAGE_READWRITE
         ・ PAGE_WRITECOPY

     (4) ReadProcessMemory、またはWriteProcessMemory
        第1引数のhProcessをターゲットのプロセス・ハンドルに、
       ReadProcessMemory、またWriteProcessMemoryを呼び出した。
        APIの戻り値はTRUEであったが、期待とは異なり次のような結果となった。

         ・ ReadProcessMemory
           0x00ではない値が入っているはずなのに、0x00と読まれた。

         ・ WriteProcessMemory
           書き込みしようとした値が書かれず、元の値のままであった。

        Read、Writeしたいメモリは、デバッガのデバッグ/メモリ・ツールで
       読めています。
        書き込みができていないことは、その後の処理が書き込み前と
       同じとなってしまうことから判断しました。

    【ソース・コード・イメージ】

    /*
     *  ターゲット・プロセスのメモリ書き換え
     *
     *  以下の処理は、デバッグ・メイン・ループにおいて、
     * CREATE_PROCESS_DEBUG_EVENT, LOAD_DLL_DEBUG_EVENT等の後の
     * 最初のBREAKPOINTイベントの処理として行っています。
     */
    DWORD  ReplaceMemory(
      PVOID  AddrRW,
      PVOID*  pWdata,
      ・・・ )
    {
      void*  rdata ;
      MEMORY_BASIC_INFORMATION  mbi ;
      DWORD  dwOldProtect ;

      if ( !::VirtualQueryEx( m_hTarget, AddrRW, &mbi, sizeof(
            MEMORY_BASIC_INFORMATION ) ) ) {
        return( ::GetLastError() ) ;
      }

      if ( !::VirtualProtectEx(
            m_hTarget, mbi.BaseAddress, mbi.RegionSize,
            PAGE_EXECUTE_READWRITE, &dwOldProtect ) ) {
        return( ::GetLastError() ) ;
      }

      if ( !::ReadProcessMemory(
            m_hTarget, AddrRW, &rdata, sizeof( rdata ), NULL ) ) {
        return( ::GetLastError() ) ;
      }

      if ( !::WriteProcessMemory(
            m_hTarget, AddrRW, pWdata, sizeof( *pWdata ), NULL ) ) {
        return( ::GetLastError() ) ;
      }

      ・・・・・・・
    }

    /*
     * メイン・ループ
     */
    void  DebuggerMain()
    {
     PVOID  AddrRW ;
     PVOID  wdata = 0x12345678 ;
     HANDLE  m_hTarget ;         // ターゲット・プロセス・ハンドル
     DEBUG_EVENT de ;           // デバッグ・イベント
     BOOL   bFirstBreak = TRUE ;     // 初めてのブレーク

     ::CreateProcess( ..., DEBUG_PROCESS, ... ) ;

     while ( ::WaitForDebugEvent( &de, INFINITE ) ) {
      switch ( de.dwDebugEventCode ) {      // イベント・コード ?
      case LOAD_DLL_DEBUG_EVENT:
       ・・・・・

      case CREATE_PROCESS_DEBUG_EVENT:     //  プロセス生成イベント ?
       ・・・・・

      case CREATE_THREAD_DEBUG_EVENT:      //  スレッド生成イベント ?
       ・・・・・

      case EXIT_PROCESS_DEBUG_EVENT:      //  プロセス終了イベント ?
      ・・・・・

      case EXCEPTION_DEBUG_EVENT:        //  例外キャッチ ?
                            //   ブレークポイント例外 ?
       if ( de.u.Exception.ExceptionRecord.ExceptionCode
               == EXCEPTION_BREAKPOINT ) {
              if ( bFirstBreak ) {     // 初めてのブレーク ?
          bFirstBreak = FALSE ;
          ReplaceMemory( AddrRW, &wdata ) ;  // ターゲット・プロセスのメモリ書き換え
      ・・・・・
    }
    ・・・・・

    • 編集済み ななち 2010年3月29日 4:31
    2010年3月28日 6:09

回答

  •  みなさん、申し訳ありません。

     他プロセスへのWriteProcessMemoryReadProcessMemoryは、期待通りに動作していました。

     Visual Studio 2008のデバッグ・ツール/メモリでは、あたかも他プロセス・メモリが
    読めているよう表示されますが、この表示は他プロセス・メモリ内容の実態を反映していない
    ことがわかりました。

     早い話が、デタラメを表示しているということです。 期待とは異なるメモリの内容を
    表示しているようです。
     おそらくWriteProcessMemoryReadProcessMemoryで扱うべき線型仮想アドレス
    (LDTに設定されているアドレスをベースにしたアドレス体系?)のメモリ内容を読んでいる
    わけではないのでしょう。

     確認においてメモリ書き換えの効果がなかったように見えたのは、他の問題、つまりバグのようでした。
    作業を進めていくうちに、他プロセスの実行状況が掴めるようになり、問題箇所を特定できるように
    なったおかげです。

     お騒がせして申し訳ありませんでした。


    Y. Peabrain
    • 回答としてマーク ななち 2010年4月26日 6:35
    2010年4月26日 6:35

すべての返信

  •   Y. Peabrainです。

     補足情報です。

     試験環境は、Windows XP with SP3です。
    同じソース・コードでは試していませんが、Windows 7でも同じ結果になるものと
    思っています。 初期は、Windows 7で動作確認を行っていました。

     また、試したアカウントの権限は、一般Userです。
     一時期異なるソース・コードで、Administrators権限でも試してみましたが、
    User権限で試した時と異なる現象は見られませんでした。

     そもそも、デバッガ・プロセスのデバッグ対象プロセスの制御は、
    User権限でも可能なはずです。

     なお、ウィルス対策ソフトに、Norton Internet Security 2010を使用しています。
    これに悪影響が与えられていることってことも、考えられるのでしょうか ?

     現在検証中のデバッガ・プロセスを裸で起動した場合、その中でターゲット・プロセスを
    起動しようとすると、Nortonに警告されてしまいます。

     他プロセス・メモリをRead、Writeしようとしている時、Nortonの警告は
    表示されていませんが、何か干渉されているのかもしれません。

    # NNTP Bridgeからの投稿だと、半角カナや特殊文字を使わなくても
    # 文字化けが発生しました。 しかも、重い!


    Y. Peabrain

    • 編集済み ななち 2010年3月28日 10:58 検証した事柄情報を追加
    2010年3月28日 6:50
  •  Y. Peabrainです。

     「Advanced Windows 第5版/第13章 Windowsメモリアーキテクチャ」によると、
    コード・ページの書き換えを行う際のメモリ・ページ保護属性が
    PAGE_EXECUTE_WRITECOPY になっていれば、
    Read、Writeを行ってもアクセス違反にはならないとのことです。

     確かにアクセス違反は、発生していませんでした。

     先にお知らせした私が試験しているケースでは
    VirtualProtectEx によるメモリ・ページ保護属性の変更は不要でしたね。

    >  (2) VirtualQueryEx
    >     ターゲット・プロセスのRead、Writeしたいメモリ・アドレスは、
    >    取得済みで、デバッガからそれが誤っていないことを確認すみです。
    >    ちなみに、メモリはコード・ページです。
    >
    >     ターゲット・メモリのアクセス権を知るため、本APIを使用。
    >
    >     ターゲット・プロセスのプロセス・ハンドルを第1引数にして、本APIを
    >    呼び出した。 APIが返すMEMORY_BSIC_INFORMATIONのProtectメンバは、
    >    0x80(=PAGE_EXECUTE_WRITECOPY)となっていた。

     ただし、なんで読み書きができていないのかっていう件は、
    今なお見当がついていません。

     何か、お気づきのことがありましたら、よろしくお願いいたします。


    Y. Peabrain
    2010年3月28日 13:19
  •  Y. Peabrainです。

     私がやろうとしていることとほぼ同じようなソース・コードを
    見つけました。
    皆さんへの参考として、示したいと思います。

      http://nienie.com/~masapico/doc_ApiSpy.html

     先に示した例と違っていることといったら、FlushInstructionCache APIを
    呼んでいることだけだと思いますが、これは既に試しています。

     個人的には、セキュリティ対策でReadProcessMemory、WriteProcessMemoryの仕様が
    変更されたのではと疑っています。

     別の方法として、ターゲット・プロセスを先に起動、
    問題のプロセスで次に示すようにターゲット・プロセスをアタッチ
    しようしてみましたが、これは「アクセス拒否」で成功しません。

     ・ OpenProcess( PROCESS_ALL_ACCESS | PROCESS_VM_WRITE, ... )

     これも気に入りません。

     CreateProcessでのプロセス起動は、プロセス・アクセス権はPROCESS_ALL_ACCESSで
    生成されるはずです。
     なぜ、アクセス拒否されるのか、理由がさっぱり見当つきません。

     " | PROCESS_VM_WRITE"は、POCESS_ALL_ACCESSに含まれているはずで、
    ただ単にターゲット・プロセスにこういうアクセスをしますよと明示している
    だけのものです。

     とりあえず、OS最新の状態ではなく、ちょっと古いXP + SP2で同じ問題を
    検証しようと思っています。


    Y. Peabrain
    2010年4月1日 4:24
  •  参考として投稿します。

     作ろうとしているものはデバッガではありませんが、デバッガ実装の技術はかなり
    利用します。

     WriteProcessMemoryによる、ターゲット・プロセスのコード・ページ直接書き換えは
    あきらめ、代わりに次の方法をとることにしました。

      (1) コード・ページを書き換えるDLLをターゲット・プロセスに挿入。
        CreateRemoteThreadにより、DLLのコードを実行。 ターゲットのプロセス空間で、
       コード・ページを書き換える。
        自プロセスのコード・ページを書き換えられることは、確認すみ。

      (2) 仮称デバッガ・プロセスからターゲット・プロセスへ、パイプによって
       コード・ページ書き換え依頼を行う。

      (3) ターゲット・プロセスは、いつでもコード・ページ書き換え依頼が受け付けられるよう、
       仮称デバッガ・プロセスからのコード・ページ依頼受信と、実際の書き換え処理を1つの
       スレッドで行うようにする。

     これにも、1つ問題があります。

     ContinueDebugEvent APIを呼び出すまでのデバッグ・イベント処理期間中は、イベントを上げた
    スレッドのみならず、ターゲット・プロセス全体が停止状態になってしまいます。

     デバッガ・プロセスからのコード・ページ書き換え依頼はデバッグ・イベント中に行います。
     この期間中は、ターゲットのコード書き換えスレッドが動作しないので、
    デバッガ・プロセス依頼と同時に、ターゲット・プロセスのコード・ページ書き換えができない
    ということになります。

      ContinueDebugEvent APIを呼び出すと、コード書き換えスレッドは動きますが、
    どのタイミングでコード書き換えを行うのかが不定となってしまいます。

     どう任意のタイミングでコード書き換えを行わせるのかが大きな問題です。

     命令コードの書き換えですから、書き換えタイミングをきちんと制御しなければ
    なりません。

     当面は以上の方法で逃げますが、最終的には、やはり他プロセスのメモリを書き換えるAPIが
    欲しいところです。

     なお、ReadProcessMemoryについては、他プロセス・メモリであっても、
    読めるケースがありました。
    読めないケースと読めるケースの違いについては、詳しくは調べていません。

    • 編集済み ななち 2010年4月18日 15:12 デバッグ・イベント処理期間中のターゲット状態説明が足らなかった
    2010年4月18日 15:05
  •  みなさん、申し訳ありません。

     他プロセスへのWriteProcessMemoryReadProcessMemoryは、期待通りに動作していました。

     Visual Studio 2008のデバッグ・ツール/メモリでは、あたかも他プロセス・メモリが
    読めているよう表示されますが、この表示は他プロセス・メモリ内容の実態を反映していない
    ことがわかりました。

     早い話が、デタラメを表示しているということです。 期待とは異なるメモリの内容を
    表示しているようです。
     おそらくWriteProcessMemoryReadProcessMemoryで扱うべき線型仮想アドレス
    (LDTに設定されているアドレスをベースにしたアドレス体系?)のメモリ内容を読んでいる
    わけではないのでしょう。

     確認においてメモリ書き換えの効果がなかったように見えたのは、他の問題、つまりバグのようでした。
    作業を進めていくうちに、他プロセスの実行状況が掴めるようになり、問題箇所を特定できるように
    なったおかげです。

     お騒がせして申し訳ありませんでした。


    Y. Peabrain
    • 回答としてマーク ななち 2010年4月26日 6:35
    2010年4月26日 6:35
  •  皆さんへの参考として、下のコードを提示したいと思います。
      このままコンパイルが通って実際に実行できるようなコードではありませんが、
    他プロセス・メモリの読み書きについて、イメージが掴めるのではないかと思います。

     このコードは、他プロセスのある関数のエントリにブレーク・ポイントが設定されていて、
    ブレークが発生した時、スタックから関数の戻りアドレスと3つの引数を読むものです。

    --------------------------------------------------------------------------------
     void* RetnAddr ;     // 関数戻りアドレス
     DWORD Args[3] ;     // 関数の引数

     DWORD dwStackBase ;    // スタック・ベース・アドレス
     CONTEXT context ;        // スレッド・コンテキスト
     LDT_ENTRY Ldt ;         // スタック・セグメントLDT
     HANDLE hProcess ;       // ターゲット・プロセスのハンドル
     HANDLE hThread ;        //   〃        スレッドのハンドル

     ::memset( Args, 0x00, sizeof( Args ) ) ;

       //
       //  レジスタ読み取りとスタック・ベースの算出
       //
     context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS ;
     if (    !::GetThreadContext( hThread, &context )
          || !::GetThreadSelectorEntry( hThread, context.SegSs, &Ldt ) ) {
      return( エラー ) ;
     }

     dwStackBase = ( Ldt.HighWord.Bytes.BaseHi << 24 ) + ( Ldt.HighWord.Bytes.BaseMid << 16 ) + Ldt.BaseLow ;

       //
       //  スタックからリターン・アドレスと引数を読む
       //
               // スタックよりAPI戻りアドレスを読む
     if (    ReadProcessMemory( hTarget, dwStackBase + context.Esp, &RetnAddr, sizeof( RetnAddr ) ) <= 0
               //  関数の引数を読み取る
          || ReadProcessMemory(                 // Read calling argument
               hTarget,                       //   target process
               context.Esp + 4 + dwStackBase ), //   1st arg address
               Args,                         //   引数値読み取り領域
               sizeof( Args ) ) <= 0 ) {         //   読み取りサイズ
      return( エラー ) ;
     }

       //
       //  関数エントリのコードを元に戻す
       //
     --context.Eip ;                      // インストラクション・ポインタを1つ戻す
     context.EFlags |= EFLAGS_TF ;        // シングル・ステップ設定
     if (    !WriteProcessMemory( hTarget,   // 関数エントリに原コードを戻す
               pApiEntry->m_dwEntryAddr,
               &pApiEntry->m_byOrigCode,
               sizeof( pApiEntry->m_byOrigCode ) )
             || !::SetThreadContext( hThread, &context ) ) {
      return( エラー ) ;
     }

    /*
     *  シングル・ステップのデバッグ・イベントが上がった時、
     * 再び関数のエントリにブレーク・ポイントを設定する。
     */

    --------------------------------------------------------------------------------

    /*
     *  他プロセス・メモリへの書き込み
     */
    int  WriteProcessMemory(
     __in  HANDLE hProcess,
     __out DWORD  dwAddr,
     __in  const void* pbuf,
     __in  int size )
    {
     SIZE_T dwWritten = 0 ;
     MEMORY_BASIC_INFORMATION mbi ;
     DWORD dwOldProtect ;

     if ( ::WriteProcessMemory( hProcess, (LPVOID)dwAddr, pbuf, size, &dwWritten ) ) {
      ::FlushInstructionCache( hProcess, (LPCVOID)dwAddr, size ) ;
      return( dwWritten ) ;
     }
     if ( ::GetLastError() != ERROR_ACCESS_DENIED ) {
      return( 0 ) ;
     }

     if ( !::VirtualQueryEx( hProcess, (LPCVOID)dwAddr,
            &mbi, sizeof( MEMORY_BASIC_INFORMATION ) ) ) {
      return( 0 ) ;
     }
          /*
           *  リージョンまたがりがないか注意すること
           */
     if (    !::VirtualProtectEx( hProcess,
            mbi.BaseAddress, mbi.RegionSize, PAGE_WRITECOPY, &dwOldProtect )
       || !::WriteProcessMemory( hProcess, (LPVOID)dwAddr, pbuf, size, &dwWritten ) ) {
      dwWritten = 0 ;
     }
     else {
      ::FlushInstructionCache( hProcess, (LPCVOID)dwAddr, size ) ;
     }
     ::VirtualProtectEx( hProcess, mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect ) ;

     return( dwWritten ) ;
    }

    /*
     *  他プロセス・メモリの読み込み
     */
    int  ReadProcessMemory(
    ...........

     

     


    とほほん
    2010年5月2日 8:25