如何在 Windows 上使用 JNA 从 Java 操作内存

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18849609/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 11:36:43  来源:igfitidea点击:

How to manipulate memory from Java with JNA on Windows

javamemoryjna

提问by Loki

How do I manipulate memory from Java? I know that Java runs in it's own JVM, so it can't access process memory directly.

如何从 Java 操作内存?我知道 Java 在它自己的 JVM 中运行,所以它不能直接访问进程内存。

I heard about JNA which can be used to get interfaces between the Operating System and my Java code.

我听说 JNA 可用于获取操作系统和我的 Java 代码之间的接口。

Let's say I want to manipulate the score of Solitaire. The attempt would be something like this:

假设我想操纵 Solitaire 的分数。尝试将是这样的:

  1. get the process of solitaire
  2. get access to the memory of solitaire
  3. find out where the score is stored in the memory
  4. write my new value in the address
  1. 得到纸牌的过程
  2. 访问纸牌的记忆
  3. 找出分数在内存中的存储位置
  4. 在地址中写入我的新值

Java itself can't access that memory, so how can I do this using JNA?

Java 本身无法访问该内存,那么我如何使用 JNA 执行此操作?

采纳答案by Loki

You need to use the JNA Library. Downloadthe two Jar-Files (jna.jar and jna-platform.jar)

您需要使用 JNA 库。下载两个 Jar 文件(jna.jar 和 jna-platform.jar)

I found a tutorialon pastebin, which explains how to use this Library. But it won't be necessary to read it to understand the following.

我找到了一个关于 pastebin的教程,它解释了如何使用这个库。但是没有必要阅读它来理解以下内容。

Let's say, you want to manipulate the addresses and their values of the Windows game "Solitaire"

假设您想操纵 Windows 游戏“纸牌”的地址及其值



Know, what you do

知道你在做什么

  1. If you want to manipulate addresses and their values, know what you do!
    You need to know, what size the value, stored in the address, has. Is it 4Byte, or 8Byte or whatsoever.

  2. Know how to use tools to get dynamic and base-addresses. I use CheatEngine.

  3. Know the difference bewteen base-addresses, and dynamic-addresses:

    • Dynamic-addresseswill change every time you restart the application (Solitaire).
      They will contain the needed value, but you would need to find the address again every time. So what you need to learn first, is how to get the base-address.
      Learn this by playing through the CheatEngine Tutorial.

    • Base-addressesare static addresses. Those addresses point to other addresses mostly the following way: [[base-addres + offset] + offset] -> value. So what you need is to know the base-address, and the offsets you need to add to the addresses to get the dynamic-address.

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

  2. 了解如何使用工具获取动态地址和基地址。我使用CheatEngine

  3. 了解基地址和动态地址之间的区别:

    • 每次重新启动应用程序(纸牌)时,动态地址都会更改。
      它们将包含所需的值,但您每次都需要再次查找地址。所以你首先需要学习的是如何获取基地址。
      通过玩 CheatEngine 教程来了解这一点。

    • 基地址是静态地址。这些地址主要通过以下方式指向其他地址:[[base-addres + offset] + offset] -> value。所以你需要的是知道基地址,以及你需要添加到地址的偏移量以获得动态地址。

So now that you know what you need to know you do some research with the CheatEngine on Solitaire.

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



You found your dynamic-address and searched for the base-address? Good, let's share our results:

您找到了动态地址并搜索了基地址?好的,让我们分享我们的结果:

Base-address for the score: 0x10002AFA8
Offsets to get to the dynamic-address: 0x50(first) and 0x14(second)

得分0x10002AFA8
的基地址:获得动态地址的偏移量:( 0x50第一)和0x14(第二)

Got everything right? Good! Let's continue with actually writing some code.

一切都做对了吗?好的!让我们继续实际编写一些代码。



Create a new Project

创建一个新项目

In your new project, you need to import those libraries. I use Eclipse, but it should work on any other IDE.

在您的新项目中,您需要导入这些库。我使用 Eclipse,但它应该适用于任何其他 IDE。

User32 Interface

User32 接口

Thank's to Todd Fast, for setting up an User32 interface. It's not complete, but enough we need here.

感谢 Todd Fast 设置User32 界面。它不完整,但足够我们在这里需要。

With this interface, we get access to some functions of the user32.dll on Windows. We need the following functions: FindWindowAand GetWindowThreadProcessID

通过这个接口,我们可以访问Windows上user32.dll的一些功能。我们需要以下功能:FindWindowAGetWindowThreadProcessID

Side Note: If Eclipse tells you it needs to add unimplemented methods, just ignore it and run the code anyway.

旁注:如果 Eclipse 告诉您需要添加未实现的方法,请忽略它并运行代码。

Kernel32 Interface

Kernel32 接口

Thank's to Deject3d for the Kernel32 interface. I modified it a little bit.

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

This interface contains the methods we use to read and write to memory. WriteProcessMemoryand ReadProcessMemory. It also contains a method to open a process OpenProcess

这个接口包含我们用来读写内存的方法。WriteProcessMemoryReadProcessMemory。它还包含一个打开进程的方法OpenProcess

Actual Manipulation

实际操作

We now create a new class which will contain some helper methods and the main function as access point for the JVM.

我们现在创建一个新类,它将包含一些辅助方法和作为 JVM 访问点的主函数。

public class SolitaireHack {

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

    }
}

Let's fill in stuff we already know, like our offsets and the base-address.

让我们填写我们已经知道的东西,比如我们的偏移量和基地址。

public class SolitaireHack {

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

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

    }
}

Next we use our interfaces to get access to our Windows specific methods:

接下来,我们使用我们的接口来访问我们的 Windows 特定方法:

import com.sun.jna.Native;

导入 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)
    {

    }
}

Last but not least we create some permission constants we need, to get the permission to read and write to a process.

最后但并非最不重要的是,我们创建了一些我们需要的权限常量,以获得读取和写入进程的权限。

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)
    {

    }
}

In order to get a process, where we can manipulate the memory, we need to get the window. This window can be used to get the process id. With this id we can open the process.

为了获得一个可以操作内存的进程,我们需要获得窗口。此窗口可用于获取进程 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;
}

In the getProcessIdmethod we use the parameter, which is the title of the window, to find the window handle. (FindWindowA) This window handle is used to get the process id. An IntByReference is the JNA version of an pointer, where the process id will be stored.

getProcessId方法中,我们使用参数,即窗口的标题,来查找窗口句柄。( FindWindowA) 此窗口句柄用于获取进程 ID。IntByReference 是指针的 JNA 版本,进程 ID 将存储在其中。

If you get the process id, you can use it to open the process with openProcess. This method gets the permissions and the pid, to open the process, and returns a pointer to it. To read from a process you need the permission PROCESS_VM_READ and to write from a process you need the permissions PROCESS_VM_WRITE and PROCESS_VM_OPERATION.

如果您获得进程 ID,则可以使用它来打开进程openProcess。此方法获取权限和 pid,以打开进程,并返回指向它的指针。要从进程读取,您需要权限 PROCESS_VM_READ,而要从进程写入,您需要权限 PROCESS_VM_WRITE 和 PROCESS_VM_OPERATION。

The next thing we need to get is the actual address. The dynamic-address. So we need another method:

接下来我们需要获取的是实际地址。动态地址。所以我们需要另一种方法:

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;
}

This method needs the process, the offsets and the base-address. It stores some temporary data in a Memoryobject, which is exactly what it says. A memory. It reads out at the base-address, gets back a new address in the memory and adds the offset. This is done for all offsets and returns in the end the last address, which will be the dynamic-address.

此方法需要进程、偏移量和基地址。它将一些临时数据存储在一个Memory对象中,这正是它所说的。一段记忆。它从基地址读出,取回内存中的新地址并添加偏移量。这是对所有偏移量完成的,并在最后返回最后一个地址,这将是动态地址。

So now we want to read our score and print it out. We have the dynamic-addres where the score is stored and just need to read it out. The score is a 4Byte value. Integer is a 4Byte datatype. So we can use an Integer to read it out.

所以现在我们要读取我们的分数并打印出来。我们有存储分数的动态地址,只需将其读出即可。分数是一个 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;
}

We wrote a wrapper for our kernel32 method readProcessMemory. We know we need to read 4Byte so the bytesToRead will be 4. In the method, a Memoryobject will be created and returned, which will have the size of byteToRead and store the data, contained in our address. With the .getInt(0)method we can read out the Integer value of our memory at the offset 0.

我们为 kernel32 方法编写了一个包装器readProcessMemory。我们知道我们需要读取 4Byte,所以 bytesToRead 将为 4。在该方法中,Memory将创建并返回一个对象,该对象将具有 byteToRead 的大小并存储包含在我们地址中的数据。使用该.getInt(0)方法,我们可以在偏移量 0 处读取内存的 Integer 值。

Play a little bit with your solitaire, and get some points. Then run your code and read out the value. Check if it's your score.

玩一点纸牌游戏,并获得一些积分。然后运行你的代码并读出值。看看是不是你的分数。

Our last step will be to manipulate our score. We want to be the best. So we need to write 4Byte data to our memory.

我们的最后一步将是操纵我们的分数。我们想成为最好的。所以我们需要将 4Byte 的数据写入我们的内存中。

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

This will be our new score. newScore[0]will be the lowest byte and newScore[3]will be the highest one. So if you wanted to change your score to the value 20 your byte[]would be:
byte[] newScore = new byte[]{0x14,0x00,0x00,0x00};

这将是我们的新分数。newScore[0]将是最低字节,newScore[3]将是最高字节。因此,如果您想将分数更改为值 20,您byte[]将是:
byte[] newScore = new byte[]{0x14,0x00,0x00,0x00};

Let's write it in our memory:

让我们把它写在我们的记忆中:

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);
}

With our writeMemorymethod, we write a byte[]called data to our address. We create a new Memoryobject and set the size to the length of the array. We write the data to the Memoryobject with the correct offsets and write the object to our address.

使用我们的writeMemory方法,我们将一个byte[]被调用的数据写入我们的地址。我们创建一个新Memory对象并将大小设置为数组的长度。我们将数据写入Memory具有正确偏移量的对象并将对象写入我们的地址。

Now you should have the fantastic score of 572662306.

现在你应该有 572662306 的惊人分数。

If you don't know exactly, what some kernel32 or user32 methods do, have a look at MSDN or feel free to ask.

如果您不完全了解某些 kernel32 或 user32 方法的作用,请查看 MSDN 或随时提问。

Known issues:

已知的问题:

If you don't get the process id of Solitaire, just check it in your Task Manager and write in the pid manually. The german Solit?r won't work, i think because of the ? in the name.

如果您没有获得 Solitaire 的进程 ID,只需在您的任务管理器中检查它并手动写入 pid。德国 Solit?r 不起作用,我想是因为 ? 在名字里。

I hope you liked this tutorial. Most parts are from some other tutorials, but put all together here, so in case someone needs a starting point, this should help.

我希望你喜欢这个教程。大多数部分来自其他一些教程,但都放在一起,所以如果有人需要一个起点,这应该会有所帮助。

Thanks again to Deject3d and Todd Fast for their help. If you have issues, just tell me and I try to help you out. If something is missing, feel fre to let me know or add it by yourself.

再次感谢 Deject3d 和 Todd Fast 的帮助。如果您有问题,请告诉我,我会尽力帮助您。如果缺少某些内容,请随时告诉我或自己添加。

Thank you and have a nice day.

谢谢你,祝你有美好的一天。



Let's have a look at the full code of the SolitaireHack Class:

我们来看看 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);
    }
}

回答by Peter Lawrey

Using https://github.com/OpenHFT/Java-Langyou can do

使用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();

The DirectByte can point arbitrary addresses in memory or be allocated with malloc

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