Fingerabdruck auf Assembly-Ebene

8

Ich möchte feststellen, ob zwei Funktionen in zwei ausführbaren Dateien aus dem gleichen (C) Quellcode kompiliert wurden und dies auch dann tun würden, wenn sie von verschiedenen Compiler-Versionen oder mit verschiedenen Kompilierungsoptionen kompiliert wurden. Derzeit überlege ich, eine Art von Assembler-Level-Funktion Fingerprinting zu implementieren. Der Fingerabdruck einer Funktion sollte folgende Eigenschaften haben:

  1. zwei Funktionen, die aus derselben Quelle unter verschiedenen Umständen zusammengestellt wurden, haben wahrscheinlich den gleichen Fingerabdruck (oder einen ähnlichen),
  2. Zwei Funktionen, die aus verschiedenen C-Quellen kompiliert wurden, haben wahrscheinlich unterschiedliche Fingerabdrücke,
  3. (Bonus) Wenn die beiden Quellfunktionen ähnlich sind, sind die Fingerabdrücke auch ähnlich (für eine vernünftige Definition von ähnlich).

Was ich gerade suche, ist eine Menge von Eigenschaften von kompilierten Funktionen, die (1.) und (hoffentlich) (2.) zusammen erfüllen.

Annahmen

Natürlich ist das im Allgemeinen unmöglich, aber es könnte etwas geben, das in den meisten Fällen funktionieren wird. Hier sind einige Annahmen, die es einfacher machen könnten:

  • Linux-ELF-Binärdateien (ohne Debugging-Informationen verfügbar),
  • nicht in irgendeiner Weise verschleiert,
  • zusammengestellt von gcc,
  • auf x86 linux (Ansatz, der auf anderen Architekturen implementiert werden kann, wäre schön).

Ideen

Leider habe ich wenig oder gar keine Erfahrung mit der Montage. Hier sind einige Ideen für die oben genannten Eigenschaften:

  • Arten von Anweisungen in der Funktion enthalten (d. h. Fließkomma-Anweisungen, Speicherbarrieren)
  • Speicherzugriffe von der Funktion (liest / schreibt sie von / nach Heap? stack?)
  • Bibliotheksfunktionen aufgerufen (ihre Namen sollten im ELF verfügbar sein; auch sollte sich ihre Reihenfolge normalerweise nicht ändern)
  • Form des Kontrollflussdiagramms (ich denke, das hängt stark vom Compiler ab)

Vorhandene Arbeit

Ich konnte nur tangential verwandte Arbeit finden:

  • Automatisierter Ansatz, der Kryptoalgorithmen in kompiliertem Code identifizieren kann: Ссылка
  • Schnelle Bibliotheksidentifikation und -erkennung in IDA-Disassembler; identifiziert konkrete Befehlssequenzen, enthält aber dennoch einige möglicherweise nützliche Ideen: Ссылка

Haben Sie Vorschläge zu den Funktionseigenschaften? Oder eine andere Idee, die auch mein Ziel erreicht? Oder wurde sowas schon umgesetzt und ich habe es komplett vermisst?

    
b42 02.09.2011, 12:54
quelle

4 Antworten

5

FLIRT verwendet eine Musteranpassung auf Byte-Ebene, so dass es bei allen Änderungen in den Befehlskodierungen (z. B. unterschiedliche Registerzuweisung / neu angeordnete Anweisungen) zusammenbricht.

Informationen zum Anpassen von Grafiken finden Sie unter BinDiff. Während es Closed Source ist, hat Halvar einige der Ansätze auf seinem Blog . Sie haben sogar einige der Algos, die sie zur Generierung von Fingerabdrücken verwenden, in Form von BinCrowd-Plugins zur Verfügung. p>     

Igor Skochinsky 02.09.2011 13:30
quelle
0

Meiner Meinung nach ist der einfachste Weg, etwas wie das zu tun, die Funktionen Assembly wieder in eine höhere Form zu zerlegen, wo Konstrukte (wie for , while , Funktionsaufrufe usw.) existieren, dann passen sie an Struktur dieser höheren Konstrukte.

Dies würde das Neuanordnen von Befehlen, Schleifenhüben, Schleifenabwickeln und alle anderen Optimierungen verhindern, die mit dem Vergleich unordentlich sind. Sie können diese Strukturen auf höherer Ebene sogar bis zu ihrem Maximum an beiden Enden optimieren, um sicherzustellen, dass sie sich am selben Punkt befinden Vergleiche zwischen nicht optimiertem Debugcode und -O3 werden aufgrund fehlender Provisorien / fehlender Registerüberläufe usw. nicht fehlschlagen.

Sie können etwas wie Boomerang als Grundlage für die Dekompilierung verwenden (außer, dass Sie den C-Code nicht ausspucken würden).

    
Necrolis 02.09.2011 16:04
quelle
0

Ich schlage vor, dass Sie dieses Problem vom Standpunkt der Sprache aus betrachten, in die der Code geschrieben wurde und welche Einschränkungen dieser Code für die Compiler-Optimierung mit sich bringt.

Ich bin nicht wirklich vertraut mit dem C-Standard, aber C ++ hat das Konzept des "beobachtbaren" Verhaltens. Der Standard definiert dies sorgfältig und Compilern wird ein großer Spielraum bei der Optimierung gegeben, solange das Ergebnis das gleiche beobachtbare Verhalten ergibt. Meine Empfehlung zu versuchen zu bestimmen, ob zwei Funktionen die gleichen sind, wäre zu versuchen zu bestimmen, was ihr beobachtbares Verhalten ist (was sie tun und wie sie mit anderen Speicherbereichen interagieren und in welcher Reihenfolge).

    
SoapBox 02.09.2011 16:54
quelle
0

Wenn der Problemsatz auf einen kleinen Satz bekannter C oder C++ Quellcode-Funktionen reduziert werden kann, die von n verschiedenen Compilern mit jeweils m [n] jeder Kombination von Compiler und Optionen zu kompilieren und die resultierenden Befehlsbytes oder effizienter ihren Hash zu katalogisieren Unterschrift in einer Datenbank.

Die Auswahl der möglichen Compiler-Optionen ist möglicherweise groß, aber in der Praxis verwenden die Ingenieure in der Regel eine Reihe von Standardoptionen, die normalerweise nur minimal für das Debuggen optimiert und für die Veröffentlichung optimiert sind. Die Untersuchung vieler Projektkonfigurationen könnte ergeben, dass es in jeder Ingenieurskultur nur zwei oder drei mehr gibt, die sich auf Vorurteile oder Aberglaube beziehen, wie die Compiler funktionieren - ob nun korrekt oder nicht.

Ich vermute, dass dieser Ansatz dem am nächsten kommt, was Sie eigentlich wollen: eine Möglichkeit, mutmaßlich entzogenen Quellcode zu untersuchen. Alle vorgeschlagenen Techniken zur Rekonstruktion des Parserbaums des Compilers mögen Früchte tragen, haben aber ein großes Potenzial für übersehene symmetrische Lösungen oder mehrdeutige unlösbare Fälle.

    
wallyk 02.09.2011 17:33
quelle