Brute Force teoria i przyklady
==============================

1.Co to jest brute force?
2.Proste przyklady zastosowania brute force
3.Brute force w praktyce
4.Slowo koncowe


1.Co to jest brute force?
=========================
Wedlug mojej definicji jest to metoda szukania tajnego klucza poprzez sprawdzanie
kolejnych kombinacji klucza az do momentu gdy ten znaleziony bedzie spelnial warunki
jakiegos algorytmu.

2.Proste przyklady zastosowania brute force
===========================================

a) ciagi pseudoprzypadkowych bajtow

Prostym przykladem moze byc np. sytuacja gdy pobierane sa np. 2 znaki numeru
seryjnego i kolejno sumowane ze soba + male mnozenie

	mov	eax,offset lpSN
	mov	ecx,2			; liczba powtorzen
	sub	ebx,ebx			; tu bedzie zapisana suma
	sub	edx,edx
suma:
	mov	dl,byte ptr[eax]	; tu pobierane sa kolejne bajty sn
	add	ebx,edx			; dodawane sa do ebx
	imul	ebx,ebx,5Ah		; ebx=ebx*cl
	inc	eax			; zwieksz wskaznik tak zeby wskazywal nastepny
					; bajt sn
	loop	suma			; powtarzaj 5 razy

	cmp	eax,POPRAWNA_WARTOSC
	jne	bad_boy

I teraz nasuwa sie pytanie jak znalezc takie bajty ktore spelnia zalozenia algorytmu?
Najprymitywniejsza metoda jest generowanie przypadkowych bajtow ktore ukladamy w ciag
2 bajtow(dotyczy powyzszego algorytmu), nastepnie wykonujemy dokladnie takie same operacje
jakie wykonuje oryginalny kod i sprawdzamy wyniki,jezeli nie sa poprawne generujemy znowu
2 przypadkowe bajty i tak do skutku.

gen_start:
	mov	edi,offset lpSN		; adres sn
	mov	ecx,2			; liczba powtorzen petli
	sub	ebx,ebx			; tu bedzie wynik
gen:
	call	rnd			; generuj bajt
	stosb				; zapisz pod edi
	add	ebx,eax			; dodaj do ebx
	imul	ebx,ebx,5Ah		; pomnoz
	loop	gen			; powtorz 2 razy petle

	cmp	eax,POPRAWNA_WARTOSC	; czy wynik w ebx zgadza sie z tym z oryginalnego programu
	jne	gen_start		; jesli nie resetuj ebx i powtarzaj operacje do skutku

	int	3
	ret

rnd	proc near
	callW	GetTickCount		; generowanie liczby pseudo losowej
	rol	eax,8			;
	xor	eax,[seed]		;
	sub	[seed],eax		;
	imul	eax,[seed],4Eh
	rol	eax,1

	sub	edx,edx			; czysc edx
	mov	ecx,10			;
	div	ecx			; podziel eax przez 10
	xchg	eax,edx			; w edx reszta z zakresu 0-9
	add	al,30h			; zamien na cyfre w ASCII

	pop	ecx
	ret
rnd	endp

Niewatpliwa wada takiego rozwiazania jest powolnosc dzialania wiec traci ona swoje zastosowanie
przy dluzszych ciagach(zalezy jaki ma sie procesor :), jezeli bedziemy juz stosowali ta metode
warto "zaopatrzyc" sie w porzadna procedure generujaca liczby pseudo losowe niz kombinowc tak
jak ja ;).

b) sprawdzanie wszystkich kombinacji klucza

Jesli program wymaga zeby bajty seriala byly cyframi albo znakami ascii 00h-0Fh to
mozemy zastosowac nieco prostsze i czasami skuteczniejsze rozwiazanie.Mianowicie
generujemy kolejno wartosci licznika, zamieniamy licznik na ciag ascii, przepychamy przez
algorytm sprawdzamy wynik i jezeli sie nie zgadza zwiekszamy licznik i powtarzamy operacje.


.386
.model flat,stdcall

extrn	_wsprintfA:proc

.data
	szHex2Hex		db '%05X',0
					; %05X oznacza ze bufor wynikowy bedzie w formacie
					; "00000","00001","00002"...'FFFFF'
	szSN			db 8 dup(0)
	POPRAWNA_WARTOSC	equ 23C9D12Ah
.code
start:
	int 3				; i3here on

	or	ebx,-1			; tu bedzie wynik
					; stosuje ebx poniewaz zadna funkcja api
					; nie modyfikuje ebx
gen_start:
	inc	ebx			; gdy petla wykonywana bedzie 1 raz ebx=0

	push	ebx			; wartosc licznika
	push	offset szHex2Hex	; ciag formatujacy
	push	offset szSN		; tu bedzie zapisany wynik
	call	_wsprintfA		; konwertuj
	pop	esi			; esi adres bufora wypadkowego
	add	esp,4*2			; fix stosu

	mov	ecx,5			; liczba powtorzen petli
	sub	eax,eax
	cdq				; zeruj edx

gen:
	lodsb				; zaladuj do al kolejne bajty szSN
	add	edx,eax			; dodaj do ebx
	imul	edx,edx,5Ah		; pomnoz
	loop	gen			; powtorz 5 razy petle

	cmp	edx,POPRAWNA_WARTOSC	; czy wynik w edx zgadza sie z tym z oryginalnego programu
	jne	gen_start		; jesli nie resetuj ebx i powtarzaj operacje do skutku

	int 3				; pod lpSN mamy poprawny numer seryjny

	ret				; exit process

end	start


Ta metoda daje duze szanse na znalezienie poprawnego ciagu bajtow,gdzyz sprawdzane sa kolejne
wartosci klucza wiec znalezienie poprawnej kombinacji bajtow to tylko kwestia czasu.
W powyzszym przykladzie kolejne wartosci licznika zwiekaszamy o 1 gdy nie spelnia on
warunkow po przejsciu przez algorytm, dobrym rozwiazniem jest czasami zmniejszanie
licznika o 1 zaczynajac sprawdzac liczby od 0FFFFFFFFh.Powyzszy algorytm bazujacy na
wsprintfA jest malo optymalny ,mozna zamiast tego zastosowac wlasne procedurki konwertujace
ktore zwieksza predkosc dzialania bez koniecznosci stosowania funkcji WinApi.


c) lamanie metoda slownikowa

Nigdy w praktyce nie stosowalem tej metody(moze poza Johnny Riperem) ale warto wiedziec o jej
istnieniu.Metoda dzialanie przedstawia sie nastepujaco,pobieramy kolejne slowa ze slownika,
przepuszczamy je przez algorytm sprawdzamy wyniki i jezeli nie spelniaja warunkow bierzemy
nastepne slowo ze slownika i powtarzamy operacje do skutku albo do wyczerpania sie slow w
slowniku.Zalozmy ,ze mamy np taki algorytm w jakims programie(rozmiar szSN musi byc rowny
np.4 i bajty to cyfry 0-9 i znaki specjalne)

	mov	eax,offset szSN
	sub	ebx,ebx
	mov	ecx,4
crc:
	movzx	edx,byte ptr[eax]
	xor	ebx,edx
	rol	ebx,7
	xor	ebx,81818181h
	rol	ebx,3
	
	inc	eax
	dec	ecx
	jne	crc

	cmp	ebx,3DEEACF3h
	jne	bad_boy

Przykladowy program korzystajacy ze slownika slow 4 bajtowych skladajacych sie
z cyfr 0-9 i znakow specjalnych:

.386
.model flat,stdcall

callW	macro api
extrn	api:proc
call	api
endm


.data
	szSlownik	'dict.txt',0
.code
start:

	sub	eax,eax
	push	eax
	push	00000080h		; FILE_ATTRIBUTE_NORMAL
	push	3			; OPEN_EXISTING
	push	eax
	push	eax
	push	80000000h + 40000000h	; GENERIC_READ + GENERIC_WRITE
	push	offset szSlownik
	callW	CreateFileA		; otworz plik slownika
	inc	eax
	je	exit

	dec	eax

	push	eax

	push	0			; HIGH DWORD gdy plik >4GB
	push	eax			; uchwyt pliku
	callW	GetFileSize		; pobierz rozmiar pliku

	mov	ecx,[esp]		; uchwyt pliku
	sub	edx,edx

	push	edx
	push	eax			; rozmiar mapowanego obiektu
	push	edx			; 0 gdy plik <4GB   
	push	2			; PAGE_READONLY
	push	edx                            
	push	ecx			; uchwyt pliku
	callW	CreateFileMappingA

	push	eax

	sub	edx,edx

	push	edx			; ile bajtow mapowac,gdy 0 caly plik
	push	edx
	push	edx
	push	4			; SECTION_MAP_READ
	push	eax
	callW	MapViewOfFile

	push	eax
find:
	cmp	byte ptr[eax],0		; koniec pliku slownika zaznaczona 00h
	je	brak

	sub	ebx,ebx
	mov	ecx,4
crc:
	movzx	edx,byte ptr[eax]	; oblicz wartosc z 4 literowego slowa
	xor	ebx,edx
	rol	ebx,7
	xor	ebx,81818181h
	rol	ebx,3
	
	inc	eax
	dec	ecx
	jne	crc

	add	eax,2			; dodaj rozmiar CRLF tak zeby eax wskazywal na nastepny wyraz
					; w slowniku

	cmp	ebx,3DEEACF3h		; sprawdz czy obliczona wartosc odpowiada tej poprawnej
	jne	find			; jezeli nie to czytaj nastepny wyraz
	

	sub	eax,6			; pod eax mamy wyraz ktory spelnia wymagania algo
	int	3			; i3here on
brak:

	push	3
	pop	ecx
close:
	callW	CloseHandle
	loop	close

exit:
	push	-1
	callW	ExitProcess

end	start

Aby ta metoda byla chociaz w jakims stopniu skuteczna po pierwsze musimy posiadac odpowiednio
duzy slownik ze wszystkimi kombinacjami liter i cyfr jakie wymaga dany algorytm,co przy
algorytmach gdzie glugosc sn musi byc wieksza niz 3 znaki i w sn moga wystepowac wszystkie
bajty czyni ta metode przegrana :(

d) brute force bez znajomsci poprawnej sumy crc

Zdarza sie np. ze programy(czesto crackme) deszyfruja jakis fragment textu przez klucz
obliczony np z wprowadzonego sn i potem wychodza smieci poniewaz klucz jakim deszyfrowany
byl fragment byl rozny od tego wg jakiego text zostal zaszyfrowany.Np program zamienia
wprowadzony numer seryjny na wartos w hex-ach i wg tej wartosci deszyfruje jakis fragment
textu:

	mov	edx,NumerSeryjny	; wartosc w hex sn
	mov	eax,offset szZaszyfrowany
	mov	ecx,RozmiarZaszyfowanego
xor_big_loop:
	mov	ebx,4
xor_small_loop:
	xor	byte ptr[eax],dl	; xoruj bajty spod eax przez dl i dh
	xor	byte ptr[eax],dh	; czesto jest to wada,poniewaz wartosc poprawnego klucza
					; zawiera sie w granicach 0-255
	dec	ebx
	jne	xor_small_loop

	inc	eax
	loop	xor_big_loop

	push	0
	push	offset szCaption
	push	offset szZaszyfrowany	; i tu wychodzi ASCII sieczka bez poprawnego klucza
	push	0
	call	MessageBoxA

Aby znalezc klucz ktorym zdeszyfrujemy do oryginalnej postaci zaszyfrowany fragment textu
nalezy odwrocic procedure xor-ujaca uzywajac kolejnych wartosci klucza,i wyniki zapisac do
pliku.Ale w tym przypadku jest tylko 255 mozliwych wartosci(co wynika z wlasciwosci xor-a)
Gdy wiemy ,ze oryginalnie text zaszyfrowany jest zapisany w postaci liter czy innych znakow
alfanumerycznych bardzo uprosci sprawe sprawdzanie kolejnych zdeszyfrowanych bajtow czy sa to
znaki alfanumeryczne co wykluczy takie smieci jak np. 01h,0FFh ktore mowia ,ze nalezy sprobowac
z nastepnym kluczem.

.386
.model flat,stdcall

callW	macro api
extrn	api:proc
call	api
endm

.data
	hFile		dd ?
	szFile		db 'out.txt',0
	szZaszyfrowany	db 39h, 27h, 20h, 7Dh, 7Ch, 21h, 3Ch, 29h, 0Fh, 1Dh, 03h
	lZaszyfrowany	equ $-szZaszyfrowany

	lpBufor		db lZaszyfrowany dup(0)
			db 0Dh,0Ah

.code
start:
	push	0
	push	offset szFile
	callW	_lcreat
	mov	hFile,eax

	sub	ecx,ecx
main_loop:
	push	ecx

	mov	edx,ecx

	mov	esi,offset szZaszyfrowany
	mov	edi,offset lpBufor
	mov	ecx,lZaszyfrowany

crypt:
	lodsb				; do al kolejne bajty z zaszyfrowanego
					; ciagu
	xor	al,dl			; xoruj al przez kolejne mozliwe bajty
	stosb				; zapisz do bufora
	loop	crypt

	push	lZaszyfrowany+2		; rozmiar + CRLF
	push	offset lpBufor		; odszyfrowany fragment
	push	hFile			; uchwyt pliku
	callW	_lwrite			; zapisz

	pop	ecx
	inc	ecx
	cmp	ecx,255			; powtarzaj 255 razy
	jbe	main_loop

	push	hFile
	callW	_lclose			; zamknij plik

	push	-1
	callW	ExitProcess


end start

W wyniku otrzymamy plik out.txt z 255 linijkami odszyfrowanego ciagu szZaszyfrowany wg
kluczy od 0-255.Jedyne co teraz pozostaje to analiza wzrokowa :) ,czy nie ma wsrod tych
smieci linijki z jakims czytelnym wyrazem.Takie rozwiaznie mozna stosowac wylacznie gdy
wiemy ,ze zaszyfrowany fragment jest textem,jesli natomiast to fragment kodu programu
to mozna miec juz wieksze problemy, analiza wzrokowa nic nie da,jedyne co mi przychodzi do
glowy to sprytna procedurka ktora sprawdzalaby pierwsze bajty fragmentu kodu pod wzgledem
wystepowania instrukcji push ebp,enter czyli takich instrukcji ktore asembler sam dodaje
gdy zadeklarujemy procedure slowem kluczowym proc bez przyrostka near.


3.Brute force w praktyce
========================
Ambitnie brzmi hehe ale do rzeczy, ostatnio uzywalem brute force kiedy moj qmpel dal mi
zaszyfrowana userliste z ircowego bota voida.Jako ,ze byl posiadaczem zrodla w C++ ale
nie umie programowac w tym jezyku zwrocil sie do mnie zebym sprobowal odszyfrowac userliste.
Pobiezna analiza kodu w C++ dobrowadziala mnie do ciekawego fragmentu

      int chrum=0;
      unsigned long gemma;		// wartosc DWORD
      unsigned long migdalek;		// jak wyzej
      
      migdalek=0;
      for (;;) {
	size_t dupa;
      
	dupa=fread(&gemma,sizeof(gemma),1,f);	// czytaj DWORDa z pliku

	gemma-=(migdalek++*2);		// gemma=gemma-(migdalek++*2)

					// ponizej xorowanie DWORDa gemma
					// przez 3 klucze o rozmiarze DWORDa

	gemma=gemma^cryptkey[0]^cryptkey[1]^cryptkey[2]^cryptkey[3];
	*(s+chrum++)=gemma;		// zapisz najmniej znaczacy bajt z gemma
	...

Tak przedstawia sie procedurka ktora deszyfruje rekord user.Odczytywane sa kolejne DWORDy
z pliku nastepnie od DWORDa odejmowana jest wartosc migdalek*2,po odejmowaniu wartosc
zmiennej migdalek jest zwiekszana o 1(operator inkrementracji przyrostowej).kolejnym
krokiem jest xor-owanie gemma przez 3 klucze o rozmiarze DWORDa ale najlepsze jest to
ze tylko najmniej znaczacy bajt z gemma jest ostatecznie zapisywany,wiec klucz wedlug
ktorego bedziemy deszyfrowac nie bedzie wcale 32bitowy tylko 8bitowy :).W ponizszym
programie deszyfruje tylko fragment pliku zeby tylko znalezc poprawny klucz:

.586
.mmx			; mmx ze wzgledu na 64 bity w rejestrach mmx
			; nie lubie rejestrow zmiennoprzecinkowych :P
.model flat,stdcall

	includelib	e:\dev\masm\lib\kernel32.lib
	includelib	e:\dev\masm\lib\user32.lib
	includelib	e:\dev\masm\lib\comdlg32.lib
	includelib	e:\dev\masm\lib\comctl32.lib
	includelib	e:\dev\masm\lib\advapi32.lib

	include		e:\dev\masm\include\kernel32.inc
	include		e:\dev\masm\include\user32.inc
	include		e:\dev\masm\include\comdlg32.inc
	include		e:\dev\masm\include\comctl32.inc
	include		e:\dev\masm\include\advapi32.inc

	option		casemap	:none
	assume		fs	:flat

	memsize	equ (16+4+2)*256

.data?
	lpBuf	db memsize dup(?)

.data
	;	64 bajty zaszyfrowanego pliku
	;
	lpTemp	db 02h,2Eh,1Eh,00h,3Ch,2Eh,1Eh,00h,2Fh,2Eh,1Eh,00h,36h,2Eh,1Eh,00h
		db 3Ch,2Eh,1Eh,00h,85h,2Eh,1Eh,00h,87h,2Eh,1Eh,00h,89h,2Eh,1Eh,00h
		db 8Bh,2Eh,1Eh,00h,8Dh,2Eh,1Eh,00h,8Fh,2Eh,1Eh,00h,8Ch,2Eh,1Eh,00h
		db 93h,2Eh,1Eh,00h,53h,2Eh,1Eh,00h,8Fh,2Eh,1Eh,00h,8Ch,2Eh,1Eh,00h
	lTemp	equ $-lpTemp
	lpOut	db 'out.txt',0
	hFile	dd ?

.code
_start:
	pushad

	push	0
	push	offset lpOut
	call	_lcreat

	mov	[hFile],eax

	emms				; czysc stan rejestrow mmx
	int 3				; bpx

	sub	edx,edx			; zaczynamy od klucza=0

	sub	ebp,ebp
	mov	eax,offset lpTemp

	movq	mm1,qword ptr[eax]	; zaladuj 32bajty z temp do 2 rejestrow mmx
	movq	mm2,qword ptr[eax+8]	; poniewaz pierwsze 32bajty beda zamazane

;	movq	mm3,qword ptr[eax+8+8]
;	movq	mm4,qword ptr[eax+8+8+8]

main_loop:
	sub	ebx,ebx
	mov	ecx,lTemp/4		; licznik globalny

	mov	esi,offset lpTemp	; bufory wej
	mov	edi,esi			; wyj

decrypt:
	push	ebx			; zapamietaj wartosc poczatkowa klucza
	shl	ebx,1			; t*2;

	mov	eax,dword ptr[esi]	; kolejne dwordy z pliku
	sub	eax,ebx			; dword[licznik]-=t;
	xor	eax,edx			; dword[licznik]^=kolejne_klucze;

	pop	ebx			; mnozenie nie zmienia wartosci zmiennej t(aka migdalek)

	cmp	al,0Ah			; znak konca linii
	jne	not_end_line

	mov	byte ptr[edi],0FFh	; zapisz tam -1

	jmp	end_line

not_end_line:
	cmp	al,20h			; czy to jakies ASCII smieci?
	jb	wrong			; klucz raczej niepoprawny

	cmp	al,7Eh
	ja	wrong			; jak wyzej

	mov	byte ptr[edi],al	; zapisz rozszyfrowany bajt
next_char:
	inc	ebx			; t++;

	add	esi,4			; wskaznik na nastepny dword
	inc	edi			;
	dec	ecx
	jne	decrypt
end_line:

	mov	eax,offset lpTemp

	movq	mm0,qword ptr[eax]	; 8 rozszyfrowanych bajtow 
	movq	qword ptr[lpBuf+ebp],mm0; skopiuj do bufora
	movq	mm0,qword ptr[eax+8]	; kolejne 8 bajtow
	movq	qword ptr[lpBuf+ebp+8],mm0
	
	mov	dword ptr[lpBuf+ebp+8+8],edx	; zapisz klucz wg ktorego fragment textu zostal rozszyfrowany
	mov	word ptr[lpBuf+ebp+8+8+4],0A0Dh	; zapisz znak konca linii
	add	ebp,16+4+2

wrong:
	mov	eax,offset lpTemp

	movq	qword ptr[eax],mm1	; przywroc bajty bufora temp
	movq	qword ptr[eax+8],mm2
;	movq	qword ptr[eax+8+8],mm3
;	movq	qword ptr[eax+8+8+8],mm4

	inc	edx			; kolejny klucz wg ktorego bedzie przebiegalo deszyfrowanie
	cmp	dl,0			; czy juz 0
	jne	main_loop

	int	3

	push	ebp
	push	offset lpBuf
	push	[hFile]
	call	_lwrite

	push	[hFile]
	call	_lclose

	popad

	ret
end _start


Po zapuszczeniu tego proga w pliku out.txt zainsteresowala mnie jedna linijka

Tl}fb------ -o%8V   
Um|gc,,,,,,!,n$9W   
Zbshl######.#a+6X   
[crim""""""/"`*7Y   
X`qjn!!!!!!,!c)4Z   
Yapko      - b(5[   <--Yapko wydalo mi sie najbardziej podejrzane X-)
^fwlh''''''*'e/2\   
_gvmi&&&&&&+&d.3]   
\dunj%%%%%%(%g-0^   
]etok$$$$$$)$f,1_   
sKZAE /. 6. q   

Jak sie okazalo klucz wg. ktorego otrzymalem w wyniku Yapko to bajt 5Bh,co pozwolilo
mi na odszyfrowanie calej userlisty.

4.Slowo koncowe
===============

Brute force w niektorych przypadkach okazuje sie niezastapiony,w jeszcze innych niemozliwy
do wykorzystania ze wzgledu na dlugosc ciagow i czas jaki nalezaloby poswiecic aby otrzymac
wyniki,czasami brute force moze byc niepotrzebny, gdy mozliwe jest odwrocenie algo kodujacego,
zawsze warto pomyslec przed zastosowaniem bruta czy nie mozna odwrocic algo w celu otrzymania
sn czy czego tam,bo chyba nikt nie bylby zadowolony gdyby musial czekac 2 dni az zobaczy sn
do jakiegos proga :)


bart^CrackPl
cryogen@poland.com

