Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Neues in Java 8 und Java 7
2 Fortgeschrittene String-Verarbeitung
3 Threads und nebenläufige Programmierung
4 Datenstrukturen und Algorithmen
5 Raum und Zeit
6 Dateien, Verzeichnisse und Dateizugriffe
7 Datenströme
8 Die eXtensible Markup Language (XML)
9 Dateiformate
10 Grafische Oberflächen mit Swing
11 Grafikprogrammierung
12 JavaFX
13 Netzwerkprogrammierung
14 Verteilte Programmierung mit RMI
15 RESTful und SOAP-Web-Services
16 Technologien für die Infrastruktur
17 Typen, Reflection und Annotationen
18 Dynamische Übersetzung und Skriptsprachen
19 Logging und Monitoring
20 Sicherheitskonzepte
21 Datenbankmanagement mit JDBC
22 Java Native Interface (JNI)
23 Dienstprogramme für die Java-Umgebung
Stichwortverzeichnis

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java SE 8 Standard-Bibliothek von Christian Ullenboom
Das Handbuch für Java-Entwickler
Buch: Java SE 8 Standard-Bibliothek

Java SE 8 Standard-Bibliothek
Pfeil 23 Dienstprogramme für die Java-Umgebung
Pfeil 23.1 Programme des JDK
Pfeil 23.2 Monitoringprogramme vom JDK
Pfeil 23.2.1 jps
Pfeil 23.2.2 jstat
Pfeil 23.2.3 jmap
Pfeil 23.2.4 jstack
Pfeil 23.2.5 jcmd
Pfeil 23.2.6 VisualVM
Pfeil 23.3 Programmieren mit der Tools-API
Pfeil 23.3.1 Java-Tools in Java implementiert
Pfeil 23.3.2 Tools aus eigenen Java-Programmen ansprechen
Pfeil 23.3.3 API-Dokumentation der Tools
Pfeil 23.3.4 Eigene Doclets
Pfeil 23.3.5 Auf den Compiler-AST einer Klasse zugreifen
Pfeil 23.4 Ant
Pfeil 23.4.1 Bezug und Installation von Ant
Pfeil 23.4.2 Das Build-Skript build.xml
Pfeil 23.4.3 Build den Build
Pfeil 23.4.4 Properties
Pfeil 23.4.5 Externe und vordefinierte Properties
Pfeil 23.4.6 Weitere Ant-Tasks
Pfeil 23.5 Disassembler, Decompiler und Obfuscator
Pfeil 23.5.1 Der Diassembler javap *
Pfeil 23.5.2 Decompiler
Pfeil 23.5.3 Obfuscatoren
Pfeil 23.6 Weitere Dienstprogramme
Pfeil 23.6.1 Sourcecode Beautifier
Pfeil 23.6.2 Java-Programme als Systemdienst ausführen
Pfeil 23.7 Zum Weiterlesen
 
Zum Seitenanfang

23.5Disassembler, Decompiler und Obfuscator Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Disassembler ist ein Werkzeug, das den Bytecode und die Struktur einer Java-Klassendatei anzeigt. Ein Decompiler geht einen Schritt weiter und versucht aus Klassendateien wieder Quellcodedateien zu gewinnen, die, wenn sie später compiliert werden, wieder den gleichen Bytecode ergeben würden. Genau diese »leichte« Lesbarkeit von Bytecode oder eine Decompliation möchte ein anderes Werkzeug, der Obfuscator, erschweren. Das Ziel von Obfuscation ist das möglichst effektive Verschleiern aller Bytecodeinformationen, sodass Menschen den Spaß am Bytecode verlieren und Decompiler bei ihrer Rücktransformation sogar aus dem Tritt kommen.

Das Bytecode Outline plugin for Eclipse unter http://andrei.gmxhome.de/bytecode/index.html zeigt den Bytecode einer Klassendatei. Es basiert auf dem Framework ASM (http://asm.ow2.org/).

 
Zum Seitenanfang

23.5.1Der Diassembler javap * Zur vorigen ÜberschriftZur nächsten Überschrift

Das JDK liefert im bin-Verzeichnis der Installation mit javap ein Werkzeug aus, das zwar nicht die Implementierung von Methoden hervorzaubert, aber immerhin die statische Struktur einer Klasse mit den Vererbungsbeziehungen, Variablen, Methoden, Parametern anzeigt.

In der einfachsten Variante wird javap mit dem Klassennamen aufgerufen. Wir nehmen im Folgenden an, dass javap im Suchpfad steht und wir uns auf der Kommandozeile direkt im bin-Verzeichnis vom JDK befinden. Wir setzen zuerst den Klassenpfad und geben anschließend als Kommandozeilenargument für javap die Klasse Quadrat aus dem ersten Kapitel an, die disassembliert werden soll:

$ javap -classpath 1_01_Intro Quadrat
Compiled from "Quadrat.java"
public class Quadrat extends java.lang.Object{
public Quadrat();
static int quadrat(int);
static void ausgabe(int);
public static void main(java.lang.String[]);
}

Abzulesen sind auch Dinge, die der Compiler automatisch generiert und die im Bytecode stehen, die wir aber im Allgemeinen nicht schreiben würden, etwa der voll qualifizierte Klassenname java.lang.Object oder java.lang.String, die Vererbungsbeziehung zu Object oder der automatisch angelegte Standard-Konstruktor.

Das Tool javap erlaubt noch mehr Parameter, die die Option -help anzeigt:

$ javap -help
Usage: javap <options> <classes>...

where options include:
-c Disassemble the code
-classpath <pathlist> Specify where to find user class files
-extdirs <dirs> Override location of installed extensions
-help Print this usage message
-J<flag> Pass <flag> directly to the runtime system
-l Print line number and local variable tables
-public Show only public classes and members
-protected Show protected/public classes and members
-package Show package/protected/public classes
and members (default)
-private Show all classes and members
-s Print internal type signatures
-bootclasspath <pathlist> Override location of class files loaded
by the bootstrap class loader
-verbose Print stack size, number of locals and args for methods
If verifying, print reasons for failure

[»]Hinweis

Auch der Java-Compiler kann mit dem Schalter -Xprint ähnliche Informationen ausgeben:

$ javac.exe -Xprint Quadrat.java
public class Quadrat {
public Quadrat();
static int quadrat(int n);
static void ausgabe(int n);
public static void main(java.lang.String[] args);
}

Java-Bytecode am Beispiel

Interessanter ist der Schalter -c, der den Bytecode der Methoden/Konstruktoren/Initialisierer anzeigt. Am Beispiel:

$ javap -c -classpath 1_01_Intro Quadrat
Compiled from "Quadrat.java"
public class Quadrat extends java.lang.Object{ …

Steigen wir nicht chronologisch ein, sondern bei der statischen Methode quadrat(int). Sie bekommt als Argument ein int und liefert es multipliziert mit sich selbst zurück:

static int quadrat(int);
Code:
0: iload_0
1: iload_0
2: imul
3: ireturn

Die Ausgabe macht die Stack-Natur der JVM sichtbar. Der Übergabeparameter n von quadrat(int n) steht auf Position 0 im Stack. Um das Quadrat zu bilden, wird der aktuelle Parameter zweimal mit iload_0 auf den Stapel gelegt und dann mit imul multipliziert. imul löscht die beiden Werte vom Stapel und ersetzt sie durch das Ergebnis der Multiplikation. ireturn liefert den obersten Stack-Wert als int zurück. Das Präfix »i« bei imul und ireturn zeigt, dass die Operationen auf Ganzzahlen durchgeführt werden. Andere Präfixe sind »b« für byte, »c« für char, »d« für double oder »a« für Objektreferenzen.

Kommen wir zur statischen main-Methode:

public static void main(java.lang.String[]);
Code:
0: iconst_4
1: invokestatic #59; //Method ausgabe:(I)V
4: return

Im Rumpf der main(String[])-Methode steht der Aufruf ausgabe(4), was im Bytecode dazu führt, dass mit iconst_4 der Wert 4 auf den Stack gelegt wird und dann invokestatic die Methode ausgabe(int) aufruft.

Der Standard-Konstruktor von Quadrat ruft lediglich den Standard-Konstruktor der Oberklasse Object auf:

public Quadrat();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return

Bei Konstruktoren und Objektmethoden ist automatisch an Position 0 im Stack die this-Referenz gesetzt, da die Aufrufer automatisch an Position 0 diese Referenz übergeben. (Bei statischen Methoden ist das nicht so.) Bei unserem Konstruktor Quadrat() setzt also aload_0 diese this-Referenz auf den Stack, damit der Konstruktor der Oberklasse, also Object aufgerufen werden kann. Konstruktor-Aufrufe werden im Bytecode mit invokespecial durchgeführt. Im Kern ist ein Konstruktor eine Methode mit dem speziellen Namen <init> und der Rückgabe void. Die Angabe #8 ist ein Verweis auf eine interne Tabelle, doch der Java-Kommentar macht den Eintrag für uns lesbar.

Am komplexesten ist die statische Methode ausgabe(int). Im Original sieht sie so aus:

String s;
int i;
for ( i = 1; i <= n; i = i + 1 ) {
s = "Quadrat(" + i + ") = " + quadrat(i);
System.out.println( s );
}

Parameter und lokale Variablen werden auf dem Stack gespeichert und haben intern nur Positionen:

Variable

Positionen der Parameter/Variablen

n

0

s

1

i

2

Tabelle 23.4Variablen im Beispiel und Stack-Positionen

Der erste Ausschnitt ohne Schleifenrumpf sieht so aus:

static void ausgabe(int);
Code:
0: iconst_1
1: istore_2
2: goto 44
5: ...
...
41: iinc 2, 1
44: iload_2
45: iload_0
46: if_icmple 5
49: return

Unsere for-Schleife beginnt mit 1, und so setzt iconst_1 den Wert 1 auf den Stack, und istore_2 speichert den Wert für die Variable i auf dem Stack. Da for-Schleifen kopfgesteuerte Schleifen sind, also erst testen, bevor sie den Rumpf durchlaufen, springt goto – im Bytecode gibt es diesen Sprungbefehl – über den Schleifenrumpf zum Test. iload_2 lädt die Variable i und vergleicht sie mit n, der Parametervariablen auf Position 0 auf dem Stack. Ist i < n, dann springt if_icmple (für engl. integer compare less equal) zur Position 5, was den Schleifenrumpf ausführt. In Zeile 41 inkrementiert iinc 2, 1 die Variable i (2 steht hier für den Index der Variablen, denn die Variable i steht auf Platz 2 des Stacks), und 1 ist das Inkrement.

Der Rumpf der Schleife besteht aus zwei Teilen: einmal aus dem Zusammenhängen der Strings und einmal aus der Ausgabe auf dem Bildschirm. Zur Konkatenation müssen wir uns zurückerinnern, dass das + bei Strings zu einer Folge von append(…)-Aufrufen auf ein StringBuilder (vor Java 5 StringBuffer) wird:

5: new #20; //class java/lang/StringBuilder
8: dup
9: ldc #22; //String Quadrat(
11: invokespecial #24; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
14: iload_2
15: invokevirtual #27; //Method java/lang/StringBuilder.append:(I)Ljava/lang/¿
StringBuilder;
18: ldc #31; //String ) =
20: invokevirtual #33; //Method java/lang/StringBuilder.append:(Ljava/lang/¿
String;)Ljava/lang/StringBuilder;
23: iload_2
24: invokestatic #36; //Method quadrat:(I)I
27: invokevirtual #27; //Method java/lang/StringBuilder.append:(I)Ljava/lang/¿
StringBuilder;
30: invokevirtual #38; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
33: astore_1

Nach dem Ablauf steht die Referenz auf das String-Objekt auf dem Stack, und astore_1 überträgt sie auf den Stack an Position 1, der für die String-Variable s reserviert war.

Es folgt die Ausgabe auf dem Bildschirm:

34: getstatic #42; //Field java/lang/System.out:Ljava/io/PrintStream;
37: aload_1
38: invokevirtual #48; //Method java/io/PrintStream.println:(Ljava/lang/String;)V

Hier greift getstatic auf die statische Variable System.out zu und setzt die Referenz auf den aktuellen PrintStream auf den Stack. Dann setzt aload_1 die Referenz vom String s auf den Stack, und invokevirtual ruft println(…) aus.

Dieses Beispiel gibt einen kleinen Einblick in den Bytecode von Java. Weitere Informationen gibt »The Java Virtual Machine Specification« und insbesondere das Kapitel »Compiling for the Java Virtual Machine« unter http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html. Normale Programmierer müssen Bytecode nicht lesen und schreiben können, aber das Wissen ist nützlich und auch nötig, wenn Bytecode selbst erstellt werden soll.

Interna

Wichtig zu wissen ist, dass das Programm im Bytecode keine strikte Ablaufanweisung für die JVM ist. Java-Bytecode ist nur das »Transportformat«, in dem Programmcode zur JVM gelangt. Die JVM wiederum liest den Bytecode ein und transformiert ihn in hochoptimierten Maschinencode. Eine Interpretation gibt es zwar noch am Anfang der Ausführung, aber nach ein paar Durchläufen beginnt HotSpot mit der Übersetzung in Maschinencode, und da fallen diverse Operationen aus dem Bytecode heraus.

 
Zum Seitenanfang

23.5.2Decompiler Zur vorigen ÜberschriftZur nächsten Überschrift

Der Java-Compiler erzeugt aus der Quellcodedatei eine Klassendatei, und der Decompiler dreht die Arbeitsweise um. Decompiler sind Werkzeuge zum Reverse Engineering, bei dem es darum geht, aus einer fertigen Software, die nur etwa in Form von JAR-Dateien vorliegt, die Java-Quellen und Ressourcen zurückzugewinnen. Decompiler gibt es für die verschiedenen Programmiersprachen, und Java gehört zu den Sprachen, bei denen die Zurückübersetzung einfacher ist als bei optimierten Maschinenprogrammen, die zum Beispiel ein C++-Compiler erzeugt. Der Grund ist, dass im Bytecode viele wertvolle Informationen enthalten sind, die im herkömmlichen Maschinencode nicht auftauchen. Darunter sind etwa Typinformationen oder Hinweise, ob ein Methodenaufruf virtuell ist oder nicht. Sie sind für die Java-Laufzeitumgebung wichtig und eine große Hilfe, wenn es darum geht, mit einem Decompiler verlorenen Quellcode wiederzubeleben oder an fehlende Informationen aus Paketen von Fremdherstellern zu gelangen.

Ein Decompiler liest die Klassendatei ein und beginnt mit der Analyse. Da der Bytecode gut dokumentiert ist, ist das Extrahieren von Variablen- oder Methodennamen einfach. Aus dem Java-Bytecode für eine Methode baut ein Decompiler einen Kontrollfluss-Graphen auf und versucht, Anweisungen und Ausdrücke zu erkennen, die bei der Übersetzung bestimmter Sprachkonstrukte entstanden sein müssten. Das ist eine nicht triviale Aufgabe und immer noch Gegenstand einiger Diplomarbeiten. Und da Variablennamen durch einen Obfuscator eventuell ungültig gemacht worden sind, muss ein guter Decompiler diese illegalen Bezeichnernamen korrigieren und weitere Tricksereien des Obfuscators rückgängig machen. Diese Umbenennung ändert den Algorithmus nicht, und ein Decompiler hat es bei dieser Art von Verschleierung einfach.

Ist das legal?

Wenn wir einen Decompiler auf fremden Programmcode loslassen, kann das ein rechtliches Problem darstellen. Das Reverse Engineering von vollständigen, unter Urheberschutz stehenden Anwendungen muss nicht unbedingt ein juristisches Nachspiel haben. Vielmehr beginnt die Straftat, wenn dieser zurückgewonnene Quelltext verändert und als Eigenleistung verkauft wird.

Da mittlerweile auch andere Compiler auf dem Markt sind, die Java-Bytecode erzeugen – etwa aus EIFFEL-Programmen oder aus diversen LISP-Dialekten –, ist über den Umweg Compiler/Klassendatei/Decompiler ein Crosscompiling denkbar. Hier sind jedoch einige Einschränkungen bezüglich der auf dem Markt befindlichen Decompiler erkennbar. Denn fremde Compiler, die Java-Bytecode erstellen, haben andere Techniken, die der Decompiler dann nicht immer passend übersetzen kann.

Java Decompiler Project (JD) und Alternativen

Das Angebot an leistungsstarken Decompilatoren ist sehr übersichtlich. Das beste Tool (aber auch nicht ganz fehlerfrei) ist zurzeit JD (http://jd.benow.ca/). Das frei verfügbare – aber nicht quelloffene – Programm ist als Bibliothek JD-Core, als alleinstehende grafische Anwendung JD-GUI und Eclipse-Plugin[ 155 ](Für NetBeans und IntelliJ IDEA gibt es Initiativen von anderer Seite.)JD-Eclipse verfügbar. JD selbst ist in C++ geschrieben und benötigt daher keine JVM. JD verarbeitet den Bytecode verschiedener Compiler, wobei die Ausgaben der Standard-Compiler vom JDK 1.1 bis JDK 7 selbstverständlich mit in der Liste sind, genauso wie vom Eclipse-Compiler. (Die Unterscheidung ist nicht ganz uninteressant, da die Compiler sich in machen Details in der Bytecode-Abbildung doch unterscheiden.) JD-GUI ist für die Plattformen Windows, Linux und Mac unter http://jd.benow.ca/#jd-gui-download verfügbar und bietet neben dem Decompilieren einzelner Java-Klassen und ganzer Java-Archive eine angenehme Quellcodedarstellung mit farblicher Unterlegung und Drag & Drop.

Sehr lange war ein anderer Decompiler namens Jad die Referenz. Doch nur von 1997 bis 2001 hat Pavel Kouznetsov das Kommandozeilenprogramm in C++ entwickelt, und dann hat er 2009 seine Webseite vom Netz genommen. Eine Privatperson hat jedoch die Webseite gespiegelt, und unter http://www.varaneckas.com/jad lebt das Projekt (auf unbestimmte Zeit) weiter. Wer Projekte bis Java 1.4 decompilieren möchte, ist mit dem Tool sehr gut bedient. Für neuere Projekte hilft JadRetro (http://jadretro.sourceforge.net/) noch ein wenig nach, indem es Java 5-Bytecode auf Java 1.4 anpasst und kleine Änderungen im Bytecode durchführt. FrontEnd Plus ist eine grafische Oberfläche für Jad, doch auch sie ist aus dem Internet verschwunden, seitdem es Jad nicht mehr offiziell gibt.

Decompilation der Quadrat-Klasse mit JD-GUI

Abbildung 23.7Decompilation der Quadrat-Klasse mit JD-GUI

 
Zum Seitenanfang

23.5.3Obfuscatoren Zur vorigen ÜberschriftZur nächsten Überschrift

Die Existenz eines Disassemblers wie javap und eines Decompilers wie JD verunsichert Hersteller, da diese in der Regel nicht möchten, dass ihr Quellcode untersucht wird und vielleicht neu zusammengesetzt den Weg zurück in den Markt findet. Ein Obfuscator ist ein Werkzeug, das diverse Transformationen am Bytecode vornimmt, um eine einfache Zurückverwandlung zu verhindern. Es bieten sich eine Reihe von Versteck-Aktionen an:

  • das Löschen von Debug-Informationen

  • das Umbenennen aller Bezeichner für Pakete, Klassen, Methoden und Variablen. Klassen heißen dann zum Beispiel C1, C2, … und Methoden m1, m2, …

  • das Benennen von Bezeichnern mit Java-Schlüsselwörtern oder nicht in Java erlaubten Zeichenfolgen

  • das Verlegen von Anweisungen oder Anweisungsfolgen in Methoden (also die Umkehroperation zum Methoden-Inline)

  • das Berechnen von Konstanten, sodass etwa 1 in println(1); dynamisch durch int abcde = 2; println(abcde-1); berechnet wird

  • das Einführen leerer Anweisungen, die zur Laufzeit wegoptimiert werden, aber den Bytecode füllen, etwa if ( 1 != 1) { int a = 123334; }

  • das dynamische Entschlüsseln von Strings, sodass etwa aus println("google") ein println($("tokepa")) wird; die Methode $(String) entschlüsselt »tokepa« zurück zu »google«.

  • das Umsortieren oder das Einfügen unsinniger Bytecode-Folgen, sodass sie nicht mehr dem Muster entsprechen, die ein Java-Compiler erzeugt, und auch nicht so einfach vom Decompiler wiedererkannt werden.[ 156 ](Ideen beschreibt das Paper »Protection Methods of Java Bytecode« unter http://tutego.de/go/ bytecodeprotection.)

Effektivität

Ein Obfuscator verhindert das einfache Zurückverwandeln in Java-Quellcode, kann aber niemals verhindern, dass javap den Bytecode offenbart. Ein Obfuscator kann somit nur Dinge möglichst unleserlich machen, aber wirklich geschützt ist das Programm nicht. Dennoch lohnt sich der Einsatz eines Obfuscators, denn er schreckt ab und verdirbt die Lust am Schnüffeln, denn Klassennamen von C1 bis C3484 mit Methoden von m1 bis m12743 verraten erst einmal überhaupt nichts. Noch besserer Schutz ist nur mit einem eigenen Klassenlader zu erreichen, der verschlüsselten Bytecode einliest. Hier muss dann die JVM gepatcht werden, um an diese Klassendateien zu kommen. Wirklich unmöglich ist das allerdings auch nicht.

Das Obfuscator-Programm ProGuard

ProGuard (http://proguard.sourceforge.net/) ist ein Open-Source-Projekt unter der GPL-Lizenz,[ 157 ](Obwohl ProGuard selbst unter der GPL steht, darf die Software auch auf Software angewendet werden, die nicht unter der GPL steht.) das Java-Klassen und JAR-Archive verschleiert. Neben der Verschleierung hat es den Nebeneffekt, dass die Klassendateien kleiner werden, was insbesondere für Applets und Midlets (mobile Anwendungen) interessant ist; auch für Android ist es standardmäßig Teil des Android Build-Systems. Die Software lässt sich über eine Swing-Oberfläche oder über die Kommandozeile bedienen oder aber auch über ein Ant-Skript steuern. Die Webseite nennt ein Beispiel, das alle Typen bis auf Applets eines Java-Archivs berücksichtigt. Auf der Kommandozeile ist Folgendes anzugeben:

$ java -jar proguard.jar -injars in.jar -outjars out.jar ¿
-libraryjars $JAVA_HOME/lib/rt.jarkeep public class * extends java.applet.Applet

Einige Dinge dürfen nicht umbenannt werden, sodass sie von ProGuard ausgenommen werden müssen. Das betrifft etwa bestimmte Klassen, die dynamisch geladen werden, wie Plugins, wo es auf den genauen Paket- und Klassennamen ankommt.

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java SE 8 Standard-Bibliothek Java SE 8 Standard-Bibliothek
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel
Java ist auch eine Insel


Zum Rheinwerk-Shop: Professionell entwickeln mit Java EE 8
Professionell entwickeln mit Java EE 8


Zum Rheinwerk-Shop: Besser coden
Besser coden


Zum Rheinwerk-Shop: Entwurfsmuster
Entwurfsmuster


Zum Rheinwerk-Shop: IT-Projektmanagement
IT-Projektmanagement


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2018
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern