如何在Windows上使用JNA操作Java内存
如何操纵Java的内存? 我知道Java在它自己的JVM中运行,因此无法直接访问进程内存。
我听说过JNA可以用来获取操作系统和我的Java代码之间的接口。
假设我想操纵纸牌的得分。 尝试将是这样的:
- 得到纸牌的过程
- 获得纸牌记忆
- 找出分数存储在内存中的位置
- 在地址中写下我的新值
Java本身无法访问该内存,因此如何使用JNA执行此操作?
您需要使用JNA库。 下载两个Jar文件(jna.jar和jna-platform.jar)
我找到了一个关于pastebin的教程 ,它解释了如何使用这个库。 但是没有必要阅读它来理解以下内容。
比方说,你想操纵Windows游戏“Solitaire”的地址及其值
知道,你做了什么
-
如果你想操纵地址及其价值, 那就知道你做了什么!
您需要知道存储在地址中的值的大小。 是4Byte,还是8Byte或者是什么。 -
知道如何使用工具来获取动态和基地址。 我用的是CheatEngine 。
-
了解基地址和动态地址之间的区别:
-
每次重新启动应用程序时, 动态地址都会发生变化(纸牌)。
它们将包含所需的值,但您每次都需要再次找到该地址。 所以你需要先学习的是如何获得基地址。
通过阅读CheatEngine教程了解这一点。 -
基地址是静态地址。 这些地址主要通过以下方式指向其他地址:[[base-addres + offset] + offset] – > value。 因此,您需要了解基址,以及需要添加到地址以获取动态地址的偏移量。
-
所以既然你知道你需要知道什么,你就用CheatEngine on Solitaire做一些研究。
您找到了动态地址并搜索了基地址? 好的,让我们分享我们的结果:
分数的基地址:
0x10002AFA8
到达动态地址的偏移量:0x50
(第一个)和0x14
(第二个)
一切都好吗? 好! 让我们继续实际编写一些代码。
创建一个新项目
在新项目中,您需要导入这些库。 我使用Eclipse,但它应该适用于任何其他IDE。
User32接口
感谢Todd Fast,设置User32界面 。 它不完整,但我们需要足够的。
通过此界面,我们可以访问Windows上user32.dll的某些function。 我们需要以下函数: FindWindowA
和GetWindowThreadProcessID
附注:如果Eclipse告诉您需要添加未实现的方法,请忽略它并运行代码。
Kernel32接口
感谢Deject3d的Kernel32接口 。 我修改了一下。
该接口包含我们用于读写内存的方法。 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特定方法:
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; }
在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
对象中,这正是它所说的。 记忆。 它读出基地址,获取内存中的新地址并添加偏移量。 这是针对所有偏移完成的,并在最后一个地址返回,这将是动态地址。
所以现在我们想要阅读我们的分数并将其打印出来。 我们有动态地址,其中存储了分数,只需要阅读它。 得分为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; }
我们为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或随意询问。
已知的问题:
如果您没有获得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); } }
使用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
分配