Bytecode in String

Java Bytecode in String verpackt

Vielleicht kennt Ihr den Code ’99 Bottles of Beer‘, der einen Songtext ausgibt. Dieser Code wird in verschiedensten Sprachen implementiert. Auf der Seite http://www.99-bottles-of-beer.net könnt Ihr euch das ganze ansehen. Es gibt eine Java Variante, die auf mich schon immer faszinierend wirkte und zwar diese hier. Es ist die sogenannte bytecode Variante. Wow dachte ich, wie funktioniert das ganze!?!? Etliche male wollte ich mich daran machen, um herauszufinden wie man das implementieren kann und selbst das gleiche tun. Aber ich fand nicht die Zeit dazu. Jetzt endlich hab ich mich hingesetzt und das Coding studiert. Nun will ich euch mein Wissen nicht länger vorenthalten und euch in das Geheimnis des Codes einweihen!

In diesem Tutorial werde ich ein Programm erzeugen, dass die gleiche Technik benutzt!

So, erstmal der Code so wie er auf der Seite zu finden ist:

public class BeerSongLoader extends ClassLoader {

private static final String BEER_SONG =

" #§$%MBVC?*9@QW68Q986@9 " +

" @6######%§##%@#9##§§##§9 " +

" #C##§@#9# ##$##§9#?# " +

" #§Q#?##§W #?##§6#9# " +

" #§8##$##9 ###$##$§# ?##$$#?## $%#?##$M#? ##$B#?# #$V#?# " +

" #$C#?##$? #9###$## $*#C##$9#9##$@ ##$Q#9##$W##$6 #C##$8#§### V%QV*V6V " +

" *CM%6#§###%$?$*BV#§# ##MM%V8 VMVB#§# ##?CMV8B %CMC$V* V6VC#§##§M$?$*MQV9V§ " +

" CVV§$8VQV§V6VC$8B%CMC$ V*V6VC% @#Q##§B ##§V#§## §CV9V§C VV§$8VQV§V6VC$8B%C " +

" MC$V*V6VCM$CBV*VQVMVBC$ #§##%WC@%#$QV%V?V8V* V%VB$Q%#$%V6V8$#VWV 8C$VB$#V$ V8CMCM " +

" VQVBC%CQ% §$%%§$#V$V 8CMCMVQVBCQ%§%QC@%# CW$#V$V8CMCMVQVBC%C W$#V8VV$# " +

" V$VBVBC$# §###§V6#§# ##§M6#C## %##Q##%§ ##%$#Q## " +

" %%##%M#§# ##6$#V8V6$ #CMV?VB$# CCV§VQVQ$ Q$##§###$ " +

" $6#9#§##$WC @%#$QV%V?V 8V*V%VB$Q%#$%MCV8 $#CMV8$#CMV?VB$#C% CMV8C$VB$ " +

" #V§V6VM$#V$CBC*$#C%V8VWV B$#VWV8C$VB#§##$ VCQ%#%QBMV§V@VB$ #V8V6VB$#VMV8C " +

" CV6$#V§V6VM$#C#V§C%C%$# V*CM$#V§C$V 8CBV6VMCW$Q$ ##§###$C@%##§# " +

" " +

" ##$C@%§#§###8$#V8V6$#C " +

" MV?VB$#CCV§VQVQ$6#9#9#Q# " +

" #§?##§*#§ ##§#V9V§CV " +

" V§$8VQV§V 6VC$8M8V$ " +

" V9VBV%CM# C##%B#Q## %V##%C#C# #%?#Q##%*# #%9#§## #?M$VB " +

" VBC$B%V8V 6VC#§##§ #V9V§CVV§$8VQV §V6VC$8B%CMC$V *V6VC#§###C C$VBC#VQ " +

" V§V%VB#§##MM$?MQV9V§ CVV§$8V QV§V6VC $8M%V?V§ C$B%VBC §CBVBV6V%VB%@MQV9V§C " +

" VV§$8VQV§V6VC$8M%V?V§C $B%VBC§ CBVBV6V %VB%@$*M QV9V§CV V§$8VQV§V6VC$8B%CM " +

" C$V*V6VC%@#§###VV§C#C#V BV6VM#§##$W$?MQV9V§C VV§$8VQV§V6VC$8B%CM C$V*V6VC% @$*MQV " +

" 9V§CVV§$8 VQV§V6VC$8 B%CMC$V*V6VCM$CBV*V QVMVBC$%@#§##§§V9V§ CVV§$8VQV " +

" §V6VC$8M* V6CMVBVCVB C$#§###CC VV§VQCBV BM8VV#§# " +

" #§V$?M*$* MQV9V§CVV§ $8VQV§V6V C$8M*V6CM VBVCVBC$% " +

" @#§##§CV9V§ CVV§$8CMVB C?CM$8MWVBC%C%V§V CVBMVV8C$VWV§CM#§# ##VVVV8C$ " +

" VWV§CM#§##%*$?MQV9V§CVV§ $8VQV§V6VC$8B%CM C$V*V6VC%@B@MQV9 V§CVV§$8VQV§V6 " +

" VC$8M8V$V9VBV%CM%@$*MQV 9V§CVV§$8VQ V§V6VC$8B%CM C$V*V6VC%@##$§ " +

" " +

" ##§M##§§###########$## " +

" #§##§B##§V###§##§C###### " +

" §§###§### §#######B$ " +

" 9@C###§@§ ######### " +

" ##§##§?## §*###§##§ C######?V ###C###M## ####C9@ @###$B " +

" *@C###%MQ §#V%%W§Q *@##V*§$#MM6$@ @@###$B*@C###% $W§$#B§$#V@ V###C@V# " +

" ##?§$#*@V###?$W@V### ?§$#9@V ###?§$# @@V###?§ $#Q@V## #?$W§$#W§$#6@V###C@V " +

" ###?§$#8@V###?@V##§##B @W##§§B *#%§Q@? ##§$B%B* #M§Q§#V %V#§#VMC#@?##§$B%@ " +

" ?##§%@V###?BC?M#$889C88 **$@@V##§#@######### ####C§$*M§QCC@@#V*? M%V*Q9?C% B§Q%CM " +

" @C9V@Q8C$ *@6#M9B§%6 %@Q*§B6#C?CQ?*9WQ%9 9QVC#89B§#*6CQ%#*B§ V?9$9QMV§ " +

" M@Q@V6V88 8V9§@W96M$ §??V8M@C% %**MQ@§* $§VC%$@* " +

" C§8?M98QC #M$9W%$6B9 VV$$§WCMQ $%#W8§#QB ?W8C?$W9@ " +

" 96C9@V?%$*6 89BVWV@W8@ VM$M#CC8V$@M$6M*% V#$MC6W#§%%%*8#CM@ M*B@%9$W? " +

" $9$99M$?*8B$VM8CC§CW6W@* %W%CC?@#§?B8@W%9 ?*%$9B%8Q*§BQ$Q9 6@V9#?V98C$M$M " +

" M@%??C88M$86M$QV§WC?B?V Q%§B8§#%?MQ B6§BM?B9@#$% WQMW?BBBB?#§§6 ";

@Override public Class<?> loadClass(String name) throws ClassNotFoundException {

String alphabet = "";

byte[] code = new byte[946];

int i = 0, j = 0;

boolean firstNibble = false;

while (i < 946) {

while (BEER_SONG.charAt(j) == ' ') j++;

if (alphabet.length() < 16)

alphabet += BEER_SONG.charAt(j++);

else if (firstNibble = !firstNibble)

code[i] = (byte)(alphabet.indexOf(BEER_SONG.charAt(j++)) * 16);

else

code[i++] += alphabet.indexOf(BEER_SONG.charAt(j++));

}

return name.equals("BeerSong") ? defineClass(name, code, 0, 946)

: findSystemClass(name);

}

public static void main(String[] args) throws Throwable {

Class<?> beerSong = new BeerSongLoader().loadClass("BeerSong");

System.out.println(beerSong.newInstance());

}

}

Prinzipielle Funktionsweise

Wie ja jeder weiss – oder?? – wandelt der Java Compiler den Java-Code in einen Bytecode um. Der Interpreter übersetzt diesen dann in lauffähigen Code der jeweiligen Plattform (Linux, Windows, …). Mit Java Reflection ist es möglich Klassen zur Laufzeit zu laden oder auch zu modifizieren. Mit Reflection kann man so einiges interessantes tun, ich möchte aber hier nicht näher darauf eingehen. Jedenfalls lädt das gezeigte Programm eine Klasse zur Laufzeit und führt sie dann aus.

Normalerweise würde man die Klasse aus dem „.class“ File laden, dieses Programm hat dieses .class File aber als String abgelegt. Nun wird mit einer speziellen Technik der Code aus dem String ausgelesen und die Klasse dann in den Speicher geladen. Zum Schluss fürt der Code die Methode der geladenen Klasse aus. Das Ergebnis ist dann in diesem Fall die Ausgabe des 99 Bottle Songs.

Step-by-Step

Die gezeigte Klasse erbt von ClassLoader, diese stellt die Methode loadClass zur Verfügung, die dazu verwendet wird um eben die Klasse zu laden. In der main Methode wird nun eine neue Instanz von BeerSongLoader erzeugt und die Methode loadClass aufgerufen (Zeile 71). Ihr wird der Name der Klasse übergeben. Dieser Name muss mit dem Namen der „echten“ Klasse übereinstimmen, die in dem String „versteckt“ ist!

In der Methode loadClass passiert nun folgendes:

Zeile 53:

Es wird ein String deklariert „alphabet“

Zeile 54:

Ein ByteArray das den echten Bytecode speichern wird

Zeile 55:

Zähler i und j

Zeile 56:

Boolsche Variable für firstNibble

Zeile 57:

Schleife, für die Länge des Bytecodes (Anzahl Bytes)

Zeile 58:

Leerzeichen werden ignoriert

Zeile 59:

Wenn das Alphabet die Länge < 16 hat, dann wird…

Zeile 60:

…das Alphabet befüllt mit dem Inhalt des Strings an der aktuellen Stelle

Zeile 61:

Dieser Ausdruck wechselt das boolean immer von true zu false und umgekehrt, also der Code darunter wird bei jedem 2ten Schleifendurchlauf aufgerufen

Zeile 62:

Es wird das aktuelle char aus dem Stringcode ausgelesen. Dann wird die Position im Alphabet gesucht, an der dieses char steht. Der Wert der Position wird dann mit 16 multipliziert und in das Code-ByteArray geschrieben.

Zeile 64:

Diese wird immer abwechselnd mit Zeile 62 ausgeführt (siehe Zeile 61). Wieder wird die Position des aktuellen Zeichens im Alphabet gesucht. Der Wert der Position wird dann zum Eintrag im ByteArray, den Zeile 62 erzeugt hat, dazugezählt.

Wenn die Schleife zu Ende ist (Zeile 57) dann ist der Code komplett im ByteArray vorhanden. Mit Zeile 66, die nichts anderes bedeutet als

1

2

3

4

5

if(name.equals("BeerSong")){

return defineClass(name, code, 0, 946);

}else{

return findSystemClass(name);

}

wird also im Falle des Aufrufs von uns die gerade gelesene Klasse erzeugt und retouriniert. Falls der Aufruf eine andere Klasse anfordert wird diese mit der Methode findSystemClass zurückgegeben.

Stop Stop Stop…. was ist da jetzt genau passiert, werdet Ihr euch vielleicht fragen. Deshalb sehen wir uns mal an, ob wir rausfinden können, was im String drinnen steht! Dann ist es auch erheblich einfacher vor allem die Schleife (Zeile 57) besser zu verstehen.

Der „Stringcode“

Also wenn man sich so eine „.class“ Datei in einem Hexeditor ansieht bekommt man so etwas zu sehen:

😉

Die Streber unter euch werden wissen, dass jede .class Datei mit den Hexadezimalen Werten „CAFEBABE“ beginnt….

Nun gut, prinzipiell ist es so, dass das hier der Bytecode ist, hier in hexadezimaler Ansicht. Der String, der im Code die Worte „Beer Beer Beer“ wiedergibt, beinhaltet genau so einen Code. Der Abgebildete Code wird später für mein Step-By-Step Tutorial verwendet. Wir wissen aber bereits, dass jede Java Klasse mit cafebabe beginnt und diesen Hex-Wert muss daher auch unser Bier-Programm beinhalten. Das wollen wir nun suchen!

Wie bereits erwähnt befüllt Zeile 59+60 das Alphabet. Dieses ist ebenfalls in dem Bier-String enthalten, also die ersten 16 Zeichen, die ungleich ‚ ‚ sind, sind also das Alphabet. Das wäre dann ‚#§$%MBVC?*9@QW68‘. Ab dem darauf folgenden Zeichen beginnt der eigentliche Bytecode. Dieser wurde aber nach einer Regel so aufbereitet wie wir ihn sehen. Die Worte „Beer“ spielen überhaupt keine Rolle, da wir wissen, dass die Leerzeichen ignoriert werden spielen nur alle anderen Zeichen eine Rolle. Wir könnten genausogut alle Leerzeichen entfernen und es würde genau so funktionieren, nur wäre dann alles nur halb so lustig… .

Also der erste „echte“ Bytecode ist dann also im String das Zeichen „Q“. Wenn wird uns nun an Zeile 62 erinnern, passiert folgendes:

    1. aktuelles Zeichen aus String lesen –> „Q“

    2. Position im Alphabet bestimmen ‚#§$%MBVC?*9@QW68′ –> 12

    3. (PS: wir beginnen bei 0)

    4. Diesen Wert mit 16 multiplizieren –> 12*16=192

    5. Den Wert in das ByteArray schreiben

Pfuh, geschafft! Gut, das erste Byte im Bytecode war ja ‚CA‘ … hmmm… Ah! Wandeln wir mal 192 in Hexadezimal um… ‚C0‘. Also doch nicht! Gut, der aufmerksame Leser hat bemerkt, dass der Eintrag im ByteArray ja noch verändert wird bei jedem 2ten Durchlauf und zwar mit der Zeile 64:

    1. aktuelles Zeichen aus String lesen –> „9“

    2. Position im Alphabet –> 10

    3. Zu vorhandenem Wert im ByteArray (192) addieren –> 202

Tja, 202 ist in Hex ‚CA‘ und ich lüge nicht wenn ich sage, dass nächsten beiden Buchstaben ‚FE‘ ergeben usw… .

Wir wissen also nun, dass 2 Zeichen im String immer ein Byte ergeben! Wenn Ihr den ByteCode wie im Hexeditor sehen wollt, dann ersetzt Zeile 63 und 64 durch folgende:

1

2

3

4

5

else{

int iold=i;

code[i++] += alphabet.indexOf(BEER_SONG.charAt(j++));

System.out.print(Integer.toHexString(code[iold]&0xFF));

}

Der Ausdruck code[iold]&=0xFF hat den Hintergrund, dass ein Byte in Java signed, also vorzeichenbehaftet ist und daher nur die Werte von -128 bis 127 annehmen kann. Ein Byte kann ja 8 Bit aufnehmen und einen Maximalwert von 255 unsigned haben. Durch die logische UND Verknüpfung mit Hex FF also Dezimal 255 wandeln wir das signed in ein unsigned Byte um!

Was dahinter steckt

Einige werden es sicher schon bemerkt haben, dass was hier passiert ist nichts anderes als die Umwandlung von Hexadezimal in Dezimalzahlen! Das einzige was hier anders ist, ist das Alphabet. Sehen wir uns das nochmal genauer an.

Um eine Hexadezimalzahl nach Dezimal zu wandeln muss jede Ziffer mit der Potenz der Basis multipliziert werden. Der Exponent der Basis entsprich der Stelle der Ziffer. Also z.B. Hex FF = 15*16^1 + 15*16^0 = 15*16 + 15*1 = 255. Und nichts anderes wird hier getan. Die erste, höherwertige Ziffer wird mit 16 multipliziert (Zeile 62), die zweite Ziffer wird mit 1 multipliziert – fällt natürlich weg – und wird dann addiert (Zeile 64).

Die boolsche Variable firstNibble wird dazu verwendet die höherwertigen 4 Bits zu benennen. Wenn wir uns ein Byte in Binärform ansehen sieht das ja so aus z.B. ‚1111 0000‘. Die ersten vier ‚1111‘ entsprechen dann dem firstNibble und entspricht hier der ersten der beiden Ziffern des Hexadezimalwertes, welcher dann mit 16 multipliziert wird.

Vergleichen wir das Alphabet von Hexadezimal mit dem des Beer-Programms:

1

2

0 1 2 3 4 5 6 7 8 9 A B C D E F

# § $ % M B V C ? * 9 @ Q W 6 8

Wie sehen, dass einfach andere Zeichen verwendet werden. Das entspricht eigentlich einer Verschlüsselung. Im Hexadezimalformat ist uns der Wert der jeweiligen Ziffer bekannt. Im Beer-Programm wird der Wert der jeweiligen Ziffer mit der Stringmethode ‚indexOf‘ ermittelt.

So, nun sollten wir auch im Stande sein, das Gegenstück zu bewerkstelligen, also die Umwandlung von .class Dateien in einen eigenen Stringcode, was dann nichts anderes bedeutet, als Dezimalzahlen in Hexadezimalzahlen umzuwandeln. Jedoch mit einem Unterschied: das Alphabet ist nicht wie in Hexadezimal 01234567890ABDEF, sondern beliebig!

So, da wir nun alles wissen was wir brauchen, werde ich euch jetzt den Weg vorstellen, eigene Klassen in so einen Stringcode umzuwandeln.

Eigenen Stringcode erzeugen

Wir erstellen der einfacheit eine ganz simple Klasse:

1

2

3

4

5

6

7

8

9

10

11

public class ByteCodeTest {

public static void main(String[] args) {

new ByteCodeTest();

}

public String toString(){

return "Manfred";

}

}

Diese Klasse tut wenn man sie ausführt gar nichts! Sie erzeugt ein Objekt und überschreibt die toString Methode desselben. Wir benötigen von dieser Klassen jetzt die .class Datei. In Eclipse einmal ausführen und schon sollte sie im bin Verzeichnis stehen!

Nun stelle ich das Programm vor, dass aus .class Dateien einen Stringcode erzeugt! Wie muss nun der Algorithmus des Progamms aussehen:

    1. Ein Alphabet festlegen

    2. Einlesen der .class Datei als ByteArray

    3. Schleife über alle Bytes

    4. Das Byte durch 16 dividieren und vom Alphabet das Zeichen nehmen, welches an der Position des Resultats steht

    5. Den Rest der Division durch 16 nehmen und wieder passendes Zeichen vom Alphabet nehmen

    6. Ende der Schleife

Alphabet festlegen

Unser Alphabet wird wie im Beer-Beispiel aus 16 Buchstaben bestehen. Das ist hier auch gar nicht anders möglich, da wir ja gesehen haben, dass im Prinzip Dezimal in Hexadezimalzahlen gewandelt werden und Hexadezimal hat die Basis 16.

Wir wählen als Alphabet ‚ABCDEFGHIJKLMNOP‘. Welche Zeichen wir hier nehmen ist völlig egal! Es müssen nur 16 sein! Wir könnten probehalber auch mit den Hexadezimalen Ziffern arbeiten, dann werden wir sehen, dass wir als Stringcode genau den gleichen Code erhalten, den wir auch im Hexeditor sehen!

Einlesen der class Datei

Die Datei werden wir mit einem FileInputStream einlesen und dann ein ByteArray befüllen.

Stringcode erzeugen

Der Hauptteil funktioniert nun wie folgt:

Jedes einzelne Byte des Bytecodes wird in einer Schleife durchlaufen, dabei erzeugen wir einmal das Zeichen für das obere Nibble und dann für das untere Nibble. Wir teilen den Byte Wert – der vorher nach unsigned gewandelt wird – durch 16. Dann suchen wir den Buchstaben des Alphabets welcher auf dieser Stelle steht und geben diesen aus. Im gleichen Schleifendurchlauf berechnen wir den Rest der Division mit dem Modulo Operator. Ebenfalls suchen wir wieder das Zeichen im Alphabet für dieses Ergebnis und geben es aus. Wie gesagt im Prinzip Dezimal zu Hexadezimal.

Schlussendlich benötigen wir noch die Länge des Codes in Bytes. Wir können entweder die Länge unseres erzeugen Strings durch 2 teilen und erhalten diese. Oder wir lesen sie durch die Methode von File aus.

So jetzt hier der vollständige Code:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

import java.io.File;

import java.io.FileInputStream;

public class ByteCode2String {

private byte[] content;

private String alphabet;

private File file;

public ByteCode2String(String alphabet, File file) throws Exception{

this.alphabet = alphabet;

this.file = file;

loadClass();

}

private void loadClass() throws Exception{

FileInputStream fis = new FileInputStream(file);

content = new byte[(int)file.length()];

fis.read(content);

}

private long getCodeLength(){

return file.length();

}

private void showHexContent(){

int i=1;

String hex="";

System.out.println("== Hex Content ==");

for(byte b:content){

hex = Integer.toHexString(b&0xFF);

switch(hex.length()){

case 0:

hex = "00";

case 1:

hex = "0"+hex;

}

System.out.print(hex+" ");

if(i++%16 == 0){

System.out.println();

}

}

}

private void showStringCode(){

int teile;

int rest;

int unsigned;

String buchstabe;

int i=1;

for(byte b:content){

unsigned = b&255;

teile=unsigned/16;

rest =unsigned%16;

buchstabe = alphabet.substring(teile, teile+1);

System.out.print(buchstabe);

buchstabe = alphabet.substring(rest, rest+1);

System.out.print(buchstabe);

if(i++%16 == 0){

//System.out.println();

}

}

}

public static void main(String[] args) throws Exception{

File file = new File("C:\\ByteCodeTest.class");

ByteCode2String bc = new ByteCode2String("ABCDEFGHIJKLMNOP", file);

bc.showStringCode();

System.out.println("\n"+bc.getCodeLength());

}

}

Die Methode showHexContent is optional aufzurufen und zeigt den Inhalt wie in einem Hexeditor dargestellt (ohne Dump) an. Falls das gleiche Alphabet verwendet wird, wie es auch die Hexadezimalzahlen haben, dann ist die Ausgabe von showHexContent und showStringCode die selbe! Wie schon erwähnt entspricht die Umwandlung der Konvertierung von Dezimal nach Hexadezimal wie die Zeilen 58 und 59 verdeutlichen. Die Ausgabe sieht ca. so aus:

Den String in der ersten Zeile müssen wir nun vollständig kopieren! Eine leicht angepasste Variante des ursprünglichen Beer-Loaders nun hier

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

import java.lang.reflect.Method;

public class ByteCodeTestLoader extends ClassLoader{

private static final String alphabet="ABCDEFGHIJKLMNOP";

private static final String byteCode=

"MKPOLKLOAAAAAADD AABJAHAAACABAAAM ECHJHEGFEDGPGEGF FEGFHDHEAHAAAEAB"+

"AABAGKGBHGGBCPGM GBGOGHCPEPGCGKGF GDHEABAAAGDMGJGO GJHEDOABAAADCICJ"+

"FGABAAAEEDGPGEGF AKAAADAAAJAMAAAF AAAGABAAAPEMGJGO GFEOHFGNGCGFHCFE"+

"GBGCGM GFABA ABCEMG PGDGBG MFGGBH CGJGBGCGMGFFEGBGCGMGFABAAAEHEGIGJHD"+

"ABAAAO EMECH JHEGFE DGPGEG FFEGFH DHEDLABAAAEGNGBGJGOABAABGCIFLEMGKGB"+

"HGGBCP GMGBG OGHCPF DHEHCG JGOGHD LCJFGAKAAABAAAJABAAAEGBHCGHHDABAABD"+

"FLEMGKGBHGGBCPGM GBGOG HCPFDH EHCGJGOGHDLAB AAAIHE GPFDHEHCGJGOGHABAA"+

"BECICJEMGKGBHGGB CPGMG BGOGHC PFDHEHCGJGOGH DLAIAA BGABAAAHENGBGOGGHC"+

"GFGEABAAAKFDGPHF HCGDG FEGGJG MGFABAABBECHJ HEGFED GPGEGFFEGFHDHECOGK"+

" GBHGG BAACB AAABAA ADAAAA AAAAAA ADAAABAAAFAAAGAAABAAAHAAAAAACPAAABAA"+

" ABAAA AAAAF CKLHAA AILBAA AAAAAC AAAKAAAAAAAGAAABAAAAAAABAAALAAAAAAAM"+

" AAABA AAAAA AFAAAM AAANAA AAAAAJ AAAOAAAPAAABAAAHAAAAAADFAAABAAABAAAA"+

"AAAHLLAAABLHAABA LBAAAAAAACAAAKAA AAAAAK AAACAA AAAAAEAAAGAAAFAAALAA"+

"AAAAAMAAABAAAAAA AHAABBAABCAAAAAA ABAABD AABEAA ABAAAHAAAAAACNAAABAA"+

"ABAAAAAAADBCBFLA AAAAAAACAAAKAAAA AAAGAA ABAAAA AAAIAAALAAAAAAAMAAAB"+

" AAAAAAADAAAMAAANAAAAAAABAABHAAAAAAACAABI";

private static final int codeLength=500;

@Override

public Class<?> loadClass(String name) throws ClassNotFoundException {

int i = 0, j = 0;

boolean firstNibble = false;

byte[] code = new byte[codeLength];

while (i < codeLength) {

while (byteCode.charAt(j) == ' ')

j++;

if (firstNibble = !firstNibble) {

code[i] = (byte) (alphabet.indexOf(byteCode.charAt(j++)) * 16);

} else {

code[i++] += alphabet.indexOf(byteCode.charAt(j++));

}

}

if (name.equals("ByteCodeTest")) {

return defineClass(name, code, 0, codeLength);

} else {

return findSystemClass(name);

}

}

public static void main(String[] args) throws Throwable{

Class<?> dateTest = new ByteCodeTestLoader().loadClass("ByteCodeTest");

//Method m = dateTest.getMethod("main", String[].class);

//m.invoke(null, (Object)null);

System.out.println(dateTest.newInstance());

}

}

Das Alphabet und der ByteCode sind in diesem Code getrennt. Natürlich könnten wir das Alphabet auch in den anderen String einbauen, soll hier aber der Übersicht dienen! Die Leerzeichen im String durfen nur mit der Leertaste gemacht sein, Tabulatoren dürfen nicht eingesetzt werden, da ja nur ‚ ‚ übersprungen werden!

Ist nun die Klasse geladen, wird mit der Methode „newInstance“ eine neue Instanz erzeugt. Da wir dann mittels System.out.println diese ausgeben, wird automatisch die toString() Methode von unserer Klasse aufgerufen und diese gibt ja „Manfred“ zurück!

Es ist auch möglich eine gewisse Methode mit Reflection aufzurufen, dafür habe ich den Code auskommentiert! Man kann auch diese Methode verwenden!

So, ich hoffe ich habe das halbwegs verstänglich erklärt! Feedback und Fragen erbeten!