Sie befinden sich hier: Termine » Prüfungsfragen und Altklausuren » Hauptstudiumsprüfungen » Lehrstuhl 1 » Software Reverse Engineering 2015-08-04   (Übersicht)

Software Reverse Engineering 2015-08-04

Fach: Software Reverse Engineering (SS 2015)

Prüfer: Tilo Müller

Beisitzer: Mykola Protsenko

Note: 1,0

Atmosphäre war sehr entspannt und nett. Wie schon in den früheren Protokollen hat man ein Blatt mit einem Disassembler, Platz für C-Code und vorgezeichnetem Stack bekommen.

Außerdem gab es während der Prüfung mehrmals Auszüge aus Vorlesungsfolien (Integer-Overflows, Shellcode), die man dann erklären musste oder darin rumfuhrwerken durfte.

Vorbereitet habe ich mich dadurch, dass ich die Vorlesungsfolien zusammengefasst habe und zusätzlich nochmal die Exploit-Techniken auf einem Blatt Papier geübt habe. Einen Teil der Übungen habe ich nochmal überflogen (um in das Assembler-Lesen wieder rein zu kommen), aber man hat ja relativ viel Zeit mit den Übungen verbracht, so dass ich die nicht nochmal im Detail durchgegangen bin.

Fragen

(Der Disassembler war von der strchr-Funktion. Also Suchen und Zurückgeben des ersten Vorkommens eines bestimmten chars in einem String.)

F: 32 oder 64 Bit?

A: 32 wegen e's im Registernamen

F: Kannst du auch schon sagen für welche Plattform das Programm vermutlich geschrieben wurde?

A: Da cdecl-Konvention benutzt wurde wahrscheinlich Linux

F: Was macht denn die cdecl-Konvention aus?

A: Parameter in umgekehrter Reihenfolge auf den Stack, caller-cleanup, Rückgabewert in eax.

F: Was bedeutet denn caller-cleanup?

A: Dass die aufrufende Funktion die Argumente vom Stack räumt.

F: Genau. Wie heißt denn die Calling Convention von Windows?

A: stdcall

F: Was ist da anders?

A: Die aufgerufene Funktion räumt den Stack auf.

F: Was macht denn Windows bei printf?

A: Da variable Parameterliste schaltet es explizit auf cdecl um.

F: Es gibt ja die Begriffe caller-saved- und callee-saved-Register. Was bedeutet denn das?

A: Man muss ja seine Registerinhalte sichern, bevor sie in einer neuen Funktion verwendet werden und das sagt an welcher Stelle das passiert. Also ob die aufrufende oder die aufgerufene Funktion das macht.

F: Was sind denn die caller-saved-Register bei cdecl?

A: eax, ecx, edx

F: Und bei stdcall?

A: Genauso

F: Okay zurück zum Assembler. Wieviele Parameter haben wir denn und wo stehen die?

F: Kannst du schon erkennen welchen Typ sie haben?

A: Einer ist entweder eine Ganzzahl oder ein Pointer, der andere ist ein einzelnes Byte also ein char.

(Kurz weiter geschaut)

Und weil hier der erste Parameter derefenziert wird, ist der erste wohl ein Pointer.

F: Soweit richtig. Weißt du auch welchen Typ der Pointer hat?

A: Weil bei der Derefenzierung ein einzelnes Byte geschrieben wird, wohl ein char-Pointer.

F: Gut. Dann schau mal, ob du noch herausfindest, welchen Typ der Rückgabewert hat und dann können wir schon mal den Funktionsrumpf hinschreiben.

Rückgabetyp: char* und Rumpf hingeschrieben

F: Wir haben hier ja eine Schleife. Weil am Anfang ein Sprung zur Bedingung ist, sage mir doch erstmal was dort passiert. (Es war ein loop-body und ein loop-cond Label im Code gegeben).

F: Okay und jetzt was im Schleifenrumpf passiert.

F: Dann schreib mal noch den restlichen C-Code.

F: Okay, gibt es lokale Variablen?

A: Nein.

F: Gut, dann mal doch mal den Stackaufbau.

F: Kannst du esp und ebp auch noch einzeichnen?

F: Gut kommen wir zum EFLAGS-Register. Es gibt ja ein Carry und ein Overflow Bit. Warum gibt es zwei?

A: Eines ist für vorzeichenlose, das andere für vorzeichenbehaftete Überlaufe

F: Weißt du auch welches für welches ist? Wenn nicht, ist auch nicht so schlimm.

A: Carry für vorzeichenlos, Overflow für vorzeichenbehaftet

F: Ähm, jetzt muss ich selber nochmal kurz nachschauen, ich verwechsel die immer. Ja stimmt.

F: Kannst du mir sagen, welches von beiden bei dieser Rechnung mit 8-bit-Zahlen gesetzt wird:

0xff + 0x01

A: Das Carry-Flag

F: Hast du auch ein Beispiel, bei dem beide gesetzt werden?

A:

 0x80 + 0xff 

F: Und jetzt eins, wo nur das Carry-Flag gesetzt wird?

A: Hab schon angefangen das dritte Beispiel zu malen. Ähh, nur Carry-Flag steht schon da oben.

F: (lacht) Äh, ja wie gesagt, leicht zu verwechseln. Nur das Overflow-Flag natürlich.

A:

 0x81 - 0x02 

F: Super. Es gibt ja den Assemblerausdruck repz ret. Ein return zu wiederholen macht doch gar keinen Sinn, warum macht der Compiler das?

A: Das ist eine Optimierung für AMD-Prozessoren.

F: Wie das? Also was ist da anders?

A: Das ist ein 2-Byte Befehl durch das repz-Präfix und das scheint besser für das Cache-Aligning zu sein.

F: Du hast das Präfix erwähnt, wie ist denn so ein Befehl aufgebaut?

A: (Hab hier halt das Format aufgemalt und dazu erzählt, was wo steht/wie groß. Den genauen Aufbau von Mod R/M und SIB wollte er dann gar nicht mehr wissen.)

F: Was ist denn der Unterschied von Displacement und Immediate?

A: Immediate ist für Konstanten, Displacement für Adresszugriff mit festen Adressen. (Genauer wäre gewesen, dass der feste Teil einer Adresse im Displacement gespeichert wird)

F: Wozu gehört denn das Displacement noch?

A: Zur SIB-Adressierung

F: Und wie sieht da die Formel aus?

A:

 base + idx*scale + displacement 

F: Und das scale hat 2-Bit, kann also 0,1,2,3 sein?

A: Also es kann 4 Werte annehmen, aber 1,2,4 und 8 um die typischen Wortbreiten zu unterstützen.

F: Okay. Frame-Pointer-Omission, was ist das? Normalerweise wird ja per ebp addressiert, wie funktioniert das?

A: Da spart man sich den ebp und addressiert immer per esp. Dadurch spart man sich im Prolog/Epilog Instruktionen und hat ebp als General Purpose Register frei.

F: Genau, kannst du an dem Beispiel mal FPO anwenden?

A: Erstmal alle unnötigen Instruktionen gestrichen und bei Adressierungen das ebp durch esp ersetzt

F: Soweit gut, aber es fehlt noch was.

A: Ja? (schaut ob er ein ebp übersehen hat)

F: Ja so indirekt ist noch was bei den Adressierungen.

A: Achja, wir haben ja das push weggestrichen, also muss die Addressierung nicht mehr ebp+0x8 und ebp+0xc sein, sondern esp+0x4 und esp+0x8.

F: Genau, jetzt passts. Kannst du im Stack noch esp/ebp anpassen?

A: streicht ebp und setzt esp eins nach oben

F: Okay, Shellcode. Ihr durftet den in der Übung ja einfach kopieren, wir haben in der Vorlesung für Linux aber mal selber einen entwickelt. Ich hast du mal den ersten Schritt aus den Vorlesungsfolien (also den Code mit Datenseqment). Welche Eigenschaften muss so ein Shellcode haben, damit er auf dem Stack ausführbar ist und per str-Operationen übertragen werden kann?

A: Er darf kein Datenseqment haben und er darf keine 0-Bytes haben, sonst wird nur der halbe Shellcode kopiert.

F: Fang mal damit an, das Datenseqment zu entfernen.

A: Ich hab dann das „section .data“ gestrichen und gleich erklärt, dass man aber noch irgendwie einen Pointer des „/bin/sh“-Strings auf dem Stack braucht und man deswegen ein call benutzt. Dazu noch die das „code“ und „data“-Label aus der Vorlesung eingezeichnet und ein „call code“.

F: Warum jetzt ein call?

A: Damit wird ja die nächste Adresse (also hier den String) auf den Stack gelegt, als Return-Instruction-Pointer.

F: Hier fehlt jetzt aber noch was.

A: Ja, das jmp data.

F: Und welchen Befehl ersetzt das jetzt?

A: Das push args

F: Gut, dann die 0-Bytes. Was ist mit den mov edx, 0? (Was aus dem „push 0“ geworden ist, habe ich vergessen).

A: Einfach xor edx, edx verwenden.

F: Und bei dem mov eax, 11 ist noch ein verstecktes. Warum? Wie macht man das?

A: Naja weil das 11 ein 0x0…0b ist. Da kann man einfach nur die unteren 8-Bit von eax beschreiben.

F: Okay. Was ist mit der 0 bei dem „/bin/sh“,0?

A: Naja, das tut da ja eigentlich nicht weh, ist ja eh am Ende des Shellcodes.

F: (stutzt). Hmm, stimmt. In der Vorlesung haben wir da ja noch eine '0' genommen und die dann manuell genullt, so selbstmodifizierender Code und so. Aber du hast natürlich Recht.

F: Okay du bekommst jetzt nochmal ein bisschen Code aus der Vorlesung zu Integer-Overflow. Wir gehen jetzt mal die drei Beispiele durch und du erklärst mir was dabei jeweils das Problem ist. (Siehe Folien. Wenn man mal eine Kleinigkeit übersehen hat, z.B. dass der Datentyp oben gegeben ist, wurde man darauf hingewiesen.)

F: (Am Schluss kam noch eine schnelle Frage zum Off-by-One Exploit, aber ich kann mich an den Wortlaut nicht mehr erinnern. Ihm hat aber eine schnelle allgemeine Antwort gereicht.)

F: Geht das auch mit FPO?

A: Nö, da ist ja kein Stackframe, also kein ebp mehr da.

(Disclaimer: Ich glaube ein paar Fragen fehlen und die Reihenfolge dürfte anders gewesen sein. Auch die Antworten sind knapper als in der Prüfung.)