簡體   English   中英

如何在 Windows 上使用 JNA 從 Java 操作內存

[英]How to manipulate memory from Java with JNA on Windows

如何從 Java 操作內存? 我知道 Java 在它自己的 JVM 中運行,所以它不能直接訪問進程內存。

我聽說 JNA 可用於獲取操作系統和我的 Java 代碼之間的接口。

假設我想操縱 Solitaire 的分數。 嘗試將是這樣的:

  1. 得到紙牌的過程
  2. 訪問紙牌的記憶
  3. 找出分數在內存中的存儲位置
  4. 在地址中寫入我的新值

Java 本身無法訪問該內存,那么我如何使用 JNA 執行此操作?

您需要使用 JNA 庫。 下載兩個 Jar 文件(jna.jar 和 jna-platform.jar)

我找到了一個關於 pastebin 的教程,它解釋了如何使用這個庫。 但是沒有必要閱讀它來理解以下內容。

假設您想操縱 Windows 游戲“紙牌”的地址及其值


知道你在做什么

  1. 如果你想操縱地址和它們的值,知道你在做什么!
    您需要知道存儲在地址中的值的大小。 是 4Byte 還是 8Byte 或其他。

  2. 了解如何使用工具獲取動態地址和基地址。 我使用CheatEngine

  3. 了解基地址和動態地址之間的區別:

    • 每次重新啟動應用程序(紙牌)時,動態地址都會更改。
      它們將包含所需的值,但您每次都需要再次查找地址。 所以你首先需要學習的是如何獲取基地址。
      通過玩 CheatEngine 教程來了解這一點。

    • 基地址是靜態地址。 這些地址主要通過以下方式指向其他地址:[[base-addres + offset] + offset] -> value。 所以你需要的是知道基地址,以及你需要添加到地址的偏移量以獲得動態地址。

因此,既然您知道您需要知道什么,您就可以使用紙牌上的 CheatEngine 進行一些研究。


您找到了動態地址並搜索了基地址? 好的,讓我們分享我們的結果:

得分的基地址: 0x10002AFA8
到達動態地址的偏移量: 0x50 (第一個)和0x14 (第二個)

一切都做對了嗎? 好的! 讓我們繼續實際編寫一些代碼。


創建一個新項目

在您的新項目中,您需要導入這些庫。 我使用 Eclipse,但它應該適用於任何其他 IDE。

User32 接口

感謝 Todd Fast 設置User32 界面 它不完整,但足夠我們在這里需要。

通過這個接口,我們可以訪問Windows上user32.dll的一些功能。 我們需要以下函數: FindWindowAGetWindowThreadProcessID

旁注:如果 Eclipse 告訴您需要添加未實現的方法,請忽略它並運行代碼。

Kernel32 接口

感謝 Deject3d 提供Kernel32 接口 我稍微修改了一下。

這個接口包含我們用來讀寫內存的方法。 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 是指針的 JNA 版本,進程 ID 將存儲在其中。

如果您獲得進程 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 的值。 Integer 是 4Byte 數據類型。 所以我們可以使用一個Integer來讀出它。

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 處讀取內存的 Integer 值。

玩一下您的紙牌,並獲得一些積分。 然后運行你的代碼並讀出值。 看看是不是你的分數。

我們的最后一步將是操縱我們的分數。 我們想成為最好的。 所以我們需要將 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方法,我們將一個稱為 data 的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);
    }
}

使用https://github.com/OpenHFT/Java-Lang你可以做到

long size = 1L << 40; // 1 TB
DirectStore store = DirectStore.allocate(size);
DirectBytes slice = store.createSlice();
slice.writeLong(0, size);
slice.writeLong(size - 8, size);
store.free();

DirectByte 可以指向內存中的任意地址或使用malloc分配

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM