如何在Windows上使用JNA操作Java中的内存
如何从 Java 操作内存?我知道Java在它自己的JVM中运行,所以它不能直接访问进程内存。
我听说过JNA,它可以用来获取操作系统和我的Java代码之间的接口。
假设我想操纵纸牌的分数。尝试将如下所示:
- 获取纸牌的流程
- 访问纸牌的记忆
- 找出分数在内存中的存储位置
- 在地址中写入我的新值
Java本身无法访问该内存,那么我如何使用JNA来做到这一点呢?
如何从 Java 操作内存?我知道Java在它自己的JVM中运行,所以它不能直接访问进程内存。
我听说过JNA,它可以用来获取操作系统和我的Java代码之间的接口。
假设我想操纵纸牌的分数。尝试将如下所示:
Java本身无法访问该内存,那么我如何使用JNA来做到这一点呢?
您需要使用 JNA 库。下载两个 Jar 文件 (jna.jar 和 jna-platform.jar)
我找到了一个关于pastebin的教程,它解释了如何使用这个库。但是没有必要阅读它来理解以下内容。
比方说,你想操纵Windows游戏“纸牌”的地址及其值
如果您想操纵地址及其值,请知道您该怎么做!
您需要知道,存储在地址中的值的大小。是4Byte,还是8Byte或任何东西。
了解如何使用工具获取动态地址和基址。我使用作弊引擎。
了解基址和动态地址的区别:
每次重新启动应用程序(纸牌)时,动态地址都会更改。
它们将包含所需的值,但每次都需要再次查找地址。因此,您首先需要学习的是如何获取基址。
通过玩作弊引擎教程来学习这一点。
基址是静态地址。这些地址主要通过以下方式指向其他地址:[[基数加法 + 偏移量] + 偏移量] ->值。因此,您需要的是知道基址,以及需要添加到地址以获取动态地址的偏移量。
所以现在你知道你需要知道什么,你用纸牌上的CheatEngine做了一些研究。
您找到了动态地址并搜索了基址?很好,让我们分享我们的结果:
分数的基址:
到达动态地址的偏移量:(第一)和(第二)0x10002AFA8
0x50
0x14
一切都做对了吗?好!让我们继续实际编写一些代码。
在新项目中,您需要导入这些库。我使用Eclipse,但它应该在任何其他IDE上运行。
感谢 Todd Fast 设置 User32 界面。它并不完整,但我们在这里需要的已经足够了。
通过此界面,我们可以访问Windows上user32.dll的某些功能。我们需要以下函数:和FindWindowA
GetWindowThreadProcessID
附注:如果 Eclipse 告诉您需要添加未实现的方法,只需忽略它并运行代码即可。
感谢 Deject3d 的 Kernel32 接口。我修改了一下。
此接口包含我们用于读取和写入内存的方法。 和。它还包含一个打开进程的方法WriteProcessMemory
ReadProcessMemory
OpenProcess
现在,我们创建一个新类,它将包含一些帮助器方法和 main 函数作为 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特定方法:
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)
{
}
}
最后但并非最不重要的一点是,我们创建了一些我们需要的权限常量,以获得读取和写入进程的权限。
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;
}
在该方法中,我们使用参数(即窗口的标题)来查找窗口句柄。() 此窗口句柄用于获取进程 ID。IntByReference 是指针的 JNA 版本,其中将存储进程 ID。getProcessId
FindWindowA
如果获取进程 ID,则可以使用它通过 打开进程。此方法获取权限和 pid,以打开进程,并返回指向它的指针。要从进程中读取,您需要PROCESS_VM_READ权限,而从进程中写入则需要PROCESS_VM_WRITE和PROCESS_VM_OPERATION的权限。openProcess
我们需要得到的下一件事是实际的地址。动态地址。所以我们需要另一种方法:
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 字节值。整数是一种 4 字节数据类型。所以我们可以使用整数来读出它。
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 方法编写了一个包装器。我们知道我们需要读取 4Byte,因此字节ToRead 将为 4。在该方法中,将创建并返回一个对象,该对象将具有byteToRead的大小并存储包含在我们的地址中的数据。使用该方法,我们可以在偏移量0处读出内存的Integer值。readProcessMemory
Memory
.getInt(0)
用你的纸牌玩一会儿,并获得一些分数。然后运行代码并读出该值。检查它是否是您的分数。
我们的最后一步将是操纵我们的分数。我们希望成为最好的。因此,我们需要将4字节数据写入内存。
byte[] newScore = new byte[]{0x22,0x22,0x22,0x22};
这将是我们的新分数。 将是最低的字节,并且将是最高的字节。因此,如果您想将分数更改为值20,则为:newScore[0]
newScore[3]
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);
}
}
使用 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