如何从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
(第二)
一切都好吗?好!让我们继续实际编写一些代码。
在新项目中,您需要导入这些库。我使用Eclipse,但它应可在任何其他IDE上使用。
感谢Todd Fast设置了User32接口。它还不完整,但是我们在这里需要足够的东西。
通过此接口,我们可以访问Windows上的user32.dll的某些功能。我们需要以下功能:FindWindowA
和GetWindowThreadProcessID
旁注:如果Eclipse告诉您它需要添加未实现的方法,请忽略它并以任何方式运行代码。
感谢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特定方法:
导入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);
}
}
我编写了以下类来包装win32事件对象的创建 我在Windows7机器上使用JNA3.3,当我试图创建这个类的实例时,我得到了以下堆栈跟踪。 线程“main”java.lang.UnsatifiedLinkError中出现异常:查找函数“CreateEvent”时出错:找不到指定的过程。
问题内容: 我完全没有使用Windows API的经验,所以请原谅我的无知。 我想删除文件到回收站。如何使用JNA做到这一点,以及如何检测是否不可能(例如,由于文件位于网络共享上)? 问题答案: 使用已定义和方法的。
问题内容: 在Stack Overflow上使用Selenium WebDriver上传文件时,我已经看到很多问题和解决方案。但是它们都不适合以下情况。 有人给出了以下解决方案 但是我仍然找不到窗口句柄。我该如何处理? 我正在寻找上述方案的解决方案。 请在以下任何网站上进行检查。 问题答案: 如果使用Zamzar网站,它应该可以正常运行。你无需单击该元素。你只需在其中输入路径。具体来说,这绝对可以
问题内容: 我需要使用标题检查是否打开了一个外部窗口(另一个Java程序,但不受我正在使用的程序控制),如果打开了,则根据Java中的用户命令将其最大化或最小化(我只知道窗口的标题,什么也不知道)。Google只说我可以用来获取窗口句柄并使用该句柄对其进行操作,但是我找不到如何执行此操作。 我可以在此处找到有关如何使用JNI的参考: 在JavaSwing中,如何获取对窗口的Win32窗口句柄(hw
问题内容: 我正在尝试使用Java为网站编写selenium测试。但是,在测试文件上传时遇到了一个问题。 当我单击文件上传按钮时,它将自动打开Windows文件上传。我有代码可以将文本成功地上传到上传框中,只是我无能为力,无法阻止Windows框自动显示,并且网站不自动打开Windows文件上传也不是真正的选择。通过研究这个主题,我了解到seleniumWebdriver无法解决这个问题。所以我的