#Titel: API-Crypting (Oder: Wie man die Heuristik Methode von Antiviren Software austrickst) #Datum: 03.03.2008 #Author: Eddy14 #Webseite: www.eddys-blog.6x.to #Email: eddy14@pen.tj #Inhaltsverzeichnis: ### 0x1 - Vorwort ### 0x2 - Einleitung ### 0x3 - API Befehle ### 0x4 - Der Quellcode (und die Erklärung) ### 0x5 - Tieferer Einblick (Reverse Engineering) ### 0x6 - Abschluss/Links _________________________ \/\/\/\/\/0x1\/\/\/\/\/\/ ######################### # Vorwort # ######################### Hallo und herzlich Willkommen in meinem API-Crypting Tutorial :) Ich wollte dieses Paper schon vor 2,5 Monaten fertig haben, aber irgendwie hat mein Dev-C++ gestreikt und ich hatte auch sonst keine besondere Lust :P Genug gelabert, erstmal sag ich euch was ihr an Vorwissen für dieses Tutorial mitbringen solltet: -Gute Kenntnisse in C (Pointer, Typecastings etc.) -Kenntnisse in der Windows Programmierung (Die WinAPI!) -Ihr solltet schonmal mit DLLs gearbeitet haben (wichtig, sonst verwirrt euch der DLL angelehnte Code) -Ihr solltet schonmal Strings verschlüsselt haben (Cäsar Verschlüsselung reicht) Auf gehts in die Einleitung :) _________________________ \/\/\/\/\/0x2\/\/\/\/\/\/ ######################### # Einleitung # ######################### Naja, erstmal: Wieso schreib ich dieses Tutorial? Zum einen weil ich zu wenig Tutorials geschrieben habe, und zum anderen, weil ich gerade dabei war eine Remote-Shell für Windows zu schreiben (ein kleines Backdoor; btw => zu der Remote-Shell, also allgemein Pipes unter Windows, werde ich auch ein Tutorial schreiben) und hab mit born2die zusammen festgestellt dass die Antiviren Software meinen Backdoor erkannt hatte, und mir war unerklärlich woran es lag, denn es war selbstgeschriebene Software, keine Signatur hätte darauf passen sollen! Es war die verdammte Heuristik! :-O In meinem Programm war etwas implementiert, dass ungefähr diesen Aufbau hatte: ------------------------- GetWindowsDirectory(&puffer, sizeof(puffer)); CopyFile(argv[0], puffer, FALSE); ------------------------- (dazwischen noch strcat, zum anhängen des Dateinamens) Was dem Windows so viel sagte wie: ------------------------- Finde dein Windows-Directory heraus und kopier mich dahin! ------------------------- was für den Antivir so aussah wie: ------------------------- Buuuh, ich bin Bööööse und ein Virus! Ich werde mich selbst nun ganz leet ins Windows-Directory kopieren! ------------------------- was für mich so viel sagte wie: ------------------------- Suck my balls! ------------------------- Naja, Spaß beiseite. Ich musste nicht lange überlegen: Ich wusste dass das Antivirus Programm das GetWindowsDirectory und das darauffolgende CopyFile als Malware erkannte (kenne keine andere Software die sowas sinnvoll einsetzt, oder doch?). Jedenfalls erkennt das Antivirus Programm meine Zugriffe zu dieser API, nicht jedoch wenn sie nicht im kompilierten Zustand existieren, sondern erst nach dem ausführen, ganz dynamisch! Ihr fragt wie das geht? Na ganz einfach, wir werden nicht GetWindowsDirectory direkt aufrufen (damit unser Programm es nicht direkt importiert, und somit der AV von nichts bescheid weiß) sondern erst beim Ausführen! Das ist ganz einfach. Ihr solltet euch das PE-Format (Portable Exectuable) mal ansehen. Dort werdet ihr irgendwann auf die IAT (Import Address Table) stoßen. Diese Tabelle beinhaltet Addressen zu den jeweiligen importierten Funktionen (von fremden DLLs). Anhand dieser Tabelle kann man nachvollziehen welche API-Funktionen das jeweilige Programm benutzt. Man kann aber auch während der Laufzeit Funktionen aufrufen, die garnicht in dieser Tabelle auftauchen (wird benutzt, wenn z.b. eine DLL zur Laufzeit geladen werden soll). Das meinte ich mit "dynamisch". (mehr zur IAT gibts hier: http://sandsprite.com/CodeStuff/Understanding_imports.html) Ihr fragt immernoch wie das geht? Dann lest erstmal das Kapitel "API-Befehle", und im "Quellcode" Part werde ich alles genauestens erklären :) _________________________ \/\/\/\/\/0x3\/\/\/\/\/\/ ######################### # API Befehle # ######################### Erstmal die drei "Befehle" die wir testweise verwenden: -GetWindowsDirectory (GetWindowsDirectoryA) -CopyFile -MessageBox (MessageBoxA) Und die zwei wichtigen Befehle ohne die API-Crypting wohl nicht möglich wäre: -LoadLibrary -GetProcAddress _________________________ \/\/\/\/\/0x4\/\/\/\/\/\/ ######################### # Der Quellcode # ######################### Da ihr nun wisst welche API-Aufrufe wir brauchen, werdet ihr euch hoffentlich schon denken können was nun kommt. Oder immernoch nicht? Nun gut, ich entlüfte das ganze Geheimnis: Wir werden unsere Aufrufe als Strings verschlüsseln, und dann per LoadLibrary und GetProcAddress diese Funktion laden und aufrufen. So findet man im Ausführbaren Zustand der Exe Datei keine Informationen über z.B. GetWindowsDirectory, nur über LoadLibrary und GetProcAddress. Wem diese beiden Funktionen nichts sagen, der möchte bitte meine Anforderungen durchlesen, denn wie dort geschrieben steht: Ihr solltet schonmal eine DLL geladen haben! Nunja, das Geheimnis ist gelüftet! Vielen ist sicherlich noch nicht klar wie das ganze nun funktionieren soll. ICh werde nun Code vorführen und natürlich wie immer erklären :) BTW, ich verwende hier rohes GCC unter Windows, Dev-C++ sucked rum, deswegen. ------------------------- #include #include int main(int argc, char *argv[]) { char hello[] = "Hello World"; crypt(&hello); printf("%s\n", hello); system("PAUSE"); return 0; } ------------------------- Hier sieht man schonmal was unser Test-Programm machen soll. Es gibt an eine sogenannte "crypt" Funktion unseren Test-String und printed dann die Ausgabe (+wartet zusätzlich auf eine Eingabe). Das sollte jeder verstanden haben, denn im Prinzip gehts hier nur um die "crypt" Funktion die ich nun hier vorführe: ------------------------- int crypt(char *str) { int length; char chr; int ord; for(length=0; length siehe "(int)str[length]"]. Der ASCII-Wert von "H" ist 72. Im nachfolgenden wird auf diesen Wert 1 hinzuaddiert. Es ergibt sich also der Wert 73. Nun wird in "chr" wieder das ASCII-Zeichen gespeichert, und zwar von 73. Was hier gemacht wurde ist einfach: Es wurde verschlüsselt! Ein Zeichen wurde im Alphabet um eine Stelle nach Rechts verschoben! Es müsste sich also "I" ergeben, und das tut es auch! :) So wird nun mit jedem anderen Zeichen weitergemacht. Sogar mit Zeichen die nicht im Alphabet sind, denn es geht hier ja um die ASCII-Tabelle! So wird aus einem Leerzeichen ein Ausrufezeichen, denn das Leerzeichen ist in der ASCII-Tabelle das Zeichen mit der Nummer 32, und das darauffolgende 33 ist ein Ausrufezeichen :) Nun macht euch noch kurz Gedanken über die Decrypt Methode. Na wie wird diese wohl aussehen? Na genau so, nur dass es 1 abzieht statt hinzufügt. ------------------------- int decrypt(char *str) { int length; char chr; int ord; for(length=0; length #include int main(int argc, char *argv[]) { char hello[] = "Hello World"; crypt(&hello); printf("%s\n", hello); system("PAUSE"); return 0; } int crypt(char *str) { int length; char chr; int ord; for(length=0; length #include int main() { Sleep(1000); MessageBox(0, "test", "test", MB_OK); return 0; } ------------------------- Es ist unnütz, ich weiß! Es ist aber nur zum besseren Verständnis. Wie man leicht erkennt wartet das Programm eine Sekunde lang, um anschließend eine MessageBox anzuzeigen. Dieses Programm laden wir nun in Olly. Geht ins CPU-Fenster (also, der Status der sich ergibt sobald ihr ein Programm in Olly ladet, drückt Sicherheitshalber das "C" oben in der Symbolleiste) und macht nun einen Rechtsklick. Nun wählt ihr "Search for -> All intermodular Calls". Guckt euch da nun genau um, es gibt ja nicht wirklich viel zum lesen. Am besten ihr schaut euch im Bereich unter der Roten Zeile um. Dort werdet ihr was entdecken: ------------------------- 00401321 | CALL | kernel32.Sleep 00401348 | CALL | USER32.MessageBoxA ------------------------- (Die Adressen können unterschiedlich sein) Ihr könnt erkennen dass hier 2 Funktionen implementiert (oder anders: gecalled) werden. Einmal unser Sleep, und einmal unsere MessageBox. Dass würde ein Antivirus Programm genauso erkennen können. Bei unserer Verschlüsselung aber kann man dies nicht sehen! Da unser Programm ja zum Startzeitpunkt (oder vorher) nicht weiß dass er gleich einen Funktionsnamen entschlüsseln und die dazugehörige Funktion aufrufen wird! So, nun kompiliert ihr mal unser MessageBoxA Beispiel und ladet es in Olly. Ihr macht wieder "Search for -> All intermodular Calls" und landet wieder im gleichen Fenster. Schaut euch wieder unter der roten Zeile um, und ihr erkennt eure API-Aufrufe! LoadLibraryA, GetProcAddress sowie system und einige andere :) Aber hm, wo ist unser MessageBoxA ? Den gibts nicht! Da unser Programm es zu diesem Zeitpunkt nicht kennt! Wir gehen wieder zum CPU Fenster (ein klick auf das "C" im Menü) und machen einen schnellen Klick ins Fenster unten Links, dann ein Rechtsklick und "Search for -> Binary String". Erst wählen wir "Entire Block" aus, und springen dann in den ASCII-Bereich und geben... ------------------------- NfttbhfCpyB ------------------------- ...ein. Das ist unser Verschlüsseltes "MessageBoxA" falls ihr es vergessen haben solltet. Und Olly findet was! Ihr seht, es war kein erkannter Aufruf einer Funktion. Wir gehen nun etwas weiter, es heißt ja auch "Tieferer Einblick". Also setzen wir ein Breakpoint auf diesen String. Markiert es, macht ein Rechtsklick, geht auf "Breakpoint" und ihr seht ein kleines Menü. Wir wollen zwar ein Hardware Breakpoint machen, aber auf "Access" oder auf "Write"? Würden wir eins auf Access machen, würden wir wahrscheinlich dann breaken, wenn dieser String entschlüsselt ist, und gerade aufgerufen wird (also für GetProcAddress verwendet wird). Oder wir nehmen "Write" und breaken direkt dann, wenn wir anfangen den String zu entschlüsseln. Wir nehmen letzteres ;) Und zwar ein DWORD (sicherheitshalber). Einmal F9 und los gehts! Wir breaken auf: ------------------------- 0040131A A1 00304000 MOV EAX,DWORD PTR DS:[403000] 0040131F 8945 E8 MOV DWORD PTR SS:[EBP-18],EAX ;<- HIER ------------------------- Hm, aber was ist in EAX? Das kann ich euch sagen! "7474664E" das entspricht "ttfN" also "Nftt", was heißt, dass hier die die ersten 4 Bytes vom Crypteten String kopiert wurden :) Nun werden diese in EBP-18 gespeichert. Das wird dann wohl der "zwischenspeicher" oder halt der "Endspeicher" sein, wo das entschlüsselte "MessageBoxA" gespeichert wird. Die ganze Prozedur mit dem kopieren entsprach: ------------------------- 0040131A A1 00304000 MOV EAX,DWORD PTR DS:[403000] 0040131F 8945 E8 MOV DWORD PTR SS:[EBP-18],EAX ;<- Erste 4 Bytes kopieren 00401322 A1 04304000 MOV EAX,DWORD PTR DS:[403004] 00401327 8945 EC MOV DWORD PTR SS:[EBP-14],EAX ;<- Nächste 4 Bytes kopieren 0040132A A1 08304000 MOV EAX,DWORD PTR DS:[403008] 0040132F 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX ;<- Nächste 4 Bytes... 00401332 8D45 E8 LEA EAX,DWORD PTR SS:[EBP-18] ;<- In EAX ist nun die Adresse zum String 00401335 890424 MOV DWORD PTR SS:[ESP],EAX ;<- Dieser wird in ESP gespeichert ------------------------- Ich frag mich grad wieso der compiler das so umständlich gemacht hat, aber naja. Kurz hiernach folgt ein Call! ------------------------- 00401338 E8 EB000000 CALL 00401428 ; <- Entschluesseln ------------------------- Wie mein Kommentar bereits verspricht, hier wird entschlüsselt! Also tracen wir einmal rein, damit unsere F7-Taste auch mal was zu tun hat :) ------------------------- 00401435 8B45 08 / -> MOV EAX,DWORD PTR SS:[EBP+8] ; <- In EAX den String laden 00401438 890424 | MOV DWORD PTR SS:[ESP],EAX ; <- In ESP sichern 0040143B E8 F0050000 | CALL ; <- ermittle die Laenge des Strings 00401440 3945 FC | CMP DWORD PTR SS:[EBP-4],EAX ; <- Alle Zeichen durch? 00401443 73 2C | JNB SHORT 00401471 ; <- Dann spring mal raus aus der Schleife! 00401445 8B45 08 | MOV EAX,DWORD PTR SS:[EBP+8] ; <- Die Adressen zum String in EAX 00401448 0345 FC | ADD EAX,DWORD PTR SS:[EBP-4] ; <- Das aktuelle Zeichen... 0040144B 0FBE00 | MOVSX EAX,BYTE PTR DS:[EAX] ; <- ...in EAX kopieren 0040144E 8945 F4 | MOV DWORD PTR SS:[EBP-C],EAX ; <- In EBP-C speichern 00401451 8D45 F4 | LEA EAX,DWORD PTR SS:[EBP-C] ; <- In EAX die Adresse zum aktuellen Zeichen 00401454 FF08 | DEC DWORD PTR DS:[EAX] ; <- HIER! EAX um eins erniedrigen 00401456 8B45 F4 | MOV EAX,DWORD PTR SS:[EBP-C] ; <- In EAX das entschluesselte Zeichen 00401459 8845 FB | MOV BYTE PTR SS:[EBP-5],AL ; <- In EBP-5 das Aktuelle Zeichen speichern 0040145C 8B45 08 | MOV EAX,DWORD PTR SS:[EBP+8] ; <- EAX hat wieder die Adresse zum normalen String 0040145F 8B55 FC | MOV EDX,DWORD PTR SS:[EBP-4] ; <- EDX ist 0 00401462 01C2 | ADD EDX,EAX ; <- Auf EDX EAX addieren 00401464 0FB645 FB | MOVZX EAX,BYTE PTR SS:[EBP-5] ; <- In EAX ist wieder das entschluesselte Zeichen 00401468 8802 | MOV BYTE PTR DS:[EDX],AL ; <- Entschluesselte Zeichen wird auch im String geaendert 0040146A 8D45 FC | LEA EAX,DWORD PTR SS:[EBP-4] ; <- Wird um eins erhoeht (=> naechstes Zeichen) 0040146D FF00 | INC DWORD PTR DS:[EAX] ; <- und hingeschrieben 0040146F ^ EB C4 \ -- JMP SHORT 00401435 ; <- Wieder zurueck zum Schleifenanfang ------------------------- Oh man! So viel zu lesen und zu verstehen! Die Kommentare sollten das Abenteuer aber verschönern ;) Kurz gefasst: Alle Zeichen um eins erniedrigen! Noch kürzer: Entschlüsseln! Aber es ist doch immer wieder nett zu sehen was der Compiler aus unserem schönen C-Code macht, oder? =) So, nun setzen wir einen Breakpoint nach dem JMP, lassen das Programm dort breaken, und haben nun unser unverschlüsseltes "MessageBoxA" im Speicher rumliegen ===) Ihr glaubt mir nicht? Macht einen kurzen GOTO zu 0022FF60, und der Anblick owned euch! :P Weiter im Code. Damit unsere F8 Taste nicht schimmelt tippseln wir bisschen da drauf, so mit kommen wir auch wieder aus dem CALL heraus. Und wir sehen was spannendes! Traced da mal bisschen runter, nach der Zeile mit "%s" sehen wir ein "user32.dll", das sieht doch sehr schön aus! Und gleich danach ist ein CALL zu LoadLibraryA, und ein kleines bisschen unter diesem ein GetProcAddress. Uhh habt ihr auch dieses Kribbeln im Bauch wenn alles perfekt läuft? ;) Wir schalten mal ein Breakpoint bei dem Aufruf zu GetProcAddress weil ich euch noch was zeigen will! Wenn Olly breaked schaut ihr in den Stack. ------------------------- 0022FF20 7E360000 |hModule = 7E360000 (user32) 0022FF24 0022FF60 \ProcNameOrOrdinal = "MessageBoxA" ------------------------- Gleich so viele Infos auf einmal! :O Wir erkennen hier unser HMODULE von unserem Code (hier als Hex-Wert in seiner natürlichen Form), und zugleich erkennen wir dass wir in der user32 suchen und zwar nach "MessageBoxA". Wir steppen mal kurz über diesen CALL und sehen dann in EAX ein Wunder, unsere MessageBox wurde geladen! Bei mir ist es "7E3A058A", und wenn ich dies in der user32.dll nachschauen würde => Es würde stimmen ;) Ihr könnt euch selbst überzeugen, Rechtsklick ins CPU-Fenster, "View -> Module 'user32' " und nun Rechtsklick "Goto -> Expression", die Adresse angeben, und schwupps, das sieht doch sehr nach einer sauberen Funktion aus, hä? :) OK, ich glaub wir sind genug ins Code getraced, wir haben wohl mehr gelernt (oder auch nicht) als das Tutorial bieten sollte. Ich hoffe der Teil hat euch gefallen, falls ihr ihn durch gelesen habt ^^ _________________________ \/\/\/\/\/0x6\/\/\/\/\/\/ ######################### # Abschluss/Links # ######################### So, das wars dann wieder von mir :) Das Paper ist leider nicht so lang geworden wie ich es mir erhofft hatte :/ Aber ich hoffe wenigstens einer hat was aus diesem Tutorial gelernt :) Dieses Paper ist unter der GNU-Lizenz für freie Dokumentationen veröffentlicht worden, ihr dürft es also verbreiten und abändern wie ihr wollt, solange mein Name noch dran steht. Ach wisst ihr was, ihr könnt sogar meinen Namen da raus löschen xD Hauptsache Leute lernen aus diesem Paper, darum gehts doch ;) So, als eine Art "Anhang" hab ich meinen ganzen SourceCode hier reinkopiert, viel Spaß damit, und auf wiederlesen :) cryptme.c ------------------------- #include #include int main(int argc, char *argv[]) { char hello[] = "Hello World"; crypt(&hello); printf("%s\n", hello); system("PAUSE"); return 0; } int crypt(char *str) { int length; char chr; int ord; for(length=0; length #include int main(int argc, char *argv[]) { char func_crypted[] = "NfttbhfCpyB"; HMODULE dll; FARPROC MyMessageBox; decrypt(&func_crypted); dll = LoadLibrary("user32.dll"); MyMessageBox = GetProcAddress(dll, func_crypted); MyMessageBox(0, "Im working dude ;)", "YEAAAH", MB_OK); system("PAUSE"); return 0; } int crypt(char *str) { int length; char chr; int ord; for(length=0; length