Entwickler-Ecke

Algorithmen, Optimierung und Assembler - Programm schützen (Beispiele)


uall@ogc - So 28.08.05 16:00
Titel: Programm schützen (Beispiele)
Ich mach mal einen Thread auf in dem es darum geht wie man sein Programm vor dem debuggen/cracken schützen kann.
Ich werde hier im laufe der zeit einige Beispiele posten.

Als erstes einmal etwas, was in 2 Minuten gemacht werden kann und schon den einen oder anderen schlechten Cracker in den Wahnsinn treibt.

hier ein kleines Video wie das geht und ne Erkläung was man wie machen muss

http://uall.overclock.ch/delphiprotect01.zip

1) Man erstellt ganz normal sein Projekt.
2) Anstatt das Beenden von Delphi übernehmen zu lassen schreibt man bei OnClose (ganz am Schluss) ExitProcess(0) rein,
das wird benötigt damit beim beenden spätr das programm nicht crashed.
3) Im Hauptprogramm (der dpr) fügt man eine Variable ein die dafür sorgt das der Code zum erzeigen des Forms nur einmal ausgeführt wird (das hat interne gründe auf die ich nicht genauer eingehen werde)
4) ausserdem prüft man ob ein Debugger geladen ist (IsDebuggerPresent aus der kernel32.dll importieren bzw meine unit benutzen)
5) Man ändert bei der Sektion Tls +16bytes) mit Hilfe von Suche bei Google LORDPE die daten (sollten 4* 00 sein) ab indem man die durch den Entrypoint + ImageBase ersetzt (Entrypoinz ist d wo nacher AFFEAFFE steht)
6) der Entrypoint wird auf irgend eine Adresse gesetzt (würde er ausgeführt werden crashed es, wird er aber nur wenn ein debugger erkannt wurde!)

Code der geändert werden muss:


Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // Variablenfreigabe etc.
  ExitProcess(0);
end;



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
program Project2;

uses
  Forms, uallProtect,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

var first: boolean = true;
begin
  if (not first) or (uallProtect.IsDebuggerPresent) then exit;
  first := false;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


uall@ogc - So 28.08.05 16:04

02

Speziel für Ollydbg hab ich bisl code geschrieben der den Debugger zum crashen bringt
Dieser kann einfach bei wichtigen Funktionen (z.b. Keyeingabe testen) vorher (und nachher) aufgerufen werden.
Ist Ollydbg geladen crashed es :)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
procedure crash_ollydbg;
asm
  pushad
  push offset @@handler
  xor eax, eax
  push fs:[eax]
  mov fs:[eax], esp
  mov ebx, esp
  push eax
  push $73257325
  push esp
  push $5
  push esp
  push $2
  push eax
  push $40010006
  call RaiseException
@@handler:
  mov eax, [esp+$C]
  mov esp, [eax+$A4]
  xor eax, eax
  pop fs:[eax]
  pop eax
  popad
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  crash_ollydbg;
  form1.caption := 'hallo';
  crash_ollydbg;
end;


uall@ogc - So 28.08.05 16:15

03

Ausnutzen der Trägheit der Debugger.

Wenn man sich mit einem Debugger in ein laufendes Programm "einklingt" (Ollydbg -> attach) werden Thread nicht sofort suspended, d.h. sie laufen noch einige Millisekunden weiter. Das kann man ausnutzen um den Debugger zu erkennen und das Programm zu beenden / crashen. Hier mal ein Beispiel:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
function proc(a: integer): integer;
begin
  result := 0;
  while not uallProtect.IsDebuggerPresent do
    sleep(10);
  asm
    popad
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var tid: cardinal;
begin
  BeginThread(nil,0,@proc,nil,0,tid);
end;


uall@ogc - So 28.08.05 16:26

von SwissDelphiCenter geklaut aber auch eine Debugger erkennung (für SoftIce)

dazu muss ich aber sagen, jeder der SoftIce bedienen kann, wird das umgehen können ;)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
//SoftIce in W9x OS

function IsSoftIce95Loaded: Boolean;
var
  hFile: THandle;
begin
  Result := False;
  hFile  := CreateFileA('\\.\SICE', GENERIC_READ or GENERIC_WRITE,
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, 0);
  if (hFile <> INVALID_HANDLE_VALUE) then 
  begin
    CloseHandle(hFile);
    Result := True;
  end;
end;



// SoftIce in NT/2000 OS
function IsSoftIceNTLoaded: Boolean;
var
  hFile: THandle;
begin
  Result := False;
  hFile  := CreateFileA('\\.\NTICE', GENERIC_READ or GENERIC_WRITE,
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, 0);
  if (hFile <> INVALID_HANDLE_VALUE) then 
  begin
    CloseHandle(hFile);
    Result := True;
  end;
end;




procedure TForm1.Button1Click(Sender: TObject);
begin
  if IsSoftIce95Loaded or IsSoftIceNTLoaded then
  begin
    //Do something if Softice is loaded
  end;
end;


uall@ogc - So 28.08.05 16:37

05

Erkennung von Breakpoints (Haltepunkte)

Breakpoints werden oft benutzt um bestimmte stellen zu finden. Kommt z.b. bei der eingabe eines falschen Keys eine Nachricht, dass der Key falsch eingegeben wurde, wird der Cracker versuchen einen Breakpoint auf die API MessageBoxA zu setzen da diese wahrscheinlich aufgerufen wird.
Diesen kann man Abfragen und dann das Programm z.b. crashen lassen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure TForm1.FormCreate(Sender: TObject);
begin
  if pbyte(GetProcAddress(GetModuleHandle('user32.dll'),'MessageBoxA'))^ = $CC then // breakpoint an der API ?
  asm
    popad  //crash
    jmp eax
  end;
  MessageBoxA(0,'der eingegebene Key ist falsch',nil,0);
end;


uall@ogc - Sa 07.01.06 16:50

06

FileCheck

Es gibt ja viele Arten von Filechecks. Ich habe hier mal eine Methode gemacht, die ich sehr schön für Anfänger finde, da man nichts per Hand machen muss. Es reicht die Funktion im OnCreate aufzurufen. Ob sie Programmiertechnisch so toll ist lass ich mal aussen vor. Es funktioniert auch nicht mit irgendwelchen Packern. Beim ersten start der Exe wird eine neue Exe erstellt die den FileCheck beinhaltet. Hier der Code:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
procedure CheckExe;
var i, h, h2: integer;
    read: cardinal;
    buf: pointer;
    fsize: Cardinal;
    addrm : Pinteger;
    checksum: integer;
    checkx: integer;
begin
  i := $12345678;
  checksum := $12345678;
  h := CreateFileA(PChar(Paramstr(0)),GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE,
    nil,OPEN_EXISTING,0,0);

  if (h = 0then
  begin
    CloseHandle(h);
    Exit;
  end;

  if (i = 0then {nichts};

  fsize := GetFileSize(h,nil);
  if (fsize = 0then
  begin
    CloseHandle(h);
    Exit;
  end;

  buf := VirtualAlloc(nil,fsize,MEM_COMMIT or MEM_RESERVE,PAGE_EXECUTE_READWRITE);
  if (buf = nilthen
  begin
    CloseHandle(h);
    Exit;
  end;

  if (not ReadFile(h,buf^,fsize,read,nil)) or (read <> fsize) then
  begin
    CloseHandle(h);
    Exit;
  end;


  addrm := Pinteger(integer(buf)+integer(@CheckExe)-integer(GetModuleHandle(nil))-$BE0);
  if IsBadReadPtr(addrm,4then
  begin
    CloseHandle(h);
    Exit;
  end;

  if (addrm^ = $12345678then
  begin
    addrm^ := $00000000;
    for i := 0 to fsize div 4 -1 do
      checksum := checksum xor pinteger(integer(buf)+i*4)^;
    addrm^ := checksum;
    h2 := CreateFileA(PChar(Paramstr(0)+' new.exe'),GENERIC_READ or GENERIC_WRITE,
      FILE_SHARE_READ or FILE_SHARE_WRITE, nil,CREATE_ALWAYS,0,0);
    if (h2 <> 0then
    begin
      WriteFile(h2,buf^,fsize,read,nil);
      CloseHandle(h2);
    end;
  end else
  begin
    checkx := addrm^;
    addrm^ := $00000000;
    for i := 0 to fsize div 4 -1 do
      checksum := checksum xor pinteger(integer(buf)+i*4)^;
    if (checkx <> checksum) then
      while true do
      asm
        pop eax
      end;
  end;
  VirtualFree(buf,fsize,MEM_DECOMMIT);
  CloseHandle(h);
end;


Delete - Sa 07.01.06 17:17

Um nicht die API Funktion IsDebuggerPresent nutzen zu müssen, kann man auch seien eigene kleine Routine schreiben:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
asm
  push eax
  mov eax, fs:[$30];
  mov eax, [eax+2];
  mov [BeingDebugged], al
  pop eax
end;

BeingDebugges ist dabei eine globale Variable. Genaueres gibt es hier: http://www.michael-puff.de/Developer/Artikel/AntiCrackig_1.shtml

@uall@ogc: Motzi, Olli und ich hatten uns schon mal zusammengesetzt, und Methoden erläutert, wie man das Debuggen und somit das Crackenr erschweren könnte. Da beide aber unter chronischen Zeitmangel leiden, ist bisher nur dieser eine Artikel fertig geworden. Im zweiten sollte es um diese nette Routine von Olli gehen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
        asm
          pushad
          call  @SetupSEHAndRaiseException
          mov   ecx, dword ptr [esp+12]
          mov   eax, fs:[$30]
          movzx  eax, byte ptr [eax+$2]
          and   eax, eax
          jnz   @NoAddOpCodeSize
          mov   dword ptr [ecx+$B8], offset @AfterExceptionCause
          @NoAddOpCodeSize:
          mov   dword ptr [ecx+$04], eax
          mov   dword ptr [ecx+$08], eax
          mov   dword ptr [ecx+$0C], eax
          mov   dword ptr [ecx+$10], eax
          mov   dword ptr [ecx+$14], eax
          and   dword ptr [ecx+$18], $155
          and   dword ptr [ecx+$C0], $FFFFFEFF
          xor   eax, eax
          ret
          @SetupSEHAndRaiseException:
          xor   eax, eax
          push  dword ptr fs:[eax]
          mov   fs:[eax], esp
          @KillCode:
          and   dword ptr [eax+$18], $155
          @AfterExceptionCause:
          pop   dword ptr fs:[$0]
          add   esp, $4
          popad
        end;

Leider bin ich da nicht mehr so ganz mit gekommen und das was ich verstanden habe, habe ich leider schon wieder vergessen. Disen teil sollte eigentlich Motzi schreiben, aber nun ja, Zeitmangel. Nun würde ich gerne diese Artikelserie vortsetzen. Eventuell könntest du dich ja daran beteiligen, wenn du Lust hast. Ich würde dann als Herausgeber etc. fungieren.


uall@ogc - Sa 07.01.06 17:26

Das sollte auf 9x ME 2k XP funktionieren.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
function IsDebuggerPresent: boolean; stdcall;
asm
  MOV     EAX, FS:[030H]
  TEST    EAX, EAX
  JS      @@W9X
@@WNT:
  MOV EAX, FS:[$18]
  MOV EAX, [EAX+$30]
  MOVZX EAX, [EAX+2]
  RET
@@W9X:
  MOV EAX, [$BFFC9CE4]
  MOV ECX, [EAX]
  CMP [ECX+$54], 00000001
  SBB EAX, EAX
  INC EAX
  RET
end;


uall@ogc - Sa 07.01.06 18:21


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
  asm
    pushad                                // alle Register speichern (auf den Stack)
  @SetupSEHAndRaiseException:             //
    xor   eax, eax                        // eax auf 0 setzen
    push  offset @ExceptHandler           // unseren exception handler auf den stack           (1)
    push  dword ptr fs:[eax]              // alten exception handler auf den stack             (2)
    mov   fs:[eax], esp                   // exception handler installieren
  @KillCode:                              //
    and   dword ptr [eax+$18], $155       // code der auf jedenfall crashed [da EAX = 0]       (zu 3)
  @AfterExceptionCause:                   //                                                   (4)
    pop   dword ptr fs:[$0]               // unseren exceptionhandler wieder vom stack nehmen  (2)
    add   esp, $4                         // alte gepushe Exceptionhandler Adresse vom Stack   (1)
    popad                                 // alle Register wiede herstellen
    jmp @ende                             // zum Ende springen


  @ExceptHandler:                         // hier kommt man nach der exception im Killcode hin (3)
    mov   ecx, dword ptr [esp+$C]         // in ECX offset zum contect
    mov   eax, fs:[$30]                   // IsDebuggerPresentCode
    movzx  eax, byte ptr [eax+$2]         //      "
    and   eax, eax                        // ist eax 0?
    jnz   @NoAddOpCodeSize                // wenn nicht springe
    mov   dword ptr [ecx+$B8], offset @AfterExceptionCause // eax = 0, kein debugger da, setze richtige adresse
                                          // zum fortsetzen für den code (EIP)
  @NoAddOpCodeSize:                       //
    mov   dword ptr [ecx+$04], eax        // mache irgend nen quatsch mit den Registern
    mov   dword ptr [ecx+$08], eax        // ist alles egal bis auf ESP
    mov   dword ptr [ecx+$0C], eax        // da mit POPAD eh alles wieder hergestellt wird
    mov   dword ptr [ecx+$10], eax        //
    mov   dword ptr [ecx+$14], eax        //
    and   dword ptr [ecx+$18], $155       // kein Plan?
    and   dword ptr [ecx+$C0], $FFFFFEFF  // lösche SingleStep (Trap) Flag da es sonst wieder zur Exception kommt
    xor   eax, eax                        // unser exception Handler konnte die Exception behandeln
                                          // deshalb Rückgabe = 0, damit KiUserExceptionDistaptcher
                                          // NTContinue aufruft und somit unsere geänderter context
                                          // benutzt wird
    ret                                   //                                                   (zu 4)
  @Ende:                                  //
  end;


Delete - Sa 07.01.06 18:26

Was hältst du von Ollis Code? Nur so viel, er wurde schon geknackt, war wohl doch nicht so aufwendig zu umgehen. :(


uall@ogc - Sa 07.01.06 18:30

Ja er wurde von mir "geknackt".
Der macht nicht wirklich viel mehr als IsDebuggerPresent, sondern versucht das nur durch nen Excepion Handler schwierigier debuggbar zu machen.

KillCode einfach auskommentieren (0x90 d.h. noppen) und schon ist das umgangen.