您需要使用 JNA 库。 Download 两个 Jar-Files(jna.jar 和 jna-platform.jar)
我在 pastebin 上找到了一个tutorial,它解释了如何使用这个库。但不必阅读它即可理解以下内容。
假设您想操作 Windows 游戏“纸牌”的地址及其值
知道,你在做什么
如果你想操纵地址及其值,知道你在做什么!
您需要知道存储在地址中的值的大小。是 4Byte 还是 8Byte 或其他。
了解如何使用工具获取动态地址和基地址。我用CheatEngine。
-
了解基地址和动态地址之间的区别:
动态地址会在您每次重新启动应用程序(纸牌)时发生变化。
它们将包含所需的值,但您每次都需要再次找到该地址。所以你首先需要学习的是如何获取基地址。
通过玩 CheatEngine 教程来了解这一点。
基地址是静态地址。这些地址主要通过以下方式指向其他地址:[[base-address + offset] + offset] -> value。所以你需要知道基地址,以及你需要添加到地址以获得动态地址的偏移量。
既然您知道了您需要了解的内容,您可以使用纸牌游戏的 CheatEngine 进行一些研究。
您找到了您的动态地址并搜索了基地址?好的,让我们分享一下我们的结果:
分数的基地址:0x10002AFA8
获取动态地址的偏移量:0x50(第一)和0x14(第二)
一切正常吗?好的!让我们继续实际编写一些代码。
创建一个新项目
在您的新项目中,您需要导入这些库。我使用 Eclipse,但它应该可以在任何其他 IDE 上运行。
User32 接口
感谢 Todd Fast 设置 User32 interface。它不完整,但我们需要的足够了。
通过这个接口,我们可以访问windows上user32.dll的一些功能。
我们需要以下函数:FindWindowA 和 GetWindowThreadProcessID
旁注:如果 Eclipse 告诉您它需要添加未实现的方法,请忽略它并运行代码。
Kernel32 接口
感谢 Deject3d 提供Kernel32 interface。我稍微修改了一下。
这个接口包含我们用来读写内存的方法。 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 或随时询问。
已知问题:
如果你没有得到 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);
}
}