Form Grabbing in Firefox - PoC
Form Grabbing in Firefox. Das ist nichts neues und es gibt zuhauf Beispiele mit dokumentierem Sourcecode. Warum also ein Artikel drüber verfassen? Ein guter Bekannter zeige mir ein Video von einem Form-Grabber in Firefox und fragte mich wie das geht. Ich hatte mich nie damit beschäftigt und nur ein paar Ansätze im Kopf, doch das interesse war geweckt. Wie schaffen es Bots, Daten aus Website-Formularen so gezielt auszulesen?
In dem folgenden Artikel wird ein Proof of Concept von einem Form-Grabber mit injecteter DLL erstellt. Das besondere im Vergleich zu den 100 anderen Proof of Concepts ist hierbei, dass die injectete DLL Managed ist und somit erst ein CLRHost für das Ausführen von .NET Code reingeladen werden muss. Das Konzept sollte aus den vorherigen Artikeln wie diesem und diesem bekannt sein.
Fangen wir mit etwas Theorie an: Es gibt zwei Wege Daten aus Webformularen auszulesen. Diese Formulare sind intern auch nur Textfelder, die man markieren und kopieren kann wie man es aus Windows gewohnt ist. Das heißt im Umkehrschluss auch, dass diese Felder ein Handle und eine Windows-Klasse besitzen müssen. Mit diesem Handle können wir von externen Programmen Firefox dazu auffordern, uns den Textinhalt dieses Handles zurückzugeben. Der große Nachteil ist, dass man sich erstmal zu diesem Handle “Vorzuhangeln” muss. Hier muss man über mehrere UI Elemente und Knoten mit Unterknoten laufen, bis man am gewünschten Ziel angekommen ist. Sehr elegant ist das nicht und für jede Seite müsste dieser Pfad eingeprogrammiert werden! Wer weiteres über diesen Ansatz lesen möchte, dem sei die WM_GETTEXT Message und dieser Stackoverflowpost ans Herz gelegt. Eleganter, und vor allem universal für jede Seite ist das Abfangen von der GET/POST Request bevor diese über SSL unkenntlich gemacht wird. Und genau dieser Ansatz ist verblüffend einfach und zielführend!
Die später geschriebene DLL hängt sich an die sog. PR_Write Funktion der nss3.dll von Firefox. Diese wird verwendet, um Streams jeder Art zu beschreiben. Sei es ein Filestream welcher auf die Festplatte schreibt, oder ein Netstream welcher an eine TCP Verbindung gerichtet ist. Wie unschwer zu erkennen ist, nimmt diese Funktion drei Parameter entgegen. Der erste ist das Stream-Handle, was nicht weiter besprochen wird. Das zweite Argument enthält einen Zeiger auf einen Buffer (Zwischenspeicher), in welchem sich die zu schreibenden Daten befinden. Das dritte Argument gibt schließlich an, wieviele Daten aus dem Zwischenspeicher in den Stream geschrieben werden sollen.
Setzt man mit OllyDBG einen Breakpoint an der PR_Write Funktion, so lassen sich alle drei Argumente schön im Stack anschauen. Das deutet daraufhin, dass es sich bei der PR_Write Funktion um eine cdecl-Calling-Convention handelt. Die später für uns wichtige Calling Convention gibt an, wie die Parameter der Funktion übergeben werden. Dabei gibt es viel Möglichkeiten: Parameter können sich in Registern befinden oder auch der Reihe nach auf den Stack geladen werden. Letzeres ist wohl hier der Fall.
Schön zu sehen sind die 3 Stackargumente in umgekehrter Reinfolge. Die Return-Adresse befinder sich ganz Oben auf dem Stack, danach folgt der Zeiger auf den Stream und der Zeiger auf den Buffer. Als drittes Argument kann man die Länge des Buffers, in diesem Fall 0×221 Bytes sehen. Links im Stack Window ist die Buffer-Adresse geöffnet. Hier sieht man klar die POST Request die gesendet wird, mit allen GET und POST Parametern. (GetParam=NotSoSecret, PostParam=VeryS3cret).
Implementieren wir dieses alles in einer DLL. Keine Sorge, es wird keinen fertigen Code für Copy & Paster geben, denn es gibt neben dem eigentlichen Hook noch viele andere Probleme zu bewältigen auf die ich später noch zu sprechen komme. Ziel ist es, die PR_Write Funktion von C# aus zu hooken. Dazu brauchen wir sowohl eine Funktionsadresse, als auch eine Funktiondeklaration und einen Funktionszeiger. Die Funktionsaddresse ist einfach beschafft, denn die wohlbekannten Funktion GetModuleHandle und GetProcAddr liefern und die gesuchte exportiere Funktion in nss3.dll.
IntPtr nss3Handle = GetModuleHandle("nss3"); IntPtr PR_WriteAddr = GetProcAddress(nss3Handle, "PR_Write"); MessageBox(IntPtr.Zero, String.Format("nss3.dll located at {0:x8}. PR_Write located at: {1:x8}", nss3Handle.ToInt32(), PR_WriteAddr.ToInt32()), "Location", 0); |
In der Message Box geben wir uns zu Debugzwecken die Adresse des Moduls und der Funktion aus. Dabei fällt ein netter Trick von String.Format ins Auge: {0:x8} sagt, dass das erste Argument als hexardezimalen String der Länge 8 ausgegeben werden sollte.
Fehlt also nur noch die Funktionsdeklaration und natürlich der Funktionszeiger, dass wir richtig Detouren können. Beides ist nicht weiter schwer, wenn man sich etwas mit Delegates und Funktionsprototyen befasst. Da wir hier eine cdecl-Calling-Convention haben, welche glücklicherweise von .NET Unterstützt wird, müssen wir uns nicht weiter um das Aufräumen vom Stack kümmern.
// Ein Funktionsprototyp als UnmanagedFunctionPointer mit den Argumenten die PR_Write übergeben bekommt [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate Int32 PR_WriteDelegate(IntPtr fd, IntPtr buffer, Int32 amount); // Eine Funktionszeigerinstanz, welche auf die statische Funktion PR_WriteDelegateHooked zeigt private static readonly PR_WriteDelegate PR_WriteHandler = PR_WriteDelegateHooked; // Selbe Argumente wie PR_Write public static Int32 PR_WriteDelegateHooked(IntPtr fd, IntPtr buffer, Int32 amount) { // ... } |
Das wäre also ebenfalls geschafft. Fehlt nur noch der eigentliche Detour! Dazu verwende ich die WhiteMagic Library, die vorallem in WoW Bots ihre Anwendung findet. Über die Detour-Methode und das Innenleben dieser Bibliothek habe ich bereits im ersten Post über natives Detouring ein paar Worte verloren. Ziel ist es, die originale Funktion so zu verändern dass zunächst unsere eigene PR_Write Methode angesprungen wird. Dann werten wir die Parameter aus und lassen das Programm durch aufruf der originalen Funktion weiter ablaufen.
// Neue White Magic Instanz magic = new Magic(); // Wir machen aus der nativen Addresse von PR_Write Funktion ein Funktionszeiger var delegateAddr = magic.RegisterDelegate<PR_WriteDelegate>(PR_WriteAddr); // Und detouren die Funktion indem wir beide Funktionszeiger (Original und Hooked) übergeben var detour = magic.Detours.CreateAndApply(delegateAddr, PR_WriteHandler, "PR_Write"); |
Viel Magie passiert hier nicht. Wir holen uns ein Funktionszeiger von der originalen PR_Write Addresse und übergeben diesen originalen Funktionszeiger, sowie den gehookten Funktionszeiger der Detours-Klasse. Im dritten Parameter geben wir dem Detour noch den Namen “PR_Write”.
// In der gehookten Funktion return (int)magic.Detours["PR_Write"].CallOriginal(new object[] {fd, buffer, amount}); |
In der gehookten Funktion müssen wir natürlich noch die orignale PR_Write mit den originalen Argumenten aufrufen. Dies geschieht über den Aufruf von der Funktion CallOrigial, welche alle Argumente als Objekt-Array entgegen nimmt. Diese werden im Anschluss auf den Stack geladen und die originale Funktion aufgerufen. Fertig ist der Detour.
Dennoch gibt es ein paar Probleme, die die ganze Sache nicht so einfach macht. Um stumpfe Kopieren des Sources zu vermeiden wurde übrigens absichtlich kein Auswerten von den Daten eingebaut. Ein paar weitere Herrausforderungen die es zu bewältigen gibt wären
- Man kann zunächst nicht unterscheiden, ob der geschriebene Stream wirkliche HTTP-Daten enthält oder einfach nur binäre Daten die sonst irgendwo verarbeitet werden
- Es kommt zu zufälligen Abstürzen beim Umwandeln der Daten und Interpretieren als ASCII-String
- Die WhiteMagic Library braucht Debug-Rechte im Prozess. Der Hook funktioniert also nur, wenn der User Firefox als Administrator laufen lässt
Ich hoffe ich konnte Euch mit diesem Artikel zeigen, dass Form-Grabbing kein Hexwerk darstellt. Bots wie Zeus benutzen exakt diese Methoden, um sich in den Browser einzuklinken und die gesuchten Daten herrauszuholen.
Greez Easy
Huhey Easy;
Danke nochmals für das tolle Paper.
Werde ich sicherlich noch einige Male aufsuchen.
-Wie man sieht kochen die anderen Bots wie Zeus/SpyEye auch nur mit Wasser :).
Gruß an dich;