Kurs:Wie funktioniert eigentlich ein Computer/Hauptthemen/Maschinencode von Programmen untersuchen

Zurück zur Übersicht

Zunächst stellen wir das Binärsystem und das Hexadezimalsystem vor und erklären, wie man Zahlen von einem System zu einem anderen umwandeln kann. Dannach stellen wir die Zeichencodierung ASCII vor und vergleichen die Dateigröße eines Maschincodes mit der einer Textdatei, die den Maschinencode in Hexadezimalschreibweise gespeichert hat. Zudem wird gezeigt, wie man es in Emacs umsetzten kann.

Dualsystem|Binärsystem

Bearbeiten

Das Binär- oder Dualstystem besteht nur aus zwei Ziffern; 0 und 1. Um das System zu verdeutlichen, haben wir diese Tabelle erstellt:

27 26 25 24 23 22 21 20
128 64 32 16 8 4 2 1 Zahl im Dezimalsystem
0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 1 0 32 + 2 = 34
1 0 0 0 1 0 0 1 128 + 8 + 1 = 137
1 1 1 1 1 1 1 1 Σ = 255

Um zwischen den verschiedenen Zahlensystemen unterscheiden zu können, schreibt man hinter die Zahl einen Index. Im Binärsystem 2 , im Dezimalsystem 10 u.s.w. Man liest es dann als "10 zur Basis 2" .

Hexadezimalsystem

Bearbeiten
Hexadezimalziffern, binär und dezimal:
Hex. Dualsystem Dez.
0 0 0 0 0 00
1 0 0 0 1 01
2 0 0 1 0 02
3 0 0 1 1 03
4 0 1 0 0 04
5 0 1 0 1 05
6 0 1 1 0 06
7 0 1 1 1 07
8 1 0 0 0 08
9 1 0 0 1 09
A 1 0 1 0 10
B 1 0 1 1 11
C 1 1 0 0 12
D 1 1 0 1 13
E 1 1 1 0 14
F 1 1 1 1 15


Im Hexadezimalsystem werden Zahlen in einem Stellenwertsystem zur Basis 16 dargestellt.
Es werden die Zahlen von 0 bis 9 verwendet und zusätzlich noch mit den Buchstaben von A bis F auf insgesamt 16 Zeichen erweitert. Wie man in der Tabelle sehen kann, steht das Zeichen einer einzelnen Zahl im Hexadezimalystem für die dementsprechend geleichwertige Zahl im Dezimalsystem. Die Buchstaben stehen deshalb für die darauf folgenden Zahlen.
Um eine Dezimalzahl in eine Hexadezimalzahl umzuwandel, kann man wie folgt vorgehen:
Falls die Zahl x nicht größer als 255 ist, hat die Hexadezimalzahl maximal zwei Stellen. Die vordere nimmt den Wert von (int) x/16 , die hintere von x%16 an.
Beispiel:

    x=9010
(int) 90/16= 5
90%16 = 10 = a
--> 9010 = 5a16

Um einen Binärcode in einen Hexadezimalcode umzuwandeln, teilt man den Binärcode in Viererpäckchen, von denen man jeweils die Hexadezimalzahlen bestimmen kann.
0001 11102=1E16

ASCII (American Standard Code for Information Interchange) ist eine 7bit Zeichencodierung und umfasst somit 128 Zeichen.

 

Da 128 Zeichen aber nicht reichten, wurden noch neue Codierungen wie der ANSI-CODE oder der UNI-CODE entwickelt.

Dateigrößen

Bearbeiten

Wir haben bereits festgestellt, dass eine kompilierte Datei (Maschinencode) deutlich größer ist als der Quelltext (...). Aber kann man auch einen Unterschied zwischen der kompilierten Datei und dem als Text gespeicherten Maschinencode (in Hex-Schreibweise) der Datei feststellen?

Um dies festzustellen erstellen wir die dementsprechenden Dateien und vergleichen sie:

 

Wie man sehen kann ist die Textdatei erheblich groeßer.

Erklärung:

Um ein Byte Maschinencode zu speichern, braucht man logischerweise ein Byte Speicher (z.B. 01010011)

Wenn man sich jetzt diesen Code als Hexadezimalcode anzeigen lässt, erhält man die Ausgabe (53). Um diese Ausgabe in einer Textdatei(ASCII-Codierung) zu speichern, müssen wir also zwei Zeichen speichern, einmal die "5" und einmal die "3". Da wir den ASCII-Code benutzten, benötigen wir für jedes Zeichen 1 Byte, insgesamt also 2.

Um den Maschinencode in Hexadezimaldarstellung als Textdatei zu speichern benötigen wir also mehr Speicherplatz, als zum Speichern des Maschincodes als ausführbare Datei.

Hexadezimaldarstellung(in Emacs)

Bearbeiten

Wenn man sich eine kompilierte Datei in Hexadezimaldarstellung anzeigen lassen möchte, muss man die entsprechende Datei zunächst öffnen (Strg-X Strg-F). Dannach kann man durch den Befehl (M-x hexl-mode) die Darstellung in eine Hexadezimaldarstellung ändern.

Aufgabe 1 - Thema : Unterschiedliche Ausgabe bei gleicher Dateigröße

Bearbeiten

Aufgabenstellung

Bearbeiten

Erstellen von zwei Programmen, die identisch sind bis auf die Ausgabe. Bei einem Programm wird noch ein Ausrufezeichen zusätzlich ausgegeben:

hello.c

#include<stdio.h>

int main()
{
  printf("Hallo Welt");
}


hello2.c


#include<stdio.h>

int main()
{
  printf("Hallo Welt!");
}

Wenn man die Größe der kompilierten Dateien vergleicht, sieht man dass sie beide 5137 Bytes groß sind, obwohl sie eine andere Ausgabe haben:

 

Dies liegt daran, dass der Befehl

 printf()

beim Aufruf einen bestimmten Speicherbereich reserviert. Fügen wir nun das Ausrufezeichen zum auszugebenden Text hinzu, wird dieser in den zuvor leeren, aber reservierten Bereich geschrieben.

Ist es dann möglich die Ausgabe des Programm zu verändern nachdem es kompiliert wurde?

Zuerst öffnen wir die beiden kompilierten Dateien mit Emacs im Hexl-Mode.

Hinweis: Um in Emacs in die Hexadezimal-Anzeige zu kommen, muss man M-X drücken und dann "hexl-mode" eingeben. Im Hexl-Mode wird in der ersten Spalte die Zeilennummerierung in Hexadezimal-Schreibweise angezeigt. In den mittleren 8 Spalten sind ist Inhalt des Programms in Hexadezimal-Schreibweise und in der rechten Spalte als ASCII-Code interpretiert.

In dem Programm, das das Ausrufezeichen ausgibt suchen wir nach einer 2116. In der ASCII-Tabelle entspricht das einem Ausrufezeichen. Es gibt nur wenige Ergebnisse und wir nehmen die Stelle wo in der rechten Spalte "Hallo Welt!" steht. Parallel (im der anderen Datei) wird die selbe Stelle anhand der Zeilennummer gesucht.

 

Man kann erkennen, dass anstelle der 2116 hier eine 0016 steht. Diese Stelle ist also der Grund für die unterschiedliche Ausgabe. Wir können also an diese Stelle ein beliebiges Zeichen der ASCII-Tabelle in hexadezimaler Schreibweise setzen, wodurch sich die Ausgabe dementsprechend verändert.


Aufgabe 2 - Thema : Finden eines Additionsbefehls im kompilierten Programm

Bearbeiten

Aufgabenstellung

Bearbeiten

Wir haben ein Programm, welches zwei Zahlen miteinander addiert. Nun wollen wir herausfinden, an welcher Stelle der Befehl für die Addition im kompilierten Programm steht.

Lösungsansätze

Bearbeiten

Unsere Idee war es, zwei möglichst einfache Programme zu schreiben. Der einzige Unterschied sollte sein, dass das eine Programm 2 Zahlen addiert und das andere die gleichen Zahlen subtrahiert. Im Quellcode können wir den Unterschied sehen, nun wollen wir wissen, wo in der "Maschienensprache" der Unterschied liegt. Da es uns nicht möglich war, den binären Code zu verändern und dieses auch zu unübersichtlich gewesen wäre fiel diese Option weg. Dann wollten wir uns die Hexadezimalcodes der beiden Programme 'anschauen', vergleichen und so den Unterschied -also die Stelle, an der die beiden Zahlen verrechnet werden- finden.

Beispiellösung

Bearbeiten

Zu den Beispiellösungen:

Alle Befehle für Ubuntu bzw. Emacs werden mit'-->' gekennzeichnet.

1. Schreibe zwei Programme , 'plus.c' [1] und 'minus.c' .   2. Beide Programme kompilieren.

--> gcc 'plus.c' -o 'plus'
--> gcc 'minus.c' -o 'minus'

3. --> Objdump -d 'plus' > 'plusausgabe' [2]

--> Objdump -d 'minus' > 'minusausgabe'

4. --> diff 'plusausgabe' 'minusausgabe' [3]

Alternative: --> diff -y 'plusausgabe' 'minusaugabe' > 'vergleich' [4]

 

Fußnoten:

  1. Alle Namen die in einfachen Anführungsstrichen stehen sind beliebig wählbar
  2. Dieser Befehl zeigt das Programm in den einelnen Hexadezimalcodes und die jeweiligen Opcodes an.
  3. Dieser Befehl gibt einem den genauen Unterschied mit Zeilen angabe aus.
  4. Dieser Befehl erzeugt eine neue Datei ( in diesem Fall namens 'vergleich') in der die beiden Programme 'plusausgabe' und 'minusausgabe' nebeneinander gestellt werden und verglichen werden können. Die Datei kann einfach mit emacs geöffnet werden


Aufgabe 3: Änderung eines bestimmten Prozessorbefehls in einem kompilierten Programm

Bearbeiten

Aufgabenstellung

Bearbeiten

Es sind zwei Programme gegeben:Ein Programm addiert zwei Zahlen, das andere subtrahiert diese voneinander. Aus Aufgabe 2 ist bekannt, an welchen Stellen sich die kompilierten Programme unterscheiden. Aufgabe ist es eines der Programm so zu verändern, dass es sich so wie das andere verhält.

add.c

 #include<stdio.h>

int main(int argc, char** argv)
{
 if (argc != 3)
 {
  printf("please pass two integer to the program!");
  return -1;
 }
 int a;
 sscanf(argv[1],"%d",&a);
  
 int b;
 sscanf(argv[2],"%d",&b);
  
 int c;
 c = a + b;   //Unterschied!!
 printf("%d\n",c);
 return 0;
}


sub.c

 #include<stdio.h>

 int main(int argc, char** argv)
 {
 if (argc != 3)
 {
  printf("please pass two integer to the program!");
  return -1;
 }
 int a;
 sscanf(argv[1],"%d",&a);
  
 int b;
 sscanf(argv[2],"%d",&b);
  
 int c;
 c = a - b;  //Unterschied!!
 printf("%d\n",c);
 return 0;
}

Wie in Aufgabe 2 vergleichen wir die Objectdumps der beiden kompilierten Programme.

 

Wir benutzen einen weiteren Konsolenbefehl:"xxd". Dieser Befehl zeigt das kompilierte Programm ebenfalls in hexadezimaler Schreibweise an, setzt allerdings keine Kommentare dazu. Auch hier werden beide Ausgaben mit "diff" verglichen.

 

Der xxd-Vergleich liefert drei Stellen, die unterschiedlich sind. Werden diese Stellen in dem einen Programm durch die entsprechenden Hex-Zahlen des anderen ersetzt, so haben beide Programme beim Ausführen denselben Effekt.

Nun ist die Frage, ob wirklich alle 3 Stellen geändert werden müssen, damit beide Programme die Zahlen addieren bzw. subtrahieren. Aus dem Objectdump-Vergleich lässt sich schließen, dass der Unterschied zwischen dem Addieren und dem Subtrahieren in nur drei Hex-Zahlen besteht. Man muss hier allerdings beachten, dass der Objectdump die Zahlen nicht immer in der richtigen Reihenfolge anzeigt. Wir merken uns also nur, dass wir nach drei geänderten Hex-zahlen suchen und schauen uns nun den xxd-Vergleich an. Im mittleren Unterschied kommen genau diese Veränderungen vor. Wenn man nun in einer der Programme genau diese drei Zahlen durch die des anderen Programms ersetzt, so erreichen wir dasselbe Ergebnis: Im Screenshot sieht man das Ergebnis des ursprüngliche Programms vorher (7) und nacher(13).

 

Wenn man die Veränderungen ins binäre System übersetzt, so lässt sich erkennen, dass gerade mal 5 Bits geändert werden müssen um die Funktion des Programmes so zu beeinflussen.