おもちゃバコ

中身スカスカ♡

プロセスのメモリ領域に書き込んでみた

こんにちは

起動中のプロセスのメモリ領域に任意の数値を書き込んだ時の備忘録です。


環境

注意

ソースコードの実行は自己責任で。
何が起きても知りません。

検証用のテストプログラムを作成して, それ対して実行することを推奨します。


手順

1. プロセスオブジェクトを取得
 OpenProcess()関数ですね。
2. プロセスの仮想アドレス空間のメモリ領域を予約
 VirtualAllocEx()関数を利用します。
 アクセス権として「PROCESS_VM_OPERATION」が必要です。
 lpAddressの引数にnullptrを指定するとこの関数が自動で割り当て領域を決定してくれるそうです。
3. プロセスの予約したメモリ領域にデータを書き込む
 WriteProcessMemory()関数を利用します。
 アクセス権として「PROCESS_VM_WRITE」と「PROCESS_VM_OPERATION」が必要です。

特に難しい要素は無く,概ね予想通りの手順ですね。

書き込むデータはバイト単位です。
floatなどを書き込みたい場合は,キャストで頑張りましょう。

ソースコード

検証用プログラム

メモリを弄られてしまうプログラム君です。

#include <iostream>
int main()
{
    while (true)
    {
        std::cout << "Hello World!" << std::endl;
    }
    return 0;
}

メモリ書き込みプログラム

検証用プログラムのメモリ領域を弄るプログラムです。
検証用プログラムを実行させ,そのプロセスIDを事前に記述しておきましょう。
プロセスIDはタスクマネージャなどから確認できます。

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN

#include <cstdint>

#include <iostream>
#include <string>

int main()
{
    // 書き込みたいプロセスのプロセスID
    const DWORD targetProcessId = 0u;
    // 書き込みたい内容
    const uint8_t writeData[] = "AIUEO=KAKIKUKEKO-123456@";

    // 1. プロセスを取得
    const DWORD flags = PROCESS_VM_OPERATION | PROCESS_VM_WRITE;
    HANDLE processHandle = OpenProcess(flags, /*bInheritHandle=*/FALSE, targetProcessId);
    if (processHandle == nullptr) { std::cerr << GetLastError() << std::endl; return 1; }

    // 2. プロセスのメモリ領域を予約
    LPVOID pAddress = VirtualAllocEx(processHandle, /*lpAddress=*/nullptr, _countof(writeData), MEM_COMMIT, PAGE_READWRITE);
    if (pAddress == nullptr)
    {
        std::cerr << GetLastError() << std::endl;
        CloseHandle(processHandle);
        return 1;
    }

    // 3. メモリ領域に書き込み
    SIZE_T numberOfBytesWritten = 0u; // 転送されたバイト数
    if (WriteProcessMemory(processHandle, pAddress, writeData, _countof(writeData), &numberOfBytesWritten) == FALSE)
    {
        std::cerr << GetLastError() << std::endl;
        CloseHandle(processHandle);
        return 1;
    }

    std::cout << "Success" << std::endl;
    std::cout << "\t" << "Address = " << pAddress << std::endl;
    std::cout << "\t" << "WriteBytes = " << numberOfBytesWritten << std::endl;

    CloseHandle(processHandle);
    return 0;
}

実行手順

1. 検証用プログラムを起動
 無限ループしているはず。
2. メモリ書き込みプログラムを起動
 何事もなければ即終了するはず。
 アドレスの数値をコピーしておく。
3. Visual Studioのデバッガで検証用プログラムのメモリを確認
 コピーしたアドレスのメモリを確認する。

メモリ領域が覗ければ何でも良いです。
こだわりがなければVisual Studioのデバッガを使ったほうが早いです。

デバッグ>ウィンドウ>メモリ>メモリ1~4のどれかを選択。
アドレスにコピーした数値を貼り付け。

確認するときは「すべて中断(Ctr+Alt+Break)」してからね。

実行結果

書き込みプログラム

Success
    Address = 0000019C3B280000
    WriteBytes = 25

検証用プログラムのメモリ領域

0x0000019C3B280000  41 49 55 45 4f 3d 4b 41 4b 49 4b 55 4b 45 4b 4f 2d 31 32 33 34 35 36 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  AIUEO=KAKIKUKEKO-123456@.......................
0x0000019C3B28002F  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ...............................................

書き込めてますね。


まとめ

プロセス間通信が面倒な時に役立つかもと思い,やってみましたがいい感じでした。

任意のコードを書き込んでGetThreadContext()とSetThreadContext()を駆使してレジスタを操作すれば,いろいろ遊べそうだと思いました。
(関数戻りのスタック操作などが面倒で,そんなに単純ではなさそうでした。)