[Java] Gamehacking

03/05/2012 00:06 xNopex#1
Hallo Allerseits,

Im folgenden teile ich mit euch meine bisherigen Erfahrungen bezüglich dem Thema Speichermanipulation / Gamehacking in Java. Vorausgesetzt werden Kenntnisse in Java, C und C++.

Der direkte Zugriff auf Funktionen der WinApi ist in Java afaik leider nicht möglich. Deshalb ist es auch nicht möglich Funktionen wie Read-/WriteProcessMemory aus einem Java Programm aufzurufen, um fremden Speicher zu verändern oder auszulesen. Will man diese Funktionen trotzdem verwenden, muss man einen kleinen Umweg gehen.

Die Idee besteht darin eine native Dll zu programmieren, die entsprechende Funktionen bereitstellt, über die aus dem Java Programm auf die WinApi Funktionen zugegriffen werden können. Das Problem hierbei ist wiederum die Frage, wie der native Code der Dll aus einem Java Programm aufgerufen werden kann. Die Lösung dieser Frage ist das Java Native Interface (JNI). Das JNI erlaubt Java Programmen auf einfache Weise nativen Code aufzurufen (Anmerkung: das ganze lässt sich auch umdrehen, sodass es auch möglich ist Bytecode in einem nativen Programm aufzurufen).

Im Folgenden soll anhand eines kleinen Beispiels gezeigt werden, wie man aus einem Java Programm heraus via der WinApi Funktion WriteProcessMemory den Speicher eines fremden Prozesses verändern kann.


1. Das Zielprogramm programmieren:

Als erstes schreibe ich mir ein kleines "Opfer"-Programm, bei dem der Wert einer Variable durch unser Java Programm verändert werden soll (C++-Code):

Code:
#include <iostream>
#include <windows.h>

using namespace std;

int main()
{
    SetConsoleTitle("Target");
    int value = 1337;
    cout << "Wert: " << value << "\n";
    cout << "Adresse: " <<  &value;
    cin.get();
    cout << "Neuer Wert: " << value;
    cin.get();
    return 0;
}
Kurze Erklärung:
Via SetConsoleTitle wird der Titel des Konsolenfensters geändert. Im Java Programm kann das Zielprogramm dann über diesen Titel festgelegt werden. Der Wert der Variable value soll dann durch das Java-Programm verändert werden. Es wird zuerst der Startwert ausgegeben, dann die Adresse der Variable im Speicher.
Später kann die Adresse so abgelesen und im Java-Programm eingegeben werden. Es wird dann auf eine Eingabe gewartet, sodass Zeit bleibt den Speicher über unser Programm zu ändern. Nachdem das geschehen ist, wird der neue (hoffentlich) veränderte Wert der Variable ausgegeben.


2. Das Java Programm:

Der Java-Quellcode eines Programms, das eine native Funktion aufruft, unterscheidet sich kaum von dem Code eines Programms, das keine native Funktion aufruft. Drei Dinge müssen jedoch beachtet werden:
  1. Native Methoden werden lediglich deklariert
  2. Bei der Deklaration wird dem Compiler durch das Schlüsselwort native mitgeteilt, dass es sich hierbei um eine native Methode handelt
  3. Die Dll, in der die nativen Methoden ausgelagert werden, muss vom Java Programm geladen werden

Der Quellcode eines Java-Programms, das vom Nutzer Fenstertitel des Zielprozess, Speicheradresse der zu verändernden Variable und den neuen Wert der Variable entgegennimmt und anschließend die native Methode writeMemory aus einer Dll (wapi.dll; muss erst noch programmiert werden) aufruft, könnte daher in etwa so aussehen:

Code:
import java.util.Scanner;

public class Winapi {

	// Lade Dll, die die native Funktion enthält (wapi.dll)
	static {
		try {
			System.loadLibrary("wapi");
		} catch (UnsatisfiedLinkError ex) {
			System.err.println("Konnte Library nicht laden: " + ex.toString());
		}
	}

	// Deklaration der nativen Methode
	public static native boolean writeMemory(String windowTitle,
			long baseAddress, int value);

	public static void main(String[] args) {
		String windowTitle = new String();
		long baseAddress = 0;
		int value = 0;

		// Daten von Nutzer eingeben lassen
		Scanner inputScanner = new Scanner(System.in);
		System.out.print("Fenstertitel des Zielprozess: ");
		windowTitle = inputScanner.nextLine();
		System.out.print("Addresse: ");
		baseAddress = inputScanner.nextLong();
		System.out.print("Neuer Wert: ");
		value = inputScanner.nextInt();
		inputScanner.close();

		// Native Funktion aufrufen
		if (Winapi.writeMemory(windowTitle, baseAddress, value)) {
			System.out.println("writeMemory war erfolgreich");
		} else {
			System.err.println("writeMemory ist fehlgeschlagen");
		}
	}

}

3. Die Dll programmieren:

Damit das obige Java Programm auch läuft, muss jetzt noch die wapi.dll programmiert werden. Ich werde dies in der Programmiersprache C tun. Das JDK erleichtert einem ein wenig die Arbeit, indem es ein Tool (javah) zur Verfügung stellt, das aus dem Java-Quellcode einen fertigen Header generiert, in dem neben der Funktionsdeklaration der nativen Funktion auch alle für das JNI notwendigen Includes vorhanden sind. Mit folgendem Konsolenbefehl wird der Header erstellt:

Code:
javah –jni <name der Java-Class>
Nun ist es an der Zeit sich näher mit dem JNI zu beschäftigen. Genauere Informationen, als ich sie liefern kann, erhält man dabei immer in der JNI Spezifikation.
Als erstes muss der generierte Header genauer betrachtet werden, insbesondere die Deklaration der Funktion:

Code:
 JNIEXPORT jboolean JNICALL Java_Winapi_writeMemory
  (JNIEnv *, jclass, jstring, jlong, jint);
Vergleicht man diese mit der Deklaration aus dem Java Quellcode sollten neben den Makros JNIEXPORT und JNICALL (die für uns nicht weiter von Interesse sind) die zwei zusätzlichen Parameter auffallen, nämlich der JNIEnv-Zeiger und die jclass-Variable. Auf die jclass-Variable möchte ich an dieser Stelle nicht näher eingehen, da sie für unser Ziel uninteressant ist. Wichtiger und von praktischen Nutzen wird der JNIEnv-Zeiger sein.

Dieser Zeiger zeigt auf eine Funktionstabelle irgendwo im Speicher. Jeder Eintrag in dieser Tabelle enthält wiederum einen Zeiger auf eine JNI-Funktion. Diese JNI-Funktionen werden u.a. dafür benötigt, um mit den JNI-Datentypen zu arbeiten. Ein Blick auf die Parameter der Funktionsdeklaration und man sieht sofort, was gemeint ist: jstring, jlong, jint.

Was hat es mit diesen seltsamen Datentypen auf sich? Nimmt man Beispielsweise den jstring Parameter her, wird sofort klar, wieso diese extra Datentypen benötigt werden. Wie sollte sonst eine Java-String-Objekt an ein natives Programm übergeben werden? C kennt zum Beispiel keine Klassen und Java-Klassen kennt es dann erst recht nicht. Wird also ein String Objekt an eine native Funktion übergeben, erhält die native Funktion eine Variable vom Typ jstring. Wird eine Variable vom Typ long an eine native Funktion übergeben, erhält diese eine Variable vom Typ jlong, usw.

Abschließend ist es noch wichtig zwischen den Java-Datentypen zu unterscheiden. So gibt es in Java primitive Datentypen (int, long, double,…) und Referenzdatentypen (String, Arrays, …). Der JNI-Datentyp von primitven Datentypen korrespondiert dabei jeweils zu einem nativen Datentyp in C. Folgende "Tabelle" soll das veranschaulichen:

Code:
Java Type     Native Type               Description
 boolean         jboolean                unsigned 8 bits
                  (unsigned char)

   byte             jbyte                    signed 8 bits
                  (signed char)

   char             jchar                   unsigned 16 bits
                 (unsigned short)

  short            jshort                    signed 16 bits
                    (short)

   int                jint                      signed 32 bits
                     (long)

  long              jlong                    signed 64 bits
                   (long long)

  float             jfloat                         32 bits
                    (float)

 double          jdouble                        64 bits
                   (double)

  void              void                            N/A
Im Gegensatz dazu sind die JNI-Datentypen von Referenzdatentypen Zeiger auf spezielle Datenstrukturen. Was genau hinter diesen Zeigern steckt, ist dabei meistens uninteressant. Es wird aber offensichtlich, dass man mit diesen Datentypen anders umgehen muss. Lange Rede, kurzer Sinn: Genau dafür braucht man die JNI-Funktionen.

Das sollte als kleiner Ausflug genügen. Wen das näher interessiert, der sollte sich wirklich mit der JNI Spezifikation auseinander setzen. Zurück zur eigentlichen Programmierarbeit. Während man nun einfach mit den jlong und jint Parametern normal weiterarbeiten kann, muss die Zeichenkette der jstring Variable erst in ein char-Array kopiert werden. Dies geschieht mittels der JNI-Funktion GetStringUTFRegion. Die Funktion nimmt als ersten Parameter den jstring entgegen, der zweite Parameter gibt an, ab welchem Zeichen der jstring kopiert werden soll, der dritte Parameter gibt an, wie viele Zeichen kopiert werden sollen und der letzte Parameter gibt an, wohin die Zeichen kopiert werden sollen.
Damit wäre das schwierigste überwunden. Es liegen nun alle Paramater so vor, dass man mit ihnen wie gewohnt in C programmieren kann. Nun muss lediglich noch WriteProcessMemory aufgerufen werden und die Sache ist gegessen. Der Quellcode könnte in etwa so aussehen:

Code:
#include "Winapi.h" //automatisch generierter Header
#include <windows.h>

JNIEXPORT jboolean JNICALL Java_Winapi_writeMemory(JNIEnv* env, jclass cl, jstring windowTitle, jlong address, jint value)
{
	char strWindowTitle[256];
	ZeroMemory(strWindowTitle, 256);
	long long baseAddress = address;
	long buffer = value;
	
	//Prüfe Länge des jstrings
	int lenWindowTitle = env->GetStringLength(windowTitle);
	if(lenWindowTitle > 255)
		return JNI_FALSE;
	
	//Kopiere Zeichenkette in das char-Array
	env->GetStringUTFRegion(windowTitle, 0, lenWindowTitle, strWindowTitle);
	
	//Besorge Prozesshandle und Schreibe den neuen Wert in den Speicher des Zielprozess
	HWND hwnd = FindWindow(NULL, strWindowTitle);
	if(hwnd == NULL)
		return JNI_FALSE;
	
	DWORD pid = 0;
	GetWindowThreadProcessId(hwnd, &pid);
	if(pid == 0)
		return JNI_FALSE;
	
	HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if(handle == NULL)
		return JNI_FALSE;
	
	if(WriteProcessMemory(handle, (void*)baseAddress, &buffer, sizeof(long), NULL) == TRUE)
		return JNI_TRUE;
	else
		return JNI_FALSE;
}
Nun muss die Dll noch kompiliert werden. Dabei sollte man nicht vergessen die Pfade für die JNI-Header anzugeben (befinden sich im JDK-Include Verzeichnis).
Wirft das Java-Programm beim Aufruf der nativen Methode eine UnsatisfiedLinkError-Exception, dann konnte höchstwahrscheinlich die native Funktion in der Dll durch decoration des Compilers nicht erkannt werden und es müssen noch entsprechende Compilerflags gesetzt werden, welche aus der Spezifikation des verwendeten Compilers entnommen werden können. Also am besten Googlen. Z.B. ist es beim gcc compiler notwendig mit „-Wl,--kill-at“ zu kompilieren.

[Only registered and activated users can see links. Click Here To Register...]


4. Fazit:

Es funktioniert also: Auch in Java kann man WinApi Funktionen verwenden. Zeugs wie Injektoren sind damit mehr oder weniger auch in Java umsetzbar. Aber man muss sich ernsthaft fragen, ob sich der Aufwand lohnt. Die Kernarbeit muss so oder so in C oder vergleichbaren Sprachen gemacht werden. Ich denke also, dass die Speichermanipulation in Java mehr Spielerei ist, als dass sie einen praktischen Nutzen hat.


EDIT: Wer syntaktische, semantische oder sonstige Fehler findet, darf sie als Beitrag hier drunter anmerken und mich aufs übelste derb beleidigen. Danke.
03/05/2012 01:12 MrSm!th#2
Hm, interessant. Dass man mit Java native Funktionen aufrufen kann, war mir klar, aber ich wusste nie, wieso damit dann kein dirkter Aufruf der WinApi möglich ist, wenn man doch Zugriff auf native Funktionen hat.
Dass man dafür diese Funktionen speziell deklarieren muss, war mir nicht klar.

Eine Frage zu jlong: Du sagtest, dass dieser identisch zu long in C ist. Aber long ist doch laut deiner Aussage nicht garantiert 64bit groß, er muss nur ausreichen, um die Zahlenmenge von 2³² darzustellen, also im Grunde ein int unter anderem Namen. Zumindest die meisten Compiler behandeln ihn als solchen.
In Java ist long allerdings garantiert 64bit groß, das weiß ich.
Kann es da nicht Probleme geben, wenn man versucht, eine Zahl größer als 2³² zu übergeben?
03/05/2012 01:24 MoepMeep#3
Quote:
Originally Posted by xNopex View Post
EDIT: Wer syntaktische, semantische oder sonstige Fehler findet, darf sie als Beitrag hier drunter anmerken und mich aufs übelste derb beleidigen. Danke.
[Only registered and activated users can see links. Click Here To Register...]

Fühl dich bitte übelst derb beleidigt, danke.
03/05/2012 06:29 Tyrar#4
interessant zu wissen, hätte gedacht dass native funcs ähnlich wie in .NET sprachen gecalled werden können, also nicht speziell deklariert
03/05/2012 09:00 TheOnlyOne652089#5
Schönes Tutorial zum Thema (gibt es ja noch viel zu wenig für).


Ich gebe dir auch absolut recht das JNI überraus "umständlich" ist und die Frage ob sich das ganze lohnt schnell aufkommt.


Für die meisten Hacks sollte C++ die Wahl sein, wenn man ohnehin entsprechende DLL's selbst schreiben müsste.



Als alternative gibt es aber JNA (Java Native Access).

[Only registered and activated users can see links. Click Here To Register...]


Das ganze ist performance mässig eingeschränkter (was aber kaum ins Gewicht fällt wenn man ohnehin mit Sleeps arbeitet).


JNA hat dabei den ungemeinen Vorteil das man keinen C/C++ Code braucht, man spricht die DLL einfach "direkt" an und führt die Befehle in reinem Java Code aus.

Ordentliche in Frameworks verpackt lässt sich damit äußerst elegant arbeiten und man kann sich auf die Vorteile von Java stützen, anstatt sich mit den Nachteilen von C/C++ herumzuärgern (was ja doch leider zu komplexen Code Gebilden führt und massiv fehleranfällig wird sobald man tatsächlich mal etwas ändert).



Wenn du Lust und Zeit hättest kannst du dich mit mal mit JNA beschäftigten, als direkten Vergleich zu JNI, in den aller meisten Anwendungsgebieten sollte das einfach die "geschicktere" Wahl sein.



Ich schaue gerade eine einheitliche einfache API für JNA calls für Kernel32 und User32 zu bauen.

Die beiden sind aber äußerst groß mit vielen Funktionen, wobei man ja auch nur wenige benötigt ala "OpenProcess", "readProcessMemory", "writeProcessMemory", "FindWindow".

Für einfache "send" Befehle und MouseMoves gibt es ohnehin die "Robot" class.


Prinzipiel kann man aber sagen das man für die meisten Hacks wohl schlicht JNA benutzen "könnte", ohnehin nur eine Frage der Zeit bis das JNA auch im Standard landet.


JNA writeMemory

*Für die nötigen Rechte braucht man je nach Process AdminRechte, benutzt man Eclipse startet man Eclipse selbst einfach mit AdminRechten, ansonsten wird man bei OpenProcess bereits ErrorCode = 5 bekommen.

Code:
public interface Kernel32 extends StdCallLibrary {

	Kernel32 INSTANCE = (Kernel32) Native.loadLibrary(
			Platform.isWindows() ? "kernel32" : null, Kernel32.class,
			W32APIOptions.UNICODE_OPTIONS);

	int GetLastError();

	boolean WriteProcessMemory(Pointer hProcess, int inBaseAddress,
			Pointer inputBuffer, int nSize, IntByReference inNumberOfBytesWriten);

}

public final class JnaTEST {

	public static void main(final String[] args) {

		//Kernel32 Instance beziehen
		Kernel32 lib = Kernel32.INSTANCE;

		final int dwDesiredAccess = 0x1F0FFF; //ALL_ACCESS
		final boolean bInheritHandle = true;
		final int dwProcessId = 5088; //PID of Process

		Pointer handle = lib.OpenProcess(dwDesiredAccess,
					bInheritHandle, dwProcessId);

		if (handle == null) {
			int lastError = lib.GetLastError();
			System.out.println("lastError = " + lastError);
			throw new RuntimeException("no such pid");
		}

		final int inBaseAddress = 0x148712A4; //Address to manipulate
		final int bufferSize = 32; //size of address type
			
		Memory inputBuffer = new Memory(bufferSize);
		int dataToWrite = 1;
		inputBuffer.setInt(0, dataToWrite);
		boolean successWrite = memoryWorker.writeProcessMemory(handle,
					inBaseAddress, inputBuffer, bufferSize,
					outNumberOfBytesReadByRef);
	}
}
03/05/2012 10:36 xNopex#6
Quote:
Eine Frage zu jlong:[...]
Jap habs nochmal nachgeschaut und du hattest Recht. Hab die entsprechenden Stellen mal ausgebessert.

Quote:
Fühl dich bitte übelst derb beleidigt, danke.
Ich hab das schnell im Win-Editor getippt und da gibts nich, wie in Eclipse son schönes Feature, das einem das dann so schön macht unso.. Aber ich habs mal ausgebessert.

Quote:
Wenn du Lust und Zeit hättest kannst du dich mit mal mit JNA beschäftigten, als direkten Vergleich zu JNI, in den aller meisten Anwendungsgebieten sollte das einfach die "geschicktere" Wahl sein.
Sieht interessant aus, danke für den Hinweis. Ich schaue es mir bei Gelegenheit mal an.
03/31/2012 16:22 Kinu#7
Auch von mir Danke für das Tut :)

Hab bis jetzt auch nur mit JNA gearbeitet, da es wesentlich besser zu handhaben ist als JNI. Werds denk ich bei Gelegenheit mal testen :)
05/09/2012 19:06 .Dash#8
Gut gemacht. Wusste garnicht das man in Java native Funktionen laden kann O.o
06/08/2012 17:35 Fowl3628800#9
@xNopex
Schönes tut

@TheOnlyOne652089
Ich hab versucht deinen Code ans laufen zu bekommen, habe
Code:
memoryWorker
durch
Code:
lib
ersetzt, aber wie muss ich
Code:
outNumberOfBytesReadByRef
definieren?