Skip to content

Instantly share code, notes, and snippets.

@th0rex
Last active March 27, 2018 19:09
Show Gist options
  • Save th0rex/0026ba83ea1e6e625515c6164f29c1f8 to your computer and use it in GitHub Desktop.
Save th0rex/0026ba83ea1e6e625515c6164f29c1f8 to your computer and use it in GitHub Desktop.
% Sprache und Schriftart einstellen
\usepackage[ngerman]{babel}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{helvet}
\renewcommand{\familydefault}{\sfdefault}
% Taken from http://pr0gramm.com/top/latex/2286360 and adapted
\usepackage{minted}
% Farben definieren
\usepackage[usenames,dvipsnames]{xcolor}
\definecolor{echtes_grau}{HTML}{161618}
\definecolor{bewaehrtes_orange}{HTML}{ee4d2e}
\definecolor{std_weiß}{HTML}{f2f5f4}
\definecolor{std_ausgegraut}{HTML}{888888}
\definecolor{olivgruen_des_friedens}{HTML}{bfbc06}
\definecolor{link_blau}{HTML}{75c0c7}
\definecolor{altes_prink}{HTML}{ff0082}
\definecolor{episches_blau}{HTML}{008fff}
\definecolor{gebannt}{HTML}{444444}
\definecolor{fliesentisch}{HTML}{6c432b}
\definecolor{neuschwuchtel}{HTML}{e108e9}
\definecolor{schwuchtel}{HTML}{ffffff}
\definecolor{edler_spender}{HTML}{1cb992}
\definecolor{altschwuchtel}{HTML}{5bb91c}
\definecolor{moderator}{HTML}{008fff}
\definecolor{administrator}{HTML}{ff9900}
\definecolor{lebende_legende}{HTML}{1cb992}
% Farben setzen
\pagecolor{echtes_grau}
\color{std_weiß}
% Grafikpakete für Bilder und Vektorgrafiken
\usepackage{graphicx}
\usepackage{float}
% Hyperref konfigurieren
\usepackage{hyperref}
\usepackage[all]{hypcap}
\hypersetup{
colorlinks,
urlcolor={link_blau},
linkcolor={link_blau}
}
\urlstyle{same}
% Blocksatzformatierung
\addtolength\emergencystretch{0.2\linewidth}
% Makro: Bilder (\image{path}{caption}{width}{label})
\newcommand{\image}[4]{
\begin{figure}[H]
\centering
\includegraphics[width=#3\linewidth]{#1}
\caption{#2}
\label{#4}
\end{figure}
}
% Makro: Titel
\renewcommand{\title}[1]{
\Huge
\color{bewaehrtes_orange}
\textbf{#1}
\newline
\newline
\normalsize
\color{std_weiß}
}
% Makro: Überschriften
\newcommand{\heading}[1]{
\leavevmode\\\\
\Large
\color{bewaehrtes_orange}\\
\textbf{#1}
\leavevmode\\
\normalsize
\color{std_weiß}
}
% Makro: altes Pink
\newcommand{\pink}[1]{
\color{altes_prink}
#1
\color{std_weiß}
}
\newcommand{\blt}{
\item[
\textcolor{olivgruen_des_friedens}
{\textbullet}
]
}
\newcommand{\subblt}{
\item[
\textcolor{episches_blau}
{-}
]
}
\newcommand{\register}[1]{{
\color{altschwuchtel}{#1}
}}
\newcommand{\mnemonic}[1]{{
\color{neuschwuchtel}{#1}
}}
\newcommand{\imm}[1]{{
\color{episches_blau}{#1}
}}
\newcommand{\name}[1]{{
\color{bewaehrtes_orange}{#1}
}}
\newcommand{\operand}[1]{
\register{#1}
}
\usepackage{ragged2e}
\usemintedstyle{perldoc}
\documentclass[varwidth,border=0.2in]{standalone}
\input{config}
\begin{document}
\title{Reverse Engineering Teil 1}
\raggedright
Moin und frohes Neues! Da hier ja recht großes Interesse an den drei Posts über hacking von
\color{bewaehrtes_orange}KernelNoName
\color{std_weiß}
bestand und sogar ein ctf Team für die 34C3 ctf gebildet wurde, schreibe
ich hier mal ein writeup zur \color{episches_blau}m0rph \color{std_weiß}
challenge aus der ctf. In diesem p0st versuche ich euch zu zeigen was so
meine Gedankengänge beim lösen von der Challenge waren, aber ich werde
euch auch eine kleine Einführung ins reverse engineering geben, denn
dies brauchte man hier. \\
Falls Interesse besteht kann ich auch noch ein writeup bzw. meinen Prozess
beim solven von der Challenge \color{episches_blau}readme\_revenge \color{std_weiß}
schreiben; die Challenge war aus der Kategorie binary exploitation, man musste also
ein Programm nur durch Eingaben (z.b. auf der Konsole) unter seine Kontrolle bekommen
(was imho recht interessant und vielseitig ist).
\justify
\heading{Was ist dieses Reverse Engineering}
\raggedright
Generell hat man beim reverse engineering ein fertiges Produkt und möchte dies in seine
\color{altes_prink}Bestandteile zerlegen.
\color{std_weiß}
In diesem Fall haben wir ein ausführbares Programm vorliegen und wir wissen, dass wir
die Flagge (welche ja meistens nur Text ist) finden sollen. Beim 34C3 fingen alle Flaggen
mit ``34C3\_'' an, somit ist es deutlich erkennbar wenn man die Flagge gefunden hat.
\justify
\heading{Solven von m0rph}
\raggedright
Führen wir also erstmal das Programm aus, vielleicht findet sich da schon die Flagge oder
ein Hinweis darauf, wie das Programm funktioniert.
\begin{minted}{zsh}
th0rex@void /v/R/3/r/m/m0rph> ./morph
th0rex@void /v/R/3/r/m/m0rph>
\end{minted}
Das ist jetzt gar nicht mal so viel an output. Das Programm erwartete auch keine Eingabe von uns.
Was nun ? Vielleicht findet sich die Flagge ja als Klartext in dem Programm an sich ... ?
Also erstmal gegoogelt wie man nochmal strings aus einem Programm liest und unsere shell skills
rausgeholt.
\begin{minted}{zsh}
th0rex@void /v/R/3/r/m/m0rph> strings morph | grep 34C3
th0rex@void /v/R/3/r/m/m0rph>
\end{minted}
Verflixt nochmal! In dem Programm gibt es auch keine strings die mit ``34C3'' anfangen...
Vielleicht kann man ja irgendwie die Befehle sehen, die das Programm auf dem Prozessor ausführt.
\justify
\heading{Einführung in x86 Assembly und Prozessorarchitektur}
\raggedright
Bevor wir uns die \color{episches_blau}Instructions, \color{std_weiß}also die \color{episches_blau}Anweisungen,
\color{std_weiß} die auf dem Prozessor ausgeführt werden
angucken, sollten wir uns erst angucken wie dieses magische Stück hardware überhaupt funktioniert.\\
Wie schon erwähnt gibt es sogenannte \color{episches_blau}Instructions \color{std_weiß}. Diese
\color{episches_blau}Instructions \color{std_weiß} haben allgemein gesagt folgende Darstellung als Text:
\begin{minted}{nasm}
instruction operand1, operand2, ..., operandN
\end{minted}
\color{std_weiß}
Dabei ist \mint{nasm}|instruction| ein \imm{Befehl} den der Prozessor ausführen soll.
Gängige Befehle sind z.B.
\color{neuschwuchtel}mov, add, sub, call, jmp \color{std_weiß}. \\
Die Idee von diesen mnemonics ist, dass sie (teilweise sehr abgekürzt) beschreiben was sie tun.
\begin{minted}{nasm}
mov operand1, operand2
\end{minted}
setzt \register{operand1} auf den Wert in \register{operand2}. \\
\begin{minted}{nasm}
add operand1, operand2
\end{minted}
addiert \register{operand2} auf \register{operand1} und speichert das Ergebnis in \register{operand1}.\\
\begin{minted}{nasm}
sub operand1, operand2
\end{minted}
subtrahiert \register{operand2} von \register{operand1}.\\
\mint{nasm}|call operand1| ruft eine Funktion an der Addresse \register{operand1} auf. \\
\mint{nasm}|jmp operand1| springt an zu der Addresse \register{operand1}. \\
Wenn man des Englischen mächtig ist, kann man die meisten wahrscheinlich erraten. Ansonsten benutzt man
dieses Sache im Neuland, die von manchen Google genannt wird.
In dem kleinen Absatz sind vielleicht auch einige unbekannte Begriffe gefallen. Eine \imm{Addresse} ist einfach nur eine Zahl.
Euer Arbeitsspeicher (RAM) wird einfach von \imm{0} bis \imm{zu einer recht großen Zahl} durchnummeriert und an diesen Zahlen
befinden sich irgendwelche Werte. Ihr könnt euch das wie eine Paketstation vorstellen.
In Fach \imm{0} könnte ein Paket liegen, was den Text der E-Mail, die ihr grade verfasst, beinhaltet. Wenn euer brauser jetzt
die E-Mail verschickt, sendet er einfach den Text aus Fach \imm{0} an den netten E-Mail Server, der hoffentlich nicht mitliest.
Für die Genauigkeit will ich noch hinzufügen, dass in jedes Fach von der Paktestation eigentlich nur ein \name{Byte} passt, somit
würde eure E-Mail in ganzen vielen Fächern in z.b. einer Reihe liegen. \\
Kurz zur Erinnering, ein \name{Byte} kann \imm{256} verschiedene Werte beinhalten, von \imm{0x0} bis zu \imm{0xFF}.
\imm{0x} bezeichnet dabei, dass die folgenden Zeichen im \name{Hexadezimal} Format interpretiert werden. \\
Aber an diesen Positionen im Arbeitsspeicher liegen irgendwo auch die \imm{Instructions}
von dem Programm was ihr grade ausführt, die euer Apfel, Fenster oder Pinguin Betriebssystem netterweise dort hinein
geladen hat. \\
Wie ihr vielleicht wisst ist der Zugriff auf den Arbeitsspeicher aber doch recht langsam. Deswegen gibt es zum einen
\imm{Cache} in eurer CPU, der einfach nur etwas schneller ist, und vorallem direkt in
der CPU ist. Außerdem gibt es noch \imm{Register}, von denen es viel viel weniger gibt als
es Cache gibt, dafür ist der Zugriff auf diese praktisch sofort möglich, wohingegen man beim Cache teilweise auch 10-30ns
warten muss (was extrem viel ist, moderne CPUs schaffen mehrere Befehle pro Nanosekunde). Diese \imm{Register} haben alle einen
Namen. \\
Zu den Registern z.b. \register{rax, rbx, rcx, rdx, rip, rsi, rdi, rsp, rbp, 8-r15}.
Diese Register werdet ihr gleich sehr oft sehen, deswegen gehe ich ein bisschen mehr auf diese ein. \\
Bei manchen von ihnen steht die Abkürzung auch wieder direkt für ihren Nutzen. \register{rip}
ist der \name{InstructionPointer}, also die Zahl von der Packetbox wo sich die nächste
\imm{Instruction} befindet, die ausgeführt wird. \register{rsp}
ist der \name{StackPointer}, der zeigt auf das letzte Element im Stack. Dies könnt ihr
euch als speziellen Address Bereich vorstellen, wo Funktionen lokale Variablen ablegen können. \\
Wir euch vielleicht aufgefallen ist, fangen diese Register alle mit ``r'' an. Das bezeichnet hierbei, dass wir gerne die
\imm{64 Bit Variante} von dem Register wollen. Tauscht ihr das ``r'' durch ein ``e'' aus,
greift ihr auf die \imm{32 Bit Variante} von dem Register zu.
\justify
\heading{Was bringt mir der ganze Scheiß}
\raggedright
Als ich über \imm{Instructions} geschrieben habe, hatte ich \operand{Operanden} noch nicht erklärt.
Dies sind Dinger, die einer Instruction sagen wo die Daten sind, mit denen sie arbeiten soll.
Diese \operand{Operanden} können vieles sein. Einfach nur \imm{Zahlen}, \register{Register}
oder auch unsere altbekannten \imm{Paketboxen}.
Speicherzugriff (also Paketboxen) geschiet folgendermaßen
\begin{minted}{nasm}
mov ebx, dword [0x12345678]
; ^ liest von der Addresse 0x12345678
; und schreibt den Wert an der Addresse
; in ebx.
; Das dword sagt, dass 4 Bytes
; gelesen werden
mov rax, qword [register + register2 + 8 * register3]
; ^ liest von der Addresse die sich aus
; register + register2 + 8 * register3
; berechnet
\end{minted}
Also merken, das Zeug in den eckigen Klammern ist immer ein \name{Speicherzugriff}, guckt also was in der angegbenen
\imm{Paketbox} drinne ist. Kurz für die Genauigkeit anzumerken ist, dass das auch nicht immer stimmt und Zeug in eckigen
Klammern auch mal kein Speicherzugriff sein kann, davon sind wir aber bei dieser Challenge nicht betroffen.
\justify
% TODO: Vielleicht bisschen auf calls eingehen ?
\heading{Reverse Engineeren von m0rph}
\raggedright
So, nun wollen wir unser frisch gewonnenes Wissen aber auch anwenden. Also öffnen wir das Programm einmal in einem
\name{Disassembler}. Ein \name{Disassembler} zeigt uns die
Instructions von dem Programm an, und kann meistens noch viel viel mehr. Normal würde ich dafür \name{BinaryNinja}
nutzen, welches man sich (auch als demo) von \url{https://binary.ninja} herunterladen kann. Da die Demoversion aber
keine 64 bit Programme disassembeln kann, nutze ich für das writeup mal \name{Radare2}, welches kostenlos ist und von
\url{http://rada.re} heruntergeladen werden kann. Warnung: das Programm hat nur eine ``Benutzeroberfläche'' auf der
Konsole, ist also wahrscheinlich nicht für jeden was. Ich kopier aber immer die Befehle und die Ausgabe hier rein, so dass
ihr auch ohne Programm folgen könnt.
\justify
\heading{Aller Anfang ist schwer}
\raggedright
\begin{minted}{console}
th0rex@void /v/R/3/r/m/m0rph> r2 morph -A
[x] Analyze all flags starting with sym. and entry0 (aa)
...
[0x000007a0]> s main
[0x00000a76]>
\end{minted}
Damit wären wir dann in der \name{main} Funktion von dem Programm, der Teil der uns interessiert.
\begin{minted}{nasm}
; Befehl: pd 24
;(fcn) main 371
; main ();
; var int local_30h @ rbp-0x30
; var int local_24h @ rbp-0x24
; var int local_1ch @ rbp-0x1c
; var int local_18h @ rbp-0x18
; var int local_10h @ rbp-0x10
; var int local_8h @ rbp-0x8
; DATA XREF from 0x000007bd (entry0)
push rbp
mov rbp, rsp
sub rsp, 0x30
mov dword [local_24h], edi
mov qword [local_30h], rsi
mov r9d, 0
mov r8d, 0xffffffff
mov ecx, 0x22
mov edx, 7
mov esi, 0x1000
mov edi, 0
call sym.imp.mmap
mov qword [local_18h], rax
mov rax, qword [local_18h]
mov rdi, rax
call sub.malloc_8d0
mov rcx, qword [0x00202010]
mov rax, qword [local_18h]
mov edx, 0x2f5
mov rsi, rcx
mov rdi, rax
call sym.imp.memcpy
cmp dword [local_24h], 2
je 0xae5
\end{minted}
In echt gibt euch radare da noch ein bisschen mehr aus, aber ich hab das mal auf das wichtigste gekürzt. \\
Generell sieht so etwas ja erstmal nach recht viel aus und kann auch Überwältigend wirken, deswegen gucken wir
uns jetzt nur ``Interessante'' Sachen an.
\justify
\heading{Finden von Interessantem}
\raggedright
Generell Interessant sind die meisten Funktionsaufrufe und alle \imm{Instructions},
die den \name{InstructionPointer} verändern. Diese sind interessant, weil Sachen die den
\name{InstructionPointer} ändern oft an eine Bedingung gebunden sind. Die Bedingung
könnte z.b. sein ``Falls die Flagge falsch ist, beende das Programm, ansonsten geh woanders hin''. Was ändert nun den
InstructionPointer? \mnemonic{jmp, call, jne, je} und viele weitere. Sowohl \mnemonic{jmp} als auch \mnemonic{call} tuen dies
ohne eine Bedingung zu prüfen.
\mnemonic{je} springt zu der angegeben \imm{Addresse}, falls
ein vorheriger Vergleich das Ergebniss hatte, dass die \operand{Operanden} gleich sein.
\mnemonic{je} steht für \name{Jump if Equal} wohingegen
\mnemonic{jne} für \name{Jump if Not Equal} steht, also
springt dies zu der angegebenen \imm{Addresse}, falls der vorherige Vergleich ergab, dass die
\operand{Operanden} unterschiedlich waren. \\
Was sind nun Befehle, welche Operanden vergleichen? In dem Disassembly ist schon ein \mnemonic{cmp}
zu sehen, was ausgeschrieben für \name{Compare} steht, also die beiden \operand{Operanden} vergleicht.
\justify
\heading{Was tut das Interessante nun}
\raggedright
Gucken wir uns nun mal an an welchen Stellen wir in dem disassembly interessantes finden:
\begin{enumerate}
\item
\mint{nasm}|call sym.imp.mmap|
Dies ruft eine Funktion auf, die Funktion wurde schon von unserem Disassembler benannt, scheint also irgendwo
in einer standard Bibliothek zu sein oder zum Betriebsystem zu gehören. Also erstmal \mint{zsh}|man mmap| gemacht oder
gegoogelt.
\begin{minted}{console}
NAME
mmap — map pages of memory
SYNOPSIS
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags,
int fildes, off_t off);
DESCRIPTION
The mmap() function shall establish a mapping between
an address space of a process and a memory object.
\end{minted}
Die Funktion macht also irgendwas mit den DHL \imm{Paketboxen} aka. unserem RAM.
Doch wo sind die Parameter zu der Funktion in dem Assembly Bildchen?
Direkt über dem Aufruf der Funktion!
\begin{minted}{nasm}
mov r9d, 0 ; letztes Argument, off
mov r8d, 0xffffffff ; fünftes Argument, fildes
mov ecx, 0x22 ; viertes Argument, flags
mov edx, 7 ; drittes Argument, prot
mov esi, 0x1000 ; zweites Argument, len
mov edi, 0 ; erstes Argument, addr
\end{minted}
Welches Register welchen Parameter beinhaltet wird durch die sogenannte \name{Calling Convention} festgelegt.
Da wir eine Funktion, die in C geschrieben wurde, aufrufen müssen wir uns die C \name{Calling Convention} unseres
Betriebssystemes mal angucken. Andere Sprachen haben oft eigene \name{Calling Conventions} und \name{Calling Conventions}
unterscheiden sich auch zwischen Betriebssystemen und Architekturen. \\
Die \name{SystemV Calling Convention} (die von Linux auf 64 bit genutzt wird) vereinbart kurzgesagt folgendes:
\begin{itemize}
\item Die ersten 6 Integer oder Pointer Parameter kommen in \register{rdi, rsi, rdx, rcx, r8, r9}
\item Die ersten 8 Floatingpoint Parameter (Kommazahlen) kommen in die Register \register{xmm0 bis xmm7}
\item Der Rest kommt in umgekehrter Reihenfolge auf den Stack
\item Der Rückgabewert der Funktion befindet sich in \register{rax}
\end{itemize}
Ich habe hier bewusst einiges ausgelassen, so \name{Calling Conventions} können schon recht komplex werden.
Lasst euch auch nicht davon verwirren, dass ich grade \register{rdi, rsi, rdx, rcx, r8, r9} genannt habe, aber
im Disassembly die 32 bit Versionen genutzt werden, das ist auch erlaubt. \\
Wenn wir uns nun die Dokumentation ein bisschen weiter durch lesen, finden wir heraus, dass dieser Funktionsaufruf
\imm{0x1000} Bytes an irgendeiner Adresse allokiert und uns diese Addresse zurück gibt.
Anschließend wird der Rückgabewert der Funktion einfach in einer lokalen Variable gespeichert, damit wir diesen
später noch benutzen können. Dies geschieht hier: \mint{nasm}|mov qword [local_18h], rax|
\item
\mint{nasm}|call sub.malloc_8d0|
Hier wird schon wieder eine Funktion aufgerufen, aber diesmal kennt unser \name{Disassembler} diese nicht. Ich habe
ehrlich gesagt keine Ahnung, wieso \name{Radare2} meint hier ein ``malloc'' mit in den Funktionsnamen zu packen, das
verwirrt imho nur, denn die Funktion macht viel mehr als nur ``malloc'', wie wir später noch sehen werden.
Doch im Moment ist der Inhalt der Funktion für uns noch uninteressant.
\item
\mint{nasm}|call sym.imp.memcpy|
Diese Funktion könnte man kennen, sie ist nicht grade selten. Sie kopiert \register{edx} Bytes von \register{rsi} nach
\register{rdi}. \\
Schauen wir doch mal nach was sich in den Registern befindet.
\begin{minted}{nasm}
mov rax, qword [local_18h]
; [local_18h] enthält Rückgabe von mmap()
; ...
mov rdi, rax
\end{minted}
Unser \name{Ziel} ist also der Speicher, den wir in Schritt 1 uns allokiert haben. Wäre ja auch dumm den Speicher zu
holen, ihn aber nicht zu nutzen.
\begin{minted}{nasm}
mov rcx, qword [0x00202010]
; ...
mov rsi, rcx
\end{minted}
\name{mmap} liest die Daten von \register{rsi} und \register{rsi} beinhaltet was auch immer an Addresse
\imm{0x202010} steht.
Gucken wir doch mal nach ob, das was dort steht schon feststeht.
\begin{minted}{console}
[0x00000a76]> pfx @0x202010
0x00202010 = 0x00000c78
\end{minted}
Aha! \register{rsi} enthält also vor dem Aufruf einfach nur \imm{0xc78}. Vielleicht stehen die Daten ja auch schon fest
... ?
\begin{minted}{console}
[0x00000a76]> pfT @0xc78
0x00000c78 = 56 52 8a 07 3c 33 0f 85 db 02 ... (VR..<3....)
\end{minted}
Die Daten stehen also auch schon fest, sagen einem (immerhin mir) zu diesem Zeitpunkt noch nichts.
Der Wert von \register{edx} ist recht einfach zu finden wenn man sich das Disassembly anguckt:
\mint{nasm}|mov edx, 0x2f5|
Schnell im Kopf zu Dezimal umgerechnet ... \imm{757}! Wir kopieren also \imm{757} Bytes von \imm{0xc78} zu dem Speicher
den wir uns mit \name{mmap} geholt haben. Was das für Daten sind die wir da kopieren gucken wir uns später an.
\item
\begin{minted}{nasm}
mov dword [local_24h], edi
; ...
cmp dword [local_24h], 2
je 0xae5
\end{minted}
Hier sehen wir ein \mnemonic{cmp} gefolgt von einem \mnemonic{je}. Das heißt, wenn die verglichenden \operand{Operanden}
gleich sind, springen wir zur Addresse \imm{0xae5}. \imm{local\_24h} wird ganz zu beginn der \name{main} Funktion auf
\register{edi} gesetzt, und \register{edi} enthält den \name{argc} Parameter von der \name{main} Funktion, also
die Anzahl an Argumenten die auf der Konsole übergeben wurde. Gucken wir uns mal an was passiert, wenn \name{argc}
ungleich 2 ist. Dann wird nicht gesprungen und es werden einfach die nächsten Instructions ausgeführt:
\begin{minted}{nasm}
mov edi, 1
call sym.imp.exit
\end{minted}
Aha! Deswegen hat das Programm auch nichts ausgegeben als wir es mit \mint{console}|./morph| ausgeführt haben.
Führen wir es doch mal mit 2 Parametern aus:
\begin{minted}{console}
th0rex@void /v/R/3/r/m/m0rph> ./morph abc
th0rex@void /v/R/3/r/m/m0rph>
\end{minted}
Leider tut es immer noch nicht so viel.
\end{enumerate}
Nun gucken wir uns mal an, was bei \imm{0xae5}, also dem Ziel von dem \mnemonic{je} passiert:
\begin{minted}{nasm}
;[0x00000ae5]> s 0xae5
;[0x00000ae5]> pd 7
; JMP XREF from 0x00000ad9 (main)
mov rax, qword [local_30h]
add rax, 8
mov rax, qword [rax]
mov rdi, rax
call sym.imp.strlen
cmp rax, 0x17
je 0xb08
\end{minted}
Hier wird \operand{local\_30h} nach \register{rax} kopiert, das wird dann um \imm{8} erhöht und davon wird dann gelesen.
In \operand{local\_30h} befindet sich \name{argv} von der \name{main} Funktion, also holen wir uns hier die Addresse von dem
zweiten Argument. Mit dieser Addresse als Parameter wird dann \name{strlen} aufgerufen, was uns einfach die Länge des strings
zurück gibt. Wenn diese gleich \imm{0x17}, also \imm{23} ist, wird zu \imm{0xb08} gesprungen. Ansonsten passiert wieder das hier
\begin{minted}{nasm}
mov edi, 1
call sym.imp.exit
\end{minted}
Deswegen wurde also grade auch nichts ausgegeben. Also mal das Programm mit 23 Zeichen als 2. Argument ausführen:
\begin{minted}{console}
th0rex@void /v/R/3/r/m/m0rph> ./morph (ruby -e "'A'*23")
th0rex@void /v/R/3/r/m/m0rph>
\end{minted}
Verflucht nochmal! Schon wieder kriegen wir keine Ausgabe.
\justify
\heading{Und weiter geht der Spaß}
\raggedright
Gucken wir uns doch mal an, was anschließend passiert.
\begin{minted}{nasm}
;[0x00000ae5]> s 0xb08
;[0x00000b08]> pd 4
mov eax, 0
call sub.time_987
; ^ wir ignorieren das hier
mov dword [local_1ch], 0
jmp 0xbcc
;[0x00000b08]> s 0xbcc
;[0x00000bcc]> pd 2
cmp dword [local_1ch], 0x16
jle 0xb1e
\end{minted}
Wir setzen \operand{local\_1ch} auf 0, springen zu \imm{0xbcc} und vergleichen dort \operand{local\_1ch} mit \imm{0x16}, also
\imm{22}. Falls der Wert in \operand{local\_1ch} kleiner oder gleich (\name{Jump if Less than or Equal}) ist, springen wir zu
\imm{0xb1e}. Das hier sieht schon wieder stark nach einer Schleife aus. \\
Ich muss zugegeben, dass der folgende Teil vielleicht nach recht viel aussieht, es aber in wirklichkeit nicht ist.
Die Instructions sind recht simpel und gehen wie ihr seht eigentlich nicht über das hinaus was wir schon gesehen haben.
Ihr könnt (und solltet) das vielleicht einfach mal alleine im Kopf so durchgehen, was da so passiert. Sucht zuerst wieder
\name{Interessantes} und guckt dann, wo die Werte davon her kommen. Googlet ggf auch \name{mnemonics} die ihr nicht kennt, aber
nicht dieses ganze \mnemonic{movsxd} oder \mnemonic{movzx} Zeugs. Das sind einfach nur \mnemonic{mov}s, nur dass die Größe von
der rechten Seite anders ist als die von der linken und deswegen haben die so lustiges Zeugs nach dem \mnemonic{mov}.
\begin{minted}{nasm}
;[0x00000b1e]> pd 50
mov rax, qword [0x00202020]
mov edx, dword [local_1ch]
movsxd rdx, edx
shl rdx, 3
add rax, rdx
mov rax, qword [rax]
mov qword [local_10h], rax
mov rax, qword [0x00202020]
mov edx, dword [local_1ch]
movsxd rdx, edx
add rdx, 1
shl rdx, 3
add rax, rdx
mov rax, qword [rax]
mov qword [local_8h], rax
cmp qword [local_8h], 0
je 0xb99
mov rax, qword [local_10h]
mov rax, qword [rax]
mov rdx, qword [local_8h]
movzx edx, byte [rdx + 8]
movsx edx, dl
mov rcx, qword [local_8h]
mov rcx, qword [rcx]
mov rsi, qword [local_30h]
add rsi, 8
mov rdi, qword [rsi]
mov rsi, qword [local_10h]
movzx esi, byte [rsi + 9]
movzx esi, sil
add rdi, rsi
mov rsi, rcx
call rax
;^ sehr interessant, call auf ein register!
jmp 0xbc8
; 0xb99:
mov rax, qword [local_10h]
mov rax, qword [rax]
mov rdx, qword [local_30h]
add rdx, 8
mov rcx, qword [rdx]
mov rdx, qword [local_10h]
movzx edx, byte [rdx + 9]
movzx edx, dl
lea rdi, qword [rcx + rdx]
mov rcx, qword [local_18h]
mov edx, 0
mov rsi, rcx
call rax
;^ auch sehr interessant!
; 0xbc8:
add dword [local_1ch], 1
; 0xbcc: (das hier haben wir schon gesehen!)
cmp dword [local_1ch], 0x16
jle 0xb1e
\end{minted}
So, hier jetzt einmal meine pseudo C mäßige Version von dem Disassembly. Das ist übrigens auch immer recht gut, wenn ihr nicht
weiter wisst, übersetzt einfach jede assembly Zeile in erstmal recht dummes C und fasst dann den C code zusammen.
\begin{minted}{c}
typedef void(*callback)(char*, void*, uint8_t);
struct S {
callback cb; // offset 0
uint8_t key; // offset 8
uint8_t index; // offset 9
};
struct S** data_202020 = NULL;
int main(int argc, char** argv) {
// Der ganze vorherige kram
// [local_1ch] habe ich i benannt
for(int i = 0; i <= 22; ++i) {
/*
mov rax, qword [0x00202020]
mov edx, dword [local_1ch]
movsxd rdx, edx
shl rdx, 3
add rax, rdx
mov rax, qword [rax]
mov qword [local_10h], rax
wird zu:
*/
struct S* current = data_202020[i];
// local_10h in der letzten Zeile = current bei mir
// wie ihr seht ist assembly doch recht verbose :)
/*
mov rax, qword [0x00202020]
mov edx, dword [local_1ch]
movsxd rdx, edx
add rdx, 1
shl rdx, 3
add rax, rdx
mov rax, qword [rax]
mov qword [local_8h], rax
wird zu:
*/
struct S* next = data_202020[i+1];
// local_8h = next bei mir
/*
cmp qword [local_8h], 0
je 0xb99
*/
if(next == NULL) {
/*
mov rax, qword [local_10h]
mov rax, qword [rax]
mov rdx, qword [local_30h]
add rdx, 8
mov rcx, qword [rdx]
mov rdx, qword [local_10h]
movzx edx, byte [rdx + 9] ; current->index
movzx edx, dl
lea rdi, qword [rcx + rdx] ; argv[1] + current->index
mov rcx, qword [local_18h]
mov edx, 0
mov rsi, rcx
call rax
*/
current->cb(argv[1] + current->index,
result_from_mmap,
0);
} else {
/*
mov rax, qword [local_10h]
mov rax, qword [rax]
mov rdx, qword [local_8h]
movzx edx, byte [rdx + 8]
movsx edx, dl
mov rcx, qword [local_8h]
mov rcx, qword [rcx] ; next->cb
mov rsi, qword [local_30h]
add rsi, 8
mov rdi, qword [rsi] ; argv[1]
mov rsi, qword [local_10h]
movzx esi, byte [rsi + 9] ; current->index
movzx esi, sil
add rdi, rsi
mov rsi, rcx
call rax
*/
current->cb(argv[1] + current->index,
(void*)next->cb,
next->key);
}
}
}
\end{minted}
Und hier einmal ohne die comments mit dem dazugehörigen assembly:
\begin{minted}{c}
typedef void(*callback)(char*, void*, uint8_t);
struct S {
callback cb; // offset 0
uint8_t key; // offset 8
uint8_t index; // offset 9
};
struct S** data_202020 = NULL;
int main(int argc, char** argv) {
// Der ganze vorherige kram
for(int i = 0; i <= 22; ++i) {
struct S* current = data_202020[i];
struct S* next = data_202020[i+1];
if(next == NULL) {
current->cb(argv[1] + current->index,
result_from_mmap,
0);
} else {
current->cb(argv[1] + current->index,
(void*)next->cb,
next->key);
}
}
}
\end{minted}
Gucken wir uns mal an, wie ich auf die Namen von den Feldern der struct gekommen bin. Die Größe und Position
kann man ja recht einfach an dem \mint{nasm}|byte [addr + 9]| usw herausfinden. \\
Wir hatten vorhin die Funktion an \imm{0x8d0} nicht angeguckt, also die die nach \name{mmap} aufgerufen wird.
Doch dafür wird es jetzt Zeit. Wir erinnern uns, dass sie als einzigen Parameter nur die Addresse von dem Speicher,
den wir durch \name{mmap} bekommen haben, nimmt. Ihr könnt hier wieder selber versuchen herauszufinden, was die Funktion
so tut.
\begin{minted}{nasm}
push rbp
mov rbp, rsp
sub rsp, 0x20
mov qword [local_18h], rdi
mov edi, 0xc0
call sym.imp.malloc
mov qword [0x00202020], rax
mov dword [local_ch], 0
jmp 0x96a
; 0x8f6:
mov edi, 0x10
call sym.imp.malloc
mov qword [local_8h], rax
mov edx, dword [local_ch]
mov eax, edx
shl eax, 4
add eax, edx
movsxd rdx, eax
mov rax, qword [local_18h]
add rax, rdx
mov rdx, rax
mov rax, qword [local_8h]
mov qword [rax], rdx
mov eax, dword [local_ch]
imul eax, eax, 0x111
cdq
shr edx, 0x18
add eax, edx
movzx eax, al
sub eax, edx
mov edx, eax
mov rax, qword [local_8h]
mov byte [rax + 8], dl
mov eax, dword [local_ch]
mov edx, eax
mov rax, qword [local_8h]
mov byte [rax + 9], dl
mov rax, qword [0x00202020]
mov edx, dword [local_ch]
movsxd rdx, edx
shl rdx, 3
add rdx, rax
mov rax, qword [local_8h]
mov qword [rdx], rax
add dword [local_ch], 1
; 0x96a
cmp dword [local_ch], 0x16
jle 0x8f6
mov rax, qword [0x00202020]
add rax, 0xb8
mov qword [rax], 0
nop
leave
ret
\end{minted}
Hier die C Version mit assembly:
\begin{minted}{c}
void sub_8d0(char* addr) {
/*
push rbp
mov rbp, rsp
sub rsp, 0x20
mov qword [local_18h], rdi
mov edi, 0xc0
call sym.imp.malloc
mov qword [0x00202020], rax
mov dword [local_ch], 0
wird zu:
*/
data_202020 = malloc(0xc0); // 192
for(int i = 0; i <= 22; ++i) {
/*
mov edi, 0x10
call sym.imp.malloc
mov qword [local_8h], rax
wird zu:
*/
struct S* new = (struct S*)malloc(0x10); // 16
/*
mov edx, dword [local_ch]
mov eax, edx
shl eax, 4
add eax, edx ; index * 17
movsxd rdx, eax
mov rax, qword [local_18h]
add rax, rdx
mov rdx, rax
mov rax, qword [local_8h]
mov qword [rax], rdx
wird zu:
*/
new->cb = addr + i * 17;
/*
mov eax, dword [local_ch]
imul eax, eax, 0x111
cdq
shr edx, 0x18
add eax, edx
movzx eax, al
sub eax, edx
mov edx, eax
mov rax, qword [local_8h]
mov byte [rax + 8], dl
wird zu:
*/
new->key = (uint8_t)(i * 11);
/*
mov eax, dword [local_ch]
mov edx, eax
mov rax, qword [local_8h]
mov byte [rax + 9], dl
wird zu:
*/
new->index = i;
/*
mov rax, qword [0x00202020]
mov edx, dword [local_ch]
movsxd rdx, edx
shl rdx, 3
add rdx, rax
mov rax, qword [local_8h]
mov qword [rdx], rax
wird zu:
*/
data_202020[i] = new;
}
/*
mov rax, qword [0x00202020]
add rax, 0xb8
mov qword [rax], 0
nop
leave
ret
wird zu:
*/
data_202020[23] = NULL;
}
\end{minted}
Und hier ohne assembly comments:
\begin{minted}{c}
void sub_8d0(char* addr) {
data_202020 = malloc(0xc0); // 192
for(int i = 0; i <= 22; ++i) {
struct S* new = (struct S*)malloc(0x10); // 16
new->cb = addr + i * 17;
new->key = (uint8_t)(i * 11);
new->index = i;
data_202020[i] = new;
}
data_202020[23] = NULL;
}
\end{minted}
Hier sieht man schonmal, dass an Offset 9 einfach nur der index hingeschrieben wird, deswegen können wir das Feld auch
gleich so nennen. Das erste Feld habe ich erstmal \name{cb} genannt, weil das irgendeine Addresse von einer Funktion
enthält, wie wir aus dem Kram aus der \name{main} wissen. Jetzt ist nur noch die Frage, welche Funktionen dort ausgeführt
werden und wieso ich \name{key} so genannt habe. \\
Vielleicht wisst ihr noch, dass wir am Anfang von der \name{main} noch ein \name{memcpy} in den Speicher von unserem
\name{mmap} Ergebniss hatten. Die \name{callbacks} zeigen jetzt in den Speicher von \name{mmap}, also gucken wir uns mal
an was dort hineinkopiert wird.
\begin{minted}{nasm}
;[0x00000a76]> s 0xc78
;[0x00000c78]> pD 17
push rsi
push rdx
mov al, byte [rdi]
cmp al, 0x33 ; '3' als char
jne 0xf5f
jmp 0xf41
\end{minted}
Wenn unser momentanes Zeichen (aus dem ersten Parameter) also ungleich der 3 ist, gehen wir zu \imm{0xf5f}. Dort wird auch
einfach nur das Programm beendet. Gucken wir uns nun also noch \imm{0xf41} an.
\begin{minted}{nasm}
pop rax ; key
pop rdi ; nächste Funktion
mov r8d, 0
; 0xf49
cmp r8, 0x11
je 0xf6b
mov bl, byte [rdi]
mov cl, al
xor bl, cl
mov byte [rdi], bl
inc r8
inc rdi
jmp 0xf49
\end{minted}
Falls ihr den Code jetzt hier übersetzt, seht ihr, dass jede Funktion 17 bytes von der nächsten Funktion mit dem 3. Parameter
xored. Der 3. Parameter war ja das 2. Feld in der struct, also habe ich das \name{key} genannt. \\
Jetzt kurz ein skript geschrieben, welches für uns das xor macht. Dieses Skript ist für BinaryNinja weil ich jetzt nicht gucke
ob sowas auch für radare2 geht.
\begin{minted}{python}
for i in range(22):
br = BinaryReader(bv)
br.seek(0xc78 + (i+1) * 17)
data = map(ord, br.read(17))
data = map(lambda x: x ^ (((i + 1) * 0x11) & 0xFF), data)
data = "".join(map(chr, data))
bw = BinaryWriter(bv)
bw.seek(0xc78 + (i+1) * 17)
bw.write(data)
bv.create_user_function(0xc78 + (i+1) * 17)
\end{minted}
Jetzt können wir uns all die Funktionen angucken und sehen, dass alle gleich aufgebaut sind. Nun bei jedem das \mnemonic{cmp}
angucken, und man kommt auf die Flag:
\begin{quote}
34C3\_M1GHTY\_M0RPh1nG\_g0
\end{quote}
Kurz noch überprüfen, ob diese Flagge auch richtig ist. Dafür müssen wir aber voher den call auf die Funktion an \imm{0x987}
durch \mnemnoc{nop}s ersetzen, da diese den Inhalt von \name{data_202020} vertauscht, was wohl als anti debugging zeugs gedacht
war.
\begin{minted}{console}
th0rex@void /v/R/3/r/m/m0rph> ./morph_patched 34C3_M1GHTY_M0RPh1nG_g0
What are you waiting for, go submit that flag!
th0rex@void /v/R/3/r/m/m0rph>
\end{minted}
Geschafft! \\
Ich möchte mich Entschuldigen dafür, dass ich am Ende relativ viel einfach übersprungen habe, bzw. euch das selber hab machen
lassen, aber dieser P0st wäre viel viel zu lang geworden, wenn ich da noch detailierter drauf eingegangen wäre. Wie gesagt, falls
Interesse besteht kann ich noch meine Lösung für \name{readme_revenge} hier hochladen. Hoffentlich habt ihr was gelernt!
%\justify
\end{document}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment