No i stalo sie. Wreszczie siegnalem po klawiature, coby wspomoc mag Buttheada. 
Tematem ponizszego wywodu bedzie opis zlamania crackme#1 LobiX'a, ktore przez 
dlugi czas pozostawalo nienaruszone. Z gory informuje, ze text ten nie jest 
pisany z mysla o newbies - zakladam, ze znasz podstawy asma i umiesz poruszac 
sie w SI. Dobra, tyle tego gledzenia, czas zabrac sie do roboty: 
Nasz cel wisi m in pod http://www.ptasiek.px.pl w dziale crackmes - 109 kb lub 
zgodnie ze slowami Lobix'a poprawiona wersja: 
http://kd.olkusz.pl/~lobix/crackme1.zip

Ok, po odpaleniu pierwsze spostrzezenia - crackme jest napisane w Delphi - 
swiadczy o tym np. ikonka proga. Skoro prog jest w Delphi, to nie bedzie 
poslugiwal sie standardowymi funkcjami textowymi API, dlatego tez, wpisujemy 
w name Ptasiek, w s/n: 312915, wchodzimy do SI i zastawiamy pulapke na funkcje 
hmemcpy. Naciskamy przycisk OK, i SI przechwycil odwolanie sie proga do tej 
funkcji. Naciskamy F5 - bo funkcja jest wywolywana 2x (name+sn) i teraz pare 
razy F12, az znajdziemy sie w interesujacym fragmencie kodu crackme. Dobra 
wreszcie jestesmy tutaj, gdzie nie widac koncowego RETa:

014F:00429F88  MOVZX     EAX,BYTE PTR [EAX]
014F:00429F8B  MOV       [0042C69C],EAX
014F:00429F90  LEA       EDX,[EBP-08]
014F:00429F93  MOV       EAX,[EBP-04]
014F:00429F96  MOV       EAX,[EAX+000001B0]
014F:00429F9C  CALL      00411F74
014F:00429FA1  MOV       EAX,[EBP-08]
014F:00429FA4  CALL      0040344C

Teraz przejdzmy sie powoli po kodzie - F10, az:

014F:00429FA9  MOV       EBX,EAX
014F:00429FAB  SUB       EBX,02
014F:00429FAE  JL        00429FEC;      czy dlugosc imienia<2
014F:00429FB0  INC       EBX
014F:00429FB1  MOV       DWORD PTR [ESI],00000002
014F:00429FB7  LEA       EDX,[EBP-08]

Przechodzimy przez fragment kodu majstrujacy cos przy naszym name, gdyz na 
razie chodzi nam o ogolne zorientowanie sie w sytuacji. Na tym etapie warto 
przejrzec czy s/n lub name sa ograniczone jakimis warunkami:

014F:0042A00E  CALL      0040344C       ; czy nasza dlugosc s/n jest<>0 i
014F:0042A013  MOV       EBX,EAX        ; <>ffffffff i okolicy
014F:0042A015  TEST      EBX,EBX        ; tak czy nie?:-)
014F:0042A017  JLE       0042A041                                    
014F:0042A019  MOV       DWORD PTR [ESI],00000001
014F:0042A01F  LEA       EDX,[EBP-08]
014F:0042A022  MOV       EAX,[EBP-04]
014F:0042A025  MOV       EAX,[EAX+000001D4]
014F:0042A02B  CALL      00411F74
014F:0042A030  MOV       EAX,[EBP-08]
014F:0042A033  MOV       EDX,[ESI]                ; w edx+eax-1 adres s/n
014F:0042A035  CMP       BYTE PTR [EDX+EAX-01],2D ; czy znak z s/n='-'
014F:0042A03A  JZ        0042A041       ; jesli tak to nie zmniejszaj ebx
014F:0042A03C  INC       DWORD PTR [ESI]; i co ciekawe zakoncz 
014F:0042A03E  DEC       EBX            ; sprawdzanie
014F:0042A03F  JNZ       0042A01F       ; ESI wskazuje na nr znaku s/n, ktory 
                                        ; jest myslnikiem
014F:0042A041  DEC       DWORD PTR [ESI]; a jesli go niema to dlugosc s/n
014F:0042A043  LEA       EDX,[EBP-08]
014F:0042A046  MOV       EAX,[EBP-04]
014F:0042A049  MOV       EAX,[EAX+000001D4]
014F:0042A04F  CALL      00411F74
014F:0042A054  MOV       EAX,[EBP-08]
014F:0042A057  CALL      0040344C ;     dlugosc s/n
014F:0042A05C  CMP       EAX,[ESI];     czy dlugosc= [ESI] - przypominam w ESI 
                                  ;     nr myslnika
014F:0042A05E  JNZ       0042A075 ;     czy jest myslnik?

No, z tego wynika, ze s/n musi byc z myslnikiem wiec odpowiednio zmodyfikujmy 
nasza dane: s/n=312-915. I ponownie wrocmy do tego miejsca w kodzie:

014F:0042A08D  LEA       EDX,[EBP-08] ;---procedury przerabiajace I-a czesc s/n
014F:0042A090  MOV       EAX,[EBP-04] ;z text na hex czyli z 312 na $312
014F:0042A093  MOV       EAX,[EAX+000001D4]
[...]
014F:0042A0E2  MOV       EAX,[EBP-0C]
014F:0042A0E5  CALL      00405624      ;-----w EAX I-a czesc s/n szestnastkowo
014F:0042A0EA  SUB       EAX,[0042C69C];minus jakas wartosc
014F:0042A0F0  MOV       [0042C698],EAX;hm. wynik roznicy zapisywany w [42c698]

No, omijamy pare kolejnych zamieszan z naszym name i dochodzimy do punktu 
kulminacyjnego:

014F:0042A1E7  SHL       EDX,04
014F:0042A1EA  SUB       AX,DX
014F:0042A1ED  MOV       [0042C6A0],AX
014F:0042A1F3  XOR       EAX,EAX
014F:0042A1F5  MOV       ESI,[0042C698]
014F:0042A1FB  MOV       ECX,00000005
014F:0042A200  ADD       AL,[ESI] ; tutaj nastapil wyjatek bo w ESI wartosc 
                                  ; naszego odejmowania

Z tego wynika, ze LobiX rozwiazal sprawdzanie s/n w dosc niekonwencjonalny 
sposob - postawil sprawe na lut szczescia, zmuszajac crackera do wygenerowania 
poprawnego s/n, aby moc isc dalej. Zas dalej mamy co nastepuje:

014F:0042A200  ADD       AL,[ESI] ;dodawaj do AL po bajcie kolejne bajty spod 
                                  ;ESI musi sie miescic
014F:0042A202  INC       ESI      ;w granicach adresow danych/kodu
014F:0042A203  LOOPNZ    0042A200 ;zapetl 5 razy
014F:0042A205  XOR       AL,[0042C698] ;Xoruj wynik z I-ym bajtem
014F:0042A20B  CMP       AL,[0042B6F4] ;czy rowne innemu bajtowi?
014F:0042A211  JNZ       0042A21E                           
014F:0042A213  MOV       EAX,[EBP-04]
014F:0042A216  CALL      [0042C698] ;wejdz do procedury, ktorej adres jest w 
                                    ;zmiennej [42c698]

Tu lezy pies pogrzebany - trzeba wygenerowac poprawny s/n dzieki ktoremu bedzie
mozna zaladowac poprawna procke. Ale jak to zrobic? Przyjrzyjmy sie warunkom i 
wyciagnijmy wnioski: 
Pod 42B6F4 jest wartosc stala BYTE 72h.
Cale sprawdzanie opiera sie na nastepujacym schemacie:
Bajty sa pod adresem ESI czyli stanowia I-e instrukcje procedury do ktorej 
musimy sie dostac. Dodawanie odbywa sie na bajtach, a wiec czesci liczb wyzsze 
od 255 sa ignorowane!!! 1y bajt+2i bajt+3i bajt+4y bajt+5y bajt XOR Ostatni 
bajt adresu ESI - czyli naszej procki. To wszystko ma byc rowne 72h. OK, ale 
skad wziac ostatni bajt adresu procki - hm. przyjrzyjmy sie naszemu progowi pod
np. wdasmem. Mozna zauwazyc, ze ostatnie bajty offsetow = ostatnim bajtom VA. 
Dzieje sie tak dlatego, ze kod programu jest upychany do jednej sekcji, a wiec 
nie ma przerw pomiedzy VA. Dlaczego ostatni bajt - bo w pamieci wszystkie 
wartosci zapisywane sa od konca czyli liczba: 12 34 56 78 bedzie zapisana tak:
78 56 34 12 Czyli ostatni bajt jest na pierwszym miejscu. No, dobrze, ale to 
chyba za malo, aby napisac brute force, ktory sprawdzi nam wszystkie adresy w 
pliku pod katem naszych ustalen? Zgadza sie, gdy uzylem bruteforce na tym 
etapie to prog podal mi cos kolo 600 offsetow. Co zatem zrobic? Wykorzystamy 
tutaj wspaniala wlasciwosc kompilatora Delphi jaka jest dodawanie wstawki 
PUSH EBP; MOV EBP,ESP na poczatku kazdej procedury, ktora zawiera zmienne 
lokalne. A wiec nasz programik bedzie sprawdzal offsety na podstawie pierwszych
danych i dodatkowo sprawdzal bedzie wartosci spod znalezionych offsetow i 
porownywal z opcodami ww. sekwencji. Oto listing brute force'a:

[ edito: pliczek znajduje sie w menu filez pod nazwa: brutefrc.zip ]

;---------cut here------------
.386
locals
jumps
.model flat,STDCALL
extrn lstrlenA:Proc
extrn WriteFile:Proc
extrn GetOpenFileNameA:Proc
extrn MessageBoxA:Proc
extrn CreateFileA:Proc
extrn GetFileSizeA:Proc
extrn CloseHandle:Proc
extrn ExitProcess:Proc
extrn GetFileSize:Proc
extrn CreateFileMappingA:Proc
extrn MapViewOfFile:Proc
extrn UnmapViewOfFile:Proc
extrn CloseHandle:Proc
extrn _wsprintfA:Proc
.data
include win32.inc
nazwa_pliku db 'CrackMe.exe',0
uchwyt_pliku dd ?
pomoc dd 0
uchwyt_pamieci_pliku dd ?
adres_pamieci_pliku dd ?
rozmiar_pliku dd ?
format db '%lx',0;       potrzebne dla funkcji wsprintfa
jest db 'JEST',0
dane db 10 dup (0)
nazwa_zapisu db 'zapis.txt',0;plik do ktorego zapisujemy znalezione
uchwyt_zapisu dd ?;             offsety
koniec_linii db 13,10; koniec linii, jesli kilka offsetow, to zeby
.code;               ;bylo estetyczniej
start:
push 0
push 0
push OPEN_EXISTING
push 0
push FILE_SHARE_READ OR FILE_SHARE_WRITE
push GENERIC_READ
push offset nazwa_pliku
call CreateFileA        ;otwieramy plik crackme
mov uchwyt_pliku,eax

push 0
push 0
push CREATE_ALWAYS
push 0
push FILE_SHARE_READ
push GENERIC_WRITE
push offset nazwa_zapisu
call CreateFileA;       tworzymy plik z wynikami
mov uchwyt_zapisu,eax
push 0
push uchwyt_pliku
call GetFileSize
mov rozmiar_pliku,eax

push 0
push 0
push 0
push PAGE_READONLY
push 0
push uchwyt_pliku
call CreateFileMappingA;        mapujemy plik - dzieki temu mamy dostep
mov uchwyt_pamieci_pliku,eax;   do zawartosci pliku tak jakbysmy 
                            ;   operowali w pamieci
push 0
push 0
push 0
push FILE_MAP_READ
push uchwyt_pamieci_pliku
call MapViewOfFile
mov adres_pamieci_pliku,eax
mov esi,eax
mov ecx,rozmiar_pliku
add esi,400h    ;w ecx rozmiar pliku - 400 =rozmiar kodu i danych
sub ecx,400h    ;w esi offset 400 =offset entrypointa
szukaj:
lodsb
mov bl,al;      I-y bajt do bl
lodsb
mov bh,al;      II-i do bh
lodsb
mov dl,al;      III-i do dl
lodsb
mov dh,al;      IV-y do dh
lodsb;          V do al
add al,dl;a teraz dodawanie BAJT po BAJCIE
add al,dh
add al,bl
add al,bh
mov edx,esi
sub edx,adres_pamieci_pliku;w edx offset V-go bajtu
sub edx,5;dlatego edx:=edx-5 - aby wskazywal na I-y bajt
xor al,dl; xorujemy AL z najmniej znaczacym (ostatnim) bajtem offsetu
cmp al,72h;ktory jest rowny ostatniemu bajtowi VA i porownujemy z 72h
jne nie_znalazl; jesli rozne to nie to:-(
call zapisz; rozpocznij drugi etap weryfikacji offsetu

nie_znalazl:
sub esi,4; od ESI - 4 bo odczytal 5 bajtow, a chcemy zeby sprawdzal po
loop szukaj;kolei, a nie co 5 bajtow.
koniec:
push adres_pamieci_pliku
call UnmapViewOfFile
push uchwyt_pamieci_pliku
call CloseHandle
push uchwyt_pliku
call CloseHandle
push uchwyt_zapisu
call CloseHandle
push 0
call ExitProcess
zapisz PROC
pusha;  rejestry na stos
push esi
push edi
sub esi,5; esi znowu na I-y bajt
lodsb;
cmp al,55h; czy AL pierwszy bajt opcode PUSH EBP
jne koniec_zapisu
lodsb
cmp al,8bh;i tak dalej z kolejnymi opcodami
jne koniec_zapisu
lodsb
cmp al,0ech
jne koniec_zapisu
sub esi,3;      jesli weryfikacja wypadla pomyslnie to jest nasz
sub esi,adres_pamieci_pliku; zamieniamy ESI na offset
; poniewaz wczesniej byl on zwiekszony o adres mapy pliku 

push esi
push offset format
push offset dane
call _wsprintfA;        zamieniamy esi na text
add esp,0ch
push offset dane
call lstrlenA
push 0
push offset pomoc
push eax
push offset dane;       i zapisujemy do pliku wyjscia
push uchwyt_zapisu
call WriteFile
push 0
push offset pomoc
push 2
push offset koniec_linii;wstawiamy znaki 13,10 czyli koniec linii
push uchwyt_zapisu
call WriteFile


koniec_zapisu:
pop edi
pop esi
popa
ret
zapisz ENDP
end start
;----------cut here-----------

No teraz wystarczy skompilowac proga:
tasm32 /ml /m3 nazwa
tlink32 /Tpe /c /aa nazwa,,,import32.lib

I odpalic proga w katalogu naszego crackme. Dobra, po jakiejs chwili - zalezy 
jaki sprzet posiadasz, prog konczy dzialanie i zagladamy do pliku zapis.txt. 

Cool!! - tylko jeden offset - no to jestesmy w domu:-)))
offset 29944

Sprawdzamy np. w hiewie jaki jest jego adres VA : 42a544
No to teraz wystarczy cofnac sie do naszego tajemniczego odejmowania:

014F:0042A0E5  CALL      00405624;      -----w EAX I-a czesc s/n
014F:0042A0EA  SUB       EAX,[0042C69C];minus jakas wartosc
014F:0042A0F0  MOV       [0042C698],EAX- wynik umiesc w tej komorce

Hm. z tego wynika, ze wynikiem odejmowanie jest adres, teraz juz znany, naszej 
procki. Pod adresem 42C69C kryje sie wartosc uzyskana z przeliczania name, 
poniewaz nie chemy pisac keygena, wiec nie bedziemy sie tutaj rozwodzic nad 
samych algorytmem wystarczy ze wiemy, ze dla name: Ptasiek wynosi 17B2F0C0 Dla 
niedowiarkow: d*(42C69C) Ok teraz nasze rownianie ma postac:
I-a czesc s/n-17b2f0c0=42a544
Z tego po niezwykle skomplikowanych obliczeniach matematycznych:) mamy:
17F59604 - (czyli 42a544+17b2f0c0)
I to jest wlasnie I-a czesc naszego s/n, poniewaz jest ona odczytywana jako 
wartosc hex. Ponownie wiec wpiszmy name: Ptasiek i s/n: 17F59604-915 i...
3/4-te drogi mamy juz za soba...
Dochodzimy do kolejnego krytycznego punktu:

014F:0042A559  PUSH      0042A8D5
014F:0042A55E  PUSH      DWORD PTR FS:[EAX]
014F:0042A561  MOV       FS:[EAX],ESP
014F:0042A564  MOV       ESI,00B915D8
014F:0042A569  MOV       AX,[0042C6A0]; AX to prawdopodobnie kombinacja
014F:0042A570  CMP       AX,FFFF; z drugiej czesci s/n
014F:0042A574  JZ        0042A585; czy rowna FFF - sprawdzmy ten trop
014F:0042A576  CALL      0042A57B
014F:0042A57B  POP       EAX; EAX=42A57B - zawsze
014F:0042A57C  XOR       AX,[0042C6A0];xoruj ax z nasza niewiadoma
014F:0042A583  JMP       EAX;skacz po kombinacji AX

Po zmianie flagi przy skoku 42A574 dochodzimy do msgboxa: "Sorry not here"
No, tak sytuacja prawie bez wyjscia, ale zaraz tuz po msgboxie jest skok, a 
zanim jakis kod, wierzcie lub nie ale zylka hazardzisty odezwala sie ze 
zdwojona sila:

014F:0042A7E0  PUSH      DWORD PTR [0042B6FC]
014F:0042A7E6  PUSH      DWORD PTR [0042C6AC]
014F:0042A7EC  PUSH      00
014F:0042A7EE  CALL      USER32!MessageBoxA; co by bylo, gdybysmy
014F:0042A7F3  JMP       0042A8BF; teraz ustawili eip (czyli adres     
014F:0042A7FB  MOV       EAX,[0042C694]; instrukcji na tutaj)
014F:0042A800  CALL      00411F74
014F:0042A805  MOV       EDX,[EBP-04]
014F:0042A808  MOV       EAX,EDI
014F:0042A80A  CALL      00403328

Ok, zrobmy tak w koncu nie wiele tracimy:
gdy jestesmy na JMP... zmieniamy adres instrukcji: r eip 42a7FB
To spowoduje, ze znajdziemy sie ponizej skoku, puscmy F5 i co widac?
Ano, napis "Wrong...", ale za to napis na pasku okna = Registered. Taaki blad 
Lobix'a - przeciez taka postac bad/good boy jest charakterystyczna dla progow 
sharewarowych. Wiec, jestesmy na dobrej drodze - teraz trzeba przesledzic 
algorytm przerabiajacy druga czesc s/n. Wracamy do tego fragmentu kodu:

014F:0042A1B8  LEA       EDX,[EBP-08]
014F:0042A1BB  MOV       EAX,[EBP-04]
014F:0042A1BE  MOV       EAX,[EAX+000001B0]
014F:0042A1C4  CALL      00411F74
014F:0042A1C9  MOV       EAX,[EBP-08]
014F:0042A1CC  MOV       EDX,[ESI];w EDI I-y znak name
014F:0042A1CE  MOVZX     EAX,BYTE PTR [EDX+EAX-01]; nasz name
014F:0042A1D3  ADD       [EDI],EAX; dodaj kolejny znak do [EDI]
014F:0042A1D5  INC       DWORD PTR [ESI]
014F:0042A1D7  DEC       EBX
014F:0042A1D8  JNZ       0042A1B8                                    
014F:0042A1DA  MOV       EAX,[0042C6AC]
014F:0042A1DF  CALL      00405624      ; w AX zwraca wartosc drugiej czesci
014F:0042A1E4  MOV       DX,[EDI]      ; s/n ale traktuje ja jako dec, czyli 
014F:0042A1E7  SHL       EDX,04        ; 915 dziesietnie=393 hex
014F:0042A1EA  SUB       AX,DX         ; a nastepnie odejmij od tego wynik
014F:0042A1ED  MOV       [0042C6A0],AX ; przerobki name
014F:0042A1F3  XOR       EAX,EAX

Dobra teraz w wiadomy sposob odczytujemy wynik przerobki name tuz po SHL EDX,4
Wynosi on DX=2D10 (Dla name=Ptasiek)
Ok. teraz, musi zajsc nastepujaca zaleznosc, aby s/n byl poprawny:
AX=II-a czesc s/n odczytana jako liczba dziesietna - DX
[Mniej znaczace slowo z 42a57b ] xor AX=a7F8h
^^^czyli a57b ^^^^^^^^ XOR AX=A7F8=czyli mniej znaczace slowo 42a57b
Z tego wynika ze AX musi byc rowne 283
283h=II-a czesc s/n - 2D10
Czyli II-a czesc s/n =2F93
Ale uwaga, pamietajmy, ze druga czesc s/n jest przetwarzana jako liczba 
dziesietna, czyli trzeba ja na nia zamienic czyli =  12179. A wiec, wszystkie 
dane maja postac:

name: Ptasiek
s/n:  17F59604-12179

Po ich wpisaniu powinnismy zobaczyc gratulacyjnego msgboxa. Niestety po sieci 
krazy tez zle skompilowana versja crackme - o rejestracji swiadczy jedynie 
napis na pasku okna zas msgbox nadal informuje o bledzie, no ale to juz wina 
zastosowanego zabezpieczenia (obsluga wyjatkow).

Uff, no sie rozpisalem... Ciekawe komu bedzie sie chcialo to czytac?:)
Tradycyjnie jesli czegos nie rozumiesz, z czyms sie nie zgadzasz to 
mailnij na adres: ptasiek@px.pl
No toby bylo na tyle
Pozdr
Ptasiek/CP!