13

Java からメモリを操作するにはどうすればよいですか? Javaは独自のJVMで実行されるため、プロセスメモリに直接アクセスできないことを知っています。

オペレーティング システムと Java コードの間のインターフェイスを取得するために使用できる JNA について聞いたことがあります。

ソリティアのスコアを操作したいとしましょう。試行は次のようになります。

  1. ソリティアのプロセスを取得する
  2. ソリティアの記憶にアクセスする
  3. スコアがメモリのどこに保存されているかを調べる
  4. 新しい値をアドレスに書き込む

Java 自体はそのメモリにアクセスできないので、JNA を使用してこれを行うにはどうすればよいでしょうか?

4

2 に答える 2

51

JNA ライブラリを使用する必要があります。2 つの Jar ファイル (jna.jar および jna-platform.jar) をダウンロードします。

このライブラリの使用方法を説明しているペーストビンのチュートリアルを見つけました。しかし、以下を理解するためにそれを読む必要はありません。

Windows ゲーム「ソリティア」のアドレスとその値を操作するとします。


知って、あなたが何をするか

  1. アドレスとその値を操作したい場合は、何をすべきかを知っておいてください!
    アドレスに格納されている値のサイズを知る必要があります。それは 4Byte か、8Byte か、それとも何であろうと。

  2. ツールを使用して動的アドレスとベースア​​ドレスを取得する方法を知っている。私はチートエンジンを使用しています。

  3. ベースアドレスとダイナミックアドレスの違いを知っておいてください:

    • 動的アドレスは、アプリケーション (ソリティア) を再起動するたびに変更されます。
      それらには必要な値が含まれますが、毎回アドレスを見つける必要があります。したがって、最初に学ぶ必要があるのは、ベースアドレスを取得する方法です。
      CheatEngine チュートリアルをプレイして、これを学びましょう。

    • ベースアドレスは静的アドレスです。これらのアドレスは、ほとんど次の方法で他のアドレスを指します: [[ベースアドレス + オフセット] + オフセット] -> 値。したがって、必要なのは、ベースアドレスと、動的アドレスを取得するためにアドレスに追加する必要があるオフセットを知ることです。

知っておくべきことがわかったので、ソリティアの CheatEngine で調査を行います。


動的アドレスを見つけて、ベースアドレスを検索しましたか? それでは、結果を共有しましょう。

スコアのベース0x10002AFA8
アドレス: 動的アドレスに到達するためのオフセット: 0x50(1 つ目) と0x14(2 つ目)

すべて正しいですか?良い!実際にコードを書いてみましょう。


新しいプロジェクトを作成する

新しいプロジェクトでは、これらのライブラリをインポートする必要があります。私は Eclipse を使用していますが、他の IDE でも動作するはずです。

User32 インターフェイス

User32 インターフェイスをセットアップしてくれた Todd Fast に感謝します。完全ではありませんが、ここで十分です。

このインターフェイスを使用して、Windows 上の user32.dll のいくつかの機能にアクセスできます。次の関数が必要ですFindWindowAGetWindowThreadProcessID

補足: Eclipse が未実装のメソッドを追加する必要があると通知した場合は、無視してコードを実行してください。

Kernel32 インターフェイス

Kernel32 インターフェイスの Deject3d に感謝します。少し修正しました。

このインターフェイスには、メモリの読み取りと書き込みに使用するメソッドが含まれています。WriteProcessMemoryReadProcessMemory。プロセスを開くメソッドも含まれていますOpenProcess

実際の操作

ここで、いくつかのヘルパー メソッドと、JVM のアクセス ポイントとしてのメイン関数を含む新しいクラスを作成します。

public class SolitaireHack {

    public static void main(String... args)
    {

    }
}

オフセットやベースアドレスなど、すでにわかっていることを入力しましょう。

public class SolitaireHack {

    final static long baseAddress = 0x10002AFA8L;
    final static int[] offsets = new int[]{0x50,0x14};

    public static void main(String... args)
    {

    }
}

次に、インターフェイスを使用して、Windows 固有のメソッドにアクセスします。

com.sun.jna.Native をインポートします。

public class SolitaireHack {

    final static long baseAddress = 0x10002AFA8L;
    final static int[] offsets = new int[]{0x50,0x14};

    static Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32",Kernel32.class);
    static User32 user32 = (User32) Native.loadLibrary("user32", User32.class);

    public static void main(String... args)
    {

    }
}

最後になりましたが、プロセスへの読み取りと書き込みのアクセス許可を取得するために、必要ないくつかのアクセス許可定数を作成します。

import com.sun.jna.Native;

public class SolitaireHack {

    final static long baseAddress = 0x10002AFA8L;
    final static int[] offsets = new int[]{0x50,0x14};

    static Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32",Kernel32.class);
    static User32 user32 = (User32) Native.loadLibrary("user32", User32.class);

    public static int PROCESS_VM_READ= 0x0010;
    public static int PROCESS_VM_WRITE = 0x0020;
    public static int PROCESS_VM_OPERATION = 0x0008;

    public static void main(String... args)
    {

    }
}

メモリを操作できるプロセスを取得するには、ウィンドウを取得する必要があります。このウィンドウは、プロセス IDを取得するために使用できます。この ID を使用して、プロセスを開くことができます。

public static void main(String... args)
{
    int pid = getProcessId("Solitaire");
    Pointer process = openProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, pid);
}

public static int getProcessId(String window) {
     IntByReference pid = new IntByReference(0);
     user32.GetWindowThreadProcessId(user32.FindWindowA(null, window), pid);

     return pid.getValue();
}

public static Pointer openProcess(int permissions, int pid) {
     Pointer process = kernel32.OpenProcess(permissions, true, pid);
     return process;
}

このgetProcessIdメソッドでは、ウィンドウのタイトルであるパラメータを使用して、ウィンドウ ハンドルを見つけます。( FindWindowA) このウィンドウ ハンドルは、プロセス ID を取得するために使用されます。IntByReference は、プロセス ID が格納されるポインターの JNA バージョンです。

プロセス ID を取得すると、それを使用して でプロセスを開くことができますopenProcess。このメソッドは、アクセス許可と pid を取得してプロセスを開き、そのプロセスへのポインターを返します。プロセスから読み取るには PROCESS_VM_READ 権限が必要で、プロセスから書き込むには PROCESS_VM_WRITE および PROCESS_VM_OPERATION 権限が必要です。

次に取得する必要があるのは、実際のアドレスです。動的アドレス。したがって、別の方法が必要です。

public static void main(String... args)
{
    int pid = getProcessId("Solitaire");
    Pointer process = openProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, pid);

    long dynAddress = findDynAddress(process,offsets,baseAddress);
}

public static long findDynAddress(Pointer process, int[] offsets, long baseAddress)
{

    long pointer = baseAddress;

    int size = 4;
    Memory pTemp = new Memory(size);
    long pointerAddress = 0;

    for(int i = 0; i < offsets.length; i++)
    {
        if(i == 0)
        {
             kernel32.ReadProcessMemory(process, pointer, pTemp, size, null);
        }

        pointerAddress = ((pTemp.getInt(0)+offsets[i]));

        if(i != offsets.length-1)
             kernel32.ReadProcessMemory(process, pointerAddress, pTemp, size, null);


    }

    return pointerAddress;
}

このメソッドには、プロセス、オフセット、およびベースアドレスが必要です。一時的なデータをMemoryオブジェクトに保存しますが、これはまさにそれが言っていることです。記憶。ベースアドレスで読み取り、メモリ内の新しいアドレスを取得し、オフセットを追加します。これはすべてのオフセットに対して行われ、最後に最後のアドレス (動的アドレス) が返されます。

それでは、スコアを読み込んで印刷したいと思います。スコアが保存されている動的アドレスがあり、それを読み取る必要があります。スコアは 4Byte 値です。整数は 4 バイトのデータ型です。したがって、整数を使用して読み取ることができます。

public static void main(String... args)
{
    int pid = getProcessId("Solitaire");
    Pointer process = openProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, pid);

    long dynAddress = findDynAddress(process,offsets,baseAddress);

    Memory scoreMem = readMemory(process,dynAddress,4);
    int score = scoreMem.getInt(0);
    System.out.println(score);
}

public static Memory readMemory(Pointer process, long address, int bytesToRead) {
    IntByReference read = new IntByReference(0);
    Memory output = new Memory(bytesToRead);

    kernel32.ReadProcessMemory(process, address, output, bytesToRead, read);
    return output;
}

kernel32 メソッドのラッパーを作成しましたreadProcessMemory。4Byte を読み取る必要があることがわかっているため、bytesToRead は 4 になります。このメソッドでは、Memoryオブジェクトが作成されて返されます。これは、byteToRead のサイズを持ち、アドレスに含まれるデータを格納します。メソッドを使用.getInt(0)すると、オフセット 0 でメモリの整数値を読み取ることができます。

ソリティアで少し遊んで、ポイントを獲得しましょう。次に、コードを実行して値を読み取ります。それがあなたのスコアかどうかを確認してください。

最後のステップは、スコアを操作することです。私たちは最高になりたいです。したがって、メモリに 4Byte データを書き込む必要があります。

byte[] newScore = new byte[]{0x22,0x22,0x22,0x22};

これが新しいスコアになります。newScore[0]が最下位バイトにnewScore[3]なり、最上位バイトになります。したがって、スコアを値 20 に変更したい場合は、次のようにbyte[]なります。
byte[] newScore = new byte[]{0x14,0x00,0x00,0x00};

私たちの記憶に書きましょう:

public static void main(String... args)
{
    int pid = getProcessId("Solitaire");
    Pointer process = openProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, pid);

    long dynAddress = findDynAddress(process,offsets,baseAddress);

    Memory scoreMem = readMemory(process,dynAddress,4);
    int score = scoreMem.getInt(0);
    System.out.println(score);

    byte[] newScore = new byte[]{0x22,0x22,0x22,0x22};
    writeMemory(process, dynAddress, newScore);
}

public static void writeMemory(Pointer process, long address, byte[] data)
{
    int size = data.length;
    Memory toWrite = new Memory(size);

    for(int i = 0; i < size; i++)
    {
            toWrite.setByte(i, data[i]);
    }

    boolean b = kernel32.WriteProcessMemory(process, address, toWrite, size, null);
}

このwriteMemoryメソッドでは、byte[]呼び出されたデータをアドレスに書き込みます。新しいMemoryオブジェクトを作成し、サイズを配列の長さに設定します。正しいオフセットでオブジェクトにデータをMemory書き込み、オブジェクトをアドレスに書き込みます。

これで、572662306 という素晴らしいスコアが得られるはずです。

一部のkernel32またはuser32メソッドが何をするのか正確にわからない場合は、MSDNを参照するか、お気軽にお問い合わせください。

既知の問題点:

Solitaire のプロセス ID を取得できない場合は、タスク マネージャーで確認し、手動で pid を書き込んでください。名前に ä が含まれているため、ドイツ語の Solitär は機能しません。

このチュートリアルが気に入っていただければ幸いです。ほとんどの部分は他のいくつかのチュートリアルからのものですが、ここにまとめてあるので、誰かが出発点を必要とする場合に役立つはずです.

Deject3d と Todd Fast の協力に感謝します。問題がある場合は、私に言ってください。私はあなたを助けようとします. 何かが足りない場合は、お気軽にお知らせいただくか、ご自身で追加してください。

ありがとう。良い一日を。


SolitaireHack クラスの完全なコードを見てみましょう。

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;

public class SolitaireHack {

    final static long baseAddress = 0x10002AFA8L;
    final static int[] offsets = new int[]{0x50,0x14};

    static Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32",Kernel32.class);
    static User32 user32 = (User32) Native.loadLibrary("user32", User32.class);

    public static int PROCESS_VM_READ= 0x0010;
    public static int PROCESS_VM_WRITE = 0x0020;
    public static int PROCESS_VM_OPERATION = 0x0008;

    public static void main(String... args)
    {
        int pid = getProcessId("Solitaire");
        Pointer process = openProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, pid);

        long dynAddress = findDynAddress(process,offsets,baseAddress);

        Memory scoreMem = readMemory(process,dynAddress,4);
        int score = scoreMem.getInt(0);
        System.out.println(score);

        byte[] newScore = new byte[]{0x22,0x22,0x22,0x22};
        writeMemory(process, dynAddress, newScore);
    }

    public static int getProcessId(String window) {
         IntByReference pid = new IntByReference(0);
         user32.GetWindowThreadProcessId(user32.FindWindowA(null, window), pid);

         return pid.getValue();
    }

    public static Pointer openProcess(int permissions, int pid) {
         Pointer process = kernel32.OpenProcess(permissions, true, pid);
         return process;
    }

    public static long findDynAddress(Pointer process, int[] offsets, long baseAddress)
    {

        long pointer = baseAddress;

        int size = 4;
        Memory pTemp = new Memory(size);
        long pointerAddress = 0;

        for(int i = 0; i < offsets.length; i++)
        {
            if(i == 0)
            {
                 kernel32.ReadProcessMemory(process, pointer, pTemp, size, null);
            }

            pointerAddress = ((pTemp.getInt(0)+offsets[i]));

            if(i != offsets.length-1)
                 kernel32.ReadProcessMemory(process, pointerAddress, pTemp, size, null);


        }

        return pointerAddress;
    }

    public static Memory readMemory(Pointer process, long address, int bytesToRead) {
        IntByReference read = new IntByReference(0);
        Memory output = new Memory(bytesToRead);

        kernel32.ReadProcessMemory(process, address, output, bytesToRead, read);
        return output;
    }

    public static void writeMemory(Pointer process, long address, byte[] data)
    {
        int size = data.length;
        Memory toWrite = new Memory(size);

        for(int i = 0; i < size; i++)
        {
                toWrite.setByte(i, data[i]);
        }

        boolean b = kernel32.WriteProcessMemory(process, address, toWrite, size, null);
    }
}
于 2013-09-17T12:15:33.220 に答える