Register for your free account! | Forgot your password?

You last visited: Today at 05:08

  • Please register to post and access all features, it's quick, easy and FREE!

Advertisement



[Java] Gamehacking

Discussion on [Java] Gamehacking within the Coding Tutorials forum part of the General Coding category.

Reply
 
Old   #1
 
xNopex's Avatar
 
elite*gold: 0
Join Date: May 2009
Posts: 827
Received Thanks: 471
[Java] Gamehacking

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.





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.
xNopex is offline  
Thanks
19 Users
Old 03/05/2012, 01:12   #2


 
MrSm!th's Avatar
 
elite*gold: 7110
Join Date: Jun 2009
Posts: 28,913
Received Thanks: 25,414
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?
MrSm!th is offline  
Old 03/05/2012, 01:24   #3
 
elite*gold: 42
Join Date: Jun 2008
Posts: 5,425
Received Thanks: 1,888
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.


Fühl dich bitte übelst derb beleidigt, danke.
MoepMeep is offline  
Thanks
1 User
Old 03/05/2012, 06:29   #4
 
Tyrar's Avatar
 
elite*gold: 0
Join Date: Oct 2008
Posts: 1,637
Received Thanks: 1,119
interessant zu wissen, hätte gedacht dass native funcs ähnlich wie in .NET sprachen gecalled werden können, also nicht speziell deklariert
Tyrar is offline  
Old 03/05/2012, 09:00   #5
 
TheOnlyOne652089's Avatar
 
elite*gold: 50
Join Date: May 2008
Posts: 2,214
Received Thanks: 1,825
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).




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);
	}
}
TheOnlyOne652089 is offline  
Thanks
1 User
Old 03/05/2012, 10:36   #6
 
xNopex's Avatar
 
elite*gold: 0
Join Date: May 2009
Posts: 827
Received Thanks: 471
Quote:
Eine Frage zu jlong:[...]
*** 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.
xNopex is offline  
Old 03/31/2012, 16:22   #7
 
Kinu's Avatar
 
elite*gold: 10
Join Date: May 2006
Posts: 2,786
Received Thanks: 773
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
Kinu is offline  
Old 05/09/2012, 19:06   #8
 
.Dash's Avatar
 
elite*gold: 0
Join Date: May 2012
Posts: 26
Received Thanks: 1
Gut gemacht. Wusste garnicht das man in Java native Funktionen laden kann O.o
.Dash is offline  
Old 06/08/2012, 17:35   #9
 
elite*gold: 0
Join Date: Mar 2012
Posts: 6
Received Thanks: 0
@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?
Fowl3628800 is offline  
Reply


Similar Threads Similar Threads
[JAVA Error] Could not create the java virtual machine
07/21/2013 - Technical Support - 10 Replies
Schönen Abend! Leider hat es sich aus einem unerfindlichen Grund ergeben, dass sobald ich die Minecraft.exe starten will die Errormeldung kommt. Die Tips auf Minecraft.net habe ich schon ohne Erfolg befolgt. Hoffe ihr könnt mir weiterhelfen... Mein PC:
[Java] Could not create the Java virtual machine
06/22/2011 - Minecraft - 1 Replies
hallo ihr minecraftler ^^ habe seit heute das problem das wenn ich minecraft starte original als auch cracked das diese fehlermeldung kommt: Java virtual machine Launcher Could not create the Java virtual machine



All times are GMT +2. The time now is 05:09.


Powered by vBulletin®
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
SEO by vBSEO ©2011, Crawlability, Inc.

Support | Contact Us | FAQ | Advertising | Privacy Policy | Terms of Service | Abuse
Copyright ©2026 elitepvpers All Rights Reserved.