小编典典

如何在Windows上使用JNA从Java处理内存

java

如何从Java处理内存?我知道Java在它自己的JVM中运行,因此它不能直接访问进程内存。

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

假设我要操纵纸牌得分。尝试将是这样的:

  1. 得到纸牌的过程
  2. 获得对单人纸牌的记忆
  3. 找出分数存储在存储器中的位置
  4. 在地址中写下我的新值

Java本身无法访问该内存,那么如何使用JNA做到这一点?


阅读 213

收藏
2020-10-18

共1个答案

小编典典

您需要使用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对象中存储了一些临时数据,这正是它所说的。记忆。它在基地址处读取,在存储器中获取新地址并添加偏移量。对所有偏移量都执行此操作,最后返回最后一个地址,即动态地址。

因此,现在我们想读取分数并将其打印出来。我们拥有动态分数,分数存储在此,只需要读出即可。分数是一个4字节的值。整数是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方法,我们将一个byte[]被调用的数据写入我们的地址。我们创建一个新Memory对象,并将大小设置为数组的长度。我们将数据写入Memory具有正确偏移量的对象,然后将对象写入我们的地址。

现在,您应该获得572662306的出色得分。

如果您不完全了解某些kernel32或user32方法的功能,请查看MSDN或随意询问。

已知的问题:

如果您没有获得纸牌的进程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);
    }
}
2020-10-18