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

Lok*_*oki 8 java memory jna

如何操纵Java的内存?我知道Java在它自己的JVM中运行,所以它无法直接访问进程内存.

我听说过JNA可以用来获取操作系统和我的Java代码之间的接口.

让我们说我想操纵纸牌的得分.尝试将是这样的:

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

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

Lok*_*oki 30

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

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

比方说,你想操纵Windows游戏"Solitaire"的地址及其值


知道,你做了什么

  1. 如果你想操纵地址及其价值,那就知道你做了什么!
    您需要知道存储在地址中的值的大小.它是4Byte,还是8Byte或者是什么.

  2. 知道如何使用工具来获取动态和基地址.我用的是CheatEngine.

  3. 了解基地址和动态地址之间的差异:

    • 每次重新启动应用程序时,动态地址都会发生变化(纸牌).
      它们将包含所需的值,但您每次都需要再次找到该地址.所以你需要先学习的是如何获得基地址.
      通过阅读CheatEngine教程了解这一点.

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

所以既然你知道你需要知道什么,你就用CheatEngine on Solitaire做一些研究.


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

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

    }
}
Run Code Online (Sandbox Code Playgroud)

让我们填写我们已经知道的东西,比如我们的补偿和基地址.

public class SolitaireHack {

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

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

    }
}
Run Code Online (Sandbox Code Playgroud)

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

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 void main(String... args)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

    }
}
Run Code Online (Sandbox Code Playgroud)

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

在该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;
}
Run Code Online (Sandbox Code Playgroud)

此方法需要进程,偏移量和基址.它将一些临时数据存储在一个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;
}
Run Code Online (Sandbox Code Playgroud)

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

玩一下你的单人纸牌,并获得一些积分.然后运行您的代码并读出值.检查这是否是你的分数.

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

byte[] newScore = new byte[]{0x22,0x22,0x22,0x22};
Run Code Online (Sandbox Code Playgroud)

这将是我们的新分数.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);
}
Run Code Online (Sandbox Code Playgroud)

使用我们的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 感人的!真的很棒的指示。涵盖了您应该了解的有关该主题的所有内容 +1 (2认同)