Codeverschleierung mit Präprozessor
-
Ein Blick mit dem IDA Pro Free Disassembler zeigt mir immer wieder wie relativ einfach manchmal eigene Freischaltungen zu hacken sind:
int main(char** argv, int argc) { if (TestReg()) { printf("Freischaltung fehlt!"); return 0; } //... return 0; }
In der Visualisierung sieht man hier schön: Ein Pfad beendet das Program, das andere führt das Program weiter aus. Hierfür gibt es einige
schöne Code Verschleierungstechniken wie das erzwingen von Inline, einführen von Datenmüll, Trennung Freischaltungsprüfung
und Fehlermeldung,...Eine andere Gegenmaßnahme ist es Abfragen zu verkomplizieren.
// Codeobfu(0) = false // Codeobfu(1) = true // sonst false bool Codeobfu_CheckForEqual(int iValue) // Den Compiler müsste man evt. zwingen diese Funktion zu inlinen { if (asin(iValue) > 1.3) // asin(1) = PI/2 = 1.57 > 1.3 return true; if (acos(iValue) > 1.3) // acos(0) = PI/2 = 1.57 > 1.3 return true; return false; } int main(char** argv, int argc) { if (Codeobfu_CheckForEqual(TestReg())) { printf("Freischaltung fehlt!"); return 0; } //... return 0; }
Nun meine Frage:
Ist es mit dem C (oder auch C++) Präprozessor möglich eine Funktion aufzurufen und deren Rückgabewert in einer Präprozessoranweisungen zu nutzen?// Codeobfu(0) = false // Codeobfu(1) = true // sonst false bool Codeobfu_CheckForEqual(int iValue) // Den Compiler müsste man evt. zwingen diese Funktion zu inlinen { // Variante A #if time() % 3 == 0 if (asin(iValue) > 1.3) // asin(1) = PI/2 = 1.57 > 1.3 return true; if (acos(iValue) > 1.3) // acos(0) = PI/2 = 1.57 > 1.3 return true; #endif // Variante B #if time() % 3 == 1 return iValue != 0; #endif // Variante C #if time() % 3 == 2 //... #endif return false; } int main(char** argv, int argc) { if (Codeobfu_CheckForEqual(TestReg())) { printf("Freischaltung fehlt!"); return 0; } //... return 0; }
Dadurch hätte ich den Vorteil dass nach jedem Compiliervorgang das Disassembly der main() anders aussieht. Vorausgesetzt ist
natürlich dass der Optimierer hier keinen Strich durch die Rechnung macht und die Funktion komplett durchgetestet ist.
-
Wenn es hier um Malware geht, sollte aus moralischen Gründen niemand helfen.
Wenn nicht, solltest du dich lieber um die Qualität deines Produktes bemühen. Wenn es da ähnlich aussieht wie dein Ansatz zur Verschleierung, ist da noch viel zu tun. Vielleicht bezahlen einige Verrückte am Ende sogar gerne dafür, wenn es gut ist und die Kunden respektvoll behandelt.
-
Bitte ein Bit schrieb:
Ist es mit dem C (oder auch C++) Präprozessor möglich eine Funktion aufzurufen und deren Rückgabewert in einer Präprozessoranweisungen zu nutzen?
Nope. Frag dein Buildsystem nach der Buildnummer und entscheide basierend darauf. Andererseits: was soll es bringen unterschiedliche Prüfvarianten pro build zu haben? Letztlich sind es doch endlich viele und die meisten davon wahrscheinlich wieder trivial zu umgehen. Lies Eldad Eilam (2005): "Reversing: Secrets of Reverse Engineering".
-
TyRoXx schrieb:
Wenn es hier um Malware geht, sollte aus moralischen Gründen niemand helfen.
Wenn nicht, solltest du dich lieber um die Qualität deines Produktes bemühen. Wenn es da ähnlich aussieht wie dein Ansatz zur Verschleierung, ist da noch viel zu tun. Vielleicht bezahlen einige Verrückte am Ende sogar gerne dafür, wenn es gut ist und die Kunden respektvoll behandelt.
Du bist echt ein Heinz wie er im Buche steht.
Falls du es genau wissen willst, in einem übernommenen Projekt mit Quellen habe ich diese Art von if-then-else Freischaltung gefunden. In den Disassembler geworfen, brauchte ich keine 30 Sekunden um das Program zu hacken. Und das ist Mist^10. Das ist ein Fehler, den ich behoben habe.
Bloß finde mal eine Anleitung zum Thema "Kopierschutz schreiben". Außer dem Vanilla Skype Paper, der API Beschreibung zu einem Dongle habe ich so nix gefunden. Also entweder lässt du Verbesserungsvorschläge rüberwachsen oder spare dir deine dummen Bemerkungen über Code-Qualität.
Oder regst du dich, in einer dunkelen Vorahnung, etwa auf, ich würde etwa so etwas wie Steam schreiben?
Passwort Swordfish schrieb:
Nope. Frag dein Buildsystem nach der Buildnummer und entscheide basierend darauf. Andererseits: was soll es bringen unterschiedliche Prüfvarianten pro build zu haben? Letztlich sind es doch endlich viele und die meisten davon wahrscheinlich wieder trivial zu umgehen. Lies Eldad Eilam (2005): "Reversing: Secrets of Reverse Engineering".
Danke für die Info. Ich schaue mal dass ich an das Buch komme.
Es war auch nur mal so eine Idee bzw. eine Wissenfrage.
Im Kern will ich einfache Fehler bezüglich Sicherheit der Freischaltung vermeiden. Wie schon gesagt, wenn ich mein eigenes Program in den Dissassembler stecke und meinen Kopierschutz direkt als einfache if-then-else Anweisung grafisch dargestellt bekomme, ist das meines Erachtens schon ein Fehler. Mit meinen Code Verschleierungstechniken sieht man die Anweisung auch, versteckt zwischen 100 und mehr weiteren Anweisungsblöcken.
Eine hohe Sicherheit werde ich nie erreichen. Es soll nur Script Kiddies und Semiprofessionellen die Lust am Disassembling nehmen und den Trotzigen wenigstens ein wenig Arbeit machen.
-
Bitte ein Bit schrieb:
Du bist echt ein Heinz wie er im Buche steht.
Er hat recht. Aber dies scheiße kommuniziert halt.
Bitte ein Bit schrieb:
Falls du es genau wissen willst, in einem übernommenen Projekt mit Quellen habe ich diese Art von if-then-else Freischaltung gefunden. In den Disassembler geworfen, brauchte ich keine 30 Sekunden um das Program zu hacken. Und das ist Mist^10. Das ist ein Fehler, den ich behoben habe.
Mit der Aufgabenstellung kann man was anfangen - aber dann sage ich dir gleich, dass das so nicht funktioniert.
Der Präprozessor kommt ja vor der eigentlichen Programmkompilierung. Sprich, der kennttime()
garnicht. Wenn du dann sowas baust:#define MY_CALL time(0) int main(void) { #if MYCALL % 3 /*Bla*/ #endif return 0; }
, dann hat der Präprozessor da nur
time() % 3
stehen. Wenn er Funktionsaufrufe kennen würde, könnte er damit arbeiten. Kann er aber nicht, er kennt nur Makros und Konstanten.Im Internet findest du dann zwar Referenzen auf
__TIME__
und__DATE__
- das sind Konstanten, die in der Regel immer vorhanden sind (sprich, ich kenne keinen Compiler, bei dem das anders ist) - aber leider sind das Strings, keine Ganzzahlen.Aaaaaber einen kleinen Trick lässt dir der Präprozessor - allerdings nur, wenn dein Compiler auch mitspielt. Denn der Compiler nimmt - oder sollte zumindest - Code aus konstanten Bedingen weg (
if(1==0)printf("Bla\n");
sollte komplett entfernt werden, da die Bedingung niemals zutrifft).#include <stdio.h> #define PUT(x) (((x)[6]-'0') % 2) int main(void) { if(PUT(__TIME__)) { printf("%s\n",__TIME__); } return 0; }
Das Programm habe ich zu verschiedenen Zeiten (bei denen die Zehnerstelle der Sekunden einmal ohne Rest und einmal mit Rest durch 2 teilbar ist) übersetzt. Das Programm unterschiedet sich deutlich.
Version, die was ausgibt:
000000000040054a <main>: 40054a: 48 83 ec 08 sub $0x8,%rsp 40054e: bf 14 06 40 00 mov $0x400614,%edi 400553: e8 d8 fe ff ff callq 400430 <puts@plt> 400558: b8 00 00 00 00 mov $0x0,%eax 40055d: 48 83 c4 08 add $0x8,%rsp 400561: c3 retq 400562: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 400569: 00 00 00 40056c: 0f 1f 40 00 nopl 0x0(%rax)
Version, die nüschte ausgibt:
00000000004004fa <main>: 4004fa: b8 00 00 00 00 mov $0x0,%eax 4004ff: c3 retq
Aaaaaber das kann dir ebenfalls auf die Füße fallen. Es kann nämlich sein, dass das Programm
1 == 0
prüft, was normalerweise gar nicht geändert wird, aber das Programm wird für eine Maschine übersetzt, welches es mit dem Schreibschutz von statischen Daten nicht so genau nimmt (davon würden mir spontan eine einfallen, die ich schon mal programmiert habe). Der Code fasst dann die Speicherstelle, an der die 1 steht, an und ändert diese zu 0. 0 == 0 ist positiv, und damit die Bedingung erfüllt.Da hängst du sehr oft einfach von deinem Compiler ab. Das Programm oben habe ich mit dem GCC übersetzt - wenn ich da nicht -O1 (leichte Optimierungen einschalten) angebe, sind die Binaries fast kongruent. Und im
main
-Code sind diese bis auf ein einziges Bit gleich.Zusammenfassung: Plan hab' ich zwar, aber keine Idee.
Bitte ein Bit schrieb:
Im Kern will ich einfache Fehler bezüglich Sicherheit der Freischaltung vermeiden. Wie schon gesagt, wenn ich mein eigenes Program in den Dissassembler stecke und meinen Kopierschutz direkt als einfache if-then-else Anweisung grafisch dargestellt bekomme, ist das meines Erachtens schon ein Fehler. Mit meinen Code Verschleierungstechniken sieht man die Anweisung auch, versteckt zwischen 100 und mehr weiteren Anweisungsblöcken.
Und genau deswegen glaube ich nicht, dass mein Ansatz in Ordnung wäre. Denn er beruht darauf, dass Optimierungen eingeschaltet werden, deren Nutzen Willkür des Compilers sind.
Ich habe meine Nase bereits in ein paar ältere Kopierschutzmechanismen gesteckt (nur zu Forschungszwecken natürlich - das einzige, was mir von x86 Assembly hängengeblieben ist). Und viele davon kannst du mit einem einfachen:
#define NOPS "\x90" /*Liste beliebig erweitern.*/ fwrite(NOPS,1,strlen(NOPS),file);
kampfunfähig machen (äh, nicht ganz so einfach, aber da gehe ich nicht ins Detail rein ;))
Bitte ein Bit schrieb:
Eine hohe Sicherheit werde ich nie erreichen. Es soll nur Script Kiddies und Semiprofessionellen die Lust am Disassembling nehmen und den Trotzigen wenigstens ein wenig Arbeit machen.
Die Trotzigen haben Spaß daran, sage ich dir mal. Und verhöhnen dich auch noch, wenn du dir eine Blöße hast geben lassen.
-
Die Trotzigen haben Spaß daran, sage ich dir mal. Und verhöhnen dich auch noch, wenn du dir eine Blöße hast geben lassen.
Dagegen kann ich nichts machen. Ist halt eine Web 2.0 Gesellschaft, in der alles geklaut ist, was bei zwei nicht auf den Bäumen ist.
Aber danke für die Infos.
Er hat recht.
Inwiefern?
Natürlich muss man den Nutzen jeder Maßnahme abwägen. Fünf Stunden Arbeit damit andere 5 Minuten ihren Spaß haben, ist in dem Gastronomie Bereich gut, aber nicht unbedingt in der Informatik.
Oder glaubst du dass ich diese Maßnahme als eierlegende Wollmilch-Sau betrachte und gleichzeitig hierbei vergesse die (anderen) Maßnahmen ala Verschlüsselung vergesse zu testen?
Oder das ich unausgegorene Dinge benutze, also Element nutze welche zwar vielleicht auf dem aktuellen System laufen aber nicht bei anderen? Ich glaube ich habe genug Fehler gefixt bzw. Erfahrung gesammelt um Seiteneffekte zu hassen und den Standard schätzen zu lernen! Und gerade so einige Hacks rund um die Freischaltung / Hacken sehen nicht gerade portabel bzw. fehlerfrei aus.
-
Bitte ein Bit schrieb:
Oder glaubst du dass ich diese Maßnahme als eierlegende Wollmilch-Sau betrachte und gleichzeitig hierbei vergesse die (anderen) Maßnahmen ala Verschlüsselung vergesse zu testen?
Erstens das.
Bitte ein Bit schrieb:
Oder das ich unausgegorene Dinge benutze, also Element nutze welche zwar vielleicht auf dem aktuellen System laufen aber nicht bei anderen? Ich glaube ich habe genug Fehler gefixt bzw. Erfahrung gesammelt um Seiteneffekte zu hassen und den Standard schätzen zu lernen! Und gerade so einige Hacks rund um die Freischaltung / Hacken sehen nicht gerade portabel bzw. fehlerfrei aus.
Braucht es auch nicht, solange das Binary modifiziert wurde.
Aber mal abgesehen davon: ich kenne keinen Kopierschutz, der bisher nicht geknackt wurde.Ich weiß einfach nicht, ob das die Mühe wert ist.