Layered obfuscation: a taxonomy of software obfuscation techniques for layered security

In diesem Abschnitt werden die Verschleierungstechniken für bestimmte Codeelemente untersucht. Diese Schicht deckt die meisten Veröffentlichungen im Bereich Software-Verschleierung ab. Wie in Fig. 4, je nachdem, auf welche Elemente eine Verschleierungstechnik abzielt, Wir unterteilen diese Kategorie in fünf Unterkategorien: Verschleiern von Layouts, Verschleiern von Steuerelementen, Verschleiern von Datenverschleierungsfunktionen, und Verschleierungsklassen.

Abb. 4
 abbildung4

Die Verschleierungstechniken der Code-Element-Schicht

Obfuscating layout

Layout obfuscation verschlüsselt das Layout von Codes oder Anweisungen, während die ursprüngliche Syntax erhalten bleibt. In diesem Abschnitt werden vier Strategien zur Verschleierung von Layouts erläutert: bedeutungslose Klassifikatoren, Entfernen redundanter Symbole, Trennen verwandter Codes und Junk-Codes.

Bedeutungslose Bezeichner

Dieser Ansatz wird auch als lexikalische Verschleierung bezeichnet, bei der sinnvolle Bezeichner in bedeutungslose umgewandelt werden. Für die meisten Programmiersprachen ist die Annahme aussagekräftiger und einheitlicher Benennungsregeln (z. B. ungarische Notation (Simonyi 1999)) als gute Programmierpraxis erforderlich. Obwohl solche Namen in Quellcodes angegeben sind, verbleiben einige standardmäßig in der veröffentlichten Software. Beispielsweise werden die Namen globaler Variablen und Funktionen in C / C ++ in Binärdateien gespeichert, und alle Namen von Java sind in Bytecodes reserviert. Da solche aussagekräftigen Namen die gegnerische Programmanalyse erleichtern können, sollten wir sie verschlüsseln. Um die verschleierten Bezeichner verwirrender zu machen, schlugen Chan und Yang (2004) vor, absichtlich dieselben Namen für Objekte unterschiedlichen Typs oder innerhalb verschiedener Domänen zu verwenden. Solche Ansätze wurden von ProGuard (2016) als Standardverschleierungsschema für Java-Programme übernommen.

Redundante Symbole entfernen

Diese Strategie entfernt redundante symbolische Informationen aus freigegebener Software, z. B. die Debug-Informationen für die meisten Propgramme (Low 1998). Außerdem gibt es andere redundante Symbole für bestimmte Formate von Programmen. Zum Beispiel enthalten ELF-Dateien Symboltabellen, die die Paare von Bezeichnern und Adressen aufzeichnen. Wenn Sie Standardkompilierungsoptionen zum Kompilieren von C / C ++ – Programmen verwenden, z. B. LLVM (Lattner und Adve 2004), enthalten die generierten Binärdateien solche Symboltabellen. Um solche redundanten Informationen zu entfernen, können Entwickler das Strip-Tool von Linux verwenden. Ein weiteres Beispiel mit redundanten Informationen sind Android Smali-Codes. Standardmäßig enthalten die generierten SMALI-Codes Informationen, die mit beginnen.linie und .quelle, die zu Verschleierungszwecken entfernt werden kann (Dalla Preda und Maggi 2017).

Verwandte Codes trennen

Ein Programm ist leichter zu lesen, wenn seine logisch verwandten Codes auch physikalisch nahe beieinander liegen (Collberg et al. 1997). Daher kann das Trennen verwandter Codes oder Anweisungen die Schwierigkeiten beim Lesen erhöhen. Es ist sowohl auf Quellcodes (z. B. Umordnungsvariablen (Low 1998)) als auch auf Assemblercodes (z. B. Umordnungsanweisungen (Wroblewski 2002)) anwendbar. In der Praxis ist die Verwendung bedingungsloser Sprünge zum Umschreiben eines Programms ein beliebter Ansatz, um dies zu erreichen. Entwickler können beispielsweise die Assemblycodes mischen und dann goto verwenden, um den ursprünglichen Kontrollfluss zu rekonstruieren (Sie und Yim 2010). Dieser Ansatz ist beliebt für Assembly-Codes und Java-Bytecodes mit der Verfügbarkeit von Goto-Anweisungen (Dalla Preda und Maggi 2017).

Junk-Codes

Diese Strategie fügt Junk-Anweisungen hinzu, die nicht funktionieren. Für Binärdateien können wir Anweisungen ohne Bedienung (NOP oder 0x00) hinzufügen (Dalla Preda und Maggi 2017; Marcelli et al. 2018). Außerdem können wir auch Junk-Methoden hinzufügen, z. B. nicht mehr funktionierende Methoden in Android-Smali-Codes (Dalla Preda und Maggi 2017). Die Junk-Codes können typischerweise die Signaturen der Codes ändern und sich daher der statischen Mustererkennung entziehen.

Da die Layout-Verschleierung die ursprüngliche Codesyntax nicht manipuliert, ist sie weniger anfällig für Kompatibilitätsprobleme oder Fehler. Daher sind solche Techniken in der Praxis die beliebtesten. Darüber hinaus können die Techniken bedeutungsloser Bezeichner und das Entfernen redundanter Symbole die Größe von Programmen verringern, was sie weiter attraktiv macht (ProGuard 2016). Die Wirksamkeit der Layout-Verschleierung ist jedoch begrenzt. Es ist vielversprechend widerstandsfähig gegen Deobfuscation-Angriffe, da einige Transformationen in eine Richtung erfolgen, die nicht rückgängig gemacht werden können. Einige Layoutinformationen können jedoch kaum geändert werden, z. B. die Methodenbezeichner aus dem Java SDK. Solche Restinformationen sind für Gegner unerlässlich, um die verschleierten Informationen wiederherzustellen. Zum Beispiel Bichsel et al. (2016) haben versucht, ProGuard-verschleierte Apps zu entschlüsseln, und sie haben erfolgreich etwa 80% der Namen wiederhergestellt.

Verschleiern von Steuerelementen

Diese Art von Verschleierungstechniken transformiert die Steuerelemente von Codes, um die Programmkomplexität zu erhöhen. Dies kann über falsche Kontrollflüsse, probabilistische Kontrollflüsse, Dispatcher-basierte Steuerelemente und implizite Steuerelemente erreicht werden.

Falsche Steuerabläufe

Falsche Steuerabläufe beziehen sich auf die Steuerabläufe, die absichtlich zu einem Programm hinzugefügt werden, aber niemals ausgeführt werden. Es kann die Komplexität eines Programms erhöhen, z., in McCabe Komplexität (McCabe 1976) oder Harrison Metriken (Harrison und Magel 1981). Beispielsweise wird die McCabe-Komplexität (McCabe 1976) als die Anzahl der Kanten in einem Kontrollflussdiagramm minus der Anzahl der Knoten und dann plus dem Zweifachen der verbundenen Komponenten berechnet. Um die McCabe-Komplexität zu erhöhen, können wir entweder neue Kanten einführen oder einer verbundenen Komponente sowohl neue Kanten als auch Knoten hinzufügen.

Um die Unerreichbarkeit falscher Kontrollströme zu gewährleisten, haben Collberg et al. (1997) schlugen vor, undurchsichtige Prädikate zu verwenden. Sie definierten opaque predict als das Prädikat, dessen Ergebnis während der Verschleierungszeit bekannt ist, aber durch statische Programmanalyse schwer abzuleiten ist. Im Allgemeinen kann ein undurchsichtiges Prädikat konstant wahr (PT), konstant falsch (PF) oder kontextabhängig (P?). Es gibt drei Methoden, um undurchsichtige Prädikate zu erstellen: numerische Schemata, Programmierschemata und kontextbezogene Schemata.

Numerische Schemata

Numerische Schemata bilden undurchsichtige Prädikate mit mathematischen Ausdrücken. Zum Beispiel ist 7×2-1≠y2 für alle ganzen Zahlen x und y konstant wahr. Wir können solche undurchsichtigen Prädikate direkt einsetzen, um falsche Kontrollflüsse einzuführen. Abbildung 5a zeigt ein Beispiel, in dem das opaque-Prädikat garantiert, dass der falsche Kontrollfluss (d. H. der else-Zweig) nicht ausgeführt wird. Angreifer hätten jedoch höhere Chancen, sie zu erkennen, wenn wir dieselben undurchsichtigen Prädikate häufig in einem verschleierten Programm verwenden. Arboit (2002) schlug daher vor, eine Familie solcher undurchsichtigen Prädikate automatisch zu generieren, so dass ein Verschleierer jedes Mal ein eindeutiges undurchsichtiges Prädikat auswählen kann.

Abb. 5
 abbildung5

Kontrollflussverschleierung mit undurchsichtigen Prädikaten

Ein weiterer mathematischer Ansatz mit höherer Sicherheit ist die Verwendung von Kryptofunktionen wie der Hash-Funktion \(\mathcal {H}\) (Sharif et al. 2008) und homomorphe Verschlüsselung (Zhu und Thomborson 2005). Zum Beispiel können wir ein Prädikat x==c durch \(\mathcal {H}(x)==c_{hash}\) ersetzen, um die Lösung von x für diese Gleichung zu verbergen. Beachten Sie, dass ein solcher Ansatz im Allgemeinen von Malware verwendet wird, um die dynamische Programmanalyse zu umgehen. Wir können auch Kryptofunktionen verwenden, um Gleichungen zu verschlüsseln, die nicht erfüllt werden können. Solche undurchsichtigen Prädikate verursachen jedoch viel Aufwand.

Um opake Konstanten zu komponieren, die gegen statische Analyse resistent sind, haben Moser et al. (2007) schlug vor, 3-SAT-Probleme zu verwenden, die NP-schwer sind. Dies ist möglich, weil man effiziente Algorithmen haben kann, um solche harten Probleme zu komponieren (Selman et al. 1996). Zum Beispiel demonstrierten Tiella und Ceccato (2017), wie man solche undurchsichtigen Prädikate mit k-Clique-Problemen zusammensetzt.

Um opake Konstanten zu komponieren, die gegen dynamische Analyse resistent sind, haben Wang et al. (2011) vorgeschlagen, undurchsichtige Prädikate mit einer Form ungelöster Vermutungen zu erstellen, die viele Male wiederholt werden. Da Schleifen für die dynamische Analyse eine Herausforderung darstellen, sollte der Ansatz in der Natur gegen dynamische Analyse resistent sein. Beispiele für solche Vermutungen umfassen Collatz-Vermutung, 5x + 1-Vermutung, Matthews-Vermutung. Abbildung 5b zeigt, wie die Collatz-Vermutung verwendet wird, um falsche Kontrollflüsse einzuführen. Unabhängig davon, wie wir x initialisieren, endet das Programm mit x=1, und originalCodes() kann immer ausgeführt werden.

Programmierschemata

Da die kontradiktorische Programmanalyse eine große Bedrohung für undurchsichtige Prädikate darstellt, können wir herausfordernde Programmanalyseprobleme verwenden, um undurchsichtige Prädikate zu erstellen. In: Collberg et al. schlug zwei klassische Probleme vor, Zeigeranalyse und gleichzeitige Programme.

Im Allgemeinen bezieht sich die Zeigeranalyse auf die Bestimmung, ob zwei Zeiger auf dieselbe Adresse zeigen können oder dürfen. Einige Zeigeranalyseprobleme können für die statische Analyse NP-schwer oder sogar unentscheidbar sein (Landi und Ryder 1991). Ein weiterer Vorteil ist, dass Zeigeroperationen während der Ausführung sehr effizient sind. Daher können Entwickler belastbare und effiziente undurchsichtige Vorhersagen mit gut gestalteten Zeigeranalyseproblemen erstellen, z. B. Zeiger auf einige Objekte mit dynamischen Datenstrukturen beibehalten (Collberg et al. 1998a).

Gleichzeitige Programme oder parallele Programme sind ein weiteres herausforderndes Problem. Im Allgemeinen hat eine parallele Region von n Anweisungen n! verschiedene Arten der Ausführung. Die Ausführung wird nicht nur durch das Programm bestimmt, sondern auch durch den Laufzeitstatus eines Host-Computers. In: Collberg et al. (1998a) schlug vor, gleichzeitige Programme zu verwenden, um den zeigerbasierten Ansatz durch gleichzeitige Aktualisierung der Zeiger zu verbessern. Majumdar und Thomborson (2006) schlugen vor, verteilte parallele Programme zu verwenden, um undurchsichtige Prädikate zu erstellen.

Außerdem komponieren einige Ansätze undurchsichtige Prädikate mit Programmiertricks, wie z. B. der Nutzung von Ausnahmebehandlungsmechanismen. Dolz und Parra (2008) schlugen beispielsweise vor, den Try-catch-Mechanismus zu verwenden, um undurchsichtige Prädikate für .Net und Java zu erstellen. Die Ausnahmeereignisse umfassen Division durch Null, Nullzeiger, Index außerhalb des Bereichs oder sogar bestimmte Hardwareausnahmen (Chen et al. 2009). Die ursprüngliche Programmsemantik kann über maßgeschneiderte Ausnahmebehandlungsschemata erreicht werden. Solche undurchsichtigen Prädikate haben jedoch keine Sicherheitsgrundlage und sind anfällig für fortgeschrittene Cyberangriffe.

Kontextbezogene Schemata

Kontextbezogene Schemata können verwendet werden, um opake Variantenprädikate (dh {P?}). Die Prädikate sollten einige deterministische Eigenschaften enthalten, damit sie zur Verschleierung von Programmen verwendet werden können. Zum Beispiel Drape und et al. (2009) vorgeschlagen, solche undurchsichtigen Prädikate zu komponieren, die unter einer kontextbedingten Einschränkung invariant sind, z. B. ist das undurchsichtige Prädikat x mod3 ==1 konstant wahr, wenn x mod3: 1?x++ : x = x+3. In: Palsberg et al. (2000) vorgeschlagene dynamische opake Prädikate, die eine Folge korrelierter Prädikate enthalten. Das Bewertungsergebnis jedes Prädikats kann in jedem Lauf variieren. Solange die Prädikate jedoch korreliert sind, ist das Programmverhalten deterministisch. Abbildung 5c zeigt ein Beispiel für dynamische undurchsichtige Prädikate. Egal wie wir *p und *q initialisieren, das Programm entspricht y=x+3,x=y+3 .

Der Widerstand falscher Kontrollflüsse hängt hauptsächlich von der Sicherheit undurchsichtiger Prädikate ab. Eine ideale Sicherheitseigenschaft für undurchsichtige Prädikate besteht darin, dass sie zum Brechen im schlimmsten Fall exponentielle Zeit benötigen, zum Konstruieren jedoch nur Polynomzeit. Beachten Sie, dass einige undurchsichtige Prädikate mit solchen Sicherheitsbedenken entwickelt wurden, jedoch möglicherweise mit Fehlern implementiert werden. Zum Beispiel die von Ogiso et al. (2003) basieren auf trivialen Problemstellungen, die leicht vereinfacht werden können. Wenn solche undurchsichtigen Prädikate ordnungsgemäß implementiert werden, versprechen sie, widerstandsfähig zu sein.

Probabilistische Kontrollflüsse

Falsche Kontrollflüsse können Probleme bei der statischen Programmanalyse verursachen. Sie sind jedoch anfällig für dynamische Programmanalysen, da die falschen Kontrollflüsse inaktiv sind. Die Idee probabilistischer Kontrollströme verfolgt eine andere Strategie zur Bekämpfung der Bedrohung (Pawlowski et al. 2016). Es führt Replikationen von Kontrollströmen mit derselben Semantik, aber unterschiedlicher Syntax ein. Wenn dieselbe Eingabe mehrmals empfangen wird, kann sich das Programm für unterschiedliche Ausführungszeiten unterschiedlich verhalten. Die Technik eignet sich auch zur Bekämpfung von Seitenkanalangriffen (Crane et al. 2015).

Beachten Sie, dass die Strategie probabilistischer Kontrollflüsse falschen Kontrollflüssen mit kontextbezogenen undurchsichtigen Prädikaten ähnelt. Sie unterscheiden sich jedoch in ihrer Natur, da kontextbezogene undurchsichtige Prädikate tote Pfade einführen, obwohl sie keine Junk-Codes einführen.

Dispatcher-basierte Steuerelemente

Ein Dispatcher-basiertes Steuerelement bestimmt die nächsten Codeblöcke, die zur Laufzeit ausgeführt werden. Solche Steuerelemente sind für die Verschleierung des Steuerflusses unerlässlich, da sie die ursprünglichen Steuerflüsse vor statischer Programmanalyse verbergen können.

Ein wichtiger Dispatcher-basierter Verschleierungsansatz ist das Control-Flow-Flattening, das Tiefencodes in flache Codes mit größerer Komplexität umwandelt. In: Wang et al. (2000) schlug zunächst den Ansatz vor. Abbildung 6 zeigt ein Beispiel aus ihrem Papier, das eine while-Schleife mit switch-case in eine andere Form umwandelt. Um eine solche Transformation zu realisieren, besteht der erste Schritt darin, den Code in eine äquivalente Darstellung mit if-then-goto Anweisungen umzuwandeln, wie in Abb. 6; dann modifizieren sie die goto-Anweisungen mit Switch-Case-Anweisungen, wie in Fig. 6. Auf diese Weise wird die ursprüngliche Programmsemantik implizit durch Steuerung des Datenflusses der Schaltvariablen realisiert. Da die Ausführungsreihenfolge von Codeblöcken durch die Variable dynamisch bestimmt wird, kann man die Steuerflüsse nicht kennen, ohne das Programm auszuführen. Cappaert und Preneel (2010) formalisierten die Abflachung des Kontrollflusses als Verwendung eines Dispatcher-Knotens (z. B. Switch), der den nächsten auszuführenden Codeblock steuert. Außerdem gibt es mehrere Verbesserungen bei der Code-Flow-Abflachung. Um beispielsweise den Widerstand gegen statische Programmanalyse auf der Schaltvariablen zu erhöhen, Wang et al. (2001) vorgeschlagen, Zeigeranalyseprobleme einzuführen. Um das Programm weiter zu komplizieren, Chow et al. (2001) vorgeschlagen, gefälschte Codeblöcke hinzuzufügen.

Abb. 6
 abbildung6

Von Wang et al. vorgeschlagener Ansatz zur Abflachung des Kontrollflusses. (2000)

László und Kiss (2009) schlugen einen Mechanismus zur Abflachung des Kontrollflusses vor, um bestimmte C ++ – Syntax wie try-catch, while-do , continue . Der Mechanismus basiert auf einem abstrakten Syntaxbaum und verwendet ein festes Layoutmuster. Für jeden zu verschleiernden Codeblock wird eine while-Anweisung in der äußeren Schleife und eine Switch-Case-Verbindung in der Schleife erstellt. Die Switch-Case-Verbindung implementiert die ursprüngliche Programmsemantik, und die switch-Variable wird auch zum Beenden der äußeren Schleife verwendet. Cappaert und Preneel (2010) fanden heraus, dass die Mechanismen möglicherweise anfällig für lokale Analysen sind, dh die Switch-Variable wird sofort zugewiesen, sodass Gegner den nächsten auszuführenden Block ableiten können, indem sie nur in einen aktuellen Block schauen. Sie schlugen einen verstärkten Ansatz mit mehreren Tricks vor, z. B. die Verwendung einer Referenzzuweisung (z. B. swVar = swVar + 1) anstelle einer direkten Zuweisung (z. B. swVar = 3), das Ersetzen der Zuweisung über if-else durch einen einheitlichen Zuweisungsausdruck und die Verwendung von Einwegfunktionen bei der Berechnung des Nachfolgers eines Basisblocks.

Neben der Abflachung des Kontrollflusses gibt es mehrere andere Dispatcher-basierte Verschleierungsuntersuchungen (z. B. (Linn and Debray 2003; Ge et al. 2005; Zhang et al. 2010; Schrittwieser und Katzenbeisser 2011)). Linn und Debray (2003) schlugen vor, Binärdateien mit Verzweigungsfunktionen zu verschleiern, die die Ausführung basierend auf den Stapelinformationen steuern. In ähnlicher Weise haben Zhang et al. (2010) schlug vor, Verzweigungsfunktionen zum Verschleiern objektorientierter Programme einzusetzen, die einen einheitlichen Methodenaufrufstil mit einem Objektpool definieren. Um die Sicherheit solcher Mechanismen zu erhöhen, haben Ge et al. (2005) schlugen vor, die Steuerinformationen in einem anderen eigenständigen Prozess zu verbergen und eine Kommunikation zwischen Prozessen zu verwenden. Schrittwieser und Katzenbeisser (2011) schlugen vor, diversifizierte Codeblöcke zu verwenden, die dieselbe Semantik implementieren.

Dispatcher-basierte Verschleierung ist resistent gegen statische Analyse, da sie das Kontrollflussdiagramm eines Softwareprogramms verbirgt. Es ist jedoch anfällig für dynamische Programmanalysen oder hybride Ansätze. Zum Beispiel, Udupa et al. (2005) schlug einen hybriden Ansatz vor, um die verborgenen Kontrollflüsse sowohl mit statischer als auch mit dynamischer Analyse aufzudecken.

Implizite Steuerelemente

Diese Strategie konvertiert explizite Steuerbefehle in implizite. Dies kann Reverse Engineers daran hindern, die richtigen Kontrollflüsse zu adressieren. Zum Beispiel können wir die Steueranweisungen von Assemblercodes (z. B. jmp und jne) durch eine Kombination von mov und anderen Anweisungen ersetzen, die dieselbe Steuerungssemantik implementieren (Balachandran und Emmanuel 2011).

Beachten Sie, dass sich alle vorhandenen Ansätze zur Verschleierung des Kontrollflusses auf die Transformation auf syntaktischer Ebene konzentrieren, während der Schutz auf semantischer Ebene selten diskutiert wurde. Obwohl sie eine gewisse Widerstandsfähigkeit gegen Angriffe aufweisen können, bleibt ihre Verschleierungswirksamkeit in Bezug auf den semantischen Schutz unklar.

Verschleiern von Daten

Aktuelle Techniken zur Verschleierung von Daten konzentrieren sich auf gängige Datentypen wie Ganzzahlen, Zeichenfolgen und Arrays. Wir können Daten durch Teilen, Zusammenführen, Prozedurisieren, Codieren usw. transformieren.

Data splitting/merging

Data splitting verteilt die Informationen einer Variablen in mehrere neue Variablen. Beispielsweise kann eine boolesche Variable in zwei boolesche Variablen aufgeteilt werden, und durch Ausführen logischer Operationen kann der ursprüngliche Wert erhalten werden.

Datenzusammenführung hingegen aggregiert mehrere Variablen zu einer Variablen. In: Collberg et al. (1998b) demonstrierte ein Beispiel, das zwei 32-Bit-Ganzzahlen zu einer 64-Bit-Ganzzahl zusammenführt. Ertaul und Venkatesh (2005) schlugen eine andere Methode vor, die mehrere Variablen mit diskreten Logarithmen in einen Raum packt.

Datenprozedur

Datenprozedur ersetzt statische Daten durch Prozeduraufrufe. In: Collberg et al. (1998b) vorgeschlagen, Strings durch eine Funktion zu ersetzen, die alle Strings durch Angabe bestimmter Parameterwerte erzeugen kann. Drape und et al. (2004) vorgeschlagen, numerische Daten mit zwei inversen Funktionen f und g zu kodieren. Um einer Variablen i einen Wert v zuzuweisen, weisen wir ihn einer injizierten Variablen j als j=f(v) zu. Um i zu verwenden, rufen wir stattdessen g(j) auf.

Datenkodierung

Datenkodierung kodiert Daten mit mathematischen Funktionen oder Chiffren. Ertaul und Venkatesh (2005) schlugen vor, Zeichenfolgen mit affinen Chiffren (z. B. Caser-Chiffre) zu kodieren und diskrete Logarithmen zum Packen von Wörtern zu verwenden. Fukushima et al. (2008) schlugen vor, die klaren Zahlen mit exklusiven Oder-Operationen zu codieren und dann das Berechnungsergebnis vor der Ausgabe zu entschlüsseln. Kovacheva (2013) schlug vor, Zeichenfolgen mit der RC4-Chiffre zu verschlüsseln und sie dann zur Laufzeit zu entschlüsseln.

Array-Transformation

Array ist eine der am häufigsten verwendeten Datenstrukturen. Um Arrays zu verschleiern, haben Collberg et al. (1998b) diskutierten mehrere Transformationen, z. B. das Aufteilen eines Arrays in mehrere Subarrays, das Zusammenführen mehrerer Arrays zu einem Array, das Falten eines Arrays, um seine Dimension zu vergrößern, oder das Abflachen eines Arrays, um die Dimension zu verringern. Ertaul und Venkatesh (2005) schlugen vor, die Array-Indizes mit zusammengesetzten Funktionen zu transformieren. In: Zhu et al. (2006); Zhu (2007) schlug vor, homomorphe Verschlüsselung für die Array-Transformation zu verwenden, einschließlich Indexänderung, Faltung und Schmeichelei. Zum Beispiel können wir die Elemente eines Arrays mit i shm mod n mischen, wobei i der ursprüngliche Index ist, n die Größe des ursprünglichen Arrays ist und m und n relativ Primzahlen sind.

Methoden verschleiern

Methode inline/outline

Eine Methode ist eine unabhängige Prozedur, die von anderen Anweisungen des Programms aufgerufen werden kann. Methode inline ersetzt den ursprünglichen prozeduralen Aufruf durch den Funktionskörper selbst. Method Outline arbeitet auf die entgegengesetzte Weise, die eine Folge von Anweisungen extrahiert und eine Methode abstrahiert. Sie sind gute Unternehmen, die die ursprüngliche Abstraktion von Prozeduren verschleiern können (Collberg et al. 1997).

Methodenklonung

Wenn eine Methode stark aufgerufen wird, können wir Replikationen der Methode erstellen und zufällig eine davon aufrufen. Um die kontradiktorische Interpretation zu verwirren, sollte jede Version der Replikation irgendwie eindeutig sein, z. B. durch unterschiedliche Verschleierungstransformationen (Collberg et al. 1997) oder verschiedene Signaturen (Ertaul und Venkatesh 2004).

Methode Aggregation / Streuung

Die Idee ähnelt der Datenverschleierung. Wir können irrelevante Methoden in einer Methode aggregieren oder eine Methode in mehrere Methoden aufteilen (Collberg et al. 1997; Niedrig 1998).

Methodenproxy

Dieser Ansatz erstellt Proxy-Methoden, um das Reverse Engineering zu verwirren. Zum Beispiel können wir die Proxys als öffentliche statische Methoden mit randomisierten Bezeichnern erstellen. Es kann mehrere verschiedene Proxys für dieselbe Methode geben (Dalla Preda und Maggi 2017). Der Ansatz ist äußerst nützlich, wenn die Methodensignaturen nicht geändert werden können (Protsenko und Müller 2013).

Obfuscating classes

Obfuscating classes teilt einige ähnliche Ideen mit Verschleierungsmethoden wie Splitting und clone (Collberg et al. 1998b). Da Klasse jedoch nur in objektorientierten Programmiersprachen wie JAVA und .NET existiert, diskutieren wir sie als eindeutige Kategorie. Im Folgenden stellen wir die wichtigsten Strategien zur Verschleierung von Klassen vor.

>Modifikatoren

Objektorientierte Programme enthalten Modifikatoren (z., öffentlich, privat), um den Zugriff auf Klassen und Mitglieder von Klassen einzuschränken. Das Löschen von Modifikatoren entfernt solche Einschränkungen und macht alle Mitglieder öffentlich (Protsenko und Muller 2013). Dieser Ansatz kann die Implementierung anderer Klassenverschleierungsmethoden erleichtern.

Splitting / Coalescing class

Die Idee von Coalescing / Splitting besteht darin, die Absicht von Entwicklern beim Entwerfen der Klassen zu verschleiern (Sosonkin et al. 2003). Beim Zusammenführen von Klassen können wir lokale Variablen oder lokale Befehlsgruppen in eine andere Klasse übertragen (Fukushima et al. 2003).

Class hierarchy flattening

Interface ist ein leistungsfähiges Werkzeug für objektorientierte Programme. Ähnlich wie method Proxy können wir Proxys für Klassen mit Schnittstellen erstellen (Sosonkin et al. 2003). Ein wirksamerer Weg besteht jedoch darin, die ursprüngliche Vererbungsbeziehung zwischen Klassen mit Schnittstellen aufzubrechen. Indem wir jeden Knoten eines Teilbaums in der Klassenhierarchie dieselbe Schnittstelle implementieren lassen, können wir die Hierarchie abflachen (Foket et al. 2012).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.