Find Pattern Tutorial

08/29/2011 02:07 .ErpeL#1
Find Pattern Tutorial

Find Pattern wird dafür genutzt, bestimmte Funktionsadressen aus einem Prozess auszulesen.
Dazu wird der Byte-OpCode einer EXE, DLL, BIN oder dergleichen durchlaufen und nach einem vordefiniertem Byte-Muster durchsucht.

Am Anfang etwas Theorie:

Problemstellung:

Die Find Pattern Funktion:

Funktionsadressen berechnen:

Alles in allem:

Download als PDF:
08/29/2011 05:37 link#2
Nettes Tutorial, +k.

Quote:
Anschließend wird dieser Code in ein BYTE-Array kopiert, und die unbekannten (die gewünschten Adressen wie z.B. 61C028, 0041E1B0 usw.) doch 0-Bytes ersetzt.
Ich fände so etwas wie "Anschließend wird dieser Code in ein BYTE-Array kopiert und die Bytes, die sich von Version zu Version oder durch Relozierung des Moduls ändern könnten (also z.B. Adressen oder Offsets) und daher durch die noch folgende String Maske ignoriert werden, zur besseren Lesbarkeit durch 0-Bytes ersetzt." evtl. etwas leichter verständlich

Und sollte RealtiveToAbsolut nicht eher RelativeToAbsolute lauten? :)


Hier noch die FindPattern-Funktion in FASM, allerdings ohne einen fünften Parameter:
Code:
FindPattern:
        push    ebx ebp
    .loop:
        mov     ebp,eax
        call    DataCompare
        xchg    eax,ebp
        jc      .found
        inc     eax
        loop    .loop
        xor     eax,eax
    .found:
        pop     ebp ebx
        retn

DataCompare:
        push    ecx
        mov     edx,edi
        mov     ecx,esi
    .loop:
        cmp     byte [edx],0
        stc
        je      .fin
        mov     bl,[eax]
        cmp     byte [edx],'x'
        jnz     .match
        cmp     [ecx],bl
        clc
        jnz     .fin
    .match:
        inc     eax
        inc     ecx
        inc     edx
        jmp     .loop
    .fin:
        pop     ecx
        retn

        ;-

        push    _module
        call    [GetModuleHandle]
        mov     ecx,1234h
        mov     esi,_ptrn
        mov     edi,_ptrn_mask
        call    FindPattern
        ;...


  _module db 'xyz.dll',0
  _ptrn db 088h,066h,0FFh,000h,000h,000h,000h,086h
  _ptrn_mask db 'xxx????x',0

Btw. fehlen nicht Credits? z.B. für die Funktion, welche du als Basis genommen hast?
08/29/2011 07:18 buFFy!#3
funktionen kommen im original vom gamedeception, allerdings kursieren die so schon seit ewigkeiten im netz und fast jeder nutzt sie.

würde übrigens das findpattern in einen try except block setzen.
ansonsten ganz nett gemacht!

edit: man kann für die signaturen das ollydbg plugin 'SigMaker' (von P47R!CK, gd) benutzen.
08/29/2011 12:27 .BritainAndy#4
Sehr schönes Tutorial, danke !
08/29/2011 12:56 Akorn#5
Quote:
Originally Posted by link View Post
Nettes Tutorial, +k.


Ich fände so etwas wie "Anschließend wird dieser Code in ein BYTE-Array kopiert und die Bytes, die sich von Version zu Version oder durch Relozierung des Moduls ändern könnten (also z.B. Adressen oder Offsets) und daher durch die noch folgende String Maske ignoriert werden, zur besseren Lesbarkeit durch 0-Bytes ersetzt." evtl. etwas leichter verständlich

Und sollte RealtiveToAbsolut nicht eher RelativeToAbsolute lauten? :)


Hier noch die FindPattern-Funktion in FASM, allerdings ohne einen fünften Parameter:
Code:
FindPattern:
        push    ebx ebp
    .loop:
        mov     ebp,eax
        call    DataCompare
        xchg    eax,ebp
        jc      .found
        inc     eax
        loop    .loop
        xor     eax,eax
    .found:
        pop     ebp ebx
        retn

DataCompare:
        push    ecx
        mov     edx,edi
        mov     ecx,esi
    .loop:
        cmp     byte [edx],0
        stc
        je      .fin
        mov     bl,[eax]
        cmp     byte [edx],'x'
        jnz     .match
        cmp     [ecx],bl
        clc
        jnz     .fin
    .match:
        inc     eax
        inc     ecx
        inc     edx
        jmp     .loop
    .fin:
        pop     ecx
        retn

        ;-

        push    _module
        call    [GetModuleHandle]
        mov     ecx,1234h
        mov     esi,_ptrn
        mov     edi,_ptrn_mask
        call    FindPattern
        ;...


  _module db 'xyz.dll',0
  _ptrn db 088h,066h,0FFh,000h,000h,000h,000h,086h
  _ptrn_mask db 'xxx????x',0

Btw. fehlen nicht Credits? z.B. für die Funktion, welche du als Basis genommen hast?
Hatte auch mal in Assembler eine eigene SearchPattern funktion geschreiben aber in Nasm. Mit den Befehlen SCASB und CMPSB im zusammenhang mit Wiederholungspräfixen. Hab ein paar stunden dran gehangen um die funktion zu schreiben hab aber am ende dann vergessen die Quellcode datei abzuspeichern und hatte nach dem Ausschalten alles verloren:rolleyes: .
Das hat mich so angegotzt das ich bis heute keine lust hatte die funktion neuzuschreiben :).
08/29/2011 21:54 link#6
@Akorn: hehe
jo, normalerweise bin ich voll der Fan von z.B.
or ecx,-1
repnz scasb

Ich wollt's aber dann doch nicht so kompliziert haben..
Meine Funktion von oben ist ja eher straight-forward, also auf 'nem ziemlich niedrigen Niveau
(gab für mich ja sowieso nichts anderes zur Auswahl, eh? :P)

Naja, hab mich gerade mal dran gesetzt und es mit scasb und cmpsb umgesetzt, ist allerdings ein wenig ugly geraten..

Code:
format PE GUI 4.0
entry start

include 'win32a.inc'

section '.code' code readable executable

  start:
        push    0
        call    [GetModuleHandle]
        mov     ecx,[eax+03Ch]
        push    ptrn_size            ;4. lstrlen(ptrn_mask)
        push    ptrn_mask            ;3. Patternmaske
        push    ptrn                 ;2. Pattern
        push    dword [eax+ecx+01Ch] ;SizeOfCode
        add     eax,[eax+ecx+02Ch]   ;BaseOfCode
        push    eax                  ;1. Startadresse
        call    FindPattern
        push    eax
        push    fmt
        push    buffer
        call    [wsprintf]
        add     esp,0Ch
        push    MB_OK
        push    0
        push    buffer
        push    0
        call    [MessageBox]
        push    0
        call    [ExitProcess]

        ;Erster Fund
        mov     eax,10h
        mov     ebx,20h
        mov     ecx,30h
        nop
        ;Zweiter Fund <-
        mov     eax,10h
        mov     ebx,20h
        mov     ecx,30h
        db      91h

  FindPattern:
        push    ebx esi edi
        cld
        mov     edi,[esp+10h]
        mov     ecx,[esp+14h]
        mov     edx,[esp+1Ch]
        dec     dword [esp+20h]
    .loop:
        mov     esi,[esp+18h]
        mov     al,[esi]
        repnz   scasb
        jnz     .not_found
        xchg    ebx,ecx
        push    edi
        inc     esi
        mov     ecx,[esp+24h]
    .compare:
        repe    cmpsb
        jnz     .check_wildcard
        pop     eax
        dec     eax
        jmp     .fin
    .check_wildcard:
        jecxz   .no_match
        mov     eax,[esp]
        sub     eax,edi
        neg     eax
        cmp     byte [edx+eax],'?'
        je      .compare
    .no_match:
        pop     edi
        xchg    ebx,ecx
        jmp     .loop
    .not_found:
        xor     eax,eax
    .fin:
        pop     edi esi ebx
        retn    14h

section '.data' data readable writeable

  ptrn db 0B8h,000h,000h,000h,000h,0BBh,000h,000h,000h,000h,0B9h,000h,000h,000h,000h,091h
  ptrn_mask db 'x????x????x????x'
  ptrn_size = $-ptrn_mask

  fmt db 'found @%08Xh',0

  buffer rb 256

section '.idata' import data readable

  library kernel32,'KERNEL32.DLL',\
          user32,'USER32.DLL'

  import kernel32,\
         GetModuleHandle,'GetModuleHandleA',\
         ExitProcess,'ExitProcess'

  import user32,\
         MessageBox,'MessageBoxA',\
         wsprintf,'wsprintfA'
08/30/2011 00:50 Akorn#7
WTF hab meine SearchPattern funktion doch wieder gefunden:)
@link Eigentlich sind diese Befehle ganz nützlich aber ich habe mittlerweile von ein paar i-net seiten, optimierungs Manauals, und nen Kumpel erfahren das diese Befehle wie SCASB CMPSB etc mit REP und .co eigentlich viel Langsamer sind als wen man dessen funktionen einfach mittels normalen spring Vergleichs und schiebe befehlenen schreibt:(
08/30/2011 03:56 link#8
Dann zeig doch mal :P
Das hier habe ich vor 2 Monaten mal in einem anderen Forum zu dem Thema geschrieben:
Quote:
I guess by "useless" instructions you mean those, which actually consist of two operations and were implemented to provide some higher level of code to draw simplicity but ended up being much slower and are therefore negatively connotated

examples are loop, enter, lods, stos, movs
on most processors they are considerably slower than their two instruction-pendants

kind of compareable to inc eax vs add eax,1 as the latter is often faster on newer processors
while 'inc' is considered more an 'exotic' instruction like the high-level instructions mentioned above

however, there are some exceptions like leave or rep lods/stos/movs, which are pretty fast
Auf mark.masmcode.com steht dazu Folgendes:
Quote:
Avoid complex instructions ( lods, stos, movs, cmps, scas, loop, xadd, enter, leave). Complex instructions are instructions that do multiple things. For instance stosb writes a byte to memory and also increments EDI. They stopped making these fast with the original Pentium because they were trying to make it more RISC like. Using REP and the string instructions is still fast. That is the only exception to the case.
Hab gerade sogar zufällig eine Wikipedia-Seite genau zu diesem Thema gefunden :)

Die zweite Funktion von mir ist vom Ansatz her auf jeden Fall etwas komplizierter, würde ich sagen, ob sie auch wirklich langsamer ist, werde ich später einfach mal kurz testen..
Hängt ja von vielem ab, wie z.B. Stalls oder Speicherzugriffen (die erste Version müsste wahrscheinlich den Stack wegen des Calls häufiger verwenden, was auch ziemlich langsam ist)

Edit:
Habe auch mal irgendwo gelesen, dass der Pentium ein CISC-Prozessor mit einem RISC-Kern sei, der intern die Befehle erst einmal dekodiert und dann für diesen translatiert, was, wenn dem so ist, solchen komplexen Instruktionen, die im RISC nicht implementiert sind, einen zusätzlichen Overhead verpassen würde

FindPatternBench.asm:
Code:
format PE console 4.0
entry start

include 'win32a.inc'

section '.code' code readable executable

  start:
        mov     [display_handle],STD_OUTPUT_HANDLE
        mov     [read_handle],STD_INPUT_HANDLE
        push    0
        call    [GetModuleHandle]
        mov     ecx,[eax+03Ch]
        mov     edx,[eax+ecx+01Ch]
        mov     [SizeOfCode],edx
        add     eax,[eax+ecx+02Ch]
        mov     [BaseOfCode],eax

        ;FindPatternHL
        xor     eax,eax
        cpuid
        rdtsc
        push    eax edx
        push    ptrn_size
        push    ptrn_mask
        push    ptrn
        push    [SizeOfCode]
        push    [BaseOfCode]
        call    FindPatternHL
        mov     edi,eax
        xor     eax,eax
        cpuid
        rdtsc
        mov     ecx,eax
        pop     eax ebx
        xchg    ecx,edx
        sub     edx,ebx
        sbb     ecx,eax
        mov     esi,buffer
        push    ecx
        push    edx
        push    edi
        push    _FindPatternHL
        push    _fmt
        push    esi
        call    [wsprintf]
        add     esp,18h
        call    display_string

        ;FindPattern
        xor     eax,eax
        cpuid
        rdtsc
        push    eax edx
        mov     eax,[BaseOfCode]
        mov     ecx,[SizeOfCode]
        mov     esi,ptrn
        mov     edi,ptrn_mask
        call    FindPattern
        mov     edi,eax
        xor     eax,eax
        cpuid
        rdtsc
        mov     ecx,eax
        pop     eax ebx
        xchg    ecx,edx
        sub     edx,ebx
        sbb     ecx,eax
        mov     esi,buffer
        push    ecx
        push    edx
        push    edi
        push    _FindPattern
        push    _fmt
        push    esi
        call    [wsprintf]
        add     esp,18h
        call    display_string

        mov     edx,wait_for_enter
        mov     ecx,1
        call    read_string
        push    0
        call    [ExitProcess]

        ;Erster Fund
        mov     eax,10h
        mov     ebx,20h
        mov     ecx,30h
        nop
        ;Zweiter Fund <-
        mov     eax,10h
        mov     ebx,20h
        mov     ecx,30h
        db      91h

  FindPatternHL:
        push    ebx esi edi
        cld
        mov     edi,[esp+10h]
        mov     ecx,[esp+14h]
        mov     edx,[esp+1Ch]
        dec     dword [esp+20h]
    .loop:
        mov     esi,[esp+18h]
        mov     al,[esi]
        repnz   scasb
        jnz     .not_found
        xchg    ebx,ecx
        push    edi
        inc     esi
        mov     ecx,[esp+24h]
    .compare:
        repe    cmpsb
        jnz     .check_wildcard
        pop     eax
        dec     eax
        jmp     .fin
    .check_wildcard:
        jecxz   .no_match
        mov     eax,[esp]
        sub     eax,edi
        neg     eax
        cmp     byte [edx+eax],'?'
        je      .compare
    .no_match:
        pop     edi
        xchg    ebx,ecx
        jmp     .loop
    .not_found:
        xor     eax,eax
    .fin:
        pop     edi esi ebx
        retn    14h

  FindPattern:
        push    ebx ebp
    .loop:
        mov     ebp,eax
        call    DataCompare
        xchg    eax,ebp
        jc      .found
        inc     eax
        loop    .loop
        xor     eax,eax
    .found:
        pop     ebp ebx
        retn

  DataCompare:
        push    ecx
        mov     edx,edi
        mov     ecx,esi
    .loop:
        cmp     byte [edx],0
        stc
        je      .fin
        mov     bl,[eax]
        cmp     byte [edx],'x'
        jnz     .match
        cmp     [ecx],bl
        clc
        jnz     .fin
    .match:
        inc     eax
        inc     ecx
        inc     edx
        jmp     .loop
    .fin:
        pop     ecx
        retn

  display_string:
        invoke  GetStdHandle,[display_handle]
        mov     edx,eax
        mov     edi,esi
        or      ecx,-1
        xor     al,al
        repne   scasb
        neg     ecx
        sub     ecx,2
        invoke  WriteFile,edx,esi,ecx,bytes_count,0
        retn

  read_string:
        push    ecx edx
        invoke  GetStdHandle,[read_handle]
        pop     edx
        mov     ecx,[esp]
        invoke  ReadFile,eax,edx,ecx,bytes_count,0
        pop     edx
        or      eax,eax
        jz      file_error
        cmp     edx,[bytes_count]
        jne     file_error
        clc
        retn
    file_error:
        stc
        retn

section '.data' data readable writeable

  ptrn db 0B8h,000h,000h,000h,000h,0BBh,000h,000h,000h,000h,0B9h,000h,000h,000h,000h,091h
  ptrn_mask db 'x????x????x????x',0
  ptrn_size = $-ptrn_mask-1

  _FindPatternHL db 'FindPatternHL',0
  _FindPattern db 'FindPattern  ',0
  _fmt db '%s: eax = %08Xh   @ %I64d ticks',10,10,0

  BaseOfCode dd ?
  SizeOfCode dd ?

  buffer rb 256
  wait_for_enter db ?

  display_handle dd ?
  read_handle dd ?
  bytes_count dd ?

section '.idata' import data readable

  library kernel32,'KERNEL32.DLL',\
          user32,'USER32.DLL'

  import kernel32,\
         ExitProcess,'ExitProcess',\
         GetModuleHandle,'GetModuleHandleA',\
         GetStdHandle,'GetStdHandle',\
         ReadFile,'ReadFile',\
         WriteFile,'WriteFile'

  import user32,\
         MessageBox,'MessageBoxA',\
         wsprintf,'wsprintfA'
Ergebnis:
Code:
FindPatternHL: eax = 00401106h   @ 2385 ticks

FindPattern  : eax = 00401106h   @ 5509 ticks
08/30/2011 17:59 Akorn#9
Quote:
Dann zeig doch mal :P
Ja muss mal kucken ob mein code überhaupt geht ich hatte ihn ja nie ausprobiert:). Allerdings ist die Funktion noch nicht ganz ausgereift da es zum suchen keine maske verwendete.
11/27/2011 15:38 .Scare©®#10
nice tut, hatt mir supper geholfen.. nur hab ich 1 2 hänger da ich es in vb.net machenwürde...
11/27/2011 17:07 MrSm!th#11
Quote:
Aus den OpCode muss jetzt eine Suchmaske erstellt werden, dazu sucht man sich ein Codefragment aus welches die gewünschte Funktion beinhaltet, es kann ruhig etwas weiter nach oben bzw. unten gehen (wie im Bild oben rot markiert), damit es später besser gefunden werden kann.
Das ist so nicht ganz richtig.
Man sollte Suchmasken so klein wie möglich halten, damit sie nicht durch Patches, die Code in der Umgebung verändern, veralten.
Natürlich muss man sie groß genug machen, damit es eindeutige Suchergebnisse sind, aber dafür ist gar nicht mal so viel nötig.
Wenn es keine Masken wie der Prolog einer __stdcall Funktion sind, dann reichen ein paar Bytes schon für eine eindeutige Suche.

Quote:
Dies ist nötig weil die Adresse relativ zu dem Offset codiert ist. Man muss also erst den Wert auslesen der auf dem Offset liegt. In unserem Beispiel wäre das 994A, auf diesen Wert muss jetzt das Offset draufaddiert werden und dann noch die Länge der Adresse in Bytes also 4. Also 0x41E1B0 = 0x994A + (Offset +20) + 0x4.
Kleiner Tipp:

Ich würde den Parameter OffsetAddress nennen und in der Rechnung im letzten Satz 0x994A + *(Offset +20) ... schreiben.

Das ganze hat mich erst total verwirrt, wollte erst schon schreiben, dass es doch nichts bringt, das Offset zu sich selbst zu addieren, bis ich gemerkt habe, dass es einmal dereferenziert wird und so das Offset genommen wird und einmal die Adresse davon.
Der Name Offset ist einfach total irreführend^^ Und in der Rechnung ists dann genau das gleiche: man erkennt nicht direkt, dass es einmal als Pointer verwendet wird.

Eine Deklaration als void* würde vielleicht auch nicht schaden.
11/29/2011 16:56 .ErpeL#12
Quote:
Originally Posted by MrSm!th View Post
Das ist so nicht ganz richtig.
Man sollte Suchmasken so klein wie möglich halten, damit sie nicht durch Patches, die Code in der Umgebung verändern, veralten.
Natürlich muss man sie groß genug machen, damit es eindeutige Suchergebnisse sind, aber dafür ist gar nicht mal so viel nötig.
Wenn es keine Masken wie der Prolog einer __stdcall Funktion sind, dann reichen ein paar Bytes schon für eine eindeutige Suche.


Kleiner Tipp:

Ich würde den Parameter OffsetAddress nennen und in der Rechnung im letzten Satz 0x994A + *(Offset +20) ... schreiben.

Das ganze hat mich erst total verwirrt, wollte erst schon schreiben, dass es doch nichts bringt, das Offset zu sich selbst zu addieren, bis ich gemerkt habe, dass es einmal dereferenziert wird und so das Offset genommen wird und einmal die Adresse davon.
Der Name Offset ist einfach total irreführend^^ Und in der Rechnung ists dann genau das gleiche: man erkennt nicht direkt, dass es einmal als Pointer verwendet wird.

Eine Deklaration als void* würde vielleicht auch nicht schaden.
aufgrund Ihrer Akademischen ausdrucksweise muss ich annehmen, dass es sich bei Ihrer Person um einen von mir nicht sehr gemochten bob namens Steven handelt (dessen Gehirn von einem NULL Pointer dereferenziert worden ist), ich bitte dies zu entschuldigen !
04/03/2012 23:13 xenocracy_2001#13
keine hilfe bekommen
02/18/2013 14:50 vedmaka#14
Very nice tutorial, but can you translate it to English, please?
02/20/2013 12:58 Mi4uric3#15
Quote:
Originally Posted by vedmaka View Post
Very nice tutorial, but can you translate it to English, please?
He ripped it and didn't give any credits, so you can simply find it with [Only registered and activated users can see links. Click Here To Register...].