如何从Java处理内存?我知道Java在它自己的JVM中运行,因此它不能直接访问进程内存。
我听说过JNA,可用于获取操作系统和Java代码之间的接口。
假设我要操纵纸牌得分。尝试将是这样的:
Java本身无法访问该内存,那么如何使用JNA做到这一点?
您需要使用JNA库。下载两个Jar文件(jna.jar和jna-platform.jar)
我找到了关于pastebin 的教程,其中介绍了如何使用此库。但是,无需阅读即可理解以下内容。
假设您要操纵Windows游戏“单人纸牌”的地址及其值
如果要操纵地址及其值,请 知道该怎么做! 您需要知道存储在地址中的值的大小。是4Byte还是8Byte或其他。
知道如何使用工具获取动态地址和基本地址。我使用CheatEngine。
知道基本地址和动态地址之间的区别:
*每次重新启动应用程序时, *动态地址 都会更改(单人纸牌)。 它们将包含所需的值,但是您每次都需要再次查找地址。因此,您首先需要学习的是如何获取基址。 通过阅读CheatEngine教程了解这一点。
基地址 是静态地址。这些地址主要通过以下方式指向其他地址:[[base-addres + offset] + offset]-> value。因此,您需要知道基本地址以及需要添加到地址以获得动态地址的偏移量。
现在,您知道了您需要了解的知识,并开始使用纸牌上的CheatEngine做一些研究。
您找到了动态地址并搜索了基地址?好,让我们分享一下我们的结果:
分数的基本地址:0x10002AFA8 到达动态地址的偏移量:( 0x50第一)和0x14(第二)
0x10002AFA8
0x50
0x14
一切都好吗?好!让我们继续实际编写一些代码。
在新项目中,您需要导入这些库。我使用Eclipse,但它应可在任何其他IDE上使用。
感谢Todd Fast设置了User32接口。它还不完整,但是我们在这里需要足够的东西。
通过此接口,我们可以访问Windows上的user32.dll的某些功能。我们需要以下功能:FindWindowA和GetWindowThreadProcessID
FindWindowA
GetWindowThreadProcessID
旁注:如果Eclipse告诉您它需要添加未实现的方法,请忽略它并以任何方式运行代码。
感谢Deject3d提供Kernel32接口。我做了一点修改。
此接口包含我们用于读取和写入内存的方法。WriteProcessMemory和ReadProcessMemory。它还包含打开流程的方法OpenProcess
WriteProcessMemory
ReadProcessMemory
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。
getProcessId
如果获得了进程ID,则可以使用来打开它openProcess。此方法获取权限和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对象中存储了一些临时数据,这正是它所说的。记忆。它在基地址处读取,在存储器中获取新地址并添加偏移量。对所有偏移量都执行此操作,最后返回最后一个地址,即动态地址。
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值。
readProcessMemory
.getInt(0)
与您的单人纸牌玩一点,并获得一些积分。然后运行您的代码并读出值。检查是否是您的分数。
我们的最后一步将是操纵我们的分数。我们想成为最好的。因此,我们需要将4Byte数据写入内存。
byte[] newScore = new byte[]{0x22,0x22,0x22,0x22};
这将是我们的新成绩。newScore[0]将是最低的字节,newScore[3]将是最高的字节。因此,如果您想将分数更改为20,byte[]则将是: byte[] newScore = new byte[]{0x14,0x00,0x00,0x00};
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具有正确偏移量的对象,然后将对象写入我们的地址。
writeMemory
现在,您应该获得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); } }