[X] Reverse Engineering: Patching
-
Hallo,
Ja sicher, habe nur nicht alles in einem Ordner gehabt und daher ist is es alles bisschen chaotisch geworden.
MFG winexec*
-
Hier eine neuere Version: Ist mir hier lieber, habe alles in einer Textdatei, da sieht das so hingeschmissen aus:
Reverse Engineering: Patchen
Inhalt
1. Benötigtes
1.1 OllyDbg
1.2 Peid
2. Introduction
3. Materie
3.1 Beispiel: Fehlerkorrektur
3.2 If-else-Bedingung aushebeln
3.3 Schleifenbedingung korrigieren
3.4 Programmstellen indizieren
4. Kurzreferenz: Hexbefehle
5. Nachwort1 Benötigtes
In diesem Artikel verwende ich Disassembler/Debugger, um Programme zu analysieren. Diese sind hier erhältlich:
-OllyDBG http://www.ollydbg.de/
-Peid http://peid.has.it/Ich informiere Sie kurz über diese Produkte.
1.1 OllyDbg
OllyDbg ist ein von Oleh Yuschuk entwickelter 32-Bit Debugger für Windows Betriebssysteme. Hauptsächlich wird OllyDbg zur binären Codeanalyse verwendet werden.
OllyDbg bietet folgende Features:
-Debugging von Multithread Programmen
-Anhängen an laufende Prozesse
-Konfigurierbarer Disassembler mit Unterstützung der Formate MASM und IDEAL
-MMX, 3DNow, SSE, ASCII und UNICODE Unterstützung
-Hardware- und Software-Breakpoints
-Suchen über Speicherbereiche
-Modifikation von Speicherbereichen on-the-fly1.2 Peid
Peid erkennt die meisten bekannten Packers, Cryptors und Compiler und bietet diese Features:
-Einfach zu handhabende GUI.
-Shell Eingliederung, Kommandozeilenunterstützung.
-Task viewer und controller.
-Hex Viewer2 Introduction
Patches sind Programme, die andere Programme in der Struktur abändern. Sie "patchen" mit Hilfe vom hexadezimalen Zahlensystem bestimmte Bytes in der [ich nehme an] Executable.
Es sei gesagt, dass Sie sich hiermit auf dem Gebiet des Reverse Engineering bewegen. In der Internetkultur hat sich Reverse Engineering zu einer Sportart entwickelt. Siehe Hackits/Crackits.
Mehr dazu hier: http://en.wikipedia.org/wiki/Reverse_engineering
Patchen ist nicht immer das erweitern, sondern auch das reduzieren bzw. ausbessern von Code.
Man springt relativ vom Anfang der Datei die Entfernung zu den gesuchten Variablen/Codestücken. Daher benötigen wir den Offset. Der Patch selber ist schnell hergestellt, solange es nur kleine Ausbesserungen sind; die Suche nach dem richtigen Stück Code aber, welches wir bearbeiten wollen oft nicht.
//Tip: Protokollieren Sie alle Daten. Sie werden diese im Patch benötigen.
3 Materie
In diesem Artikel zeige ich drei Anwendungsmöglichkeiten. Das erste Problem dient als Einführung.
Info:
Ich drücke hier ausdrücklich aus, dass sich Offsets und Programmadressen je nach dem Compiler, mit dem das Programm kompiliert wurde, richten. Ich verwende hier CodeBlocks v1.0.
Außerdem sollte sich jeder im klaren sein, dass der Herausgeber des Programmes das Reverse Engineering erlaubt haben muss. Ich muss nicht sagen, dass es strafbar ist fremde Software durch RE zu "klauen".
3.1 Beispiel: Fehlerkorrektur
Als Einführung in das Thema soll eine Fehlerkorrektur mit einer kleinen Story dienen.
Dazu nehmen Sie bitte diesen Sourcecode als Beispiel:
//prim.c //--------- #include <stdio.h> int main(){ char *primzahlen[15] = {"2", "3", "5", "7", "11", "12", "13", "17", "19", "23", "29", "31", "37", "41", "43"}; // 12 ist keine Primzahl for(int i = 0; i < 15; i++){ printf("%s ",primzahlen[i]); } }
Nehmen wir an, eine Array gefüllt mit Primzahlen wäre in einer Funktion, die
zu einem Programm gehört, dass wir für eine Firma geschrieben haben.Nun braucht dieses Programm eine Berechnung der Primzahlen. Denken wir uns einfach es ist eine Datenbank und ein Angestellter, der einigermaßen aufmerksam ist, sieht nun dort eine 12: Er ist entsetzt.
Nun läuft er schnell zum Chef und dieser ruft uns gleich, mit leicht zorniger Stimme, an. Er verlangt das wir das Problem auf der Stelle beheben sonst...
Wir brauchten eine Hintergrundgeschichte. Nun haben wir sie.
Also haben wir uns noch einmal den Quellcode angeguckt. Und da war diese 12.
Es war die Einzige falsche Primzahl in diesem Array[Eigentlich kann man diese auch berechnen, aber mir ist kein besseres Problem eingefallen].Nun patchen wir diese 12, die niemand haben möchte.
Kompilieren wir den Quelltext oben und schmeißen das Programm erst in OllyDBG.
Nun müssten wir das Programm in hexadezimaler Form und in Assemblercode sehen.
Mit den Schritten:
-rechte Maustaste im Hauptfenster
-Search For
-All Referenced Text Strings,bekommen wir alle Strings im Programm aufgelistet.
wir klicken auf unsere ASCII "%s", womit ja unsere 12 auch ausgegeben wird.
004012C0 |> C70424 2930400>MOV DWORD PTR SS:[ESP],prim.00403029 ; |ASCII "%s "
Nun werden wir zur Stelle im Programm geleitet, wo wir auf den Stack zugreifen.
Wir sehen, dass unsere Variable auf dem Stack bei der Adresse prim.00403029 ist.
Dies ist die .rdata-Sektion, wie man sich mit den entsprechenden Zahlen unter View -> Memory bewusst machen kann.
Nun beenden wir OllyDBG und benutzen PEID. Mit PEID kann man sehr leicht den benötigten Offset einsehen.
Mit Peid öffnen wir also nocheinmal das Programm und gehen wie folgt vor:
Der Pfeil bei EP Sektion führt uns zu der Sektionsübersicht. Unser Code ist in .rdata und dort wollen wir hin.
Rechtsklick -> Hexviewer zeigt uns nun die hexadezimale Ansicht unserer Sektion.
Wir sehen schon gleich unsere nette 12 dastehen[gekennzeichnet als 31 32 hex],
was die hexadezimalen Werte der ASCII-Werte sind, die eine Taste kennzeichnen.Wir klicken mit der schönen Maus darauf und unten links wird unser Offset auch schon angegeben.
Da hätten wir 0x0000100B und 0x0000100C
Für weitere Informationen über Hexeditoren siehe: http://en.wikipedia.org/wiki/Hex_editor
Nun schreiben wir unser Programm, um diese eckelhafte 12 wegzubekommen.
//prim_patch.c //--------- #include <stdio.h> typedef struct { long oSet; int hexV; } PYTE; static PYTE pytes[2] = { {0x0000100B,0x08}, //offsets und hexacode values; {0x0000100C,0x00}, //0x08 für backspace->löscht letztes Nullbyte //0x00 für das Setzen von einem Nullbyte. //Sinn: Schönheitmackel der 2 Leerzeichen //entfernen }; int main(void){ FILE *patchFile = fopen("prim.exe","r+"); for(int i = 0; i < 2; i++){ fseek(patchFile, pytes[i].oSet, SEEK_SET); fwrite(&pytes[i].hexV, 1, 1, patchFile); } fclose(patchFile); }
Somit haben wir unsere 12 los.
Eine komplette Rekonstruktion des Programmes hätte einen nicht unermesslichen Zeitwand gekostet. Durch das Patchen wird der Betrieb nur eine kurze Zeitspanne unterbrochen, um das System zu updaten. Der Chef der Firma nimmt uns den Fehler doch nicht so dermaßen übel und wir sind glücklich, dass das so ist.
3.2 If-else-Bedingung aushebeln
Nun, da wir uns reichlich mit den Grundlagen beschäftigt und drumherumgeredet haben, kommen wir zum nächsten Punkt.
Zuerst: Wie wird in Assembly eine If-else-Bedingung ausgedrückt //mir fällt kein anderer Ausdruck ein
Es gibt zwei Möglichkeiten:
Springe zum else-Zweig
oder
Springe zum if-Zweig
Nehmen Sie bitte dieses Programm als Beispiel:
//if_else.cpp //--------- #include <stdio.h> int cp(char passw[]){ const char p[]= "www.c-plusplus.net"; int n = sizeof(p) / sizeof(char); for(int i = 0; i < n; i++){ if(passw[i] != p[i]){ return 0; } } return 1; } int main(){ char pw[17]; printf("pw_ "); fgets(pw, 18, stdin); if(cp(pw) == 1){ puts("Zugriff gewaehrt"); } else{ puts("Zugriff verweigert"); } }
Zur Erklärung, mit dem "Es gibt nur einen Zweig", hier eine Programmablaufbeschreibung:
schreibe "pw_ "
lese pw
lade passwortstring
vergleiche passwortstring mit pw
wenn gleich bzw. wenn nicht gleich, dann springe
weiterer codeablaufDa wir dies hätten, suchen wir unsere Programmstelle und haben dann diesen oder ähnlichen Code vorliegen:
004012F0 /$ 55 PUSH EBP 004012F1 |. B8 10000000 MOV EAX,10 004012F6 |. 89E5 MOV EBP,ESP 004012F8 |. 53 PUSH EBX 004012F9 |. 83EC 54 SUB ESP,54 004012FC |. 83E4 F0 AND ESP,FFFFFFF0 004012FF |. E8 EC040000 CALL if_else.004017F0 00401304 |. E8 87010000 CALL if_else.00401490 00401309 |. C70424 1230400>MOV DWORD PTR SS:[ESP],if_else.00403012 ; ||ASCII "pw_ " 00401310 |. BB 12000000 MOV EBX,12 ; || 00401315 |. E8 96050000 CALL <JMP.&msvcrt.printf> ; |\printf 0040131A |. 8B0D DC504000 MOV ECX,DWORD PTR DS:[<&msvcrt._iob>] ; |msvcrt._iob 00401320 |. 895C24 04 MOV DWORD PTR SS:[ESP+4],EBX ; | 00401324 |. 8D5D D8 LEA EBX,DWORD PTR SS:[EBP-28] ; | 00401327 |. 894C24 08 MOV DWORD PTR SS:[ESP+8],ECX ; | 0040132B |. 891C24 MOV DWORD PTR SS:[ESP],EBX ; | 0040132E |. E8 6D050000 CALL <JMP.&msvcrt.fgets> ; \fgets 00401333 |. 8B15 00304000 MOV EDX,DWORD PTR DS:[403000] 00401339 |. 8955 B8 MOV DWORD PTR SS:[EBP-48],EDX 0040133C |. A1 04304000 MOV EAX,DWORD PTR DS:[403004] 00401341 |. 8945 BC MOV DWORD PTR SS:[EBP-44],EAX 00401344 |. 8B0D 08304000 MOV ECX,DWORD PTR DS:[403008] 0040134A |. 894D C0 MOV DWORD PTR SS:[EBP-40],ECX 0040134D |. 8B15 0C304000 MOV EDX,DWORD PTR DS:[40300C] 00401353 |. 8955 C4 MOV DWORD PTR SS:[EBP-3C],EDX 00401356 |. 31D2 XOR EDX,EDX 00401358 |. 0FB705 1030400>MOVZX EAX,WORD PTR DS:[403010] 0040135F |. 66:8945 C8 MOV WORD PTR SS:[EBP-38],AX 00401363 |. 8DB6 00000000 LEA ESI,DWORD PTR DS:[ESI] 00401369 |. 8DBC27 0000000>LEA EDI,DWORD PTR DS:[EDI] 00401370 |> 0FB64C2A B8 /MOVZX ECX,BYTE PTR DS:[EDX+EBP-48] ; | 00401375 |. 3A0C1A |CMP CL,BYTE PTR DS:[EDX+EBX] ; | 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ; | 0040137A |. 42 |INC EDX ; | 0040137B |. 83FA 12 |CMP EDX,12 ; | 0040137E |.^7C F0 \JL SHORT if_else.00401370 ; | 00401380 |. C70424 1730400>MOV DWORD PTR SS:[ESP],if_else.00403017 ; |ASCII "Zugriff gewaehrt" 00401387 |. E8 04050000 CALL <JMP.&msvcrt.puts> ; \puts 0040138C |. 8B5D FC MOV EBX,DWORD PTR SS:[EBP-4] 0040138F |. 31C0 XOR EAX,EAX 00401391 |. C9 LEAVE 00401392 |. C3 RETN 00401393 |> C70424 2830400>MOV DWORD PTR SS:[ESP],if_else.00403028 ; |ASCII "Zugriff verweigert" 0040139A |. E8 F1040000 CALL <JMP.&msvcrt.puts> ; \puts 0040139F |. 8B5D FC MOV EBX,DWORD PTR SS:[EBP-4] 004013A2 |. 31C0 XOR EAX,EAX 004013A4 |. C9 LEAVE 004013A5 \. C3 RETN
Bedingter Sprung: 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ;
Zur Erklärung:
0040132E |. E8 6D050000 CALL <JMP.&msvcrt.fgets> ; \fgets 00401333 |. 8B15 00304000 MOV EDX,DWORD PTR DS:[403000] 00401339 |. 8955 B8 MOV DWORD PTR SS:[EBP-48],EDX 0040133C |. A1 04304000 MOV EAX,DWORD PTR DS:[403004] 00401341 |. 8945 BC MOV DWORD PTR SS:[EBP-44],EAX 00401344 |. 8B0D 08304000 MOV ECX,DWORD PTR DS:[403008] 0040134A |. 894D C0 MOV DWORD PTR SS:[EBP-40],ECX 0040134D |. 8B15 0C304000 MOV EDX,DWORD PTR DS:[40300C] 00401353 |. 8955 C4 MOV DWORD PTR SS:[EBP-3C],EDX 00401356 |. 31D2 XOR EDX,EDX 00401358 |. 0FB705 1030400>MOVZX EAX,WORD PTR DS:[403010] 0040135F |. 66:8945 C8 MOV WORD PTR SS:[EBP-38],AX 00401363 |. 8DB6 00000000 LEA ESI,DWORD PTR DS:[ESI] 00401369 |. 8DBC27 0000000>LEA EDI,DWORD PTR DS:[EDI]
Hier schreiben wir den String "www.c-plusplus.net" in den Speicher und lesen pw ein.
00401370 |> 0FB64C2A B8 /MOVZX ECX,BYTE PTR DS:[EDX+EBP-48] ; | 00401375 |. 3A0C1A |CMP CL,BYTE PTR DS:[EDX+EBX] ; | 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ; |
Hier ist der Test auf Null, welcher unserer Sprung ist.
In diesem Fall müssen wir unseren Sprung nur noppen[No Operation; Hexcode 90]
Wie man sieht haben wir 2 hexadezimale Befehle[75 und 19].
Diesesmal befindet sich alles in der Text-sektion, außer unserem Passwort-String.
Diese fängt bei mir bei 00401000 an und endet bei 00402000.
Falls unser Stück in der Text-Sektion ist, können wir den Offset auch Ausrechnen:
-----
Absolute Adresse des BefehlesAbsolute Adresse der .text-Sektion
+
400
-----Daher:
00401378 - 00401000 = 0x00000378
0x0000038C + 400 = 0x00000778
Unsere Matrix für den Patch sieht also nun so aus:
static PYTE pytes[2] = { {0x00000778,0x90}, {0x00000779,0x90}, };
Wie Sie den Passwortstring ändern, können Sie 4.1 entnehmen.
3.3 Schleifenbedingung korrigieren
Nun widmen wir uns den Schleifenbedingungen. Als Beispiel nehme ich hier eine For-Schleife.
Dieses Programm:
//condition.c //--------- #include <stdio.h> int main(){ for(int i = 0; i < 10; i++){ putc(i+65,stdout); } }
Ist an dieser Stelle beachtenswert:
00401290 /$ 55 PUSH EBP 00401291 |. B8 10000000 MOV EAX,10 00401296 |. 89E5 MOV EBP,ESP 00401298 |. 56 PUSH ESI 00401299 |. 53 PUSH EBX 0040129A |. 83EC 10 SUB ESP,10 0040129D |. 83E4 F0 AND ESP,FFFFFFF0 004012A0 |. E8 9B040000 CALL conditio.00401740 004012A5 |. E8 36010000 CALL conditio.004013E0 004012AA |. 31DB XOR EBX,EBX 004012AC |. EB 12 JMP SHORT conditio.004012C0 004012AE | 89F6 MOV ESI,ESI 004012B0 |> 8B42 20 MOV EAX,DWORD PTR DS:[EDX+20] 004012B3 |. 89F1 MOV ECX,ESI 004012B5 |. 43 INC EBX 004012B6 |. 8808 MOV BYTE PTR DS:[EAX],CL 004012B8 |. FF42 20 INC DWORD PTR DS:[EDX+20] 004012BB |. 83FB 09 CMP EBX,9 004012BE |. 7F 29 JG SHORT conditio.004012E9 004012C0 |> 8B15 D8504000 /MOV EDX,DWORD PTR DS:[<&msvcrt._iob>] ; msvcrt._iob 004012C6 |. 8D73 41 |LEA ESI,DWORD PTR DS:[EBX+41] 004012C9 |. 8D4A 20 |LEA ECX,DWORD PTR DS:[EDX+20] 004012CC |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] 004012CF |. 48 |DEC EAX 004012D0 |. 85C0 |TEST EAX,EAX 004012D2 |. 8941 04 |MOV DWORD PTR DS:[ECX+4],EAX 004012D5 |.^79 D9 |JNS SHORT conditio.004012B0 004012D7 |. 894C24 04 |MOV DWORD PTR SS:[ESP+4],ECX 004012DB |. 43 |INC EBX 004012DC |. 893424 |MOV DWORD PTR SS:[ESP],ESI 004012DF |. E8 FC040000 |CALL <JMP.&msvcrt._flsbuf> 004012E4 |. 83FB 09 |CMP EBX,9 004012E7 |.^7E D7 \JLE SHORT conditio.004012C0 004012E9 |> 8D65 F8 LEA ESP,DWORD PTR SS:[EBP-8] 004012EC |. 31C0 XOR EAX,EAX 004012EE |. 5B POP EBX 004012EF |. 5E POP ESI 004012F0 |. 5D POP EBP 004012F1 \. C3 RETN
Hier nun die Erklärung:
00401290 /$ 55 PUSH EBP 00401291 |. B8 10000000 MOV EAX,10 00401296 |. 89E5 MOV EBP,ESP 00401298 |. 56 PUSH ESI 00401299 |. 53 PUSH EBX 0040129A |. 83EC 10 SUB ESP,10 0040129D |. 83E4 F0 AND ESP,FFFFFFF0 004012A0 |. E8 9B040000 CALL conditio.00401740 004012A5 |. E8 36010000 CALL conditio.004013E0
Nicht beachtenswert.
004012AA |. 31DB XOR EBX,EBX
EBX wird auf 0 gesetzt.
004012AC |. EB 12 JMP SHORT conditio.004012C0 004012AE | 89F6 MOV ESI,ESI 004012B0 |> 8B42 20 MOV EAX,DWORD PTR DS:[EDX+20] 004012B3 |. 89F1 MOV ECX,ESI 004012B5 |. 43 INC EBX 004012B6 |. 8808 MOV BYTE PTR DS:[EAX],CL 004012B8 |. FF42 20 INC DWORD PTR DS:[EDX+20]
Nicht beachtenswert
004012BB |. 83FB 09 CMP EBX,9 004012BE |. 7F 29 JG SHORT conditio.004012E9
Prüfe das erste mal, ob EBX größer als 9 ist[10].
Nun kommen wir in eine Schleife.
004012C0 |> 8B15 D8504000 /MOV EDX,DWORD PTR DS:[<&msvcrt._iob>] ; msvcrt._iob 004012C6 |. 8D73 41 |LEA ESI,DWORD PTR DS:[EBX+41]
41 Hexedezimal ist 65 Dezimal..
004012C9 |. 8D4A 20 |LEA ECX,DWORD PTR DS:[EDX+20] 004012CC |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] 004012CF |. 48 |DEC EAX 004012D0 |. 85C0 |TEST EAX,EAX 004012D2 |. 8941 04 |MOV DWORD PTR DS:[ECX+4],EAX 004012D5 |.^79 D9 |JNS SHORT conditio.004012B0 004012D7 |. 894C24 04 |MOV DWORD PTR SS:[ESP+4],ECX 004012DB |. 43 |INC EBX
EBX += 1
004012DC |. 893424 |MOV DWORD PTR SS:[ESP],ESI 004012DF |. E8 FC040000 |CALL <JMP.&msvcrt._flsbuf> 004012E4 |. 83FB 09 |CMP EBX,9 004012E7 |.^7E D7 \JLE SHORT conditio.004012C0
Prüfe, ob EBX größer als 9 ist.
Dieser Teil ist unsere Bedingung.
Wir müssen nur den Wert 9 ändern, damit wir eine andere Bedingung haben.
Eine Endlosschleife würden wir herbeirufen, wenn wir
004012DB |. 43 |INC EBX
Noppen würden.
3.4 Programmstellen indizieren
Dies ist nun etwas kniffelig. Es gibt mehrere Probleme:
-Wie muss ich die Register handhaben
-Wo genau muss etwas indiziert werden
-Wie kriege ich dazu den Hexcode?/*
*/
4 Kurzreferenz: Hexbefehle
Zum Abschluss noch eine kleine Hexadezimale Befehlstabelle:
74 JE Springe wenn gleich 75 JNE Springe wenn nicht gleich 77 JA Springe wenn größer 0F86 JNA Springe wenn nicht größer 0F8C JL Springe wenn kleiner 0F87 JNL Springe wenn nicht kleiner 0F83 JGE Springe wenn größer oder gleich 0F8E JLE Springe wenn kleiner oder gleich EB JMP Unbedingter Sprung 90 NOP Keine Operation
Für den Asciizeichensatz in Hexadezimal könnt ihr euch diesen Link anschauen: http://en.wikipedia.org/wiki/Ascii
5 Nachwort
Alles Sources und Executables finden Sie hier zum download: www.nochnichtvorhanden.de
MFG winexec*
-
Ich habe den Artikel nur kurz überflogen. Mir sind aber ein paar Kleinigkeiten aufgefallen.
Die Kapitelüberschriften finde ich teilweise nicht so schön. "Benötigtes" klingt imho etwas unbeholfen. Wie wäre es mit "Voraussetzungen"?
Die zweite Kapitelüberschrift: "Introduction" da stört mich ehrlich gesagt das englisch. Warum nicht einfach "Einführung" oder "Einleitung"?Einige Formulierungen rufen in mir das Bedürfnis zu antworten hervor:
"Es sei gesagt, dass ..." - Dann sag's doch einfach!
"Ich drücke hier ausdrücklich aus, dass ..." - auch da denke ich wieder, dann mach's und red nicht drüber.Aber das ist mit Sicherheit auch Geschmacksache, sieh's einfach als Anregung. Nur das ausdrückliche ausdrücken würde ich auf jeden Fall überarbeiten.
MfG Jester
-
@winexec* Kann der Artikel bei der nächsten Runde mit raus? Der sieht schon relativ fertig aus...
-
Hallo,
Ich hatte bis jetzt noch keine Zeit nocheinmal drüberzuschauen. Ich wollte vielleicht noch unmittelbare Injektionen mit einbringen. Ich werde einmal schauen, aber es sollte möglich sein(90%).
MFG winexec*
-
Okay, das klingt gut... außerdem sind ja noch zwei Wochen Zeit.
-
Hallo,
Ich habe mich jetzt dafür entschieden, dass ich das mit der Injektion in einen weiteren Thread einarbeite oder halt in ein Thema über Bufferoverflows. Ich bin zurzeit ein wenig beschäftigt. Der Artikel könnte aber zum nächsten Termin heraus. Soll ich dann mein Profil hier in den Thread reinposten?
MFG winexec*
-
winexec* schrieb:
Hallo,
Ich habe mich jetzt dafür entschieden, dass ich das mit der Injektion in einen weiteren Thread einarbeite oder halt in ein Thema über Bufferoverflows. Ich bin zurzeit ein wenig beschäftigt. Der Artikel könnte aber zum nächsten Termin heraus.
Okay, also d.h. der Artikel ist jetzt so fertig und kann auf korrigiert werden? Dann auf T setzen
Soll ich dann mein Profil hier in den Thread reinposten?
Ne, mach dafür nen neuen Thread auf.
MfG
GPC
-
Reverse Engineering: Patching
1. Softwareseitige Vorraussetzungen
1.1 OllyDbg
1.2 Peid
2. Einführung
3. Materie
3.1 Beispiel: Fehlerkorrektur
3.2 If-else-Bedingung aushebeln
3.3 Schleifenbedingung korrigieren
4. Kurzreferenz: Hexbefehle1 Softwareseitige Vorraussetzungen
In diesem Artikel verwende ich Disassembler/Debugger, um Programme zu analysieren. Diese können Sie sich hier downloaden:
-OllyDBG http://www.winexec.de/downloads/odbg110.zip
-Peid http://www.winexec.de/downloads/PEiD-0.49-20060510.zipIch informiere Sie kurz über diese Produkte.
1.1 OllyDbg
OllyDbg ist ein von Oleh Yuschuk entwickelter 32-Bit-Debugger für Windows Betriebssysteme. Hauptsächlich wird OllyDbg zur binären Codeanalyse verwendet.
OllyDbg bietet folgende Features:
-Debugging von Multithread Programmen
-Anhängen an laufende Prozesse
-Konfigurierbarer Disassembler mit Unterstützung der Formate MASM und IDEAL
-MMX, 3DNow, SSE, ASCII und UNICODE Unterstützung
-Hardware- und Software-Breakpoints
-Suchen über Speicherbereiche
-Modifikation von Speicherbereichen on-the-fly1.2 Peid
Peid erkennt die meisten bekannten Packers, Cryptors und Compiler und bietet diese Features:
-Einfach zu handhabende GUI.
-Shell Eingliederung, Kommandozeilenunterstützung.
-Task-viewer und controller.
-Hex ViewerAlle Sources und Executables finden Sie hier zum Download:http://www.winexec.de/patching_downloads.html
2 Einführung
Patches sind Programme, die andere Programme in der Struktur abändern. Sie "patchen" mit Hilfe vom hexadezimalen Zahlensystem bestimmte Bytes in der [ich nehme an] Executable.
Sie bewegen sich hiermit auf dem Gebiet des Reverse Engineering.
In der EULA steht, dass Microsoft sich gegen Reverse Engineering verwahrt und dementsprechend das Disassemblieren ihres Codes untersagt. Wobei die Frage ist, wie man das abgrenzt, ob schon das Lesen des HEX-Codes darunter fallt. Was auf jeden Fall rechtlich bedenklich ist, ist solche Erkenntnisse kommerziell zu verwerten. Andererseits, wenn man dies wieder zu weit treibt, untergrabt man die Existenzberechtigung samtlicher Computerzeitschriften (Windows geheim, ...)
Konkret gibt es Programme, die den HEX-Code eines Programms in nicht ganz unleserliche Assembleranweisungen umsetzen konnen. Sowas meint man meist mit Disassemblieren. Wobei durch die verschiedenen Optimierungstricks moderner Compiler kaum verwertbare Struktur zu finden ist. Einen guten Disassembler zu bauen ist wesentlich komplizierter als einen guten Compiler zu bauen. Und sei es nur wegen des Problems, die Datenstrukturen zu erkennen und lesbar zu bezeichnen, von sinnentsprechend ganz zu schweigen. Der Weg Hochsprache -> Binarcode -> (Dis)Assemblerprogramm ist mit extremem Informationsverlust bei gleichzeitigem Aufblahen der Datenmenge verbunden.
In der Internetkultur hat sich Reverse Engineering zu einer Sportart entwickelt. Siehe Hackits/Crackits.
Mehr dazu hier: http://en.wikipedia.org/wiki/Reverse_engineering
Patchen bedeutet nicht immer das Erweitern, sondern auch das Reduzieren bzw. Ausbessern von Code.
Man springt relativ vom Anfang der Datei die Entfernung zu den gesuchten Variablen/Codestücken. Daher benötigen wir den Offset. Der Patch selber ist schnell hergestellt, solange es nur kleine Ausbesserungen sind; die Suche nach dem korrekten Stück Code aber, welches wir bearbeiten wollen, oft nicht.
//Tip: Protokollieren Sie alle Daten. Sie werden diese im Patch benötigen. Dazu sollte der Offset, die Speicheradresse, der Hexbefehl und eventuelle Jumpadressen vermerkt werden.
3 Materie
In diesem Artikel zeige ich drei Anwendungsmöglichkeiten. Der erste Fall dient zur Einführung.
Info:
Offsets und Programmadressen verändern sich je nach Compiler und deren Konfiguration. Ich verwende hier CodeBlocks v1.0. Die Programme sind in der Debugversion.
Außerdem sollte sich jeder im klaren sein, dass der Herausgeber des Programmes das Reverse Engineering erlaubt haben muss. Natürlich ist das "Klauen" von Software durch Reverse Engineering strafbar.
3.1 Beispiel: Fehlerkorrektur
Als Einführung in das Thema soll eine Fehlerkorrektur mit einer kleinen Story dienen.
Dazu nehmen Sie bitte diesen Sourcecode als Beispiel:
//prim.c //--------- #include <stdio.h> int main(){ char *primzahlen[15] = {"2", "3", "5", "7", "11", "12", "13", "17", "19", "23", "29", "31", "37", "41", "43"}; // 12 ist keine Primzahl for(int i = 0; i < 15; i++){ printf("%s ",primzahlen[i]); } }
Nehmen wir an, ein Array, gefüllt mit Primzahlen, wäre in einer Funktion, die
zu einem Programm gehört, welches wir für eine Firma geschrieben haben.Nun braucht dieses Programm eine Berechnung der Primzahlen. Denken wir uns einfach es ist eine Datenbank und ein Angestellter, der einigermaßen aufmerksam ist, sieht nun dort eine 12: Er ist entsetzt.
Nun läuft er schnell zum Chef und dieser ruft uns gleich, mit leicht zorniger Stimme, an. Er verlangt das wir das Problem auf der Stelle beheben sonst...
Also haben wir uns noch einmal den Quellcode angeguckt. Und da war diese 12.
Es war die Einzige falsche Primzahl in diesem Array[Eigentlich kann man diese auch berechnen, aber mir ist kein besseres Problem eingefallen].Nun patchen wir diese 12.
Kompilieren wir den Quelltext oben und schmeißen das Programm erst in OllyDBG.
Nun müssten wir das Programm in hexadezimaler Form und in Assemblercode sehen.
Mit den Schritten:
-rechte Maustaste im Hauptfenster
-Search For
-All Referenced Text Strings,bekommen wir alle Strings im Programm aufgelistet.
wir klicken auf unsere ASCII "%s", womit unsere 12 ausgegeben wird.
004012C0 |> C70424 2930400>MOV DWORD PTR SS:[ESP],prim.00403029 ; |ASCII "%s "
Nun werden wir zur Stelle im Programm geleitet, wo wir auf den Stack zugreifen.
Wir sehen, dass unsere Variable auf dem Stack bei der Adresse prim.00403029 ist.
Dies ist die .rdata-Sektion, wie man sich mit den entsprechenden Zahlen unter View -> Memory bewusst machen kann.
Nun beenden wir OllyDBG und benutzen PEID. Mit PEID kann man sehr leicht den benötigten Offset einsehen.
Wir öffnen noch einmal das Programm und gehen wie folgt vor:
Der Pfeil bei EP Sektion führt uns zu der Sektionsübersicht. Unser Code ist in .rdata.
Rechtsklick -> Hexviewer zeigt uns nun die hexadezimale Ansicht unserer Sektion.
Wir sehen schon gleich unsere nette 12 dastehen[gekennzeichnet als 31 32 hex],
welches die hexadezimalen Werte der ASCII-Werte sind, die eine Taste kennzeichnen.Mit einem Mausklick auf den Hexcode wird unser Offset unten links angegeben.
Da hätten wir 0x0000100B und 0x0000100C
Für weitere Informationen über Hexeditoren siehe: http://en.wikipedia.org/wiki/Hex_editor
Nun schreiben wir unser Programm, damit wir die 12 entfernen können.
//prim_patch.c //--------- #include <stdio.h> typedef struct { long oSet; int hexV; } PYTE; static PYTE pytes[2] = { {0x0000100B,0x08}, //offsets und hexacode values; {0x0000100C,0x00}, //0x08 für backspace->löscht letztes Nullbyte //0x00 für das Setzen von einem Nullbyte. //Sinn: Schönheitmakel der 2 Leerzeichen //entfernen }; int main(void){ FILE *patchFile = fopen("prim.exe","r+"); for(int i = 0; i < 2; i++){ fseek(patchFile, pytes[i].oSet, SEEK_SET); fwrite(&pytes[i].hexV, 1, 1, patchFile); } fclose(patchFile); }
Somit haben wir unsere 12 los.
Eine komplette Rekonstruktion des Programmes hätte einen nicht gerade kleinen Zeitwand gekostet. Durch das Patchen wird der Betrieb nur eine kurze Zeitspanne unterbrochen, um das System zu updaten. Der Chef der Firma nimmt uns den Fehler doch nicht so dermaßen übel und wir sind glücklich.
3.2 If-else-Bedingung aushebeln
Zuerst: Wie wird in Assembly eine If-else-Bedingung ausgedrückt? //mir fällt kein anderer Ausdruck ein
Meistens sieht es so aus:
Springe zum else-Zweig
oder
Springe zum if-Zweig
Nehmen Sie bitte dieses Programm als Beispiel:
//if_else.cpp //--------- #include <stdio.h> int cp(char passw[]){ const char p[]= "www.c-plusplus.net"; int n = sizeof(p) / sizeof(char); for(int i = 0; i < n; i++){ if(passw[i] != p[i]){ return 0; } } return 1; } int main(){ char pw[17]; printf("pw_ "); fgets(pw, 18, stdin); if(cp(pw) == 1){ puts("Zugriff gewaehrt"); } else{ puts("Zugriff verweigert"); } }
Zur Erklärung, mit dem "Es gibt nur einen Zweig", hier eine Programmablaufbeschreibung:
schreibe "pw_ "
lese pw
lade passwortstring
vergleiche passwortstring mit pw
wenn gleich bzw. wenn nicht gleich, dann springe
weiterer codeablaufNach einer Suche nach unserer Programmstelle haben wir dies vorliegen:
004012F0 /$ 55 PUSH EBP 004012F1 |. B8 10000000 MOV EAX,10 004012F6 |. 89E5 MOV EBP,ESP 004012F8 |. 53 PUSH EBX 004012F9 |. 83EC 54 SUB ESP,54 004012FC |. 83E4 F0 AND ESP,FFFFFFF0 004012FF |. E8 EC040000 CALL if_else.004017F0 00401304 |. E8 87010000 CALL if_else.00401490 00401309 |. C70424 1230400>MOV DWORD PTR SS:[ESP],if_else.00403012 ; ||ASCII "pw_ " 00401310 |. BB 12000000 MOV EBX,12 ; || 00401315 |. E8 96050000 CALL <JMP.&msvcrt.printf> ; |\printf 0040131A |. 8B0D DC504000 MOV ECX,DWORD PTR DS:[<&msvcrt._iob>] ; |msvcrt._iob 00401320 |. 895C24 04 MOV DWORD PTR SS:[ESP+4],EBX ; | 00401324 |. 8D5D D8 LEA EBX,DWORD PTR SS:[EBP-28] ; | 00401327 |. 894C24 08 MOV DWORD PTR SS:[ESP+8],ECX ; | 0040132B |. 891C24 MOV DWORD PTR SS:[ESP],EBX ; | 0040132E |. E8 6D050000 CALL <JMP.&msvcrt.fgets> ; \fgets 00401333 |. 8B15 00304000 MOV EDX,DWORD PTR DS:[403000] 00401339 |. 8955 B8 MOV DWORD PTR SS:[EBP-48],EDX 0040133C |. A1 04304000 MOV EAX,DWORD PTR DS:[403004] 00401341 |. 8945 BC MOV DWORD PTR SS:[EBP-44],EAX 00401344 |. 8B0D 08304000 MOV ECX,DWORD PTR DS:[403008] 0040134A |. 894D C0 MOV DWORD PTR SS:[EBP-40],ECX 0040134D |. 8B15 0C304000 MOV EDX,DWORD PTR DS:[40300C] 00401353 |. 8955 C4 MOV DWORD PTR SS:[EBP-3C],EDX 00401356 |. 31D2 XOR EDX,EDX 00401358 |. 0FB705 1030400>MOVZX EAX,WORD PTR DS:[403010] 0040135F |. 66:8945 C8 MOV WORD PTR SS:[EBP-38],AX 00401363 |. 8DB6 00000000 LEA ESI,DWORD PTR DS:[ESI] 00401369 |. 8DBC27 0000000>LEA EDI,DWORD PTR DS:[EDI] 00401370 |> 0FB64C2A B8 /MOVZX ECX,BYTE PTR DS:[EDX+EBP-48] ; | 00401375 |. 3A0C1A |CMP CL,BYTE PTR DS:[EDX+EBX] ; | 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ; | 0040137A |. 42 |INC EDX ; | 0040137B |. 83FA 12 |CMP EDX,12 ; | 0040137E |.^7C F0 \JL SHORT if_else.00401370 ; | 00401380 |. C70424 1730400>MOV DWORD PTR SS:[ESP],if_else.00403017 ; |ASCII "Zugriff gewaehrt" 00401387 |. E8 04050000 CALL <JMP.&msvcrt.puts> ; \puts 0040138C |. 8B5D FC MOV EBX,DWORD PTR SS:[EBP-4] 0040138F |. 31C0 XOR EAX,EAX 00401391 |. C9 LEAVE 00401392 |. C3 RETN 00401393 |> C70424 2830400>MOV DWORD PTR SS:[ESP],if_else.00403028 ; |ASCII "Zugriff verweigert" 0040139A |. E8 F1040000 CALL <JMP.&msvcrt.puts> ; \puts 0040139F |. 8B5D FC MOV EBX,DWORD PTR SS:[EBP-4] 004013A2 |. 31C0 XOR EAX,EAX 004013A4 |. C9 LEAVE 004013A5 \. C3 RETN
Bedingter Sprung: 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ;
Zur Erklärung:
0040132E |. E8 6D050000 CALL <JMP.&msvcrt.fgets> ; \fgets 00401333 |. 8B15 00304000 MOV EDX,DWORD PTR DS:[403000] 00401339 |. 8955 B8 MOV DWORD PTR SS:[EBP-48],EDX 0040133C |. A1 04304000 MOV EAX,DWORD PTR DS:[403004] 00401341 |. 8945 BC MOV DWORD PTR SS:[EBP-44],EAX 00401344 |. 8B0D 08304000 MOV ECX,DWORD PTR DS:[403008] 0040134A |. 894D C0 MOV DWORD PTR SS:[EBP-40],ECX 0040134D |. 8B15 0C304000 MOV EDX,DWORD PTR DS:[40300C] 00401353 |. 8955 C4 MOV DWORD PTR SS:[EBP-3C],EDX 00401356 |. 31D2 XOR EDX,EDX 00401358 |. 0FB705 1030400>MOVZX EAX,WORD PTR DS:[403010] 0040135F |. 66:8945 C8 MOV WORD PTR SS:[EBP-38],AX 00401363 |. 8DB6 00000000 LEA ESI,DWORD PTR DS:[ESI] 00401369 |. 8DBC27 0000000>LEA EDI,DWORD PTR DS:[EDI]
Hier schreiben wir den String "www.c-plusplus.net" in den Speicher und lesen pw ein.
00401370 |> 0FB64C2A B8 /MOVZX ECX,BYTE PTR DS:[EDX+EBP-48] ; | 00401375 |. 3A0C1A |CMP CL,BYTE PTR DS:[EDX+EBX] ; | 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ; |
Hier ist der Test auf Null, welcher unserer Sprung ist.
In diesem Fall müssen wir unseren Sprung nur noppen[No Operation; Hexcode 90]
Wie man sieht haben wir 2 hexadezimale Befehle[75 und 19].
Diesesmal befindet sich alles in der Text-sektion, außer unserem Passwortstring.
Diese fängt bei mir bei 00401000 an und endet bei 00402000.
//Tip: Falls unser Stück in der Text-Sektion ist, können wir den Offset auch Ausrechnen:
Absolute Adresse des Befehles
Absolute Adresse der .text-Sektion
+
400Daher:
00401378 - 00401000 = 0x00000378
0x0000038C + 400 = 0x00000778
Unsere Matrix für den Patch sieht also nun so aus:
static PYTE pytes[2] = { {0x00000778,0x90}, //75 {0x00000779,0x90}, //19 };
Wie Sie den Passwortstring ändern, können Sie 4.1 entnehmen.
3.3 Schleifenbedingung korrigieren
Nun widmen wir uns den Schleifenbedingungen. Als Beispiel nehme ich hier eine for-Schleife.
Dieses Programm
//condition.c //--------- #include <stdio.h> int main(){ for(int i = 0; i < 10; i++){ putc(i+65,stdout); } }
ist an dieser Stelle beachtenswert:
00401290 /$ 55 PUSH EBP 00401291 |. B8 10000000 MOV EAX,10 00401296 |. 89E5 MOV EBP,ESP 00401298 |. 56 PUSH ESI 00401299 |. 53 PUSH EBX 0040129A |. 83EC 10 SUB ESP,10 0040129D |. 83E4 F0 AND ESP,FFFFFFF0 004012A0 |. E8 9B040000 CALL conditio.00401740 004012A5 |. E8 36010000 CALL conditio.004013E0 004012AA |. 31DB XOR EBX,EBX 004012AC |. EB 12 JMP SHORT conditio.004012C0 004012AE | 89F6 MOV ESI,ESI 004012B0 |> 8B42 20 MOV EAX,DWORD PTR DS:[EDX+20] 004012B3 |. 89F1 MOV ECX,ESI 004012B5 |. 43 INC EBX 004012B6 |. 8808 MOV BYTE PTR DS:[EAX],CL 004012B8 |. FF42 20 INC DWORD PTR DS:[EDX+20] 004012BB |. 83FB 09 CMP EBX,9 004012BE |. 7F 29 JG SHORT conditio.004012E9 004012C0 |> 8B15 D8504000 /MOV EDX,DWORD PTR DS:[<&msvcrt._iob>] ; msvcrt._iob 004012C6 |. 8D73 41 |LEA ESI,DWORD PTR DS:[EBX+41] 004012C9 |. 8D4A 20 |LEA ECX,DWORD PTR DS:[EDX+20] 004012CC |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] 004012CF |. 48 |DEC EAX 004012D0 |. 85C0 |TEST EAX,EAX 004012D2 |. 8941 04 |MOV DWORD PTR DS:[ECX+4],EAX 004012D5 |.^79 D9 |JNS SHORT conditio.004012B0 004012D7 |. 894C24 04 |MOV DWORD PTR SS:[ESP+4],ECX 004012DB |. 43 |INC EBX 004012DC |. 893424 |MOV DWORD PTR SS:[ESP],ESI 004012DF |. E8 FC040000 |CALL <JMP.&msvcrt._flsbuf> 004012E4 |. 83FB 09 |CMP EBX,9 004012E7 |.^7E D7 \JLE SHORT conditio.004012C0 004012E9 |> 8D65 F8 LEA ESP,DWORD PTR SS:[EBP-8] 004012EC |. 31C0 XOR EAX,EAX 004012EE |. 5B POP EBX 004012EF |. 5E POP ESI 004012F0 |. 5D POP EBP 004012F1 \. C3 RETN
Hier nun die Erklärung:
00401290 /$ 55 PUSH EBP 00401291 |. B8 10000000 MOV EAX,10 00401296 |. 89E5 MOV EBP,ESP 00401298 |. 56 PUSH ESI 00401299 |. 53 PUSH EBX 0040129A |. 83EC 10 SUB ESP,10 0040129D |. 83E4 F0 AND ESP,FFFFFFF0 004012A0 |. E8 9B040000 CALL conditio.00401740 004012A5 |. E8 36010000 CALL conditio.004013E0
Nicht beachtenswert.
004012AA |. 31DB XOR EBX,EBX
EBX wird auf 0 gesetzt.
004012AC |. EB 12 JMP SHORT conditio.004012C0 004012AE | 89F6 MOV ESI,ESI 004012B0 |> 8B42 20 MOV EAX,DWORD PTR DS:[EDX+20] 004012B3 |. 89F1 MOV ECX,ESI 004012B5 |. 43 INC EBX 004012B6 |. 8808 MOV BYTE PTR DS:[EAX],CL 004012B8 |. FF42 20 INC DWORD PTR DS:[EDX+20]
Nicht beachtenswert.
004012BB |. 83FB 09 CMP EBX,9 004012BE |. 7F 29 JG SHORT conditio.004012E9
Prüfe das erste mal, ob EBX größer als 9 ist[10].
Nun kommen wir in eine Schleife.
004012C0 |> 8B15 D8504000 /MOV EDX,DWORD PTR DS:[<&msvcrt._iob>] ; msvcrt._iob 004012C6 |. 8D73 41 |LEA ESI,DWORD PTR DS:[EBX+41]
41 Hexedezimal ist 65 Dezimal..
004012C9 |. 8D4A 20 |LEA ECX,DWORD PTR DS:[EDX+20] 004012CC |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] 004012CF |. 48 |DEC EAX 004012D0 |. 85C0 |TEST EAX,EAX 004012D2 |. 8941 04 |MOV DWORD PTR DS:[ECX+4],EAX 004012D5 |.^79 D9 |JNS SHORT conditio.004012B0 004012D7 |. 894C24 04 |MOV DWORD PTR SS:[ESP+4],ECX 004012DB |. 43 |INC EBX
EBX += 1
004012DC |. 893424 |MOV DWORD PTR SS:[ESP],ESI 004012DF |. E8 FC040000 |CALL <JMP.&msvcrt._flsbuf> 004012E4 |. 83FB 09 |CMP EBX,9 004012E7 |.^7E D7 \JLE SHORT conditio.004012C0
Prüfe, ob EBX größer als 9 ist.
Dieser Teil ist unsere Bedingung.
Wir müssen nur den Wert 9 ändern. Damit bezwecken wir eine andere Bedingung.
Eine Endlosschleife würden wir herbeirufen, wenn wir
004012DB |. 43 |INC EBX
Noppen würden.
4 Kurzreferenz: Hexbefehle
Zum Abschluss noch eine kleine Hexadezimale Befehlstabelle:
74 JE Springe wenn gleich 75 JNE Springe wenn nicht gleich 77 JA Springe wenn größer 0F86 JNA Springe wenn nicht größer 0F8C JL Springe wenn kleiner 0F87 JNL Springe wenn nicht kleiner 0F83 JGE Springe wenn größer oder gleich 0F8E JLE Springe wenn kleiner oder gleich EB JMP Unbedingter Sprung 90 NOP Keine Operation
Für den Asciizeichensatz in Hexadezimal können Sie sich dies anschauen: http://en.wikipedia.org/wiki/Ascii
MFG winexec*
Anmerkung: Ich arbeite noch fleißig [wenn ich Zeit habe] an der Homepage. Das Aussehen in Opera muss natürlich noch überarbeitet werden.
-
Hallo,
Ich schalte dann morgen auf R. Das ist doch akzeptable oder?
MFG winexec*
-
-
Hi,
wir haben neue Überschriftentags (siehe hier: http://www.c-plusplus.net/forum/viewtopic-var-t-is-161549.html ), bitte pass deinen Artikel noch an, sofern möglich
Grüße
GPC
-
Hallo,
Gut, habe ich gemacht. Sieht doch ein wenig schöner aus. Könnte es sein, dass es hier Probleme mit dem Anzeigen von Tabulaturen gibt?
MFG winexec*
-
winexec* schrieb:
Gut, habe ich gemacht. Sieht doch ein wenig schöner aus.
klasse.
Könnte es sein, dass es hier Probleme mit dem Anzeigen von Tabulaturen gibt?
Ne, wieso? Mir ist bei deinem Artikel nichts aufgefallen...
MfG
GPC
-
Hallo,
Im Sinne von: Ich mache 10 Leerzeichen, aber es wird nur eines angezeigt.
MFG winexec*
-
winexec* schrieb:
Im Sinne von: Ich mache 10 Leerzeichen, aber es wird nur eines angezeigt.
ach so, ja. Das wird von der Forensoftware "weggebügelt". Wenn du z.B. Tabellen darstellen willst, musst du sie mit code tags umgeben.
GPC
-
Reverse Engineering: Patching
- Softwareseitige Voraussetzungen
1.1 OllyDbg
1.2 Peid - Einführung
- Materie
3.1 Beispiel: Fehlerkorrektur
3.2 If-else-Bedingung aushebeln
3.3 Schleifenbedingung korrigieren - Kurzreferenz: Hexbefehle
1 Softwareseitige Voraussetzungen
In diesem Artikel verwende ich Disassembler/Debugger, um Programme zu analysieren. Diese können Sie sich hier downloaden:
OllyDBG http://www.winexec.de/downloads/odbg110.zip
Peid http://www.winexec.de/downloads/PEiD-0.49-20060510.zipIch informiere Sie kurz über diese Produkte.
1.1 OllyDbg
OllyDbg ist ein von Oleh Yuschuk entwickelter 32-Bit-Debugger für Windows-Betriebssysteme. Hauptsächlich wird OllyDbg zur binären Codeanalyse verwendet.
OllyDbg bietet folgende Features:
- Debugging von Multithread-Programmen
- Anhängen an laufende Prozesse
- Konfigurierbarer Disassembler mit Unterstützung der Formate MASM und IDEAL
- MMX, 3DNow, SSE, ASCII und UNICODE Unterstützung
- Hardware- und Software-Breakpoints
- Suchen über Speicherbereiche
- Modifikation von Speicherbereichen on-the-fly
1.2 Peid
Peid erkennt die meisten bekannten Packers, Cryptors und Compiler und bietet diese Features:
- Einfach zu handhabende GUI
- Shell-Eingliederung, Kommandozeilenunterstützung
- Task-Viewer und -Controller
- Hex-Viewer
Alle Sources und Executables finden Sie hier zum Download:http://www.winexec.de/patching_downloads.html
2 Einführung
Patches sind Programme, die andere Programme in der Struktur abändern. Sie "patchen" mit Hilfe des hexadezimalen Zahlensystems bestimmte Bytes in der [ich nehme an] Executable.
Sie bewegen sich hiermit auf dem Gebiet des Reverse Engineering.
In der EULA steht, dass Microsoft sich gegen Reverse Engineering verwahrt und dementsprechend das Disassemblieren ihres Codes untersagt. Wobei die Frage ist, wie man das abgrenzt, ob schon das Lesen des HEX-Codes darunter fällt. Was auf jeden Fall rechtlich bedenklich ist, ist solche Erkenntnisse kommerziell zu verwerten. Andererseits, wenn man dies wieder zu weit treibt, untergräbt man die Existenzberechtigung samtlicher Computerzeitschriften (Windows geheim, ...).
Konkret gibt es Programme, die den HEX-Code eines Programms in nicht ganz unleserliche Assembleranweisungen umsetzen können. Sowas meint man meist mit Disassemblieren. Wobei durch die verschiedenen Optimierungstricks moderner Compiler kaum verwertbare Struktur zu finden ist. Einen guten Disassembler zu bauen ist wesentlich komplizierter als einen guten Compiler zu bauen. Und sei es nur wegen des Problems, die Datenstrukturen zu erkennen und lesbar zu bezeichnen, von sinnentsprechend ganz zu schweigen. Der Weg Hochsprache -> Binärcode -> (Dis)Assemblerprogramm ist mit extremem Informationsverlust bei gleichzeitigem Aufblähen der Datenmenge verbunden.
In der Internetkultur hat sich Reverse Engineering zu einer Sportart entwickelt. Siehe Hackits/Crackits.
Mehr dazu hier: http://en.wikipedia.org/wiki/Reverse_engineering
Patchen bedeutet nicht immer das Erweitern, sondern auch das Reduzieren bzw. Ausbessern von Code.
Man springt relativ vom Anfang der Datei die Entfernung zu den gesuchten Variablen/Codestücken. Daher benötigen wir den Offset. Der Patch selbst ist schnell hergestellt, solange es nur kleine Ausbesserungen sind; die Suche nach dem korrekten Stück Code aber, welches wir bearbeiten wollen, oft nicht.
//Tip: Protokollieren Sie alle Daten. Sie werden diese im Patch benötigen. Dazu sollte der Offset, die Speicheradresse, der Hexbefehl und eventuelle Jumpadressen vermerkt werden.
3 Materie
In diesem Artikel zeige ich drei Anwendungsmöglichkeiten. Der erste Fall dient zur Einführung.
Info:
Offsets und Programmadressen verändern sich je nach Compiler und deren Konfiguration. Ich verwende hier CodeBlocks v1.0. Die Programme sind in der Debugversion.
Außerdem sollte sich jeder im Klaren sein, dass der Herausgeber des Programms das Reverse Engineering erlaubt haben muss. Natürlich ist das "Klauen" von Software durch Reverse Engineering strafbar.
3.1 Beispiel: Fehlerkorrektur
Als Einführung in das Thema soll eine Fehlerkorrektur mit einer kleinen Story dienen.
Dazu nehmen Sie bitte diesen Sourcecode als Beispiel:
//prim.c //--------- #include <stdio.h> int main(){ char *primzahlen[15] = {"2", "3", "5", "7", "11", "12", "13", "17", "19", "23", "29", "31", "37", "41", "43"}; // 12 ist keine Primzahl for(int i = 0; i < 15; i++){ printf("%s ",primzahlen[i]); } }
Nehmen wir an, ein Array, gefüllt mit Primzahlen, wäre in einer Funktion, die zu einem Programm gehört, welches wir für eine Firma geschrieben haben.
Nun braucht dieses Programm eine Berechnung der Primzahlen. Denken wir uns einfach es ist eine Datenbank und ein Angestellter, der einigermaßen aufmerksam ist, sieht nun dort eine 12: Er ist entsetzt.
Nun läuft er schnell zum Chef und dieser ruft uns gleich, mit leicht zorniger Stimme, an. Er verlangt, dass wir das Problem auf der Stelle beheben sonst...
Also haben wir uns noch einmal den Quellcode angeguckt. Und da war diese 12.
Es war die Einzige falsche Primzahl in diesem Array (eigentlich kann man diese auch berechnen, aber mir ist kein besseres Problem eingefallen).Nun patchen wir diese 12.
Kompilieren wir den Quelltext oben und schmeißen das Programm erst in OllyDBG.
Nun müssten wir das Programm in hexadezimaler Form und in Assemblercode sehen.
Mit den Schritten
- rechte Maustaste im Hauptfenster
- Search For
- All Referenced Text Stringsbekommen wir alle Strings im Programm aufgelistet.
wir klicken auf unsere ASCII "%s", womit unsere 12 ausgegeben wird.
004012C0 |> C70424 2930400>MOV DWORD PTR SS:[ESP],prim.00403029 ; |ASCII "%s "
Nun werden wir zur Stelle im Programm geleitet, wo wir auf den Stack zugreifen.
Wir sehen, dass unsere Variable auf dem Stack bei der Adresse prim.00403029 ist.
Dies ist die .rdata-Sektion, wie man sich mit den entsprechenden Zahlen unter View -> Memory bewusst machen kann.
Nun beenden wir OllyDBG und benutzen PEID. Mit PEID kann man sehr leicht den benötigten Offset einsehen.
Wir öffnen noch einmal das Programm und gehen wie folgt vor:
Der Pfeil bei EP Sektion führt uns zu der Sektionsübersicht. Unser Code ist in .rdata.
Rechtsklick -> Hexviewer zeigt uns nun die hexadezimale Ansicht unserer Sektion.
Wir sehen schon gleich unsere nette 12 dastehen[gekennzeichnet als 31 32 hex],
welches die hexadezimalen Werte der ASCII-Werte sind, die eine Taste kennzeichnen.Mit einem Mausklick auf den Hexcode wird unser Offset unten links angegeben.
Da hätten wir 0x0000100B und 0x0000100C
Für weitere Informationen über Hexeditoren siehe: http://en.wikipedia.org/wiki/Hex_editor
Nun schreiben wir unser Programm, damit wir die 12 entfernen können.
//prim_patch.c //--------- #include <stdio.h> typedef struct { long oSet; int hexV; } PYTE; static PYTE pytes[2] = { {0x0000100B,0x08}, //offsets und hexacode values; {0x0000100C,0x00}, //0x08 für backspace->löscht letztes Nullbyte //0x00 für das Setzen von einem Nullbyte. //Sinn: Schönheitmakel der 2 Leerzeichen //entfernen }; int main(void){ FILE *patchFile = fopen("prim.exe","r+"); for(int i = 0; i < 2; i++){ fseek(patchFile, pytes[i].oSet, SEEK_SET); fwrite(&pytes[i].hexV, 1, 1, patchFile); } fclose(patchFile); }
Somit haben wir unsere 12 los.
Eine komplette Rekonstruktion des Programms hätte einen nicht gerade kleinen Zeitwand gekostet. Durch das Patchen wird der Betrieb nur eine kurze Zeitspanne unterbrochen, um das System zu updaten. Der Chef der Firma nimmt uns den Fehler doch nicht so dermaßen übel und wir sind glücklich.
3.2 If-else-Bedingung aushebeln
Zuerst: Wie wird in Assembly eine If-else-Bedingung ausgedrückt?
Meistens sieht es so aus:
Springe zum else-Zweig
oder
Springe zum if-Zweig
Nehmen Sie bitte dieses Programm als Beispiel:
//if_else.cpp //--------- #include <stdio.h> int cp(char passw[]){ const char p[]= "www.c-plusplus.net"; int n = sizeof(p) / sizeof(char); for(int i = 0; i < n; i++){ if(passw[i] != p[i]){ return 0; } } return 1; } int main(){ char pw[17]; printf("pw_ "); fgets(pw, 18, stdin); if(cp(pw) == 1){ puts("Zugriff gewaehrt"); } else{ puts("Zugriff verweigert"); } }
Zur Erklärung, mit dem "Es gibt nur einen Zweig", hier eine Programmablaufbeschreibung:
schreibe "pw_ "
lese pw
lade passwortstring
vergleiche passwortstring mit pw
wenn gleich bzw. wenn nicht gleich, dann springe
weiterer codeablaufNach einer Suche nach unserer Programmstelle haben wir dies vorliegen:
004012F0 /$ 55 PUSH EBP 004012F1 |. B8 10000000 MOV EAX,10 004012F6 |. 89E5 MOV EBP,ESP 004012F8 |. 53 PUSH EBX 004012F9 |. 83EC 54 SUB ESP,54 004012FC |. 83E4 F0 AND ESP,FFFFFFF0 004012FF |. E8 EC040000 CALL if_else.004017F0 00401304 |. E8 87010000 CALL if_else.00401490 00401309 |. C70424 1230400>MOV DWORD PTR SS:[ESP],if_else.00403012 ; ||ASCII "pw_ " 00401310 |. BB 12000000 MOV EBX,12 ; || 00401315 |. E8 96050000 CALL <JMP.&msvcrt.printf> ; |\printf 0040131A |. 8B0D DC504000 MOV ECX,DWORD PTR DS:[<&msvcrt._iob>] ; |msvcrt._iob 00401320 |. 895C24 04 MOV DWORD PTR SS:[ESP+4],EBX ; | 00401324 |. 8D5D D8 LEA EBX,DWORD PTR SS:[EBP-28] ; | 00401327 |. 894C24 08 MOV DWORD PTR SS:[ESP+8],ECX ; | 0040132B |. 891C24 MOV DWORD PTR SS:[ESP],EBX ; | 0040132E |. E8 6D050000 CALL <JMP.&msvcrt.fgets> ; \fgets 00401333 |. 8B15 00304000 MOV EDX,DWORD PTR DS:[403000] 00401339 |. 8955 B8 MOV DWORD PTR SS:[EBP-48],EDX 0040133C |. A1 04304000 MOV EAX,DWORD PTR DS:[403004] 00401341 |. 8945 BC MOV DWORD PTR SS:[EBP-44],EAX 00401344 |. 8B0D 08304000 MOV ECX,DWORD PTR DS:[403008] 0040134A |. 894D C0 MOV DWORD PTR SS:[EBP-40],ECX 0040134D |. 8B15 0C304000 MOV EDX,DWORD PTR DS:[40300C] 00401353 |. 8955 C4 MOV DWORD PTR SS:[EBP-3C],EDX 00401356 |. 31D2 XOR EDX,EDX 00401358 |. 0FB705 1030400>MOVZX EAX,WORD PTR DS:[403010] 0040135F |. 66:8945 C8 MOV WORD PTR SS:[EBP-38],AX 00401363 |. 8DB6 00000000 LEA ESI,DWORD PTR DS:[ESI] 00401369 |. 8DBC27 0000000>LEA EDI,DWORD PTR DS:[EDI] 00401370 |> 0FB64C2A B8 /MOVZX ECX,BYTE PTR DS:[EDX+EBP-48] ; | 00401375 |. 3A0C1A |CMP CL,BYTE PTR DS:[EDX+EBX] ; | 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ; | 0040137A |. 42 |INC EDX ; | 0040137B |. 83FA 12 |CMP EDX,12 ; | 0040137E |.^7C F0 \JL SHORT if_else.00401370 ; | 00401380 |. C70424 1730400>MOV DWORD PTR SS:[ESP],if_else.00403017 ; |ASCII "Zugriff gewaehrt" 00401387 |. E8 04050000 CALL <JMP.&msvcrt.puts> ; \puts 0040138C |. 8B5D FC MOV EBX,DWORD PTR SS:[EBP-4] 0040138F |. 31C0 XOR EAX,EAX 00401391 |. C9 LEAVE 00401392 |. C3 RETN 00401393 |> C70424 2830400>MOV DWORD PTR SS:[ESP],if_else.00403028 ; |ASCII "Zugriff verweigert" 0040139A |. E8 F1040000 CALL <JMP.&msvcrt.puts> ; \puts 0040139F |. 8B5D FC MOV EBX,DWORD PTR SS:[EBP-4] 004013A2 |. 31C0 XOR EAX,EAX 004013A4 |. C9 LEAVE 004013A5 \. C3 RETN
Bedingter Sprung: 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ;
Zur Erklärung:
0040132E |. E8 6D050000 CALL <JMP.&msvcrt.fgets> ; \fgets 00401333 |. 8B15 00304000 MOV EDX,DWORD PTR DS:[403000] 00401339 |. 8955 B8 MOV DWORD PTR SS:[EBP-48],EDX 0040133C |. A1 04304000 MOV EAX,DWORD PTR DS:[403004] 00401341 |. 8945 BC MOV DWORD PTR SS:[EBP-44],EAX 00401344 |. 8B0D 08304000 MOV ECX,DWORD PTR DS:[403008] 0040134A |. 894D C0 MOV DWORD PTR SS:[EBP-40],ECX 0040134D |. 8B15 0C304000 MOV EDX,DWORD PTR DS:[40300C] 00401353 |. 8955 C4 MOV DWORD PTR SS:[EBP-3C],EDX 00401356 |. 31D2 XOR EDX,EDX 00401358 |. 0FB705 1030400>MOVZX EAX,WORD PTR DS:[403010] 0040135F |. 66:8945 C8 MOV WORD PTR SS:[EBP-38],AX 00401363 |. 8DB6 00000000 LEA ESI,DWORD PTR DS:[ESI] 00401369 |. 8DBC27 0000000>LEA EDI,DWORD PTR DS:[EDI]
Hier schreiben wir den String "www.c-plusplus.net" in den Speicher und lesen pw ein.
00401370 |> 0FB64C2A B8 /MOVZX ECX,BYTE PTR DS:[EDX+EBP-48] ; | 00401375 |. 3A0C1A |CMP CL,BYTE PTR DS:[EDX+EBX] ; | 00401378 |. 75 19 |JNZ SHORT if_else.00401393 ; |
Hier ist der Test auf Null, welcher unserer Sprung ist.
In diesem Fall müssen wir unseren Sprung nur noppen (No Operation; Hexcode 90).
Wie man sieht, haben wir 2 hexadezimale Befehle (75 und 19).
Dieses Mal befindet sich alles in der Text-Sektion, außer unserem Passwortstring.
Diese fängt bei mir bei 00401000 an und endet bei 00402000.
//Tip: Falls unser Stück in der Text-Sektion ist, können wir den Offset auch Ausrechnen:
Absolute Adresse des Befehles
Absolute Adresse der .text-Sektion
+
400Daher:
00401378 - 00401000 = 0x00000378
0x0000038C + 400 = 0x00000778
Unsere Matrix für den Patch sieht also nun so aus:
static PYTE pytes[2] = { {0x00000778,0x90}, //75 {0x00000779,0x90}, //19 };
Wie Sie den Passwortstring ändern, können Sie 4.1 entnehmen.
3.3 Schleifenbedingung korrigieren
Nun widmen wir uns den Schleifenbedingungen. Als Beispiel nehme ich hier eine for-Schleife.
Dieses Programm
//condition.c //--------- #include <stdio.h> int main(){ for(int i = 0; i < 10; i++){ putc(i+65,stdout); } }
ist an dieser Stelle beachtenswert:
00401290 /$ 55 PUSH EBP 00401291 |. B8 10000000 MOV EAX,10 00401296 |. 89E5 MOV EBP,ESP 00401298 |. 56 PUSH ESI 00401299 |. 53 PUSH EBX 0040129A |. 83EC 10 SUB ESP,10 0040129D |. 83E4 F0 AND ESP,FFFFFFF0 004012A0 |. E8 9B040000 CALL conditio.00401740 004012A5 |. E8 36010000 CALL conditio.004013E0 004012AA |. 31DB XOR EBX,EBX 004012AC |. EB 12 JMP SHORT conditio.004012C0 004012AE | 89F6 MOV ESI,ESI 004012B0 |> 8B42 20 MOV EAX,DWORD PTR DS:[EDX+20] 004012B3 |. 89F1 MOV ECX,ESI 004012B5 |. 43 INC EBX 004012B6 |. 8808 MOV BYTE PTR DS:[EAX],CL 004012B8 |. FF42 20 INC DWORD PTR DS:[EDX+20] 004012BB |. 83FB 09 CMP EBX,9 004012BE |. 7F 29 JG SHORT conditio.004012E9 004012C0 |> 8B15 D8504000 /MOV EDX,DWORD PTR DS:[<&msvcrt._iob>] ; msvcrt._iob 004012C6 |. 8D73 41 |LEA ESI,DWORD PTR DS:[EBX+41] 004012C9 |. 8D4A 20 |LEA ECX,DWORD PTR DS:[EDX+20] 004012CC |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] 004012CF |. 48 |DEC EAX 004012D0 |. 85C0 |TEST EAX,EAX 004012D2 |. 8941 04 |MOV DWORD PTR DS:[ECX+4],EAX 004012D5 |.^79 D9 |JNS SHORT conditio.004012B0 004012D7 |. 894C24 04 |MOV DWORD PTR SS:[ESP+4],ECX 004012DB |. 43 |INC EBX 004012DC |. 893424 |MOV DWORD PTR SS:[ESP],ESI 004012DF |. E8 FC040000 |CALL <JMP.&msvcrt._flsbuf> 004012E4 |. 83FB 09 |CMP EBX,9 004012E7 |.^7E D7 \JLE SHORT conditio.004012C0 004012E9 |> 8D65 F8 LEA ESP,DWORD PTR SS:[EBP-8] 004012EC |. 31C0 XOR EAX,EAX 004012EE |. 5B POP EBX 004012EF |. 5E POP ESI 004012F0 |. 5D POP EBP 004012F1 \. C3 RETN
Hier nun die Erklärung:
00401290 /$ 55 PUSH EBP 00401291 |. B8 10000000 MOV EAX,10 00401296 |. 89E5 MOV EBP,ESP 00401298 |. 56 PUSH ESI 00401299 |. 53 PUSH EBX 0040129A |. 83EC 10 SUB ESP,10 0040129D |. 83E4 F0 AND ESP,FFFFFFF0 004012A0 |. E8 9B040000 CALL conditio.00401740 004012A5 |. E8 36010000 CALL conditio.004013E0
Nicht beachtenswert.
004012AA |. 31DB XOR EBX,EBX
EBX wird auf 0 gesetzt.
004012AC |. EB 12 JMP SHORT conditio.004012C0 004012AE | 89F6 MOV ESI,ESI 004012B0 |> 8B42 20 MOV EAX,DWORD PTR DS:[EDX+20] 004012B3 |. 89F1 MOV ECX,ESI 004012B5 |. 43 INC EBX 004012B6 |. 8808 MOV BYTE PTR DS:[EAX],CL 004012B8 |. FF42 20 INC DWORD PTR DS:[EDX+20]
Nicht beachtenswert.
004012BB |. 83FB 09 CMP EBX,9 004012BE |. 7F 29 JG SHORT conditio.004012E9
Prüfe das erste mal, ob EBX größer als 9 ist (10).
Nun kommen wir in eine Schleife.
004012C0 |> 8B15 D8504000 /MOV EDX,DWORD PTR DS:[<&msvcrt._iob>] ; msvcrt._iob 004012C6 |. 8D73 41 |LEA ESI,DWORD PTR DS:[EBX+41]
41 Hexedezimal ist 65 Dezimal..
004012C9 |. 8D4A 20 |LEA ECX,DWORD PTR DS:[EDX+20] 004012CC |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] 004012CF |. 48 |DEC EAX 004012D0 |. 85C0 |TEST EAX,EAX 004012D2 |. 8941 04 |MOV DWORD PTR DS:[ECX+4],EAX 004012D5 |.^79 D9 |JNS SHORT conditio.004012B0 004012D7 |. 894C24 04 |MOV DWORD PTR SS:[ESP+4],ECX 004012DB |. 43 |INC EBX
EBX += 1
004012DC |. 893424 |MOV DWORD PTR SS:[ESP],ESI 004012DF |. E8 FC040000 |CALL <JMP.&msvcrt._flsbuf> 004012E4 |. 83FB 09 |CMP EBX,9 004012E7 |.^7E D7 \JLE SHORT conditio.004012C0
Prüfe, ob EBX größer als 9 ist.
Dieser Teil ist unsere Bedingung.
Wir müssen nur den Wert 9 ändern. Damit bezwecken wir eine andere Bedingung.
Eine Endlosschleife würden wir herbeirufen, wenn wir
004012DB |. 43 |INC EBX
Noppen würden.
4 Kurzreferenz: Hexbefehle
Zum Abschluss noch eine kleine hexadezimale Befehlstabelle:
74 JE Springe wenn gleich 75 JNE Springe wenn nicht gleich 77 JA Springe wenn größer 0F86 JNA Springe wenn nicht größer 0F8C JL Springe wenn kleiner 0F87 JNL Springe wenn nicht kleiner 0F83 JGE Springe wenn größer oder gleich 0F8E JLE Springe wenn kleiner oder gleich EB JMP Unbedingter Sprung 90 NOP Keine Operation
Für den Asciizeichensatz in Hexadezimal können Sie sich dies anschauen: http://en.wikipedia.org/wiki/Ascii
MFG winexec*
- Softwareseitige Voraussetzungen
-
Hi
ich habe mir den Artikel leider nicht sehr gründlich anschauen können, ich bin zur Zeit etwas im Stress... Ich hoffe, ich habe nicht zu viele Fehler übersehen. Ich habe deshalb auch keine kor-Tags gesetzt.@winexec: irgendwo im Artikel steht noch "[ich nehme an]" ... willst du das drin lassen? Wenn nicht, lösch es einfach, wenn du meinen Beitrag übernimmst.
-
Post-Veröffentlichungs Kritik von mir:
Ich wollte mich eigentlich noch am Review beteiligen, habe es aber nicht ganz geschafft. Aber ich habe noch ein wenig Kritik an dem Artikel
1. Scheinen die rechtlichen Grundlagen ein wenig falsch dargestellt zu werden. Es gibt da irgend einen Software-Nutzungs-Abkommen der EU, in dem ganz klar steht, das Reverse Engineering erlaubt ist. Daher ist es fragwürdig ob die entsprechenden Passagen in der EULA gültig sind. Afaik gibt es da auch keine Präzedenz-Fälle, weil man Cracker ja eher wegen dem Verbreiten von Kopien verfolgt und bestraft.
2. Geht dein Source-Code (Laptop-Auflösung: 1024x768) über die Seitenbegrenzung hinaus. Das solltest du noch ändern, da es den Lesefluss stört. (Allgemein: Im Grunde fände ich eine generelle Richtlinie von 80 Buchstaben oder sogar kleiner gut. Sonst stehen wir bei einer gedruckten Ausgabe vor Formatierungsproblemen. Viele Zeitschriften erlauben für Autoren nur 40 bis 60 Zeilen)
3. Sind einige Textpassagen voller Leerzeilen. Nach jedem Satz folgt eine Leerzeile, das stört den Lesefluss!
Ansonsten ist es aber ein sehr gelungener und wichtiger Artikel. Danke, das du dir die Mühe gemacht hast, dieses verpönte Thema anzuschneiden!
-
Hallo,
Danke. Für deine konstruktive Kritik kann ich mich natürlich nocheinmal an die Arbeit machen. Das mit den Gesetzten ist aber sehr umstritten. Reverse Engineering ist schon erlaubt, aber es gibt keine klaren Grenzen.
Das mit der Formatierung müssten wir einmal allgemein regeln.
MFG winexec*