How to manipulate memory from Java with JNA on Windows

Using https://github.com/OpenHFT/Java-Lang you can do

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();

The DirectByte can point arbitrary addresses in memory or be allocated with malloc


You need to use the JNA Library. Download the two Jar-Files (jna.jar and jna-platform.jar)

I found a tutorial on pastebin, which explains how to use this Library. But it won't be necessary to read it to understand the following.

Let's say, you want to manipulate the addresses and their values of the Windows game "Solitaire"


Know, what you do

  1. If you want to manipulate addresses and their values, know what you do!
    You need to know, what size the value, stored in the address, has. Is it 4Byte, or 8Byte or whatsoever.

  2. Know how to use tools to get dynamic and base-addresses. I use CheatEngine.

  3. Know the difference bewteen base-addresses, and dynamic-addresses:

    • Dynamic-addresses will change every time you restart the application (Solitaire).
      They will contain the needed value, but you would need to find the address again every time. So what you need to learn first, is how to get the base-address.
      Learn this by playing through the CheatEngine Tutorial.

    • Base-addresses are static addresses. Those addresses point to other addresses mostly the following way: [[base-addres + offset] + offset] -> value. So what you need is to know the base-address, and the offsets you need to add to the addresses to get the dynamic-address.

So now that you know what you need to know you do some research with the CheatEngine on Solitaire.


You found your dynamic-address and searched for the base-address? Good, let's share our results:

Base-address for the score: 0x10002AFA8
Offsets to get to the dynamic-address: 0x50 (first) and 0x14 (second)

Got everything right? Good! Let's continue with actually writing some code.


Create a new Project

In your new project, you need to import those libraries. I use Eclipse, but it should work on any other IDE.

User32 Interface

Thank's to Todd Fast, for setting up an User32 interface. It's not complete, but enough we need here.

With this interface, we get access to some functions of the user32.dll on Windows. We need the following functions: FindWindowA and GetWindowThreadProcessID

Side Note: If Eclipse tells you it needs to add unimplemented methods, just ignore it and run the code anyway.

Kernel32 Interface

Thank's to Deject3d for the Kernel32 interface. I modified it a little bit.

This interface contains the methods we use to read and write to memory. WriteProcessMemory and ReadProcessMemory. It also contains a method to open a process OpenProcess

Actual Manipulation

We now create a new class which will contain some helper methods and the main function as access point for the JVM.

public class SolitaireHack {

    public static void main(String... args)
    {

    }
}

Let's fill in stuff we already know, like our offsets and the base-address.

public class SolitaireHack {

    final static long baseAddress = 0x10002AFA8L;
    final static int[] offsets = new int[]{0x50,0x14};

    public static void main(String... args)
    {

    }
}

Next we use our interfaces to get access to our Windows specific methods:

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)
    {

    }
}

Last but not least we create some permission constants we need, to get the permission to read and write to a process.

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)
    {

    }
}

In order to get a process, where we can manipulate the memory, we need to get the window. This window can be used to get the process id. With this id we can open the process.

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;
}

In the getProcessId method we use the parameter, which is the title of the window, to find the window handle. (FindWindowA) This window handle is used to get the process id. An IntByReference is the JNA version of an pointer, where the process id will be stored.

If you get the process id, you can use it to open the process with openProcess. This method gets the permissions and the pid, to open the process, and returns a pointer to it. To read from a process you need the permission PROCESS_VM_READ and to write from a process you need the permissions PROCESS_VM_WRITE and PROCESS_VM_OPERATION.

The next thing we need to get is the actual address. The dynamic-address. So we need another method:

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;
}

This method needs the process, the offsets and the base-address. It stores some temporary data in a Memory object, which is exactly what it says. A memory. It reads out at the base-address, gets back a new address in the memory and adds the offset. This is done for all offsets and returns in the end the last address, which will be the dynamic-address.

So now we want to read our score and print it out. We have the dynamic-addres where the score is stored and just need to read it out. The score is a 4Byte value. Integer is a 4Byte datatype. So we can use an Integer to read it out.

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;
}

We wrote a wrapper for our kernel32 method readProcessMemory. We know we need to read 4Byte so the bytesToRead will be 4. In the method, a Memory object will be created and returned, which will have the size of byteToRead and store the data, contained in our address. With the .getInt(0) method we can read out the Integer value of our memory at the offset 0.

Play a little bit with your solitaire, and get some points. Then run your code and read out the value. Check if it's your score.

Our last step will be to manipulate our score. We want to be the best. So we need to write 4Byte data to our memory.

byte[] newScore = new byte[]{0x22,0x22,0x22,0x22};

This will be our new score. newScore[0] will be the lowest byte and newScore[3] will be the highest one. So if you wanted to change your score to the value 20 your byte[] would be:
byte[] newScore = new byte[]{0x14,0x00,0x00,0x00};

Let's write it in our memory:

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);
}

With our writeMemory method, we write a byte[] called data to our address. We create a new Memory object and set the size to the length of the array. We write the data to the Memory object with the correct offsets and write the object to our address.

Now you should have the fantastic score of 572662306.

If you don't know exactly, what some kernel32 or user32 methods do, have a look at MSDN or feel free to ask.

Known issues:

If you don't get the process id of Solitaire, just check it in your Task Manager and write in the pid manually. The german Solitär won't work, i think because of the ä in the name.

I hope you liked this tutorial. Most parts are from some other tutorials, but put all together here, so in case someone needs a starting point, this should help.

Thanks again to Deject3d and Todd Fast for their help. If you have issues, just tell me and I try to help you out. If something is missing, feel fre to let me know or add it by yourself.

Thank you and have a nice day.


Let's have a look at the full code of the SolitaireHack Class:

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);
    }
}

Tags:

Java

Memory

Jna