C Programme vor Reverse-Engineering beschützen

asdfman

Commander
Registriert
März 2008
Beiträge
2.315
Tag,

Hin und wieder lese ich hier, wie sich Personen fragen, wie sie ihre Werke vor ungewollten
Einblicken schützen können. Inwieweit da in jedem Fall eine Schaffenshöhe erreicht ist, bei
der so ein Wunsch gerechtgfertigt ist, ist natürlich eine Frage für sich, aber darüber will ich
hier nicht urteilen.

Was ich aber möchte, ist ein weiteres meiner kleinen Nebenbeiprojekte vorstellen!

CryptPE komprimiert und verschlüsselt Win32-Anwendungen. Damit ist Reverse-Engineering
erstmal deutlich erschwert. Zur Komprimierung wird einfaches Huffman-Coding verwendet
und verschlüsselt mit RC4.

Ich habe es nicht ausprobiert, aber gehe stark davon aus, dass .NET-Binaries nicht mit
dem verwendeten Loader kompatibel sind. Also C, C++ etcpp only. Auch startet es im un-
veränderten Zustand ausschließlich Konsolenanwendungen.

https://github.com/SirDzstic/cryptpe

WTFPL-Lizenziert, damit es auch wirklich jeder nutzen kann.
Kommentare, Anregungen, Flames, etcpp erwünscht. Her damit!
 
Erst einmal: Schön, dass du dir Gedanken dazu gemacht hast :)

Nutze ich IDAPro, setzte ich halt mein ersten Breakpoint nach decode(binary, root, file_size); und habe den Pointer auf den decodierten Teil, dauert keine 2-3 Minuten

Daher erschließt sich mir bisher nicht der Sinn deines Tools. Zwar hast du den Code mittels RC4 codiert, aber der Code selbst enthält auch den Key, was im Grunde die Verschlüsselung Sinn frei macht.

Nutze ich deinen Code im Debugger, kann ich dem Code auch einfach folgen.

Bleibt nur, das der Code auf dem Speichermedium codiert ist.
 
Ja, das war mir schon klar. Deshalb sagte ich auch "erschweren".
Irgendwann muss der Code ausführbar im Speicher liegen. Da kann man machen, was man will. Aber
einfach disassembeln geht dann halt nicht mehr. Das ist zumindest eine Hürde. Wenn einem die zu
niedrig ist, hat man aber immer noch den Vorteil, dass das Binary, das ich erzeuge unter Umständen
am Ende kleiner ist als das Ursprüngliche :3

€: Eine Interessensfrage. Wie würde man vorgehen, wenn bspw. die Symbols aus dem Binary ge-
strippt wären? Dann kann man nicht mehr direkt den Breakpoint setzen. Wie viel höher wäre der
Aufwand? :E
 
Zuletzt bearbeitet:
das skype-protokoll wurde trotz aller gegenmaßnahmen wie anti-bebugging, code check, verschlüsselung des binaries, code obfuscation etc pp nachkonstruiert ^^
 
Keine Symbole?
Kein Problem, starte den Prozess SUSPENDED.
Dann hängste dich mitm Debugger rein und ließt einfach aber dem ersten Befehl mit.

Im Endeffekt kannst du nicht dagegen machen, dass jemand dein Programm auseinandernimmt.

Aber warum solltest du Angst haben, dass jemand dein Programm verwendet, du musst nur nachweißen können, dass es deins ist und dann kannst du Geld verlangen ;). Und wenn du dir Cracker-Szene anschaust, da wird jedes Programm irgendwann mal geknackt, selbst die, die nicht ganz simpel schützen (Ubisoft-ständig-online-Kopierschutz).
 
@asdfman: Wirklich schönes Projekt, aber erhöhte Sicherheit bekommst du durch deinen Ansatz nicht wirklich. Es stimmt zwar, dass irgendwann der entschlüsselte Code im Speicher liegen muss, aber man kann es einem schon sehr schwer machen dort heranzukommen. Ein Beispiel für einen relativ guten Packer ist Armadillo bzw. SoftwarePassport, wie die Software dazu heißt. Auch das ist nicht vor der Cracker-Szene sicher, aber für unerfahrene doch ein relativ großes Hinderniss.

Du solltest vielleicht schauen, dass du eher einen Packer als einen Loader schreibst, denn bei deinem Loader braucht man nur aufmerksam mit einem Debugger "durchsteppen" und man sieht sofort wo du zum EntryPoint des eigentlichen Programms springst, die Verschlüsselung interessiert dann niemanden. Tipps wie genau du einen guten Packer schreiben kannst, kann ich dir auch nicht geben, dazu habe ich mich zu wenig mit diesem Thema befasst, aber als erstes müsstest du wahrscheinlich daran arbeiten die Exe selbst zu erstellen und nicht einen Compiler für dich arbeiten zu lassen. Armadillo macht dann zum Beispiel noch CRC-Checks um festzustellen, ob die Datei manipuliert wurde, damit schützt man sich vor Crackern. Was man dann noch machen kann ist zu schauen, ob das Programm mit einem Debugger geöffnet wurde, zum Beispiel mit IsDebuggerPresent(), wobei das auch sehr leicht zu umgehen ist, aber es gibt noch andere Wege um auf einen Debugger aufmerksam zu werden.

Ich wünsche dir noch viel Spaß mit diesem Projekt und hoffe du postest über die Veränderungen, falls du noch weiter daran arbeitest.

Gruß
BlackMark
 
Habe ein wenig Code eingebaut, um Debugger zu erkennen. Aber da der Spaß ja Open Source ist, habe ich
meine Zweifel, dass es am Ende viel hilft.
 
Du hast recht, dass dein Programm Open-Source ist macht alles etwas sinnlos, weil man im Code nachsehen kann wie man den Debugger Schutz ausschalten und natürlich auch wie man den EntryPoint leicht finden kann.

Trotzdem, als Projekt um sich mit verschlüsselten Binaries auseinanderzusetzen ist es sicherlich als Referenz oder "proof of concept" sehr wertvoll.

Das einzige, das jetzt noch fehlt ist ein Unpacker für deinen Packer, wenn das Thema schon Reverse-Engineering ist ;)

Gruß
BlackMark
 
Hatte gerade eine Idee, wie man es einem Reverse-Engineer schwer machen könnte, obwohl er den Source-
code kennt:

Warum nicht das ganze Projekt cryptpe automatisch erzeugen? Man nimmt eine Reihe von Verschlüsselungs-
algorithmen (RC4, AES in verschiedenen Modes Of Operation, Twofish, etcpp) und verschiedene Kompressions-
verfahren (Huffman, LZW, LZMA, ...) und baut sich daraus den Sourcecode zusammen. Die Reihenfolge der
Blobs in bintable.h kann man natürlich auch herumwürfeln, ohne dass sich das Verhalten des Programms än-
dert, aber ein Angreifer müsste erst einmal wissen, welcher Zeiger auf welchen Blob zeigt. Für Debugger-
erkennung gibt es natürlich auch verschiedene Methoden, aus denen man zufällig wählen könnte.

Würde das etwas (wenn ja: viel?) bringen? Was meinen die Experten?
 
nö. man kann es ja mitverfolgen.

erschweren... nun ja, im weitesten sinne schon. jeh mehr gemacht wird desto länger muss man mitlesen bis man das hat, was man will.
 
@asdfman: Wenn du das Projekt automatisch generierst kann es ja trotzdem jeder sehen, man muss nur sein "testbin.exe" nehmen und sich cryptpe automatisch erzeugen lassen, dann kann man es ja ansehen und sieht wieder den ganzen Code. Wenn du den Source Code wirklich "nutzlos" für einen Reverse-Engineer machen willst, musst du die Exe manuell erstellen ( sowas wie ein eigener Compiler ), so dass es nirgends Source Code für cryptpe gibt. Am besten das was in die cryptpe.exe kommt auch verschlüsseln, so dass man aus dem Source-Code nichts herauslesen kann, was dann in cryptpe.exe sein wird. Also genau das machen, was die gängigen Packer auch machen ( Beispiel: UPX )

@Dese: Bei so etwas geht es immer darum, es dem Reverse-Engineer zu erschweren, weil irgendwie kommt man immer zum Code, schließlich muss der Computer diesen ja ausführen und dazu muss er komplett, unverschlüsselt und vorhanden sein.

Für Debugger-erkennung gibt es natürlich auch verschiedene Methoden, aus denen man zufällig wählen könnte.
Ich würde auf mehrere Methoden setzen, aber nicht zufällig daraus wählen. Es wäre besser mehrere Methoden zu implementieren und sie immer wieder tief im Code aufzurufen, so dass man nicht sofort sieht wo jetzt der Prozess beendet wird. Je tiefer man graben muss um den entsprechenden Code zu finden, desto besser und wenn es gleich an fünf verschiedenen Stellen krachen kann wird es noch schwerer für einen Reverse-Engineer.

Gruß
BlackMark
 
Zu deinem Einwand: Nein. Denn wenn man den Sourcecode zufällig aus einzelnen Modulen zusammen bauen
lässt (Verschlüsselung, Komprimierung, Anti-Debugging, Adressen der Blobs), kann man sich 5x cryptpe bauen
und hat jedes Mal einen anderen Source.
 
Falls du wirklich schwer zu Reverse Engineeren schreiben willst, dann ersetzt ein paar Stellen im Programmcode durch ungültige Befehle und führ sie mit VectoredExceptionHandling aus.
Oder auch gut: Bestimmte Assemblerbefehle kann man auf mehrere Arten schreiben (i=0 z.B. als mov eax,0 oder xor eax,eax oder sub eax,eax). und wenn du jetzt beim dekodieren da mal zufällig was umschreibst, dann kann man jedes Mal neu das Reverse Engineering anfangen.
 
@asdfman: Scheinbar verstehe ich nicht ganz was du meinst. Der Source Code für cryptpe, also der Teil der alles entschlüsselt und dann den EntryPoint aufruft, muss doch jedes mal identisch sein, sonst kannst du das ja nicht kompilieren. Könntest du mir bitte erklären wie du das genau machen willst, dass der grundlegende Code immer unterschiedlich ist, aber trotzdem eine funktionierende Exe generiert werden kann?

@Hancock: VectoredExceptionHandling ist eine gute Idee! Die zufälligen Assemblerbefehle finde ich aber nicht so störend. Das Programm wird sowieso zur Laufzeit in den Speicher geschrieben, also bringen Breakpoints nichts und dort Code zu manipulieren bringt auch nichts, weil bei jedem Neustart alles wieder beim Alten ist. Also warum sollte es einen Unterschied machen ob beim nächsten Start des Programms "xor eax, eax" statt "mov eax, 0" steht? Wenn ich das Programm entpacken will mache ich einen Memory-Dump und habe dann halt einen der möglichen Assemblerbefehle, aber da die Befehle ja das Gleiche machen ist es egal welcher verwendet wurde. Mir fällt kein Fall ein, wo es einen Reverse-Engineer behindern würde, wenn bei jedem Start des Programms Befehle anders sind, solange sie das Gleiche machen.

Gruß
BlackMark
 
Bedenke, dass du oft Programme hast, die im Elmulator zu langsam laufen, also wirst du das Programm oft laufen lassen und dich mitm Debugger herumhangeln, wenn jetzt aber jedes mal auch nur was leicht anderes drin steht (du kannst auch ganze Befehle, die voneinander unabhängig sind, vertauschen (allerdings kann das ein Programm instabil machen, denke an Sprünge)).
Oder noch besser: Befehle in Befehlen, das verwirrt jeden Debugger total.
mov edi,marke
JMP byte -1 (EB FF)
call edi dword 0 (FF d7)
marke:
pop edi
als
EB FF d7 57
schreiben.
Und das wird ein Debugger wahrscheinlich etwas aus der Bahn werfen.
Also bei mir meint VS irgendwas mit xlat [ebx].
Kannst dir ja noch paar andere Befehle ausdenken, trivial sind soche Sachen, wo du x Byte überspringst und dann dein Programmcode fortsetzt, aber verwirrt auch, weil unsinnig^^.
 
Ich habe deine Idee einmal umgesetzt und ndisasm spuckt mir dazu folgendes aus:
Code:
00000000  BF81174000        mov edi,0x401781
00000005  EBFF              jmp short 0x6
00000007  D7                xlatb
00000008  5F                pop edi
Also eigentlich keine schlechte Idee, aber der falsch erkannte Opcode ist nur ein Byte
groß und so ist der Disassembler eine Instruktion später schon wieder auf der richtigen
Schiene. Ich habe leider überhaupt keine Ahnung von Assember, deshalb wüsste ich
nicht, wie ich da weiter vorgehen sollte. Vorschläge? :/

€: Ok, so schwer war es gar nicht. Habe mal ein paar Opcodes nachgelesen:
Code:
		mov eax, label
		_emit 0xeb
		_emit 0xff
		_emit 0xe0
label:
ergibt die Anweisungen
Code:
            mov eax, label
                jmp byte -1
                jmp eax
label:
Und der ndisasm spuckt aus:
Code:
00000000  B881174000        mov eax,0x401781
00000005  EBFF              jmp short 0x6
00000007  E064              loopne 0x6d
00000009  A130000000        mov eax,[0x30]
0000000E  8B4068            mov eax,[eax+0x68]
00000011  83E070            and eax,byte +0x70
00000014  85C0              test eax,eax
00000016  7404              jz 0x1c
Die erste Anweisung der Debuggererkennung ist also kaputt. Schonmal etwas.

€2:Ok,
Code:
		mov eax, label
		_emit 0xeb
		_emit 0xff
		_emit 0xe0
		_emit 0x8d
		_emit 0x85
label:
verwirrt ndisasm völlig. Es kommt von der Debuggererkennung gar nichts mehr
vernünftig raus. Ziel erreicht?
Code:
00000000  B883174000        mov eax,0x401783
00000005  EBFF              jmp short 0x6
00000007  E08D              loopne 0xffffff96
00000009  8564A130          test [ecx+0x30],esp
0000000D  0000              add [eax],al
0000000F  008B406883E0      add [ebx-0x1f7c97c0],cl
00000015  7085              jo 0xffffff9c
00000017  C0                db 0xc0
00000018  7404              jz 0x1e
 
Zuletzt bearbeitet:
Das ist ja mal wirklich verwirrender Assembler Code :D

Wenn du es jetzt noch schaffen würdest alle ( oder zumindest einige ) Befehle des zu verschlüsselnden Programms so zu verändern, machst du jedem Reverse-Engineer das Leben sehr schwer ;)

Gruß
BlackMark
 
Ja, das stimmt natürlich. Aber RC4 ist sehr schnell, also sollten die Einbußen bei der Performance der
Entschlüsselung trotzdem noch im Rahmen bleiben. Testbin.exe ist aber auch relativ klein; sollte das
vermutlich mal mit einer größeren Datei versuchen.

Btw: Habe ein neues Commit im Repository, bei dem auch IDA aufgeben muss. Allerdings zeigt es an
den Stellen mit sinnlosen Befehlen dd [...] an, was ja wohl auch Aufmerksamkeit erregen würde. Das
liegt vermutlich wieder an meinen mangelnden Assemblerkenntnissen. Wenn mir jemand Opcodes
sagen könnte, die ich den Assembler ausspucken lassen könnte und die für IDA wie echter Code aus-
sehen (wenn auch nicht sinnvoll), wäre ich sehr froh.

Code:
mov eax, label
		_emit 0xeb
		jmp eax
		_emit 0x8d
		_emit 0xff
label:
		mov eax, fs:[30h]
		mov eax, [eax+68h]
		mov ebx, label2
		_emit 0xeb
		jmp ebx
label2:
		and eax, 0x70
		test eax, eax
		je SkipAssign
Ergibt für IDA:
Code:
.text:0040177F                 jmp     short near ptr loc_40177F+1
.text:00401781 ; ---------------------------------------------------------------------------
.text:00401781                 loopne  loc_401710
.text:00401783
.text:00401783 loc_401783:                             ; DATA XREF: .text:0040177Ao
.text:00401783                 jmp     dword ptr [ecx+30h]
.text:00401783 ; ---------------------------------------------------------------------------
.text:00401787                 align 4
.text:00401788                 dd 408B0000h
.text:0040178C ; ---------------------------------------------------------------------------
.text:0040178C                 push    401795BBh
.text:00401791                 add     bl, ch
.text:00401793                 jmp     ebx
.text:00401795 ; ---------------------------------------------------------------------------
.text:00401795                 and     eax, 70h
.text:00401798                 test    eax, eax
.text:0040179A                 jz      short loc_4017A0
 
dd ist doch gut.
Das jemand versucht, den Code zu verschleiern, dürfte jedem klar sein.

Ich bezog mich wegen der Geschwindigkeit explizit auf die Assemblerdinge, bei RC4 kommt die CPU nicht ausm Tritt, aber wenn du "komische" Sachen machst, dann schon.

Sonst: Was ganz "cooles" auf 64-bit Windows:
heavens gate: Spring ins Code-Segment 0x33 und du führst 64-bittigen Code aus. Einfach so, siehe
http://stackoverflow.com/questions/...-heavens-gate-do-segment-calls-that-trigger-a
Das verwirrt jetzt wirklich jeden Debugger, da die nur 32-bit erwarten. Da kannst du, wenn du willst, sogar ganze Passagen auf 64-bit rekompilieren, bedenk dann aber die vergrößerte Pointerlänge.
(Ich stell mir grad vor, wies wär, den kompletten Packer in 64 bit zu schreiben.)
 
Zurück
Oben