【问题标题】:ERROR_PARTIAL_COPY reading value from memory pointer in JNAERROR_PARTIAL_COPY 从 JNA 中的内存指针读取值
【发布时间】:2020-10-01 15:35:24
【问题描述】:

我正在尝试从名为Age of Empires II: Definitive Edition 的进程中的指针读取(浮点)值。

这是我的代码:

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;

public class Main {

    final static long baseAddress = 0x02BC6620L;
    final static Pointer baseAddressPointer = new Pointer(baseAddress);
    final static int[] offsets = new int[]{0x00, 0x1A8, 0x00, 0x158, 0x28, 0x270};

    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 interface User32 extends StdCallLibrary
    {
        final User32 instance = (User32) Native.loadLibrary ("user32", User32.class);
        WinDef.HWND FindWindowExA(WinDef.HWND hwndParent, WinDef.HWND childAfter, String className, String windowName);
        WinDef.HWND FindWindowA(String className, String windowName);
        void GetWindowThreadProcessId(WinDef.HWND hwnd, IntByReference intByReference);
    }

    public static void main(String[] args) {
        System.out.println("System.getProperty(\"sun.arch.data.model\") = " + System.getProperty("sun.arch.data.model"));
        int pid = getProcessId("Age of Empires II: Definitive Edition");
        System.out.println("pid = " + pid);
        com.sun.jna.platform.win32.WinNT.HANDLE process = openProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, pid);

        long theDynAddress = findDynAddress(process, offsets);
        System.out.println("theDynAddress = " + theDynAddress);
        Pointer dynAddress = new Pointer(theDynAddress);

        Memory scoreMem = readMemory(process, dynAddress, 4);
        float score = scoreMem.getFloat(0);
        System.out.println(score);
    }

    public static Memory readMemory(com.sun.jna.platform.win32.WinNT.HANDLE process, Pointer address, int bytesToRead) {
        IntByReference read = new IntByReference(0);
        Memory output = new Memory(bytesToRead);

        boolean readProcessMemorySucceeded = kernel32.ReadProcessMemory(process, address, output, bytesToRead, read);
        System.out.println("readProcessMemorySucceeded = " + readProcessMemorySucceeded);
        if (!readProcessMemorySucceeded) {
            int lasterrorReadMemory = kernel32.GetLastError();
            System.out.println("lasterror = " + lasterrorReadMemory);
        }

        return output;
    }

    public static int getProcessId(String window) {
        IntByReference pid = new IntByReference(0);
        user32.GetWindowThreadProcessId(user32.FindWindowA(null, window), pid);

        return pid.getValue();
    }

    public static com.sun.jna.platform.win32.WinNT.HANDLE openProcess(int permissions, int pid) {
        return kernel32.OpenProcess(permissions, true, pid);
    }

    public static long findDynAddress(com.sun.jna.platform.win32.WinNT.HANDLE process, int[] offsets)
    {

        int size = 4;
        Memory pTemp = new Memory(size);
        long pointerAddress = 0;

        for(int i = 0; i < offsets.length; i++)
        {
            if(i == 0)
            {
                boolean test = kernel32.ReadProcessMemory(process, baseAddressPointer, pTemp, size, null);
                int lasterror = kernel32.GetLastError();
                System.out.println("test = " + test + ", lasterror = " + lasterror);
            }

            pointerAddress = ((pTemp.getInt(0)+offsets[i]));

            if(i != offsets.length-1)
                kernel32.ReadProcessMemory(process, new Pointer(pointerAddress), pTemp, size, null);


        }

        return pointerAddress;
    }

}

这是运行jar后的终端输出:

System.getProperty("sun.arch.data.model") = 64
pid = 2192
test = false, lasterror = 299
theDynAddress = 1177510878
readProcessMemorySucceeded = false
lasterror = 299
1.64011448E10

请注意:

  • 如您所见,jar 编译为 64 位。
  • 我以管理权限运行 jar。
  • 我尝试访问的进程也是 64 位的。

我收到错误 299,标记为 ERROR_PARTIAL_COPY。我得到的价值似乎是随机的。我能做些什么来防止这种情况发生?

使用作弊引擎访问相同的值没有问题:

【问题讨论】:

  • 您说您正在读取float 值,但您调用的是scoreMem.getInt(0); 而不是getFloat()。您确定 lasterror 来自最后一次通话,而不是之前的通话吗?
  • @DanielWiddis 你是对的!我将其更改为scoreMem.getFloat(0) 并更新了代码。但是,关于lasterror,它是在scoreMemgetInt(0) / getFloat(0) 之前执行的,所以lasterror 不可能是这样,对吧?我还添加了test 的打印值以确保 - 正如您所看到的test 的值是false,所以ReadProcessMemory(...) 失败了。

标签: java winapi jna


【解决方案1】:

您的调试应该关注第一种错误情况,而不是查看最终的错误值。在这种情况下,您已经展示了它是 findDynAddress() 方法中的初始 ReadProcessMemory() 调用,它尝试从 Pointer 变量 baseAddressPointer 中读取。

在您的代码的初始版本中,您定义了:

final static long baseAddress = 0x02B7C950L;
final static Pointer baseAddressPointer = new Memory(baseAddress);

您已经定义了一个基地址(我假设您是从其他地方获得的,硬编码它通常很危险),然后您尝试使用它创建一个指针,但使用 Memory 类,@ 的子类987654328@。 Memory 构造函数的参数是一个大小,并且构造函数在给定其参数大小的情况下在新地址分配新内存,在本例中为 0x02B7C950 字节内存,大约 45.6 兆字节!

虽然您已经分配了该内存(因此您以后的代码不会读取失败,因为它正在读取您“拥有”的内存),但它包含随机数据。

你可能打算这样做:

final static Pointer baseAddressPointer = new Pointer(baseAddress);

编辑您的代码后,您仍然遇到错误。在这种情况下,您可能正在从本机代码中读取内存地址,但您只分配了 4 个字节来存储它:

int size = 4;
Memory pTemp = new Memory(size);

您正在使用 getInt() 获取应该是 64 位指针的内容:

pointerAddress = ((pTemp.getInt(0)+offsets[i]));

您应该使用 8 个字节来存储地址,并在调用后使用getLong() 来获取它。您引用的帖子中答案的序言确实提到在使用这种方法时需要注意指针大小。


一个关键是理解 CheatEngine 使用的符号。

关于截图:

  • "AoE2DE_s.exe"是同名模块的加载地址。 lpmodinfo.lpBaseOfDll 是在代码中检索它的位置。这个地址可以在 CheatEngine 中的 Memory View -> View -> Enumerate DLL's and Symbols 下看到。
  • +02BC6620 部分是简单的算术运算。 02BC6620 在代码中存储为baseAddress
  • -&gt; 表示访问了左侧的地址,并且在该地址下存储了另一个位于右侧的地址。

最后,代码应该是这样的:

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Psapi;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;

import java.util.Arrays;

public class Main {

    final static long baseAddress = 0x02BC6620L;
    private static final String MODULE_FILENAME = "AoE2DE_s.exe";
    private static final int SIZE_OF_LONG = 8;
    private static final String WINDOW_NAME = "Age of Empires II: Definitive Edition";
    final static int[] offsets = new int[]{0x00, 0x1A8, 0x00, 0x158, 0x28, 0x270};

    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 interface User32 extends StdCallLibrary
    {
        final User32 instance = (User32) Native.loadLibrary ("user32", User32.class);
        WinDef.HWND FindWindowExA(WinDef.HWND hwndParent, WinDef.HWND childAfter, String className, String windowName);
        WinDef.HWND FindWindowA(String className, String windowName);
        void GetWindowThreadProcessId(WinDef.HWND hwnd, IntByReference intByReference);
    }

    public static void main(String[] args) {
        System.out.println("System.getProperty(\"sun.arch.data.model\") = " + System.getProperty("sun.arch.data.model"));
        int pid = getProcessId(WINDOW_NAME);
        System.out.println("pid = " + pid);

        com.sun.jna.platform.win32.WinNT.HANDLE process = openProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, pid);;

        long theDynAddress = findDynAddress(process, offsets);
        System.out.printf("theDynAddress = %x\n", theDynAddress);
        Pointer dynAddress = new Pointer(theDynAddress);

        Memory scoreMem = readMemory(process, dynAddress, 8);
        float score = scoreMem.getFloat(0);
        System.out.println(score);
    }

    public static long getBaseAddress(String windowName, String moduleFilename) {
        int pid = getProcessId(windowName);

        com.sun.jna.platform.win32.WinNT.HANDLE process = openProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, pid);
        WinDef.HMODULE[] hModules = new WinDef.HMODULE[100 * 4]; //I'm not sure how big the array should be, this is some big random number
        IntByReference lpcbNeeded = new IntByReference();
        Psapi.INSTANCE.EnumProcessModules(process, hModules, hModules.length, lpcbNeeded);

        hModules = Arrays.copyOf(hModules, lpcbNeeded.getValue() / SIZE_OF_LONG);

        for(WinDef.HMODULE m: hModules){
            Psapi.MODULEINFO lpmodinfo = new Psapi.MODULEINFO();

            if (!Psapi.INSTANCE.GetModuleInformation(process, m, lpmodinfo, lpmodinfo.size())) {
                //TODO: Error handling
            }
            char[] filename = new char[100 * 4];
            int filenameLength = Psapi.INSTANCE.GetModuleFileNameExW(process, m, filename, 100 * 4);
            String filenameString = new String(filename);
            if (filenameString.contains(moduleFilename)) {
                return readMemory(process, new Pointer((Pointer.nativeValue(lpmodinfo.lpBaseOfDll) + baseAddress)), SIZE_OF_LONG).getLong(0);
            }
        }
        return -1;
    }

    public static Memory readMemory(com.sun.jna.platform.win32.WinNT.HANDLE process, Pointer address, int bytesToRead) {
        IntByReference read = new IntByReference(0);
        Memory output = new Memory(bytesToRead);
        boolean readProcessMemorySucceeded = kernel32.ReadProcessMemory(process, address, output, bytesToRead, read);
        if (!readProcessMemorySucceeded) {
            int lasterrorReadMemory = kernel32.GetLastError();
            System.out.println("lasterror = " + lasterrorReadMemory);
        }

        return output;
    }

    public static int getProcessId(String window) {
        IntByReference pid = new IntByReference(0);
        user32.GetWindowThreadProcessId(user32.FindWindowA(null, window), pid);

        return pid.getValue();
    }

    public static com.sun.jna.platform.win32.WinNT.HANDLE openProcess(int permissions, int pid) {
        return kernel32.OpenProcess(permissions, true, pid);
    }

    public static long findDynAddress(com.sun.jna.platform.win32.WinNT.HANDLE process, int[] offsets)
    {
        long pointerAddress = getBaseAddress(WINDOW_NAME, MODULE_FILENAME);
        for(int i = 0; i < offsets.length; i++)
        {
            if (i != offsets.length-1) {
                pointerAddress = readMemory(process, new Pointer(pointerAddress + offsets[i]), SIZE_OF_LONG).getLong(0);
            } else {
                pointerAddress = pointerAddress + offsets[i];
            }
        }
        return pointerAddress;
    }

}

【讨论】:

  • 感谢您指出这一点。不幸的是,有些事情仍然不对劲。我添加了打印出findDynAddress 方法的结果以进一步调试。并发结果不一样,这让我认为问题出在这种方法上。该代码基于stackoverflow.com/a/18849610/1252334。我不太明白,为什么会有pointerAddress = ((pTemp.getInt(0)+offsets[i]));。为什么 pTemp 上有 getInt(0) 方法?据我了解,它从先前指定的地址返回值。为什么需要价值?我们不应该只计算地址吗?
  • 我已更新我的答案以反映您对代码所做的更改。我相信您正在从该位置获取内存地址并按照每个新的内存地址找到另一个内存地址,直到获得分数。与 Java 中具有字段的嵌套类不同,只是内部对您隐藏。您将转到内部存储结构的第 n 个字段,该结构指向您需要第 m 个字段的另一个结构,该结构指向第 p 个字段,等等。
  • 谢谢。但是,将size 更改为8 并将getInt 更改为getLong 后,我仍然遇到相同的错误。我也应该将readMemory(...) 的最后一个参数更改为8,我相信?
  • 是的,bytesToRead 应该是 8。如果添加一些调试语句来打印所有中间地址(使用%x 格式),您可以看到它偏离轨道的位置。
  • 我设法读取了值!首先,我将final static long baseAddress = 0x02BC6620L; 替换为14B5376F960L。这是来自 CheatEngine 的计算地址。 "AoE2DE_s.exe"+02BC6620 的结果。其次,我将虚拟 0x00 作为第一个元素添加到 offsets 数组。虽然我能够正确获得我想要的值,但我仍然需要以某种方式计算 "AoE2DE_s.exe"+02BC6620,因为它会在进程重新启动时发生变化。我不明白这是如何工作的。关于另一次扫描,根据 CheatEngine "AoE2DE_s.exe" = 300905A4D300905A4D + 02BC6620 = 264231CD4D0怎么来的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-16
相关资源
最近更新 更多