Wie manipuliert man Software? Und wie sieht so eine manipulierte Software aus?
-
Ich lesen ständig von irgendwelchen Backdoors, Exploit usw. Leider reichen meine Kenntnisse nicht aus, um so etwas zu analysieren.
Ich würde aber trotzdem gerne wissen, wie so etwas theoretisch aussehen könnte.
Hier habe ich ein einfaches Programm geschrieben.
Machen wir das ganze etwas spannend und stellen uns vor, dass zahl1+zahl die Abschusscodes für die neuen russischen Atomraketen
ergeben. Jetzt muss der NSA-SuperHacker dies verhindern und das Programm so manipulieren, dass zahl1 mit zahl2 subtrahiert wird.
Der Russe darf es aber nicht bemerken.Hoffentlich versteht ihr was ich meine.^^ Ich würde nur gerne wissen, wie so ne manipulierte Software aussieht und wie ihr da theoretisch vorgeht.
Ich brauche keine 1:1 Anleitung. Nur eine triviale Lösung, damit ich mir es besser vorstellen kann.Danke für eure Antworten
#include <iostream> using namespace std; int main() { int zahl1, zahl2; cout << "Bitte Zahl 1 eingeben!" << endl; cin >> zahl1; cout << "Bitte Zahl 2 eingeben!" << endl; cin >> zahl2"; cout << "Ihr Ergebnis" << zahl1+zahl2 << endl; return 0; }
An die anderen: Nein, ich möchte kein Hacker werden und Ja, ich habe den Klassiker schon gelesen
-
Das Beispiel ist Blödsinn, daher kann man anhand dieses Beispiels auch nichts erklären.
(Genaugenommen ist die Frage schon Blödsinn, aber naja.)MixxerMarvin schrieb:
An die anderen: Nein, ich möchte kein Hacker werden und Ja, ich habe den Klassiker schon gelesen
Hast du auch die Antworten zu "F: Wirst du mir beibringen, wie man hackt?" gelesen?
-
MixxerMarvin schrieb:
Hoffentlich versteht ihr was ich meine.^^
Nee. Oder doch. Auf jeden Fall stellst du dir das alles viel zu einfach vor und in der Form macht deine Frage keinen Sinn.
-
Als starke Vereinfachung:
vorher:
mov ebx, zahl1 mov eax, zahl2 add eax, ebx
nachher:
mov ebx, zahl1 mov eax, zahl2 sub eax, ebx
Das ganze musst du dir jetzt nur noch als Opcodes vorstellen die mit einem Hexeditor verändert wurden und mit Speicheradressen arbeiten.
Der Russe darf es aber nicht bemerken.
Wenn er seine SW anhand von Prüfsummen überwacht, dann wird er das.
Und wenn er das Rechenergebnis überprüft, dann auch.
-
Um zu verstehen, wie ein Programm funktioniert, hängt man sich am besten mit einem Debugger dran.
Du siehst dann genau, was das Programm macht. Natürlich siehst du nicht deinen C++ Code, denn der wurde ja in ein ausführbares Programm übersetzt. Aber die Debugger sind schlau genug, um aus dem Binärformat dir den Assembercode anzuzeigen.
Du siehst also was dein Programm auf Assembler Ebene so macht. Du kannst schauen, welche Variablen wie gesetzt werden, welche Register verwendet werden, usw...
Und wenn du die Addition auf eine Subtraktion ändern willst, musst du zuerst mal wissen, wo diese stattfindet. Dann kannst du einfach den Assemberbefehl austauschen.Jetzt in der Praxis:
Ich hab dein Programm ein bisschen angepasst, damit ich dir das besser erklären kann:
#include <iostream> using namespace std; int main() { int zahl1=0xAABBCCDD, zahl2=0xFFAABBCC; // <-- Variablen mit eindeutigen Werten initialisieren cout << "Bitte Zahl 1 eingeben!" << endl; cin >> zahl1; cout << "Bitte Zahl 2 eingeben!" << endl; cin >> zahl2; cout << "Ihr Ergebnis" << zahl1+zahl2 << endl; return 0; }
Aus dem C++ Code hab ich nun ein ausführbares Programm erstellt.
Mit einem Debugger (ich verwende gdb als Debugger) häng ich mich nun bei dem erstellten Programm dran, um zu sehen, wie es funktioniert.
Als erstes schau ich mir den Assemblercode von main an.
Zuerst schau ich mir an, wo die beiden Variablen "zahl1" und "zahl2" zu finden sind. Du erkennst das typische Hexmuster wieder, mit dem zahl1 und zahl2 initialisiert werden.
zahl1 liegt also an der Speicheradresse esp+0x18, zahl2 bei esp+0x1c.
esp ist der Stackpointer, Stack kennst du ja sicher, dort werden die lokalen Funktionsvariablen und Übergabeparameter für Funktionen abgelegt.0x080486ae <+10>: mov DWORD PTR [esp+0x18],0xaabbccdd 0x080486b6 <+18>: mov DWORD PTR [esp+0x1c],0xffaabbcc
Mit dem Wissen über die Adressen suchen wir weiter. Irgendwo müssen die beiden Variablen ja addiert werden.
Man möchte ein add zahl1,zahl2 erwarten. Der Compiler hat sich für eine etwas optimiertere Variante zum Addieren entschieden:0x0804872e <+138>: mov edx,DWORD PTR [esp+0x18] 0x08048732 <+142>: mov eax,DWORD PTR [esp+0x1c] 0x08048736 <+146>: lea ebx,[edx+eax*1]
Es wird also zahl1 ins Register edx kopiert, zahl2 nach eax.
Der Befehlt lea addiert nun die beiden Register und schreibt das Ergebnis ins Register ebx.Ich hab 1 für zahl1 eingegeben, und 2 für zahl2.
Ein Blick auf die Register vor und nach dem lea Befehl bestätigt die Vermutung, dass das die richtige Stelle ist:Davor:
eax 0x2 2 ecx 0xbffff2a8 -1073745240 edx 0x1 1 ebx 0x3bdff4 3923956
Danach: ebx=3
eax 0x2 2 ecx 0xbffff2a8 -1073745240 edx 0x1 1 ebx 0x3 3
Hier wäre also der richtige Ort, um einzugreifen.
Man könnte das lea gegen einen anderen Befehl austauschen, sprich, den Addierbefehl gegen einen Substrahierbefehl zu tauschen.Es gibt Debugger, die einem da ziemlich unter die Arme greifen, OllyDbg ist ein Programm, wo man recht schnell den Assembercode manipulieren kann und das Ergebnis dann auch wieder als Programm abspeichern kann.
Das war mal ein kleine Einführung in das Thema.
Wenn dich das interessiert, installiere dir OllyDbg oder gdb, und kauf dir ein gutes Buch, denn ohne das nötige Hintergrundwissen kommt man nicht weit.
Als Buch empfehle ich "Hacking: The Art of Exploitation".Hier der Assemblercode der gesamten main Funktion:
0x080486a4 <+0>: push ebp 0x080486a5 <+1>: mov ebp,esp 0x080486a7 <+3>: push ebx 0x080486a8 <+4>: and esp,0xfffffff0 0x080486ab <+7>: sub esp,0x20 0x080486ae <+10>: mov DWORD PTR [esp+0x18],0xaabbccdd 0x080486b6 <+18>: mov DWORD PTR [esp+0x1c],0xffaabbcc 0x080486be <+26>: mov DWORD PTR [esp+0x4],0x80488a0 0x080486c6 <+34>: mov DWORD PTR [esp],0x804a0e0 0x080486cd <+41>: call 0x80485b0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x080486d2 <+46>: mov DWORD PTR [esp+0x4],0x80485e0 0x080486da <+54>: mov DWORD PTR [esp],eax 0x080486dd <+57>: call 0x80485d0 <_ZNSolsEPFRSoS_E@plt> 0x080486e2 <+62>: lea eax,[esp+0x18] 0x080486e6 <+66>: mov DWORD PTR [esp+0x4],eax 0x080486ea <+70>: mov DWORD PTR [esp],0x804a040 0x080486f1 <+77>: call 0x80485c0 <_ZNSirsERi@plt> 0x080486f6 <+82>: mov DWORD PTR [esp+0x4],0x80488b7 0x080486fe <+90>: mov DWORD PTR [esp],0x804a0e0 0x08048705 <+97>: call 0x80485b0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0804870a <+102>: mov DWORD PTR [esp+0x4],0x80485e0 0x08048712 <+110>: mov DWORD PTR [esp],eax 0x08048715 <+113>: call 0x80485d0 <_ZNSolsEPFRSoS_E@plt> 0x0804871a <+118>: lea eax,[esp+0x1c] 0x0804871e <+122>: mov DWORD PTR [esp+0x4],eax 0x08048722 <+126>: mov DWORD PTR [esp],0x804a040 0x08048729 <+133>: call 0x80485c0 <_ZNSirsERi@plt> 0x0804872e <+138>: mov edx,DWORD PTR [esp+0x18] 0x08048732 <+142>: mov eax,DWORD PTR [esp+0x1c] 0x08048736 <+146>: lea ebx,[edx+eax*1] => 0x08048739 <+149>: mov DWORD PTR [esp+0x4],0x80488ce 0x08048741 <+157>: mov DWORD PTR [esp],0x804a0e0 0x08048748 <+164>: call 0x80485b0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0804874d <+169>: mov DWORD PTR [esp+0x4],ebx 0x08048751 <+173>: mov DWORD PTR [esp],eax 0x08048754 <+176>: call 0x8048550 <_ZNSolsEi@plt> 0x08048759 <+181>: mov DWORD PTR [esp+0x4],0x80485e0 0x08048761 <+189>: mov DWORD PTR [esp],eax 0x08048764 <+192>: call 0x80485d0 <_ZNSolsEPFRSoS_E@plt> 0x08048769 <+197>: mov eax,0x0 0x0804876e <+202>: mov ebx,DWORD PTR [ebp-0x4] 0x08048771 <+205>: leave 0x08048772 <+206>: ret
-
Hallo,
zum Thema findest du unter diesem Link Anleitungen. Vor allem findet man eine gute Einführung in den beliebten Debugger "OllyDbg".
http://thelegendofrandom.com/blog/sample-pageAber parallel dazu wirst du dich auch mit Assemblerprogrammierung, der x86 Architektur und dem Grundprinzip von Betriebssytemen auseinandersetzen müssen.