繁体   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