AntiAnti v1.01
~~~~~~~~~~~~~~
by bart


Historia:
~~~~~~~~~
v1.01 - 29.11.2ooo 
+ dodalem metody antyTRW, antyTRACE, antyLOADER
! poprawilem text o wstawkach asm w Delphi

v1.00 - 01.1o.2ooo
+ wersja beta

Spis tresci:
~~~~~~~~~~~~
1.Sposoby utrudniajace sledzenie
a) dummy opcodes
b) wyjatki

2.Porady przy zabezpieczaniu
a) active protection
b) nietypowe instrukcje
c) skoki warunkowe
d) tricki antydebugger
 - meltice w innym wydaniu
 - debug context trick
 - callgate i blokowanie SI
 - goscie
 - czyszczenie bpx-ow
e) wykrywanie niektorych toolsow
f) tricki ze stosem

3.Koncowe porady
4.Slowo na niedziele
5.Pozdrowienia

Tytul tego arta mialem zmienic na 'AntyCracker' poniewaz spodobal mi sie tytul
jednego faq phreakerskiego 'AntySasiad', ale ostatecznie pozostawilem pierwotna
forme.

Prawie wszystkie metody anty zawieraja przykladowe kody zrodlowe, niektore sa
troche dlugie ale, wedlug mnie serwowanie samej teorii nie wystarcza, trzeba
pokazac, jak wykorzystac to w praktyce.Wszystkie procedury testowalem pod Win95
SI 3.24, SI 4.05 i nie sprawialy problemow, co nie oznacza, ze sa niezawodne.


1.Sposoby utrudniajace sledzenie

a) dummy opcodes
~~~~~~~~~~~~~~~~
1.Kazdy kto mial "przyjemnosc" wlasnorecznie sledzic kod programu spakowanego
np. PE-Cryptem wie, ze tracowanie jest cholernie trudne, mozna latwo pominac
jakiegos waznego call-a itp.Co zrobic aby sledzenie kodu mojego programu bylo
rownie meczace?Caly bajer polega na tym aby pomiedzy kolejnymi instrukcjami
oryginalnego kodu wstawiac tzw. dummy opcodes np. mamy taki fragment kodu:

	mov	edi,offset szOut
	mov	esi,offset szSrc
	mov	ecx,5
	rep	movsb

Po skompilowaniu otrzymamy w exeku:

.00401000: BF05204000	mov	edi,000402005 ;" @ "
.00401005: BE00204000	mov	esi,000402000 ;" @  "
.0040100A: B905000000	mov	ecx,000000005 ;"   "
.0040100F: F3A4		repe	movsb

Wszystko ladnie i czytelnie zarowno dla crackera jak i dla w32dsm :).Teraz
spojrzmy na ten sam kod, z malymi modyfikacjami:

syf	macro dummy
	jmp	$+3			; przeskocz---+
	db	dummy			;             |
	db	dummy xor 81h		;             |
					; <-----------+
endm

					syf	0E8h
	mov	edi,offset szOut	; oryg instrukcja
					syf	0E8h
	mov	esi,offset szSrc	; oryg instrukcja
					syf	0A8h
	mov	ecx,5			; oryg instrukcja
					syf	0E9h
	rep	movsb			; oryg instrukcja

Pierwsz wrazenie to to, ze troche utrudnia to czytanie plikow zrodlowych.
Po skompilowaniu i podejrzeniu pliku pod hiew:

.00401000: EB01			jmps	000401003   -------- (1)
.00401002: E869BF0520		call	02005C570
.00401007: 40			inc	eax
.00401008: 00EB			add	bl,ch
.0040100A: 01E8			add	eax,ebp
.0040100C: 69BE00204000EB01A829	imul	edi,d,[esi][000402000],029A
.00401016: B905000000		mov	ecx,000000005 ;"   "
.0040101B: EB01			jmps	00040101E   -------- (2)
.0040101D: E968F3A400		jmp	000A4F98A

Czy to ten sam kod co wyzej?Jasne, ze tak,ten wykonuje dokladnie to samo co
oryginalny, dla uzytkownika jest to niezauwazalne a pod W32dsm widac:

:00401000 EB01                    jmp 00401003
:00401002 E8                      BYTE E8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401000(U)
|
:00401003 69BF05204000EB01E869    imul edi, dword ptr [edi+00402005], 69E801EB

* Section Data Obj -> "zzzz"
				  |
:0040100D BE00204000              mov esi, 00402000
:00401012 EB01                    jmp 00401015
:00401014 A8                      BYTE A8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401012(U)
|
:00401015 29B905000000            sub dword ptr [ecx+00000005], edi
:0040101B EB01                    jmp 0040101E
:0040101D E9                      BYTE E9

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040101B(U)
|
:0040101E 68F3A40000              push 0000A4F3

Czyli nie za wiele :).Sledzac pod SoftIce powyzszy kod mozna dostac szalu, przy
duzej ilosci jmp-ow mozna z rozmachu zamiast F8 wcisnac F10 i sledzenie trzeba
zaczynac od nowa.Istnieje wiele kombinacji makr ktore "umilaja" sledzenie kodu:

fvxd	macro
	jmp	$+4
	db	0CDh,20h		; int 20h
endm

Makro stosowane bylo dawniej w PE-Crypcie i o ile pamietam w PELocknt, trick
polega na tym, ze bajty 0CDh,20h stanowia poczatek VxDCall-a czyli cos w stylu
api call pod ring3, tylko, ze zamiast adresu call-a po 0CDh,20h jest zapisany
numer uslugi ktora wywolujemy, w przypadku naszego makra debugger po napotkaniu
bajtow 0CDh,20h kolejne 4 bajty i poprzednie 2 bajty zinterpretuje jako jedna
instrukcje(nie bedzie widac oryginalnych instrukcji).

fcall	macro dummy
	call	$+6			; call do--+
	db	dummy			;          |
	add	esp,4			;<---------+
endm

Dzialanie tego makra ma ta pozytywna ceche iz podczas sledzenia przewaznie nikt
nie ma zwyczaju zapuszczac sie w kazdego call-a F8, a wlasnie w tym przypadku
jest to konieczne gdyz ten call nie powraca do EIP po call-u,ale korygowany jest
stos tak aby jego wartosc byla taka jak przed wykonaniem call-a(nie musze mowic
jak wazny jest stos w programch 32bit),wiec jesli nie zastosujemy F8 wylecimy z
programu jak po nacisnieciu comba CTRL-D.Udoskonalona wersja tego makra(znaczy
mniejsza objetosciowo):

fcall	macro popreg,dummy
	call	$+6			; call do------+
	db	dummy			;              |
	pop	popreg			; pop rejestr<-+
endm

To makro dziala podobnie jak to zaprezentowane wyzej z ta roznica, ze korekcja
stosu nie jest realizowana przez add esp,4 tylko poprzez instrukcje pop.W
przypadku tego makra nalezy zwrocic szczegolna uwage na rejestr ktory bedzie
sluzyl do korekcji stosu np.:

	mov	edi,offset szCostam
	fcall	edi,0E8h
	repz	stosb

Wykonanie tego kodu skonczy sie najprawdopodobnie bledem, najpierw do edi jest
zapisany adres jakiegos bufora potem makro fcall korzysta z instrukcji pop edi
zeby skorygowac stos po fake call-u, rzeczywisty kod bedzie wygladal tak:

	mov	edi,offset szCostam

	call	$+6			;\
	db	0E8h			; > makro fcall edi,0E8h
	pop	edi			;/

	repz	stosb

Przed wykonaniem repz stosb rejestr edi ulegnie zmianie w wyniku dzialania makra
i program sie posypie.Jesli w kodzie nie zalezy nam na zawartosci rejestru ecx
mozna zastosowac:

flop	macro shit
	loop	$+3			; przeskocz shit gdy ecx<>1
	db	shit
endm

W tym wypadku nalezy upewnic sie,ze przed wywolaniem w ecx jest wartosc<>1, bo
gdy bedzie 1 to zamiast za shit opcodem wyladujemy w shit opcodzie X-).Makra
mozna laczyc w jakies bardziej skomplikowane kombinacje(bazujac na bajtach tych
podstawowych):

fake	macro fakeb
	jmp	$+9			;-->----+
	db	fakeb			;       |
	db	fakeb xor 81h		;       |
	add	esp,4			;<------|---------+---+
	jmp	$+10			;       |         |   |
	call	$+6			;<------+--->-+   |   |
	db	fakeb			;             |   |   |
	jmp	$-11			;<----<-------+->-+   |
					;<--------------------+
endm

floop	macro some,iterations
	push	ecx			; zapamietaj ecx
	jmp	$+3
	db	069h
	jmp	$+4			; przeskocz bajty CD,20
	db	0CDh,20h
	db	6Ah
	IFNB	<iterations>		; jesli zdefiniowano iterations
	db	iterations		; ilosc powtorzen petli < 7Fh ze wzgledu
	ELSE				; na zastosowanie opcodu push byte(6A)
	db	20h			; jesli nie podano parametru iterations
	ENDIF
	pop	ecx
	call	$+6
	db	some
	jbe	$+5			;
	db	0C1h,0F1h,0		; sal ecx,0
	jmp	$+3			; przeskocz bajt some
	db	some
	dec	ecx
	jne	$-9			; jne do jbe $+5
	pop	ecx			; pop po call $+6
	jecxz	$+3			; ecx nigdy nie bedzie = 0
	pop	ecx			; oryginalna wartosc 
	jmp	$+3
	db	69h
endm

Powyzsze makra mozna bez problemu zaimplementowac w jezykach wyzszego poziomu
(C++,Delphi).Najpierw Delphi, tworzymy plik .inc(koncowka nie ma znaczenia), w
ktorym deklarujemy nasze makro(jako wstawka asm) np.:

asm db $EB,02,$E8,00 end;

Potem w kodzie programu dolaczamy naszego includa,miedzy kolejnymi linijkami
kodu:

{$I fake.inc}
if IsRegistered(edUser.text,edKey.text) then
begin
{$I fake.inc}
MessageBox(0,PChar('Thank you for support!'),PChar('Info'),MB_ICONINFORMATION);
...
Zamiast korzystac z .inc mozna tez bezposrednio wstawic pomiedzy linijkami
wstawke asm:

asm db $E8,$01,$00,$00,$00,$33,$83,$C4,$04 end;

np.
if CompareHashes(@FDigest,@md5sn) then
begin
asm db $E8,$01,$00,$00,$00,$33,$83,$C4,$04 end;
asm db $EB,$02,$CD,$20 end;
result:=true;			// jesli porownanie dalo TRUE
				// zwroc TRUE
asm db $EB,$02,$39,$BA end;
end
else
begin
asm db $E8,$01,$00,$00,$00,$33,$83,$C4,$04 end;
asm db $EB,$02,$E9,$00 end;
result:=false;
asm db $EB,$02,$39,$BA end;
end;

W C++ postepujemy podobnie, tworzymy plik .h gdzie deklarujemy, w przypadku gdy
korzystamy z kompilatorow Borlanda:

#define FAKE asm {DB 0EBh,02,0E8h,00};

Gdy korzystamy z narzedzi Micro$oft-u:

#define FAKE \
 __asm _emit 0xEB \
 __asm _emit 0x02 \
 __asm _emit 0xE8 \
 __asm _emit 0x00

W pliku zrodlowym includujemy plik w ktorym mamy zadeklarowane makro:

#include "fake.h"

...

FAKE
if IsRegistered()
{
FAKE
MessageBox(0,"Thanks for support","Info",MB_ICONINFORMATION);
FAKE
}

Efekty stosowania tego typu wstawek moze nie beda tak idealne, jakbysmy robili
to w czystym asm, gdzie mozemy je wstawiac pomiedzy kazda instrukcje, a nie jak
w przypdaku Delphi i C++ co blok operacji, ale mimo wad zawsze utrudni to
troche zycie i przy okazji czytelnosc deadlistingu.

Nieuniknionym ubocznym efektem stosowania tych makr jest znaczny(jak dla kogo)
wzrost wielkosci kodu programu (przy intensywnym wykorzystaniu makr) co ma
znaczenie przy projektowaniu np. exe-pakerow gdzie liczy sie kazdy bajt, wiec
pozostaje wybor czy moj program ma byc dobrze zabezpieczony czy ma zawierac jak
najmniej bajtow?

Budujac wlasne makra nalezy zwrocic uwage aby instrukcje wykonywane w makrach
nie powodowaly jakichs ubocznych efektow w stylu zmiana stosu, rejestrow, flag
stanu, chyba, ze robimy to w pelni swiadomie :)


b) wyjatki
~~~~~~~~~~
Zawsze podobala mi sie idea wyjatkow a szczegolnie ich specjalnego wywolywania
:), np. sledzimy sobie jakis kod bez wlaczonych FAULTS w SI i nagle wszystko
staje, zaden klawisz nie odpowiada, jedyne co pozostaje to zimny reset.Aby
zrobic takie badziestwo nalezy najpierw ustawic tzw. SEHa:

	sub	eax,eax
	push	offset @handler		; adres procki ktora obsluzy wyjatek
	push	dword ptr fs:[eax]	; pod fs:0 zapisany jest adres poprzedniej
					; struktury SEH
	mov	dword ptr fs:[eax],esp	; esp wskazuje na adres DWORDa ktory
					; wskazuje na @handler

po wykonaniu jakiejs niedozwolonej operacji w stylu:

	xor	eax,eax
	mov	[eax],eax

system operacyjny zwroci oblsluge programu procedurze @handler.Niestety po
nastapieniu wyjatku(tak sie nazywa wystapienie bledu) i przywroceniu kontroli
procedurze @handler zmieniony zostaje stan rejestrow w tym esp, aby przywrocic
ich oryginalna wartosc taka jaka byla przed wystapieniem wyjatku i przed
ustawieniem bramki SEH:

	sub	edx,edx
	mov	eax,dword ptr fs:[edx]	; offset DWORDa gdzie jest zapisana
	mov	esp,[eax]		; wartosc rejestru esp przed wystapieniem wyjatku
	pop	dword ptr fs:[edx]	; przywroc adres poprzedniej struktury SEH
	pop	eax			; adres @handler-a

Teraz wykorzystanie w praktyce:

	sub	eax,eax
	push	offset @handler		; adres procki ktora obsluzy wyjatek
	push	dword ptr fs:[eax]	; pod fs:0 zapisany jest adres poprzedniej
					; struktury SEH
	mov	dword ptr fs:[eax],esp	; esp wskazuje na adres DWORDa ktory
					; wskazuje na @handler
	...
	jakis kod
	...

	mov	[eax],eax		; zapis do niedostepnego obszaru pamieci
					; spowoduje blad
	...
	fake kod dla niepoznaki
	...
	call	ExitProcess

@po_wyjatku:
	...
	call	SendDlgItemMessage
	...


@handler:
	sub	edx,edx
	mov	eax,dword ptr fs:[edx]	; offset DWORDa gdzie jest zapisana
	mov	esp,[eax]		; wartosc rejestru esp przed wystapieniem wyjatku
	pop	dword ptr fs:[edx]	; przywroc adres poprzedniej struktury SEH
	pop	eax			; adres @handler-a
	jmp	@po_wyjatku

Podczas sledzenia tego kodu F10 wyladujemy w calkiem nieoczekiwanym miejscu
anizeli @handler, jesli zajarzymy, ze autor zrobil tak specjalnie piszemy w
SoftIce komende XFRAME, zakladamy bpx-a na adresie handlera ostatniej struktury
SEH jaka pokaze nam SI, FAULTS OFF i CTRL-D, po tych czynnosciach powinnismy
wyladowac w procedurze obslugujacej wyjatek.Sposobow na wywolanie wyjatkow jest
w cholere i troche, wystarczy uzyc wyobrazni:

	xor	edi,edi
	stosd				; zapis pod adres 0
	...
	int 1				; wywolanie przerwania
	...
	int 3				; bardzo skuteczne jesli ktos ma
	call	ExitProcess		; wlaczone i3here on w SI
	...
	or	esi,-1			; odczyt z pamieci
	lodsb
	...
	sub	esp,esp			;
	push	eax			; zapis pod bledny adres w pamieci
	...
	xor	eax,eax
	div	eax
	...
	db	0F0h,0Fh,0C7h,0C8h	; lock cmpxchg8b powoduje zawieszenie
					; procesorow 586

Wyjatki moga byc bardzo uzyteczne,wyobrazmy sobie taka sytuacje,nasz nowiutko
cudny program wymaga,zeby podac podczas rejestracji jakis tam name oraz numer
seryjny(wyliczony z name), zamiast porownywac wprowadozny serial i obliczony
poprzez zwykle cmp mozna chociaz troche utrudnic zycie crackerowi,zakladamy
SEHa, adres procki obslugujacej ustawiamy na procke z MessageBoxA "Thank's for
registering", wartosc poprawna umieszczamy w rejestrze np. ecx odejmujemy od
niej wartosc int wprowadzonego seriala,jesli sa takie same rejestr ecx powinien
byc rowny 0,potem wykonujemy div ecx po ktorym jest "Please Register":

	sub	eax,eax
	push	offset @handler		; adres procki ktora obsluzy wyjatek
	push	dword ptr fs:[eax]	; pod fs:0 zapisany jest adres poprzedniej
					; struktury SEH
	mov	dword ptr fs:[eax],esp	; esp wskazuje na adres DWORDa ktory
					; wskazuje na @handler
	mov	ecx,[wprowadzony_sn]	; po odejmowaniu, jesli oba seriale sa
	sub	ecx,[poprawny_sn]	; takie same w ecx powinno znalezc sie 0
	sub	edx,edx			; dzielenie przez 0 spowoduje wystapienie
	div	ecx			; bledu,co spowoduje przeniesienie nas
					; do @handler
	push	0
	push	offset lpCaption
	push	offset lpPleaseRegister
	push	0
	call	MessageBoxA
	jmp	@exit	

@handler:
	sub	edx,edx
	mov	eax,dword ptr fs:[edx]	; offset DWORDa gdzie jest zapisana
	mov	esp,[eax]		; wartosc rejestru esp przed wystapieniem wyjatku
	pop	dword ptr fs:[edx]	; przywroc adres poprzedniej struktury SEH
	pop	eax			; adres @handler-a

	push	edx
	push	offset lpCaption
	push	offset lpThanksForRegistering
	push	edx
	call	MessageBoxA
	
W praktyce, zakladanie SEHa powinno znajdowac sie duzo wczesniej,przed kodem
sprawdzajacym, aby intencje nie rzucaly sie za bardzo w oczy(ale tez nie za
wczesnie,zeby czasem jakis glupi blad nie spowodowal wyswietlenia bez wyraznego
powodu "Thanks..." ;),procka obslugujaca wyjatek powinna byc rownie dobrze
schowana, ewentualnie zaszyfrowana + naszpikowana makrami zaprezentowanymi na
poczatku tego arta.Wyjatki mozna rownie skutecznie wykorzystac w jezykach
wyzszego poziomu (czy wyzszego w porownaniu z asm to jest zawsze dla mnie
kwestia sporna ;)

iSN:=StrToInt(edSN.text);		// pobierz sn

try					// postaw SEHa
	tmp:=2785 div (iSn-987654321);	// dowolna liczba <>0, podziel przez
					// (sn-poprawny) jesli nastapi wyjatek
					// dzielenia przez 0 przeniesiemy sie do
					// kodu w except
	MessageBox(frmMain.handle,'Please register','Info',MB_ICONHAND);
	RegFlag:=0;
except					// handler obslugujacy wyjatek
	MessageBox(frmMain.handle,'Thanks for support','Info',MB_ICONINFORMATION);
	RegFlag:=1;
end;

Jesli chcemy wychwycic,obsluzyc tylko konkretny typ bledu nalezy skorzystac z
elementow klasy (EExternal)Exception i slowka kluczowego on:

try
	costam
except
	on EZeroDivide do HandleZeroDivide;
	on EOverflow do HandleOverflow;
end;


2.Porady przy zabezpieczaniu

a) active protection
~~~~~~~~~~~~~~~~~~~~
Najczestszym dzialaniem crackera majacym ulatwic mu robote jest stawianie
bpx-ow, debugger w miejsce bpx-a stawia bajt 0CCh(int 3) i podczas napotkania
tego bajtu zatrzymuje dzialanie programu, przywraca oryginalny bajt i nastepnie
mamy mozliwosc zdecydowania co dalej, ale czy da sie wykryc bpx-a w kodzie?
Oczywiscie, istnieje dobrze znany(i uzywany) sposob polegajacy na wyliczeniu
sum kontrolnych z danego fragmentu kodu po wykonaniu albo przed wykonaniem tego
kodu, ale istnieje tez bardziej finezyjna metoda praktycznie niezauwazalna
polegajaca na sprawdzaniu kodu w tle(2 watku).Pierwsza metoda mimo, ze juz
nieco staromodna jest czesto stosowana:

	mov	esi,offset chceckSN
	mov	ecx,_chceckSN
	call	CRC32

	cmp	eax,POPRAWNE_CRC
	jne	exit

	call	checkSN

exit:
	...

proc	chceckSN
	...
proc	chceckSN
_chceckSN equ $-chceckSN


Druga metoda wykorzystujaca watek jest nieco trudniejsza do obejscia i wykrycia:

.386
.model flat,stdcall

callW	macro api
	extrn	api:proc
	call	api
endm

.data
	ThreadID	dd ?
	szWait		db 'NOP',0

.code
start:
	int	3			; i3here on i zaczynamy zabawe

	sub	eax,eax
	push	offset ThreadID
	push	eax
	push	eax
	push	offset ActiveProtection	; adres procedury watku
	push	eax
	push	eax
	callW	CreateThread		; utworz watek

	db	256 dup(8Bh,0C2h)	; tak zeby zapelnic miejsce

	mov	eax,offset szWait
	cdq

	push	edx
	push	eax
	push	eax
	push	edx
	callW	MessageBoxA

	push	-1
	callW	ExitProcess

CRC32:					; kod z jakiegos wirusa
	push	ecx
	push	edx
	push	ebx
	xor	ecx, ecx
	dec	ecx
	mov	edx, ecx
NextByteCRC:
	xor	eax, eax
	xor	ebx, ebx
	lodsb
	xor	al, cl
	mov	cl, ch
	mov	ch, dl
	mov	dl, dh
	mov	dh, 8
NextBitCRC:
	shr	bx, 1
	rcr	ax, 1
	jnc	NoCRC
	xor	ax, 08320h
	xor	bx, 0EDB8h
NoCRC:	dec	dh
	jnz	NextBitCRC
	xor	ecx, eax
	xor	edx, ebx
	dec	edi
	jnz	NextByteCRC
	not	edx
	not	ecx
	pop	ebx
	mov	eax, edx
	rol	eax, 16
	mov	ax, cx
	pop	edx
	pop	ecx
	ret
Exit:
	push	-1
	call	ExitProcess

ActiveProtection	proc

;	push	100			; nie bedzie to az tak spowalnialo
;	callW	Sleep			; dzialania glownego programu

	mov	edi,Exit-start		; rozmiar bufora
	mov	esi,offset start	; bufor
	call	CRC32			; oblicz CRC32
	xor	eax,1F05E507h		; suma wyliczona z kodu(na sucho)
	je	ActiveProtection

	mov	al,90h			; nop
	mov	edi,offset start
	mov	ecx,Exit-start
	rep	stosb			; sekcja .code musi posiadac atrybuty write
					; inaczej bedzie to powodowalo bledy
	xchg	eax,ecx
	ret				; zakoncz watek

ActiveProtection	endp

end	start

Postawienie pulapki na kodzie pomiedzy wywolaniem CreteThread a ActiveProtection
spowoduje, ze ten kod zostanie nadpisywany nop-em(lepiej dac int 3 heh), zamiast
nadpisywania mozna dac zaraz jakas operacje destrukcyjna(hlt).Jedyna wada(oprocz
spowalniania dzialania calego programu, chociaz mozna dac Sleep-a np. 64ms przed
obliczaniem kolejny raz sumy kontrolnej) tego rozwiazania jest to ze kod ktory
jest wykonywany w watku, kiedy calosc sledzimy w SI jest wykonywany ze znacznym
opznieniem,wiec jesli postawimy bpx na instrukcji zaraz po call CreateThread i
bedziemy sledzic program F10 to procedura zamazujaca nie zostanie wykonana
natychmiast po postawieniu bpx-a tylko po wcisnieciu paru F10,jesli natomiast
puscimy proga przez F5 procka zostanie wykonana natychmiastowo, coz nic nie
jest idealne...Procedura wykonywana w watku powinna byc dobrze schowana w kodzie
programu zeby nie mozna bylo jak w tym przypadku zanopowac samej procedury
watku.Kod na podstawie ktorego obliczana jest suma kontrolna nie powinien
zawierac elementow samomodyfikujacych no bo to spowoduje, ze zmieni sie wartosc
obliczonej na sucho sumy i beda klocki ;).Pamietajcie zeby ustawic sekcji w
ktorej bedzie znajdywal sie kod ktory ma byc zamazany atrybuty write, zeby moc
skorzystac z rep stos,inaczej korzystajcie z WriteProcessMemory.


b) nietypowe instrukcje
~~~~~~~~~~~~~~~~~~~~~~~
Standrdowo kompilatory jezykow wyzszego poziomu(C++,LCC) generuja kod asemblera
skladajacy sie ze standardowych operacji asm typu przenies cos z jednego
rejestru/pamieci do drugiego, operacji arytmetycznych i logicznych ale jakos
nigdy nie spotkalem sie zeby jakis program masowo korzystal z instrukcji typu
rcr,bts,salc,aad itp. czesc zapyta sie co do cholery jest, odpowiem, ze sa to
instrukcje procesora dostepne juz od najwczesniejszych modeli serii x86.Czemu
nie sa standardowo wykorzystywane hmm, optymalizacja im procesory staja sie
nowoczesniejsze tym prostsze instrukcje zyskuja na szybkosci co odbija sie na
szybkosci wykonywania starszych instrukcji, te "stare" instrukcje mozna zastapic
innymi np. bt mozna zastapic test, ktore sa wykonywane w znacznie krotszym
czasie procesora.Dlaczego wiec nie wycofano ich z nowszych modeli procesorow
skoro mozna je zastapic innymi?Odpowiedz jest prosta, producenci chca zachowac
kompatybilnosc w tyl i tyle.Opisze tylko czesc "nietypowych" instrukcji:

BT	dest,src - bit test

Kopiuje bit z dest wskazany przez src do CF.Np.:

	mov	eax,1

	bt	eax,0
	jc	one

skopiuje najmniej znaczacy bit z eax do CF(Carry Flag).Instrukcja ta moze byc
uzyteczna gdy np. mamy gdzies zapisana konfiguracje programu w postaci DWORD,
kolejne bity oznaczaja czy jakas tam opcja ma byc wlaczona,zalozmy, ze np. gdy
eax=1 program ma byc zarejestrowany a jesli rejestr eax=2 program bedzie
niezarejestrowany.Normalnie zaby sprawdzic cos takiego:

	mov	eax,[config]

	cmp	eax,2
	je	niezarejestrowany
	cmp	eax,1
	je	zarejestrowany

albo z wykorzystaniem instrukcji test:

	mov	eax,[config]

	test	eax,2
	jne	niezarejestrowany
	test	eax,1
	jne	zarejestrowany

z wykorzystaniem bt:

	mov	eax,[config]

	bt	eax,1			; 1^2=2
	jc	niezarejestrowany
	bt	eax,0			; czy jedynka jest ustawiona
	jc	zarejestrowany		; jesli tak to prog jest zarejestrowany

Gdy nie spotkalismy nigdy wczesniej instrukcji bt powyzszy kod moze wydac sie
niezrozumialy, i na tym to polega aby zmusic kogos kto bedzie probowal zlamac
cos takiego do poswiecenia temu dluzszego czasu az otworzy x86 ref albo znajdzie
sobie latwiejszy cel(lub zastosuje brute force jak to niedawno widzialem hehe).
Z bt wiaze sie szereg pokrewnych instrukcji:

BTC	dest,src - bit test with compliment

Kopiuje do CF bit z dest wskazany przez src i dodatkowo ustawia w dest ten bit
tylko zanegowany.Np.:

	mov	eax,8			; 1000b
	btc	eax,2			; 2^2=4 czyli bit 0100b

Po wykonaniu tych instrukcji w eax znajdzie sie liczba 8+4=12.Wykonanie:

	mov	eax,12
	btc	eax,2

Spowoduje, ze bit odpowiadajacy za 4-ke zostanie zanegowany i wyniku w eax
znajdzie sie 8.Tu przychodzi mi pomysl na szyfrowanie,mianowicie negowanie np.
2 bitow w bajcie:

crypt	proc near
	pop	eax			; adres powrotu
	pop	esi			; offset bufora do zaszyfrowania
	pop	ecx			; rozmiar w bajtach
	push	eax			; zapamietaj adres powrotu

	push	2
	pop	edx			; 2 w edx

	mov	edi,esi			; zarowno edi i esi wskazuja na ten sam
cloop:					; bufor
	lodsb				; laduj bajt z [esi]

	btc	eax,edx			; neguj bit odpowiadajacy za 4-ke
	xor	dl,2			; edx = 2, edx = 0
	btc	eax,edx			; neguj bit odpowiadajacy za 1-ke

	stosb				; zapisz "zaszyfrowanego" DWORDa
	loop	cloop

	ret
crypt	endp

Aby zaszyfrowac i zdeszyfrowac jakis bufor, procke wywolujemy z parametrami:

	push	RozmiarBufora
	push	offset Bufor
	call	crypt

Czy jak tam kto woli:

	invoke	crypt,RozmiarBufora,addr Bufor


RCR	dest,count - Rotate Through Carry Right
RCL	dest,count - Rotate Through Carry Left

Obraca bity dest w lewo(rcl) lub prawo(rcr) o wartosc count tak samo jak to robi
instrukcja rol/ror, przy czym ostatni "wybity" bit jest zapisywany w CF np.:

	mov	eax,1
	rcr	eax,1

spowoduje, ze w eax znajdzie sie 0 a flaga CF zostanie ustawiona.Z instrukcjami
operujacymi na CF wiaze sie szereg instrukcji wsrod nich sa 3 ktore sluza do
zmiany stanu flagi CF.Wykonanie instrukcji CLC powoduje zresetowanie flagi CF,
wykonanie instrukcji STC powoduje jej ustawienie, natomiast wykonanie instrukcji
CMC spowoduje zmiane stanu flagi CF na przeciwny(negacja).Do innych instrukcji
ktore wykorzystuja CF mozna zaliczyc z pewnoscia ADC, SBB.

ADC	dest,src - add with carry

Dodaje do dest, src plus jeden jesli jest ustawiona CF.Dzialnie tej instrukcji
to nic innego jak:

	add	dest,src
	add	dest,CF


SBB	dest,src - subtraction with borrow/carry

Instrukcja odejmuje od dest wartosc src oraz odejmuje od dest 1 jesli CF jest
ustawiona.Jej dzialanie mozna zobrazowac w nastepujacy sposob:

	sub	dest,src
	sub	dest,CF

SBB mozna wykorzystac gdy chcemy np.w eax otrzymac 0 jesli eax jest rozne od 0,
a 1 jesli eax jest rowne 0.

	neg	eax			; eax=0 to po neg nadal eax=0
					; eax<>0 to eax=-eax i zostanie ustawiona CF
	sbb	eax,eax			; eax=0 i CF=0 to po sbb w eax bedzie 0
					; eax<>0 i CF=1 to po sbb w eax=(eax)-(eax+1)=-1
	inc	eax			; eax=0 po inc eax=1
					; eax=-1 po inc eax=0

Korzystajac z SBB i STC mozemy umiescic w dowolnym rejestrze wartosc 0FFFFFFFFh(-1):

	stc				; ustaw CF
	sbb	ecx,ecx			; ecx-ecx-CF=-1

SALC	- Set AL on Carry

Nieudokumentowana instrukcja dostepna we wszystkich wersjach procesorow x86,
kopiuje wartosc CF na wszystkie pozycje bitowe rejestru AL,wiec gdy CF jest
ustawiona w AL znajdzie sie wartosc 11111111b czyli 0FFh, natomiast jesli CF
nie jest ustawiona to w AL znajdzie sie 0.Mozecie miec problemy z kompilowaniem
kodu gdy napiszecie w zrodlowce salc, ale latwo mozna ominac ta niedogodnosc
stosujac makro:

salc	macro
	db	0D6h			; instrukcja jest 1 bajtowa
endm

Instrukcja SALC w przeciwienstwie do SBB i ADC nie zmienia wartosci rejestrow
flagowych.Sledzenie pod SI tej instrukcji powoduje, ze nastepuje skok do 2
instrukcji po SALC(tylko na monitorze bo faktycznie zostaja wykonane wszystkie
instrukcje) np.:

	salc				; gdy w tym miejscu nacisniemy F10 wyladujemy w-+
	stc				;                                               |
	call	edx			; <---------------------------------------------+
					; ale instrukcja stc i tak zostanie wykonana


Podobne efekty podczas sledzenia F10 daje uzycie rejestrow debug w trybie ring3.
Mimo mozliwosci wykonywania instrukcji operujacych na rejestrach debug w Win95
(bo np. w NT powoduje to automatyczne zamkniecie programu) faktycznie nie zmienia
sie ich wartosc, dopiero manipulacja na poziomie ring0(sterowniki VxD, albo
callgate) zmienia ich stan.Na cholere wiec te informacje, hmm wyobraz sobie
taka sytuacje, jakis newbie cracker probuje zlamac twoje cudo crackme ale za
cholere mu nie wychodzi, postanawia wiec, ze gdy znajdzie sie w krytycznym
punkcie JNZ zmieni flage zerowa na przeciwna i bedzie cieszyl patrzaly "Thanks
for registering", jak mu w tym troche przeszkodzic?

	test	eax,80000000h		; instrukcja ustawia nam flage zerowa
	mov	dr1,eax			; wykonanie mov drX,XXX powoduje wykonanie
					; instrukcji jne ale bez naszej ingerencji,
					; tak jakgdybys nacisnal 2 razy F10
	jne	poprawny_serial		; w wyniku nie mamy wplywu na zmiane flag gdy
					; znajdziemy sie pod jne poprawny_serial i jesli
					; nastapi skok to bezposrednio po mov dr1,eax
					; wyladujemy w lokacji poprawny_serial(w tym wypadku!)
	mov	eax,ecx


AAD	- Ascii Adjust for Division

Dzialanie tej instrukcji mozna przedstawic nastepujaco

	AL = (10 * AH) + AL		; mnozy AH razy 10 dodaje do wyniku AL
					; i ostatecznie zapisuje wartosc w AL
	AH = 0				; rejestr AH zostaje wyzerowany


AAM	- Ascii Adjust for Multiplication

	AH = AL / 10			; w AH znajdzie sie czesc calkowita z
					; dzielenia AL / 10
	AL = AL % 10			; w rejestrze AL znajdzie sie reszta z
					; dzielenia AL / 10

Przyklad:

	mov	al,0FCh			; 0FCh=252d
	aam

spowoduje, ze w AH znajdzie sie 19h=25d a w AL znajdzie sie 2 czyli reszta z
dzielenia 252/10.


SAL	- Shift Arithmetic Left

W ramach cwiczen zobaczcie jak kompiluje sie ten fragment kodu:

.386p
.model flat

.code
start:
	int	3

	mov	eax,1			; 1
	sal	eax,2			; mnozenie razy kolejne potegi 2 ki w tym
					; przypadku eax*(2^2) czyli eax bedzie=4
	ret
end start

Pod hiew widzimy:

.00401000: CC		int 3
.00401001: B801000000	mov	eax,000000001
.00401006: C1E002	shl	eax,002 ;""

SAL i SHL zasadniczo sa to te same instrukcje ale tylko zasadniczo,gdyz podczas
jakichs tam prob natknalem sie na fajna wlasciwosc instrukcji skladajacej sie z
bajtow 0C1h,0F0h:

.386p
.model flat

_sal	macro	byte
	db	0C1h,0F0h,byte		; prawdziwa instrukcja sal eax,byte, undoc.
endm
.code
start:
	int	3
	mov	eax,1
	_sal	2
	ret
end start

W SI bajty 0C1h,0F0h pokazane sa jako instrukcja INVALID ale po F10 w rejestrze
eax otrzymamy takie same wyniki jak w przypadku pierwszego programu gdzie jest
zastosowana "czysta" instrukcja shl.Pod hiew znajdziemy:

.00401000: CC		int 3
.00401001: B801000000	mov         eax,000000001 ;"   "
.00401006: C1F002	???         eax,002 ;""

Heh czyli jest ql, autor hiew-a napisal mi, ze to jest nieudokumentowany opcode
i nie bedzie zamieszczal poprawek aby poprawnie wyswietlac instrukcje sal(nie
wiem jak sie to ma do najnowszych wersji hiew).Pod W32dsm otrzymalem rowniez
ciekawe wyniki:

:00401000 CC		int 03
:00401001 B801000000	mov eax, 00000001
:00401006 C1		BYTE 0d0h
:00401007 F0		lock
:00401008 0200		add al, byte ptr [eax]

Jedynie IDA potrafila poprawnie rozpoznac SAL:

00401000 start:				; Trap to Debugger
00401000		int	3
00401001		mov	eax, 1
00401006		sal	eax, 2


UD2	- Undefined Opcode

Jesli chcemy aby nasz prog specjalnie wywolal wyjatek, mozemy skorzystac z
nieudokumentowanych instrukcji ud2, ktorych jedynym zadaniem jest wywolywanie
wyjatkow, ud2 wystepuje w 2 formach:

ud2_1	macro
	db 0Fh,0Bh			; pierwsza forma, pod hiew jest pokazana
endm					; jako ud2

ud2_2	macro
	db 0Fh,0B9h			; to takze ud2 ale juz np. hiew nie
endm					; pokazuje ud2 tylko ???

Tak jak w przypadku sal mozecie miec problemy z kompilowaniem kodu zawierajacego
bezposrednio instrukcje ud2, jesli kompilator wywala blad nieznany opcode
polecam skorzystac z powyzszych makr.

Przy okazji nietypowych instrukcji warto wspomniec cos o instrukcji push i jej
2 bajtowej formie.Kompilator zapisze skrocona forme push-a jesli wartosc podana
push-owi bedzie miescic sie w granicach 0-127, jesli przekroczymy ta wartosc
na stosie zostanie zapisana wartosc np. 0FFFFFF80h(-80h) ale dlaczego nie
00000080h?Heh liczby z zakresu 128-255 traktowane sa jako liczby ujemne, jesli
uzyjemny skroconej formy push-a dla takiej liczby,na pozostale miejsca DWORDa
zostanie skopiowany znak bajtu(1 binarna oznacza minus,0 plus)czyli w wypadku
wartosci 80h(128dec) bit 1 i tak zostanie zapisany dword na stosie jako
rozszerzony o znak:

pushb	macro byteval
	db 06Ah,byteval			; niezaleznie od wartosci wymusza
endm					; skrocona forme push-a

	pushb	080h			; zapamietaj na stos 128
	pop	eax			;

Po wykonaniu tych instrukcji w eax znajdzie sie wartosc -80h czyli 0FFFFFF80h.
Ta prosta sztuczke mozna wykorzystac np. przy szyfrowaniu:

crypt	proc near
	pushb	085h			; w ebx bedzie
	pop	ebx			; 0FFFFFF85h

	xor	bl,bh			; 85h xor 0FFh

	mov	edi,esi			; zapisz do tego samego bufora
crypt_loop:
	lodsb				; 1 bajt do zaszyfrowania
	xor	al,bl			; xor (0FFh xor 85h)
	stosd
	loop	crypt_loop

	ret
crypt	endp

Z pozoru mogloby sie wydawac ogladajac suchy deadlisting,ze kolejne bajty bufora
xorowane sa przez wartosc 85h, no bo po wykonaiu pop w ebx powinna znalezc sie
wartosc 85h ale rzeczywiscie kolejne bajty sa xorowane przez 85h xor 0FFh = 7Ah.

c) skoki warunkowe
~~~~~~~~~~~~~~~~~~
Czasami natrafie na jakis program gdzie pojedynczy skok warunkowy decyduje o
calym zachowaniu programu, wiecie registered/unregistered, usuniecie takiego
zabezpieczenia to czasami kwestia 2 nop-ow, ale gdybym mial szukac odpowiednich
kombinacji skokow warunkowych gdy jest ich np. 30 to sadze, ze zajelo by mi to
wiecej niz 5 sekund w hiew.


d) tricki antydebugger
~~~~~~~~~~~~~~~~~~~~~~
Przy okazji pisania exeprotectorow zaczalem troche sceptycznie podchodzic do
wszelkiej masci trickow antydebug, jak sie przekonalem caly wysilek jaki
wlozylem przy tworzeniu trickow antydebug w moim 2 exepakerze poszedl sie
(delikatnie mowiac)jebac, dlaczego spytacie, heh testowalem spakowane pliki na
jednym systemie operacyjnym Win95 i wszystko bylo ql, dalem spakowane pliki
Dulkowi^CookieCrk,ktory siedzial pod NT i okazalo sie, ze zaden zabezpieczony
exek nie chcial sie uruchamiac, zainstalowalem WinNT i rzeczywiscie za cholere
zabezpieczone pliki ani rusz.Kilka ciezkich nocy z SI wykazalo, ze NT nie pozwala
na najlepsze tricki antydebug, pierwsze pytanie jakie sobie postawilem to czemu
nie moge tego zastosowac?Odpowiedz byla jasna, WinNT stawia na bezpieczenstwo i
takie tricki jak modyfikacja GDT,IDT(ring3-->ring0), int 68h, uzycie rejestrow
debug(dr1,dr2...) i jeszcze troche mniej popularne nie maja prawa byc wykonywane.
Jedyna forma obrony pozostaja metody bazujace na wykrywaniu SI poprzez meltice
\\.\NTICE, szukanie w rejestrze windows kluczy stworzonych przez program
instalacyjny SI, tricki zwiazane z int 3 i magicznymi wartosciami, skanowanie
autoexeka, szukanie po katalogach windy plikow SI, niestety te metody sa malo
skuteczne gdyz wiekszosc ludzi ma juz odpowiednio zmodyfikowanego SI czyz nie?

Meltice w innym wydaniu
~~~~~~~~~~~~~~~~~~~~~~~
Przy okazji Asprotecta natknalem sie na dosyc ciekawy trick,minowicie stara
metoda CreateFileA "\\.\SIWDEBUG","\\.\SICE", przy spatchowanym SI, CreateFileA
zwraca -1 ale Asprotect wywoluje po tym GetLastError i jesli zwrocona wartosc
jest rozna od ERROR_FILE_NOT_FOUND (02h) to Asprotect konczy dzialanie exe-ka,
u mnie po CreateFileA z "\\.\SICE" eax = -1, GetLastError zwraca w rejestrze
eax=ERROR_FILE_NOT_FOUND ale juz GetLastError po CreateFileA z "\\.\SIWDEBUG"
zwraca w eax wartosc rozna od ERROR_FILE_NOT_FOUND :)

	sub	eax,eax
	push	eax
	push	80h			; FILE_ATTRIBUTE_NORMAL
	push	3h			; OPEN_EXISTING
	push	eax
	push	eax
	push	80000000h		; GENERIC_READ
	push	offset szSIWDEBUG	; '\\.\SIWDEBUG',0
	call	CreateFileA		; otworz plik

	call	GetLastError		; ostatni blad
	sub	al,2			; czy to ERROR_FILE_NOT_FOUND
	jne	@debugger_detected	; jesli nie to wykryto debugger

Ta metoda nie jest do konca sprawdzona, ale sadze, ze warto ja przetestowac
chocby, ze wzgledu na to, ze nie jest ona dosc czesto stosowana.Ptasiek(elo :)
sugerowal, ze zamiast wywolywac GetLastError mozna wyripowac z KERNEL32.DLL kod
tej procki:

_GetLastError	proc near
db	83h,3Dh,78h,0C2h,0FBh,0BFh,00h	; cmp	dword ptr[BFFBC278h], 0
db	74h,10h				; je	GetLastError_01
	
db	0A1h,78h,0C2h,0FBh,0BFh		; mov	eax,dword ptr[BFFBC278h]
db	8Bh,00h				; mov	eax,[eax]
db	85h,0C0h			; test	eax,eax
db	74h,05h				; je	GetLastError_01

db	8Bh,40h,70h			; mov	eax,dword ptr[eax+70h]
db	0EBh,05h			; jmp	GetLastError_02

					; GetLastError_01:
db	0A1h,98h,0C2h,0FBh,0BFh		; mov	eax,dword ptr[BFFBC298h]

					; GetLastError_02:
db	0C3h				; ret
_GetLastError	endp

Sprawdzalem virtualne adresy zastosowane w tej procedurze (w module KERNEL32) i
kod tej procki jest taki sam w Win95 i 98, z NT nie sprawdzalem.

Debug context trick
~~~~~~~~~~~~~~~~~~~
Dosyc dobrym sposobem na wykrycie czy program jest uruchomiony w trybie debug
(api debug) jest sprawdzanie wartosci jaka siedzi pod fs:[20h].I znowu trzeba
wspomniec o tym, ze metoda ta ogranicza sie jedynie do systemow Win9x gdzie
normalnie po uruchomieniu programu pod adresem fs:[20h] jest wartosc DWORD 0,
uruchomienie programu w trybie debug spowoduje, ze pod fs:[20h] bedzie zapisana
wartosc <>0.Pod NT niezaleznie czy program jest uruchomiony w trybie normalnym
czy debug pod fs:[20h] jest zawsze wartosc <>0 :

					; przykladowe wartosci:
	mov	eax,fs:[30h]		; WinNT fs:[00000030h] = 7FFDF000h
					; Win9x fs:[00000030h] = 8159D0F4h
	test	eax,eax			; jesli jestesmy w WinNT pomin ta metode
	jns	WinNT

	mov	ax,ds			; dodatkowy sposob pozwalajacy wykryc
	cmp	ax,137h			; czy jestesmy w WinNT
	jb	WinNT
					; przykladowe wartosci(tryb normalny):
	mov	ecx,fs:[20h]		; WinNT fs:[00000020h] = 0000004Ah
					; Win9x fs:[00000020h] = 00000000h
					; tryb api debug(NW Debugger):
					; WinNT fs:[00000020h] = 0000005Fh
					; Win9x fs:[00000020h] = 82D64028h

	jecxz	WinNT			; jesli 0 to znaczy, ze program nie jest
					; uruchomiony w trybie api debug

	ud2				; instrukcja ud2 jest stworzona po to zeby 
					; wywolywac wyjatki

WinNT:					; musimy obejsc sie bez tej metody
	...

Callgate i blokowanie SI
~~~~~~~~~~~~~~~~~~~~~~~~
Jak juz wspominalem, gdy ustawiamy w SI bpx-a, w miejsce bpx-a jest zapisywany
opcode 0CCh czyli int 3, gdy SI napotka bpx-a, przywraca w miejsce bajtu 0CCh
oryginalny bajt kodu, nastepnie zwraca nam kontrole.A co by sie stalo gdyby SI
nie przywrocil oryginalnego bajtu, program sie posypie(ale tylko wtedy gdy
bedzie postawiony breakpoint :).Jak mozna to zrobic, modyfikacja procedury
obslugujacej przerwanie 3:

	push	eax			; zapisuje Interrupt Descriptor Table,
	sidt	[esp-2]			; czyli tabele zawierajaca opisy obslugi
	pop	eax			; poszczegolnych przerwan.Kazda struktura
					; opisujaca poszczegolne przerwanie sklada
					; sie z 8 bajtow, kolejne struktury ulozone
					; sa jedna za druga

	lea	eax,[eax+3*8]		; ebx wskazuje na deskryptor opisujacy przerwanie 3
	cli				; clear interupt
	mov	edx,[eax+4]		; w HIGH word edx bedzie zapisana gateOffsetHigh
	mov	dx,[eax]		; w dx bedzie zapisana wartosc gateOffsetLow
					; edx bedzie wskazywalo na procedure obslugujaca
					; przerwanie 3

					; nadpisz bajt obslugi przerwania 3 go
	mov	byte ptr[edx],0CFh	; 0CFh = iretd czyli powrot z przerwania
					; ale bez przywrocenia oryginalnego bajtu kodu
					; w miejscu gdzie jest bpx :)

IDTGATE struc
	gateOffsetLow	dw ?		; low word offset-u procki obslugujacej przerwanie
	gateSelector	dw ?		; selektor(np. cs)
	gateFlags	dw ?		; flagi
	gateOffsetHigh	dw ?		; high word offset-u procki obslugujacej przerwanie
IDTGATE	ends
_IDTGATE		equ 8

Metoda ta daje to, ze postawienie jakiegokolwiek bpx-a spowoduje, ze program
sie posypie ze wzgledu na to, ze zamiast przywrocenia oryginalnego bajtu w
miejsce bpx-a nastapi powrot z procedury przerwania 3(iretd).Nieco podobna
metode widzialem w jakiejs starszej wersji Asprotecta tylko, ze tam zamiast
nadpisywania procedury obslugujacej przerwanie 3 opcodem iretd, pierwszy bajt
tej procki byl nadpisywany zwyklym ret(0C3h) co powodowalo blue screena gdy SI
napotkal na bpx-a, a i po wszystkim(wykonaniu krytycznego fragmentu kodu)
oryginalny bajt procedury obslugujacej przerwanie 3 byl przywracany na swoje
miejsce, mozna to rownie latwo zrealizowac:

	push	eax
	sidt	[esp-2]
	pop	eax
	lea	eax,[eax+3*8]
	cli
	mov	edx,[eax+4]
	mov	dx,[eax]
	mov	bl,byte ptr[edx]
	mov	byte ptr[edx],0CFh

	push	ebx
	push	edx
	...				; jakis krytyczny kod,postawienie tutaj
	...				; bpx-a i puszczenie proga F5 spowoduje,
					; ze sie posypie
	pop	edx
	push	ebx
	mov	byte ptr[edx],bl	; przywraca oryginalny bajt procki
					; obslugujacej int 3

Z metoda blokowania SoftIce'a wiaze sie bardzo ciekawa wlasciwosc, minowicie
chodzi o to, ze kod procedury obslugujacej przerwanie jest wykonywany w trybie
ring0,czyli takim gdzie mamy dostep do instrukcji obslugujacych porty,VxDCall-e
,operacje na rejestrach debug.Jest to chyba najwieksza luka w systemach Win9x,
luke ta wykorzystal slynny CIH m.in.do wymazania zawartosci CMOSu.Aby przeniesc
sie do ring0 wystarczy zmodyfikowac gateOffsetHigh i gateOffsetLow, ustawiajac
ich wartosci na virtualny adres naszej procedury, nastepnie wywolujemy przerwanie
w tym przypadku skorzystamy z instrukcji into, ktorej dzialanie opiera sie na
zasadzie,ze jezeli jest ustawiona Overflow Flag wywolywane jest przerwanie 4,
jesli OF nie jest ustawiona into dziala tak jak nop:

	push	ebx
	sidt	[esp-2]
	pop	ebx			; eax wskazuje na tabele IDT

	lea	ebx,[ebx+4*8]		; deskryptor przerwania 4

	cli
	fild	qword ptr[ebx]		; zaladuj caly deskryptor do rejestru
					; kooprocesora st0
	mov	eax,offset ring0	; adres procedury ring0
	mov	word ptr[ebx],ax	; gateOffsetLow
	shr	eax,16			;
	mov	word ptr[ebx+6],ax	; gateOffsetHigh

	or	eax,80000000h		;
	add	eax,80000000h		; ustawia Overflow Flag 
	into				; into lub int 4, to bez znaczenia bo
;	int	4			; wywoluja identyczny efekt

	fistp	qword ptr[ebx]		; przywroc oryginalny deskryptor

	callW	ExitProcess
ring0:
	sub	eax,eax			; przykladowo mozna "wyczyscic" bpm-y,
	mov	dr0,eax			; adresy sa zapisane w rejestrach debug
	mov	dr1,eax			;
	iretd				; powrot do trybu ring3

Popularnosc tych metod sprawila, ze narzedzia takie jak FrogSice uzbrojono w
opcje blokowania mozliwosci modyfikowania Interrupt Descriptor Table, ale
blokowanie pamieci ma te wade, ze podczas proby zapisu do obszaru chronionego
przed zapisem konczy sie najczesciej zwisem proga a bardzo czesto windy :).
Jelsi nie lubimy brutalnych metod mozna postawic SEHa przed kodem przenoszacym
nas do ring0 i jesli nastapi wyjatek znaczy, ze tabela IDT jest chroniona przed
zapisem i ladnie konczymy program ExitProcess.Opisana metoda przechodzenia do
ring0 ogranicza sie jedynie do systemow operacyjnych Win9x, ale ostatnio viri
writer Z0mbie zaprezentowal metode przechodzenia do ring0 w WinNT.Po szczegolowe
informacje odsylam was na jego strone http://z0mbie.cjb.net .Przypomniala mi sie
podobna metoda bazujaca na wywolywaniu wyjatku dzielenia przez 0.Opis przerwan
mowi:

INT 00 C - CPU-generated - DIVIDE ERROR
Desc:	generated if the divisor of a DIV or IDIV instruction is zero or the
	quotient overflows the result register; DX and AX will be unchanged.
Notes:	on an 8086/8088, the return address points to the following instruction
	on an 80286+, the return address points to the divide instruction
	an 8086/8088 will generate this interrupt if the result of a division
	is 80h (byte) or 8000h (word)

Wykorzystanie:

	sub	eax,eax
	cdq
	push	ebx
	sidt	[esp-2]			; zapisz IDT
	pop	ebx

	fild	qword ptr[ebx]		; zaladuj oryginalny deskryptor przerwania
					; 0 do st(0)
	call	ring0			; po call pod [esp] bedzie zapisany adres powrotu
	fistp	qword ptr[ebx]

	mov	eax,dr0
					; na procesorach 286+ adresem powrotu jest
					; instrukcja div(2 bajty) ktora wywolala
	add	dword ptr[esp],3	; przerwanie 0 aby nie zawiesic programu
					; nalezy zmienic wartosc zapisana na stosie
					; na poprawny adres powrotu
	iretd				; powrot do ring3
ring0:
	pop	word ptr[ebx]		; gateOffsetLow
	pop	word ptr[ebx+6]		; gateOffsetHigh
	div	eax			; 0/0 = int 0
	db	8Eh			; fake opcode
ring3:
	call	ExitProcess

Zamiast korygowac adres powrotu na stosie, mozna zwiekszyc wartosc rejestru eax
o 1 i wykonac iretd, powrocimy do instrukcji div eax ale z eax=1 wiec tym razem
nie nastapi blad.

Przy doborze przerwania ktorego adres procedury obslugujacej chcemy zmienic na
wlasny, nalezy dobrze wybrac przerwanie zeby nie okazalo sie, ze jest to jakies
przerwanie systemowe, bo najczeciej konczy sie to restartem.Po szczegolowe opisy
przerwan odsylam was do spisu Rafpha Brown'a.

Praktyczne korzysci przechodzenia do ring0 to mozliwosc manipulacji rejestrami
debug, mozliwosc odwolywania sie bezposrednio do portow,korzystanie z funkcji
VxDCall niedostepnych z poziomu WinApi(nie potrzeba zadnej tabeli importow ani
innego syfu),co wg. mnie jest najwieksza zaleta trybu ring0.Aby skorzystac z
VxDCall-i bez konieczniosci includowania setki .inc wystarczy zadeklarowac
nastepujace makra:

VxDCall	macro   Service
	db	0CDh,020h		; int 20h
	dd	Service			; numer uslugi
endm

VMMCall	macro   ServiceVMM
	db	0CDh,20h		; int 20h
	dw	ServiceVMM
	dw	0001h			; VMM
endm

Numery uslug najlepiej miec zapisane w jednym pliku w postaci equ:
...
_SHELL_ShellExecute			EQU 000170007H
_strupr					EQU 00001018FH
Unhook_VMM_Fault			EQU 00001011BH
_lstrlen				EQU 00001017CH
_PageReserve				EQU 00001011DH
...

High word oznacza nazwe VxDka z ktorego korzystamy, low word okresla numer
funkcji ktora wywolujemy.Niektore funkcje parametry przyjmuja poprzez stos tak
jak normalne funkcje WinApi, niektore poprzez rejestr(y), zeby miec pewnosc
najlepiej skorzystac z jakiegos VxD ApiHelp np. dolaczonego do pakietu VTools
Numegi sluzacego do tworzenia sterownikow VxD.

Korzystajac z VxDCall-i mozna np. ustawic paramtery dla danego obszaru pamieci
(gdzie glupie VirtualProtect nie dziala) np. mamy sekcje .code i ma ona normalnie
ustawione atrybuty read-only, wiec pozornie nie mozna do niej zapisywac ale mozna
z trybu ring0 zmienic atrybuty obszaru pamieci gdzie zostala zaladowana sekcja
.code tak aby mozliwa byla jej modyfikacja z poziomu ring3(bo z ring0 mozemy bez
problemu zmieniac co nam sie podoba):

	sub	eax,eax

	push	PC_WRITEABLE+PC_STATIC+PC_USER	; OR_MASK
	push	eax			; AND_MASK
	inc	eax
	push	eax			; ilosc stron
	push	401000h	SHR 12		; adres do strony(virtualny adres shr 12)
	VxDCall _PageModifyPermissions	; zmien wlasciwosci obszaru pamieci
	add	esp,4*4			; 

I tak pierwszy paramter okresla jakie parametry ustawiamy(OR), drugi parametr
okresla jakie parametry maja byc usuniete(AND), trzeci okresla rozmiar obszaru
pamieci(w stronach), czwarty wskazuje na adres strony(page) ktorej atrybuty
zmieniamy.Po wykonaniu VxDCall-a nalezy, poprawic wskaznik stosu gdyz funkcja
nie zdejmuje wczesniej zapamietanych parametrow(cdecl).Mozliwe jest takze
operowanie na plikach np. w celu zeskanowania autoexeka:

	VxDCall	Get_Config_Directory	; C:\...
	lea	esi,[lpAutoexec]	;
	mov	ax,word ptr[edx]	; kopiuj litere dysku
	mov	word ptr[esi],ax

	mov	eax, R0_OPENCREATFILE	; funkcja
	push	2
	pop	ebx			; tryb otwarcia pliku
	push	20h
	pop	ecx			; co zrobic jesli nie istnieje
	push	11h
	pop	edx
	VxDCall	IFSMgr_Ring0_FileIO	; wykonaj operacje(odpowiednik to int 21h
					; funkcja 6C00h)
	jc	@error			; jesli CF ustawiona to tak jak w dos-ie
					; oznacza blad
	xchg	eax,ebx			; uchwyt pliku do ebx

	mov	eax, R0_GETFILESIZE	; operacja pobrania rozmiaru pliku(uchwyt w ebx)
	VxDCall	IFSMgr_Ring0_FileIO	; rozmiar

	mov	[@file_size],eax	; zapamietaj rozmiar pliku

	mov	ecx,1000h		; rozmiar strony w bajtach
	add	eax,ecx			; dodaj rozmiar pliku
	sub	edx,edx

	div	ecx			; eax/ecx
	
	push	PAGEFIXED + PAGEZEROINIT; flagi
	sub	ecx,ecx
	push	ecx			; PhysAddr
	push	ecx			; maxPhys
	push	ecx			; minPhys
	push	ecx			; Align
	push	ecx			; handle of VM = 0 if PG_SYS
	push	PG_SYS			; allocate memory in system area
	push	eax			; liczba stron do alokacji
	VxDCall	_PageAllocate		; zaalokuj pamiec potrzebna do odczytania
					; calego pliku
	add	esp,8*4			; popraw stos

	test    eax, eax		; gdy eax==0 wtedy nastapil blad
	je	@error

	push	eax			; eax wskaznik do nowo zaalokowanej pamieci

	sub	edx,edx
	xchg	eax,esi			; esi wskazuje na bufor gdzie zostanie
					; odczytany plik
	mov	ecx,12345678		; kod musi miec atrybuty write
@file_size	equ dword ptr $-4
	mov     eax, R0_READFILE	; operacja czytaj plik
	VxDCall	IFSMgr_Ring0_FileIO	; czytaj plik do nowo zaalokowanej pamieci

	pop	esi			; offset gdzie zostal odczytany plik
	mov	edi,esi
					; upper case&encrypt
	push	eax			; rozmiar pliku w bajtach
	xchg	eax,ecx			; do ecx, zeby mozna bylo skorzystac z
					; loop
@crypt_autoexec:
	mov	al,[edi]
	cmp	al,'a'
	jb	@notlowcase
	cmp	al,'z'
	ja	@notlowcase
	sub	al,20h			; lowcase 2 upcase
@notlowcase:
;	xor	al,0FFh
;@autoexec_key	equ byte ptr $-1
	mov	[edi],al
	inc	edi
	loop	@crypt_autoexec
@alldone:
	pop	eax			; pop rozmiar pliku

; search strings
@scan_autoexec:

	cmp	dword ptr[esi],'ECIN'	; WI<NICE>
@autoexec_text1	equ dword ptr $-4
	je	@destruction

	cmp	dword ptr[esi],'EMUN'	; <NUME>GA
@autoexec_text2	equ dword ptr $-4
	je	@destruction

	inc	esi

	dec	eax
	jne	@scan_autoexec
@end_scan_autoexec:

	mov	eax, R0_CLOSEFILE	; ebx uchwyt pliku,eax operacja
	VxDCall	IFSMgr_Ring0_FileIO	; zamknij plik
@error:

Mozna rowniez skorzystac z funkcji obslugi rejestru windows, w celu wykrycia np. kluczy
utworzonych w rejestrze podczas instalacji SoftIce'a:

	mov	eax,offset hKey		; pod hKey bedzie zapisany uchwyt klucza
	push	eax			; zapamietaj na stosie
	mov	eax,offset lpSubKey1	; 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SoftICE',0
	push	eax			; zapamietaj adres
	push	HKEY_LOCAL_MACHINE	; root key
	VxDCall	_RegOpenKey		; otworz klucz
	add	esp,4*3			; popraw wskaznik stosu
	test	eax,eax			; jesli 0 znaczy, ze nie znaleziono
	jne	@skip_reg		; takiego klucza

	push	1234567			; uchwyt klucza(DWORD)
hKey	equ dword ptr $-4
	VxDCall	_RegCloseKey		; zamknij klucz
	pop	eax			; popraw wskaznik stosu
	jmp	@destruction		; jakiez to humanitarne,zamykanie uchwytu
					; a potem destrukcja hehe
@skip_reg:

Istnieja odrebne sposoby na wykrycie obecnosci SoftIce'a w systemie:

	mov	eax,7A5Fh		; 7A5Fh dla \\.\SIWVID VxD ID
	sub	edi,edi			; nazwa sterownika, gdy ID podane,
	VxDCall	Get_DDB			; pomija sie ja
	jecxz	@check_next_driver
	retf				; crash
@check_next_driver:
	mov	eax,202h		; 202h dla \\.\SICE
	sub	edi,edi
	VxDCall	Get_DDB
	jecxz	@no_debugger

Metoda ta polega na sprawdzeniu czy w danej chwili zainstalowane sa sterowniki o
zadanym ID w systemie.Pierwszy raz opis tej metody zobaczylem w pliku opisujacym
sztuczki antydebug w FrogSice, pozniej zobaczylem, ze byla ona opisana duzo
wczesniej przez viri writera w magazynie bodajze Xine albo DDT.Inna bardzo
wydajna i rzadko stosowana metoda jest skanowanie listy wszystkich zaladowanych
sterownikow VxD:

	VxDCall	VXDLDR_GetDeviceList
@scanvxd:
	mov	ebx,[eax+5]

	cmp	ebx, 0C0000000h
	jb	@continue

	lea	ecx, [ebx+0Ch]		; Name_0
	mov	edx,[ecx]		; pierwsze 4 bajty z nazwy sterownika
	not	edx			; not-uj

	cmp	edx,NOT 'GORF'		; czy to FROG - frogsice
	je	@hangup			; jelsi to zaba to zawies program

	cmp	edx,NOT 'ECIS'		; czy to SICE - main vxd
	je	@hangup			; jesli to SI zawies proga

	jmp	@continue
@hangup:
	jmp	@destruction
@continue:
	mov	eax, [eax]
	test	eax, eax
	jne	@scanvxd
@next0:

Co ciekawe metoda ta, oprocz mozliwosci znalezienia "upragnionego" sterownika na
liscie powoduje zawieszanie FrogSice bez wyraznych przyczyn :).Zapomnialem dodac
jedna rzecz, mianowicie po przejsciu w tryb ring0 zmienia sie wartosc rejestru
esp(reszta bez zmian), wartosci rejestrow segmentowych ss, cs, przed wykonaniem
jakichkolwiek operacji w ring0 preferowane jest zachownie oryginalnych rejestow
segmentowych:

ring0:					; juz jestesmy w ring0
	pushad
	push	ds es

	push	ss
	pop	ds
	push	ss
	pop	es

	call	ring0_stuff

	pop	es ds
	popad
	iretd

Bardzo uzyteczne okazuje sie podpinanie wlasnych dosc czesto wykorzystywanych
procedur pod jakies przerwanie:

instalacja_procki:
	push	ebx
	sidt	[esp-2]
	pop	ebx
	add	ebx,4*8			; deskryptor przerwania 4
	cli
	call	install
crypt:					; tu juz jestesmy w ring0
	cmp	byte ptr[esi],al	; zainicjalizuj obszar pamieci
crypt_loop:
	xor	byte ptr[esi],45h	; prosty xor
	inc	esi
	dec	edi
	jne	crypt_loop
	iretd
install:
	pop	word ptr[ebx]		; gateOffsetLow
	pop	word ptr[ebx+6]		; gateOffsetHigh

	mov	esi,offset lpBufor
	mov	ecx,lBufor
	int	4			; pod 4 podpielismy juz nasza procke ring0
	...
lpBufor	db	'ring0',0		; dane znajduja sie w tej samej sekcji co
lBufor	equ	$-lpBufor		; kod ktory wywoluje ring0,ale sekcja wcale
					; nie musi posiadac atrybutow write!

Powracajac do niekompatybilnosci z systemem NT, nie trzeba z miejsca rezygnowc z
umieszczania tych metod w kodzie naszego proga, mozna przeciez odwolac sie do
nich w zaleznosci od tego na jakim systemie jedziemy:

	push	30h
	pop	eax
	mov	eax,fs:[eax]
	test	eax,eax
	jns	skip_antidebugging	; pomin tricki anty wykonywane w ring0
					; poniewac prog zostal uruchomiony w NT
	call	ring0
skip_antidebugging:

Mozliwosc przechodzenia do ring0 w systemach win jest ich najwieksza luka ale
po chwili zastanowienia dochodzimy(przynajmniej ja tak mysle) do wniosku, ze
jest to prawdziwe blogoslawienstwo, pozwalajace wyrwac sie poza waskie granice
wyznaczone przez WinApi.

Goscie
~~~~~~
Kolejne 3 metody na wykrywanie SI sa autorstwa coxoc-a z CookieCrk, za co nalezy
mu sie big thnx :) , wykonywanie of coz w ring0:

ring0:
	db	0CDh,20h		; Get_Cur_VM_Handle
	dw	0001h,0001h

	push	ebx

	mov	eax,000Eh		; VM_RESUME
call_trw:
	db	0CDh,20h		; System_Control
	dw	0093h,0001h		; po wykonaniu VxDCall-a bajty 0CDh,20h i numer
					; uslugi zamieniaja sie na tzw.direct call-a czyli
					; call dword ptr[vadres] (0FFh,15h,DWORD vadres)
	mov	esi,dword ptr [call_trw+2]
	mov	esi,[esi]		; vadres System_Control
	cmp	byte ptr [esi],0E8h	; sprawdz pierwsze bajty procki czy to opcode
	jne	niee_trw		; relatywnego call-a(0E8h,DWORD)
	cmp	word ptr [esi+5],025FFh	; bajty absolutnego jmp-a(FF,25h,DWORD vadres)
	jne	niee_trw

	inc	jest_trw
niee_trw:

	pop	ebx
	mov	edx, 400h
call_sice:
	db	0CDh,20h		; Disable_Local_Trapping
	dw	009Ah,0001h

	mov	esi,dword ptr [call_sice+2]
					; offset DWORDa wskazujacego na adres
					; Disable_Local_Trapping
	mov	esi,[esi]		; adres Disable_Local_Trapping
	cmp	word ptr [esi],015FFh	; czy pierwsze bajty procki to czesc
					; instruckji call dword[..]?
	jne	niee_sice		; jesli nie pomin
	cmp	word ptr [esi+6],05751h
	jne	niee_sice

	inc	jest_sice

niee_sice:
	iretd

	jest_trw  db 0
	jest_sice db 0

ring0:
	push	41h			; numer przerwania
	pop	eax
	db	0CDh,20h		; Get_PM_Int_Vector
	dw	0044h,0001h		; zwraca adres procedury obslugujacej
					; przerwanie
	cmp	edx,8
	jne	sice_not_detected
	mov	jest_sice,1

sice_not_detected:
	iretd

Przetestowalem powyzsze metody i okazuje sie, ze sa one w pelni funkcjonalne i
dosyc swieze, co zwiastuje im swietlana przyszlosc :)


Dostalem takze paczke anty napisana przez WitkaG, mimo ze metody nie korzystaja
z technik ring0 sa dosyc skuteczne, ok jedziemy, pierwsza metoda oto co pisze o
niej sam autor:

; Niesamowicie prymitywna metoda wykrywajaca debugger, a wlasciwie traceowanie
; kodu pod debuggerem - wymaga procesora klasy Pentium, wszystko co sie tutaj
; dzieje to dwukrotne wywolanie instrukcji RDTSC zwracajacej ilosc wykonanych
; cykli i sprawdzenie czy roznica miesci sie w zakresie
;
; by WiteG"

.386p
.model flat,stdcall

extrn	MessageBoxA:proc

RDTSC	macro				; TASM nie zna RDTSC wiec trzeba go nauczyc ;)
	db 0Fh, 31h
endm

.data
	lpszCaption	db "Hmm... ktos mnie trace'uje",0
	lpszText	db 'Chyba widzialem koteczka !',0
.code
start:
; teoretycznie ujmujac najpierw powinno sie wyzerowac bit 2 rejestru CR4 (jesli nie jest
; wyzerowany), zeby umozliwic wykonanie RDTSC w ring3, ale u mnie chodzi bez problemu
; wiec sobie darowalem :P

	nop				; instrukcja wstawiona ze wzgledow estetycznych :)
	rdtsc				; pobierz 64-bitowa wartosc licznika do pary
					; rejestrow edx:eax
	push	eax			; zachowaj na stosie wartosc eax
	push	edx			; zachowaj na stosie wartosc edx
	rdtsc				; jeszcze raz pobierz
	pop	ecx			; zdejmij ze stosu zachowana wartosc edx i wstaw do ecx
	pop	ebx			; zdejmij ze stosu zachowana wartosc eax i wstaw do ebx
	xor	edx,ecx			; sprawdz czy wyzsze 32 bity zgodne - to uproszczenie,
					; ale ma niewielkie znaczenie... z reszta to tylko przyklad
	jne	debugger_detected

	sub	eax,ebx			; sprawdz roznice miedzy nizszymi 32 bitami
	cmp	eax,10000		; czy miesci sie w zakresie... tutaj ten zakres
					; jest zajebiscie szeroki, ale debuggery potrzebuja
					; jeszcze wiecej :P, tzn. te przeze mnie testowane
					; sice, trw, cool debugger
	jb	koniec
debugger_detected:
	push	0
	push	offset lpszCaption
	push	offset lpszText
	push	0
	call	MessageBoxA
koniec:
	ret
end start

; przykladowa metoda wykrywajaca TRW 2000 przy pomocy IDT
; przetestowane na wersjach 1.11, 1.13, 1.15, 1.20, 1.22
;
; by WiteG
.386p
.model flat,stdcall

extrn	ExitProcess	:proc
extrn	MessageBoxA	:proc

.data
	lpszCaption	db 'Info',0
	lpszText	db 'TRW 2000 wykryty!',0
.code
start:
	push	eax
	sidt 	[esp-2]			; pobierz IDT
	pop	eax			;

	add	eax, 41h*8		; przerwanie 41h
	mov	bx, word ptr [eax+6]	; gateOffsetHigh
	rol	ebx,16
	mov	bx, word ptr [eax]	; gateOffsetLow

	add	eax, 10h*8		; przerwanie 51h(41h+10h=51h)
	mov	cx, word ptr [eax+6]
	rol	ecx,16
	mov	cx, word ptr [eax]

	sub	ecx,ebx			; oblicz roznice miedzy adresami
					; procedur obslugujacych przerwanie 51h
					; i 41h, i jesli roznica bedzie rowna
	xor	ecx,128			; 128 to znaczy, ze obecny jest debugger
	jne	koniec
debugger_detected:
	push	0
	push	offset lpszCaption
	push	offset lpszText
	push	0
	call	MessageBoxA

koniec:
	push	0
	call	ExitProcess
end start


; Metoda wykrywania wszelakiego rodzaju loaderow (loader NuMegi, TRW, 'zwykle' loadery...).
; Oparta jest calkowicie na strukturach TIB i PROCESS_DATABASE, dodatkowo na ENVIRONMENT_DATABASE
;
; Najpierw z TIB pobieramy wskaznik do PROCESS_DATABASE, nastepnie odczytujemy wskaznik do
; matczynego PROCESS_DATABASE. Tym matczynym procesem powinien byc Explorer.exe tudziez inny
; shell. Ten zas nie ma ju procesw matczynych.
; Jesli po drodze jest jakis loader , to pobierany jest jego PROCESS_DATABASE, a ten ma proces
; matczyny - Explorer.exe
;
; Opis wszystkich struktur w Pietrku...
;
; by WiteG
.386p
.model flat,stdcall

extrn	ExitProcess	:proc
extrn	MessageBoxA	:proc

.data
	lpszCaption	db 'Wykryty loader !!!',0
.code
start:
	mov	eax, fs:[30h]		; eax ptr do PROCESS_DATABASE naszego programu
	mov	eax, [eax+48h]		; eax ptr do PARENT_PROCESS_DATABASE, czyli procesu
					; matczynego dla naszego
	mov	esi, eax		; zachowaj eax, przyda sie jezeli bedzie to loader
	mov	eax, [eax+48h]		; eax ptr do kolejnego PARENT_PROCESS_DATABASE
	mov	eax, [eax+40h]		; eax ptr do ENVIRONMENT_DATABASE
	mov	eax, [eax+8]
	mov	edx, [eax]		; eax ptr do parent_command_line
	test	edx, edx		; jezeli edx = 0 to nie ma loadera
	jz	koniec			; jest loader, wiec dowiedzmy sie jaki

	mov	eax, [esi+40h]
	mov	eax, [eax+8]

	push	0
	push	offset caption
	push	eax
	push	0
	call	MessageBoxA

koniec:
	push	0
	call	ExitProcess
end start

Ta metoda mimo, ze skuteczna ma pewna wade, chodzi o to, ze jak uruchomimy nasz
program spod np.windows commandera to rowniez pojawi sie 'Wykryto loader'...
skutecznosc tego polega na tym, ze zakladamy iz kazdy program uruchamiamy spod
'czystego' shella.


Czyszczenie bpx-ow
~~~~~~~~~~~~~~~~~~
Siedzac w IDA nad deadlistingiem winice.exe doszedlem do ciekawego wniosku, jak
latwo mozna "wyczyscic" bpx-y, ale wcale nie mam na mysli bc * :).SoftIce posiada
tabele skladajaca sie 256 struktur opisujacych kolejne bpx-y, tabela rozpoczyna
sie i konczy sygnatura "TDJF", kolejne struktury opisujace bpx-y maja rozmiar
387 bajtow, i znajduja sie jedna za druga.Wszystkie struktury posiadaja 8 bajtowa
sygnature "TDJFTDJF" po ktorej 387-8(rozmiar sygnatury to 8 bajtow) bajtow opisuje
bpx-a, adres virtualny, segment, w jakim procesie zostal zalozony bpx, nie chcialo
mi sie dokladnie analizowac do czego sluzy kazdy bajt struktury ale maly test
pokazal, ze wyczyszczenie jej zerami powoduje, ze dany bpx znika z listy :).
Nalezy wspomniec, ze opis pierwszego bpx-a znajduje sie na poczatku tabeli zaraz
za sygnatura "TDJF".Najwiekszym problemem okazuje sie znalezienie tej tabeli w
pamieci, gdyz sygnature "TDJF" mozna kilkakrotnie znalezc w obszarze pamieci
gdzie zostal zaladowany SI.

clear_bpx	proc near
	pushad				; zapamietaj stan rejestrow
IFDEF	RING0
	mov	eax,202h		; 202h dla \\.\SICE
	sub	edi,edi
	db	0CDh,20h		; Get_DDB
	dd	000010146H		; w ecx bedzie offset device descriptor block
					; dla SICE, albo 0 gdy SICE nie zaladowany lub
	test	ecx,ecx			; spatchowany, jesli 0 wybrobuj inna metode
	jne	got_base		;
ENDIF
	pushfd				; zapamietaj stan rejestrow flagowych
	cli				; clear interupt
	cld				; clear direction flag
	push	eax			; miejsce w pamieci dla
	push	eax			; GDT
	sgdt	[esp+2]			; zapisz GDT
	pop	ecx			; pobierz GDT limit
	shr	ecx,16+3		; liczba deskryptorow
	pop	esi			; adres startowy GDT

scan:
	lodsd				; czytaj 1 DWORDa deskryptora
	mov	edi,eax			; do edi
	lodsd				; 2 DWORD deskryptora
	cmp	ah,0FFh			; czy to specjalny code segment Winice?
	loopnz	scan			; jesli nie szukaj dalej

	popfd				; flagi

	jecxz	clear_end		; jesli 0 znaczy blad

	rol	eax,8			; oblicz adres startowy segmentu kodu
	xchg	ah,al
	shrd	edi,eax,16

	cmp	[edi],edi
	jnz	clear_end

	mov	ecx,edi
got_base:
	mov	eax,'FJDT'		; sygnatura tabeli
search_next:
	inc	ecx
	cmp	dword ptr[ecx],eax	; szukaj sygnatury
	jne	search_next

	cmp	dword ptr[ecx+383],eax	; czy za 383 bajty znajduje sie kolejna struktura?
	jne	search_next		; jesli nie szukaj tabeli

	mov	edi,ecx			; do edi offset tabeli

	mov	edx,387-8		; rozmiar struktury opisujacej bpx-a

	sub	eax,eax			; struktury wypelnij zerami
	mov	ecx,eax			; licznik okreslajacy
	mov	cl,0FFh			; ilosc struktur

	scasd				; pierwsza struktura jest o 4 bajty
					; mniejsza niz kolejnych 255
clear_it:
	push	ecx			; zapamietaj licznik

	mov	ebx,dword ptr[edi+3]	; VA kolejnego bpx-a
	mov	ah,byte ptr[edi+60]	; oryginalny bajt

	mov	ecx,edx
	rep	stosb			; wyczysc strukture
	scasd				; pomin 8 bajtowa sygnature
	scasd				; 'TDJFTDJF'

	cmp	ebx,401000h		; jelsi VA ma mala wartosc bpx prawdopodobnie
	jb	skip_fixbyte		; zalozony zostal w innym procesie

	mov	byte ptr[ebx],ah	; przywroc oryginalny bajt w miejsce 0CCh

skip_fixbyte:

	pop	ecx			; czysc 255 struktur zawierajacych opisy
	loop	clear_it		; bpx-ow(watpie czy ktos uzywa tyle bpx-ow)
clear_end:
	popad
	ret
clear_bpx	endp

Po wyczyszczeniu struktury zawierajacej opis bpx-a, na ekranie SI w miejscu
gdzie byl postawiony bpx pojawia sie 0CCh, ten bajt caly czas tam byl ale byl
obslugiwany przez SI, teraz spowoduje on wyjatek, procka przywraca oryginalne
bajty w miejsce zalozonych bpx-ow, ze wzgledow bezpieczenstwa pomijane sa bpx-y
ktorych adresy virtualne < 401000h (entry point naszego programu), dzieki temu
przywracane sa takze bajty procek WinApi a nie tylko naszego procesu :).Tu
wystepuje jeden maly a zasadniczo duzy problem, jesli bpx postawimy w innym
procesie ktory jest caly czas aktywny i bedziemy probowali czyscic bpx-y swojego
procesu, procka napotkac moze adres virtualny np. 401009h i to bedzie VA bpx-a
ale w innym procesie w wyniku czego nadpisze jakims bajtem nasz kod, ewentualnie
VA bedzie znacznie wiekszy niz dostepny w naszym procesie i nastapi wyjatek access
violation :(.Pewnym rozwiazaniem tutaj jest porownywanie nazwy swojego procesu
z nazwa zapisana w struktury opisujace kolejne bpx-y.Nazwa procesu zapisana w
strukturach opsiujacych bpx-y to nazwa exe-ka bez rozszerzenia ale gdy np. nasz
exe-k w nazwie ma spacje to SoftIce(v4.05) nie zapisuje nazwy procesu w ktorym
postawilismy bpx-a i nie ma mozliwosci usuniecia takiego bpx-a ani wyczajenia
w jakim procesie zostal on postawiony.Przesuniecia wzgledem poczatku struktury
opisujacej kazdego bpx-a, wskazujace na wazniejsze dane:

signature	equ 0			; (QWORD) sygnatura 'TDJFTDJF'
enabled		equ 9			; (BYTE) 0 oznacza, ze bpx jest wylaczony
					; 1 wlaczony, mozna przykladowo wylaczyc
					; wszystkie bpx-y zamiast je kasowac :)
bpxVA		equ 11			; (DWORD) VA gdzie zostal postawiony bpx
bpxCS		equ 24			; (WORD) CodeSegment VA bpx-a
origb		equ 68			; (BYTE) oryginalny bajt
processl	equ 123			; (BYTE) rozmiar nazwy procesu w ktorym
					; zostal postawiony bpx(bez 00h na koncu)
process		equ 124			; (?) nazwa procesu w ktorym zostal
					; postawiony bpx

Nie wolno wyzerowac bajtow sygnatury gdyz SI zglosi blad 'Breakpoint table has
been corrupted' i system zostanie zablokowany.Dobra alternatywa jest wylaczanie
wszystkich bpx-ow, wystarczy zmienic procke zamazujaca cala strukture na:

	mov	cl,0FFh			; ilosc struktur

	scasd				; pierwsza struktura jest o 4 bajty
					; mniejsza niz kolejnych 255
	scasb				; 2 bajt po sygnaturze oznacza status
	sub	eax,eax			; bpx-a
clear_it:
	stosb				; bd :)
	lea	edi,[edx+edi+8-1]	; edi na kolejny bajt oznaczajacy status
					; breakpointa
	loop	clear_it		; wylacz wszytkie bpx-y

Procke testowalem pod SI 3.24 i SI 4.05, wszystko bylo ok.Procke mozna wywolywac
z ring0 oraz z ring3, jesli korzystamy z ring3 nalezy odrzucic metode wykrywania
bazy sice poprzez Get_DDB i korzystac z wykrywania tylko poprzez GDT, jesli juz
bedziemy uzywac tej procki warto sie upewnic, ze SoftIce jest aktywny :)

e) wykrywanie niektorych toolsow
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jak wiadomo czlowiek samym chlebem nie zyje, podobnie cracker stosuje wiele
toolsow aby czesciowo ulatwic sobie zycie.Do tych uzytecznych zaliczam Filemona,
Regmona,ProcDumpa IDA,W32dsm,TRW i pare mniej waznych.Aby np. zamknac okno
ktoregos z wymienionych programow nalezy najpierw znalezc jego okno(po tekscie
na belce):

	mov	eax,offset szNumega	; np. 'NuMega SoftICE Symbol Loader',0
					; text musi byc identyczny z tym jaki
	sub	edx,edx			; pojawia sie na belce okna(case sensitive)
	push	eax			; tytul okna
	push	edx			; nazwa klasy(nie trzeba podawac)
	push	edx
	push	edx			; uchwyt okna macierzystego
	call	FindWindowExA		; jesli znajdzie takie okno to w eax
					; zwroci jego uchwyt
	test	eax,eax			; ID okna zwracany w eax
	je	no_window		; jezeli eax=0 to exit

	push	offset ProcID		; DWORD gdzie bedzie zapisany id procesu
	push	eax			; uchwyt okna nalezacego do danego procesu
	call	GetWindowThreadProcessId; pobieranie ID procesu

	sub	esi,esi
	test	eax,eax
	je	no_window

	push	[ProcID]		; proces ID
	push	esi	
	push	1			; flagi otwarcia PROCESS_TERMINATE
	call	OpenProcess

	push	-1
	push	eax
	call	TerminateProcess	; zamknij program

no_window:

Innym znacznie krotszym sposobem na zamkniecie okna, jest wyslanie wiadomosci
WM_QUIT czyli symulacja PostQuitMessage:

	sub	edx,edx
	push	edx			; lParam
	push	edx			; wParam
	push	WM_QUIT			; wiadomosc
	push	eax			; uchwyt okna uzyskany w eax po
					; FindWindowExA
	call	PostMessageA		; zamknij okno

Podobnym sposobem jest wysylanie wiadomosci WM_ENDSESSION ktora informuje o
zakonczeniu pracy systemu windows:

	sub	edx,edx
	push	edx			; lParam
	push	edx			; wParam
	push	WM_ENDSESSION		; wiadomosc
	push	eax			; uchwyt okna uzyskany w eax po
					; FindWindowExA
	call	PostMessageA		; zamknij okno

Ta metoda wykrywania jest juz archaiczna, aby zabezpieczyc swoj program przed
wykryciem ta metoda wystarczy zmienic w exe-ku text jaki pojawia sie w glownym
oknie i FindWindowEx juz nie znajdzie tego okna.Bardziej skuteczna metoda jest
skanowanie sciezek dostepu do wszystkich aktywnych procesow w poszukiwaniu
"podejrzanych" ciagow, korzystajac z funkcji biblioteki toolhelp dostepnej od
wersji Win95 OSR2:

snapshot:
	push	64
	call	Sleep

	push	0
	push	TH32CS_SNAPPROCESS	; flagi
	call	CreateToolhelp32Snapshot
	
	mov	[hSnap],eax		; uchwyt shanpshota

	push	sizeof PROCESSENTRY32	; za kazdym razem trzeba ustawiac rozmiar
	pop	lpProcessInfo.dwSize	; struktury

	push	offset lpProcessInfo	; struktura gdzie zostana zapisane info
					; o procesie
	push	eax			; uchwyt snapshota
	call	Process32First
	test	eax,eax
	je	wait_snapshot

	jmp	check_process

next_process:
	push	offset lpProcessInfo	; info o procesie
	push	[hSnap]			; uchwyt snapshoota
	call	Process32Next
	test	eax,eax
	je	wait_snapshot
check_process:				; edi na bufor gdzie znajduja sie kolejne
					; sciezki do uruchomionych exe-kow
	mov	edi,offset lpProcessInfo.szExeFile
	mov	esi,offset badexes	; nazwy "zlych" exekow
	mov	ecx,lbadexes		; liczba podejrzanych nazw
is_badexe:
	push	ecx			; zapamietaj
	
	lodsd				; 4 bajtowa czesc nazwy exeka

	sub	ecx,ecx
	dec	ecx
find_len:
	inc	ecx
	cmp	byte ptr[edi+ecx],0	; szukaj 0 w lpProcessInfo.szExeFile 
	jne	find_len

	sub	ebx,ebx

compare_process_name:
	cmp	dword ptr[edi+ebx],eax	; czy czesc nazwy "podejrzanego" exeka
					; jest czescia sciezki badz nazwy kolejnego
	jne	next_char		; aktywnego procesu?

	push	lpProcessInfo.th32ProcessID
	push	0
	push	1			; flagi otwarcia PROCESS_TERMINATE
	call	OpenProcess

	push	-1
	push	eax			; w eax uchwyt procesu
	call	TerminateProcess	; zakoncz proces

	add	esp,4			; zamiast pop ecx

	jmp	next_process

next_char:
	inc	ebx			; zwieksz index nazwy skanowanej sciezki do exeka
	dec	ecx			; zmniejsz licznik(liczba znakow)
	jns	compare_process_name

	pop	ecx			; liczba podejrzanych nazw
	dec	ecx			; zmniejsz o 1
	jne	is_badexe		; jesli nie wszystkie to powtarzaj az znajdzie

	jmp	next_process		; jesli nie sprawdz sciezke do nastepnego
					; aktywnego programu
wait_snapshot:
	push	64
	call	Sleep

	jmp	snapshot		; nieskonczona petla

badexes:
	exe01	db 'DUMP'		; ProcDump
	exe02	db 'GMON'		; Regmon
	exe03	db 'ILEM'		; Filemon
	exe04	db 'W32D'		; W32dsm
	exe05	db 'IDAG'		; IdaG
	exe06	db 'ADER'		; Loader
	exe07	db 'FROG'		; Frogsice
	exe08	db 'TICE'		; SICE
	exe09	db 'CALC'		; Calculator
	exe10	db 'NHLP'		; Winhelp
	exe11	db 'HOOK'		; ApiHooker
	exe12	db 'TRW2'		; Trw2000
	lbadexes	equ ($-badexes)/4


Metoda ma ta zalete, ze obojetnie jak spatchujemy naszego kochanego toolsa to jesli
umiescimy go w katalogu np. c:\crack\filemon i tak spowoduje to jego wykrycie w
systemie.Aby obronic sie przed ta metoda wystarczy zmienic nazwy exekow i sciezek
dostepowych aby nie zawieraly podejrzanych fragmentow ciagow.

Wpisy w rejestrze
~~~~~~~~~~~~~~~~~
Bardzo efektywna metoda wykrywania ale wciaz rzadko spotykana jest sprawdzanie
rejestru windows pod wzgledem wystepowania kluczy stworzonych przez nasze
ukochane toolsy,Filemon i Regmon w rejestrze zapisuja swoja konfiguracje,IDA to
samo, jesli programy nie zapisuja konfiguracji w rejestrze to najczesciej ich
pliki konfiguracyjne .ini mozna znalezc w katalogu c:\windows, ta metode stosuje
np.W32dsm.Metoda ta mimo zalet jest troche poroniona gdyz obecnosc w rejestrze
kluczy konfiguracyjnych wcale nie musi swiadczyc, o obecnosci tych programow w
systemie, sami dobrze wiecie, ze np. instalatory tych programow nie usuwaja tych
smieci samoczynnie(albo nie ma instalatorow).

Skanowanie autoexeka
~~~~~~~~~~~~~~~~~~~~
Juz widze usmiechy na waszych twarzach, tak wiem, to stare,ale co powiecie na
takie wykonanie, otwieramy autoexeka i szukamy plikow ktorych sciezki sa zapisane
w autoexeku, jesli plik istnieje, otwieramy go i skanujemy jego zawartosc w
poszukiwaniu interesujacych ciagow(czy to bajtow kodu czy stringow):

.386
.model flat,stdcall

callW	macro api
	extrn	api:proc
	call	api
endm

.data
	szAutoexec	db 3 dup(0)	; litera np. 'c:\'
			db 'autoexec.bat',0
	lpTemp		db 255 dup(?)
.code
start:
	int	3			; i3here on

	mov	esi,offset lpTemp

	push	255			; tylko literka dysku
	push	esi
	callW	GetWindowsDirectoryA

	mov	edi,offset szAutoexec

	lodsw				; dodaj literke dysku
	stosw				; do sciezki autoexeka

	lodsb
	stosb

	mov	eax,offset szAutoexec	;
	call	creatf			; otworz plik
	inc	eax			; w eax uchwyt
	je	exit			; jesli -1 to oznacza blad
	dec	eax

	push	eax			; zapamietaj uchwyt pliku dla CloseHandle
	
	call	readf			; mapuj plik
	push	edx			; uchwyt mapowanego obiektu
	push	eax			; wskaznik do zmapowanego pliku

	xchg	eax,esi			; esi wskazuje na obszar w pamieci
					; gdzie zostal zmapowany autoexec.bat
	jmp	first_time

copy_loop:
	inc	esi			; przeskocz znak 0Ah(line feed)
	pop	ecx			; liczba bajtow autoexeka
first_time:
	test	ecx,ecx
	js	not_found

	mov	edi,offset lpTemp
copy_line:
	lodsb
	cmp	al,'*'			; zakazane znaki w nazwach plikow
	je	skip
	cmp	al,'?'
	je	skip
	cmp	al,'|'
	je	skip
	cmp	al,'/'
	je	skip

	cmp	al,0Dh			; czy to znak konca linii?
	je	finish

	stosb				; zapisz znak do tymczasowego bufora

	dec	ecx			; kompletna ilosc bajtow autoexeka
	je	finish

	jmp	copy_line

skip:
	dec	ecx			; dec poniewaz pomijamy znak ktory nie
search_endline:				; moze byc czescia pliku
	lodsb				; nastepne bajty z esi
	dec	ecx			; dec licznika bajtow
	je	not_found		; jesli 0 zakoncz program
	cmp	al,0Ah			; jesli to znak konca linii skanuj
	jne	search_endline		; nastepna linie
	jmp	first_time		; rejestr ecx i esi sa poprawnie ustawione
finish:
	dec	ecx			; dec poniewaz pomijamy bajt 0Dh
	dec	ecx			; oraz pomijamy 0Ah,czyli 2 bajty mniej
	push	ecx			; z licznika

	sub	eax,eax			; dodaj 00h na koncu nazwy pliku
	stosb				; czyli zamien na ASCIIz

	mov	eax,offset lpTemp	; kolejne wiersze z autoexeka
	call	creatf

	inc	eax
	je	copy_loop		; jesli nie mozna otworzyc pliku
	dec	eax			; powtarzaj petle

	push	eax

	call	readf
	push	edx
	push	eax
	
scan_loop:
	cmp	dword ptr[eax],'eMuN'	; szukaj fragmentu 'NuMega Tech..'
	je	close_file		; jesli znaleziono zamknij ten plik+autoexec
	inc	eax			; i dalej zrob co chcesz :)
	loop	scan_loop
next_file:
	call	closef
	jmp	copy_loop
close_file:
	call	closef			; zamknij plik
	pop	ecx
not_found:
	call	closef			; zamknij autoexec-a

exit:
	callW	ExitProcess

;	otwiera plik ktorego nazwe zawiera bufor ktorego
;	offset podajemy w rejestrze eax
;	na wyj
;	eax uchwyt pliku lub -1 gdy blad
creatf	proc near
	sub	edx,edx
	push	edx
	push	80h			; FILE_ATTRIBUTE_NORMAL
	push	3h			; OPEN_EXISTING
	push	edx
	push	edx
	push	80000000h		; GENERIC_READ
	push	eax			; nazwa pliku
	callW	CreateFileA		; otworz plik
	ret
creatf	endp

;	mapuje caly plik do pamieci(tryb read)
;	na wej eax uchwyt pliku
;	na wyj
;	eax adres pamieci gdzie zostal zmapowany plik
;	ecx rozmiar pliku w bajtach
;	edx uchwyt zmapowanego obiektu
readf	proc near

	push	eax			; uchwyt pliku

	push	0
	push	eax			; file handle
	callW	GetFileSize

	sub	edx,edx
	pop	ecx			; uchwyt pliku

	push	eax

	push	edx
	push	eax			; lSize
	push	edx			; hSize
	push	2			; PAGE_READONLY
	push	edx
	push	ecx			; uchwyt pliku
	callW	CreateFileMappingA

	push	eax

	sub	edx,edx

	push	edx			; map entire file
	push	edx
	push	edx
	push	4			; FILE_MAP_READ
	push	eax
	callW	MapViewOfFile

	pop	edx
	pop	ecx

	ret
readf	endp

closef	proc near
	pop	ebx

	callW	UnmapViewOfFile		; parametry sa juz na stosie
	callW	CloseHandle
	callW	CloseHandle

	push	ebx
	ret
closef	endp

end start


f) tricki ze stosem
~~~~~~~~~~~~~~~~~~~
Szczerze mowiac tricki wykorzystujace stos w celach anty, sa raczej rzadko
spotykane,moze dlatego, ze nie do konca mozna to nazwac anti ale jakims
urozmaiceniem zawsze, np.:

	mov	edx,offset @after

	db	68h			; push DWORD
	xor	eax,ecx
	jmp	edx			; powrot do kodu

	db	68h			; push DWORD
	db	0EBh,02h		; jmp $+4
	db	0E8h
	db	00h

	jmp	esp
@after:
	add	esp,4*2			; fix stosu

Deadlisting pokaze tylko push-e, zadnego kodu wykonywanego po skoku pod esp:

	push	0E2FFC133h
	push	000E801EBh
	jmp	esp

Przy stosowaniu push-ow nalezy pamietac, o kolejnosci stosowania push-ow, pierwszy
fragment kodu zapisany na stosie przy pomocy push-a bedzie wykonany ostatni,
powyzszy fragment zostanie wykonany jako:

	jmp	$+4
	db	0E8h,00h
	xor	eax,ecx
	jmp	edx

Ta technike mozna wykorzystac przy zapisywaniu wiekszej porcji danych, ustawiajac
esp na koniec+4 jakiegos bufora gdzie chcemy miec zapisany np. kod, i poprzez
serie push-ow zapisac kolejne bajty(DWORD) do bufora(w odwrotnej kolejnosci):

	mov	eax,offset @after
	mov	esi,offset lpBuffer+lBuffer+4
	xchg	esi,esp

	db	68h			; push DWORD
	jmp	eax
	call	edx			; do wypelnienia DWORDa


	db	68h			; push DWORD
	xor	ax,0F386h xor 1111h	; 4 bajty

	db	68h			; push DWORD
	mov	ah,43h			;
	int	68h			; lacznie 4 bajty
	
	push	esp			; skok do --> esp
	ret
	db	9Ah
@after:
	xchg	esi,esp
	cmp	ax,1111h
	je	@debugger_detected

kod jaki zostanie wykonany:

	mov	ah,43h
	int	68h
	xor	ax,0F386h xor 1111h
	jmp	ecx

zostanie skopiowany do bufora lpBuffer a nastepnie nastapi do niego skok(push
esp,ret) po wykonaniu, nastapi powrot poprzez jmp ecx, gdzie ecx wskazuje na
adres @after.


3.Koncowe porady

Na pierwszy ogien pojdzie instrukcja CMP(TEST przy sprawdzaniu czy wartosc
jakiegos rejestru jest rowna zero), czy nie lepiej zastapic ja czyms co nie
przywoluje w myslach "Compare"?


	cmp	eax,1			; jesli takie same to flaga zerowa
	je	registered		; zostanie ustawiona i nastapi skok
na:
	dec	eax			; ten sam efekt jak wyzej, jesli po dec
					; w eax znajdzie sie 0 to ZF zostanie
	je	registered		; ustawiona i nastapi skok
lub:
	xor	eax,1			; jesli w eax znajdzie sie 1 to xor
	je	registered		; wyzeruje eax jednoczesnie ustawiajac
					; ZF i nastapi skok
Dzialanie XORa dla przypomnienia:

	1 xor 1 = 0			; dla wartosci takich samych daje 0
	0 xor 0 = 0			;
	1 xor 0 = 1			; dla roznych daje 1

lub:
	add	eax,-1			; 1-1=0 co spowoduje ustawienie ZF
	je	registered		; i nastapi skok
lub:
	sub	eax,1			; 1-1=0 czyli efek taki sam jak wyzej
	je	registered
lub:
	rcr	eax,1			; w CF zostanie 1 ka, gdy bit odpowiadajacy
	jc	registered		; za 1 ke bedzie ustawiony i nastapi skok
lub:
	bt	eax,0			; instrukcja omawiana wyzej, czy bit
					; odpowiadajacy za 1 jest ustawiony,
	jc	registered		; jesli tak to nastapi skok
lub:
	not	eax			; po not (gdy eax=1) eax = 0FFFFFFFEh(-2)
	mov	edx,-2			; edx=-2
	xor	eax,edx			; gdy takie same w eax bedzie 0
	neg	eax			; neg nie zmieni wartosci eax
	sbb	eax,eax			; gdy eax = 0 po sbb nie zostanie
	jnc	registered		; ustawiona flaga CF i nastapi skok

lub, jest tyle rozwiazan wystarczy uzyc wyobrazni i wskazane jest posiadanie
jakiejs tam wiedzy ;).Dosyc ciekawym rozwiazaniem jest przepuszczanie surowego
wyniku przez jakas procke ktora "mieli" ta surowa wartosc, zamieniajac ja na
jakas sume kontrolna cos w tym stylu np.

	mov	eax,[siakas_wartosc]

	or	edx,-1
	mov	ecx,0FFFFh
mielony:

	xor	eax,edx
	rol	eax,cl

	sub	edx,eax

	loop	mielony

	xor	eax,edx

	xor	eax,277B6BE7h		; suma kontrolna z wartosci 00000001h
	je	registered

Oczywiscie zamiast takich banalnych rozwiazan mozna skorzystac z gotowych algo
typu MD5,MD4,SHA1,CRC-32 etc.

Jesli jestem juz przy algorytmach obliczajacych sumy kontrolne, to polecam je
zastosowac kazdemu programiscie ktory jest zalewany informacjami o nielegalnych
numerach seryjnych, zamiast stosowac algorytmy w stylu "zsumuj wszystkie bajty
user name,xoruj wynik przez 100h zamien na int i porownaj z wprowadzonym sn".
Jak takie cos mialoby dzialac, moja wizja to stworzyc baze legalnych dajmy na
to 10.000 hashow z poprawnych numerow seryjnych(zachowanej w exeku w formie
skompresowanej of coz), gdy user wprowadzi numer seryjny program oblicza sume
kontrolna z wprowadzonego sn i porownuje z baza poprawnych hashow, jesli sie
zgadza to thanks for registering a jesli nie to byby cracker :).Szanse na
znalezienie ciagu(poprawne seriale wg ktorych obliczamy hashe do bazy musza
zawierac np. 20 znakow) ktorego hash bedzie pasowal do jakiegos poprawnego z
bazy przy zastosowaniu algorytmu typu MD5 czy SHA1 sa takie jak to, ze powiedza
o co chodzi ostatecznie w X-Files ;).Innym mniej objetosciowym(ajjc te bazy z
poprawnymi hashami troche by zajmowaly) sposobem jest np. szyfrowanie pierwszych
8 znakow sn wg.algorytmu jak np. Blowfish z kluczem inicjalizujacym tablice
Blowfisha utworzonym z np. 2 czesci seriala(wystarczajaco dlugiej),nastepnie
porownujemy zaszyfrowane 8 bajtow z 8 bajtami ktore zostaly zaszyfrowane wg.
ustalen autora i jesli sa takie same to ok,jesli nie to fuck out.Ta metoda ma
pewna zasadnicza wade, jesli jakis user ktory legalnie kupil nasz program
postanowi oddac przysluge ludzkosci i opublikuje w sieci swoj numer caly program
jest spalony ze wzgledu na to, ze istnieje tylko jeden poprawny sn.Ale mozna
stworzyc mniejsza baze np., ze 100 patternami zaszyfrowanych bajtow, i porownywac
zaszyfrowany sn wprowadzony przez usera z wpisami w bazie, jedyna wada tego
rozwiazania jest to,ze jesli nasz program bedzie bardzo popularny, numery
seryjne rozdawane legalnym uzytkownikom zaczna sie powtarzac,ale lepsze to niz
tylko jeden sn, na koncu tego artykulu znajdziecie przykladowy program w C++
wykorzystujacy SHA1 do generacji kluczy(UUEncoded).

Program generuje 10 przypadkowych kluczy i wyswietla obliczone na ich podstawie
checksumy w SHA1.Drugim krokiem jest zrobienie odpowiedniej procedury ktora
obliczalaby checksume z wprowadzonego sn, nastepnie obliczona checksuma bylaby
porownywana z kolejnymi checksumami zgromadzonymi w jakiejs jednej tablicy i na
podstawie porownywania program podejmowalby odpowiednie kroki.Tego typu schemat
jest bardzo bezpieczny gdyz praktycznie wyklucza on mozliwosc znalezienia
poprawnego sn, dodatkowo jesli nasz program jest zabezpieczony jakims dobrym
exeprotectorem wyklucza to takze mozliwosc zastosowania crackow.W win32asm tego
typu zabezpieczenie mozna sklecic duzo zgrabniej :P

.386
.model flat
.data
	checkme	db '123456789',0	; jeden z poprawnych sn
.code
_start:
	int	3			; i3here on

	push	offset checkme		; adres jakiegos wprowadzonego sn
	call	@check_sn		; sprawdz czy poprawny
	xchg	eax,ecx			; jesli 0 to badboy
	jecxz	@badboy			; a jesli nie to badboy killer ;)

	nop
@badboy:
	ret

;	push	offset lpszAsciizSerial
;	call	@checksn
;
;	na wyjsciu:
;	eax = 1 gdy serial jest poprawny
;	eax = 0 gdy jest niepoprawny(jego suma kontrolna nie znajduje sie
;	na liscie poprawnych CRC wyliczonych z zalozonych wczesniej seriali)
;
@check_sn	proc near
	pop	eax
	pop	ebx
	push	eax

	call	@initcrc		; inicjalizuj tabele potrzebna do
					; wyliczenie CRC
	push	ebx			; ciag w formacie ASCIIz z ktorego
					; obliczmy CRC
	call	@crc32			; wywolaj procke obliczajaca
	test	eax,eax			; czy procedura zwrocila kod bledu 0?
	je	@wrong_sn		; jesli tak to wyjdz z procki glownej

	xchg	eax,edx			; wyliczona suma CRC do edx

	mov	esi,offset @correctsn	; adres tablicy z poprawnymi sn
	mov	ecx,@tablelen/4		; rozmiar w DWORDach tej tablicy
@check_all:
	lodsd				; laduj kolejne DWORDy z tabeli
	xor	eax,edx			; jesli eax i edx sa takie same to eax=0
	je	@correct_sn		; i ZF zostanie ustawiona

	loop	@check_all
@wrong_sn:				; wroc do programu z eax=0
					; gdy petla zakonczy dzialanie to sn
					; jest bledny
	or	eax,-1			; eax=-1 po inc w eax bedzie 0 czyli sn
					; jest bledny
@correct_sn:
	inc	eax			; zwieksz eax,tak, ze eax=1
	ret

;	call	@initcrc
;
@initcrc	proc near
	mov	ecx, 256		; liczba elementow tabeli
	mov	edx, 0EDB88320h		; wartosc poczatkowa
@BigLoop:
	mov	eax, ecx
	push	ecx

	push	8
	pop	ecx

@SmallLoop:
	shr	eax, 1
	jnc	@@
	xor	eax, edx
@@:
	loop	@SmallLoop
	pop	ecx
	mov	dword ptr[crc32tbl+ecx*4-4], eax
	loop	@BigLoop

	ret

@initcrc	endp

;	push	offset lpAsciizString
;	call	@crc
;
@crc32	proc near
	pop	eax
	pop	esi
	push	eax
	
	or	eax,-1
	mov	ecx,eax

@findlength:
	inc	ecx
	cmp	byte ptr[esi+ecx],0
	jne	@findlength

	jecxz	@crcerror

	sub	edx,edx
@@@:
	mov	dl, [esi]
	xor	dl, al

	shr	eax, 8
	xor	eax, dword ptr[crc32tbl+edx*4]

	inc	esi
	loop	@@@

	not	eax
@done:
	ret

@crcerror:
	sub	eax,eax
	ret

@crc32	endp

@correctsn	dd 0DB34D795h		; "987654321"
		dd 03E543DF2h		; "123456789"
		dd 0C9597BF3h		; "135792468"
@tablelen	equ $-@correctsn

.data

crc32tbl	db 256 dup(?)		; tabela dla @initcrc i @crc32


@check_sn	endp

end _start

Szyfrowanie i kompresowanie
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Zaszyfrowane stringi sa jednym z ciekawszych sposobow na wydluzenie czasu
potrzebnego do zlokalizowania interesujacych nas fragmentow kodu, trzeba to
gowno rippowac i pisac odpowiedniego deszyfratora, bardzo pomocna w takich
sytuacjach okazuje sie IDA i jej skrypty, zamiast pisac jakiegos deszyfratora
wystarczy napisac skrypt w jezyku psudo C++ i go zapuscic.Szyfrowanie jest jak
najbardziej ok ale zamiast szyfrowac, mozna rownie dobrze skompresowac czy to
fragment ciagu czy danych.Obecnie mozna za free skorzystac z takich bibliotek jak
Aplib,tworca RARa udostepnil juz dawno zrodla w C++ zarowno do kompresji jak i do
dekompresji, opisy huffmana walaja sie po sieci.Jednym z najprostszych algorytmow
kompresji jest ByteRun, zasada jego dzialania opiera sie na tym, ze czytamy
kolejno bajty z zrodla, jesli bajty zrodla kolejno sie powtarzaja pierwszy bajt
bufora wyjsciowego bedzie zanegowanym licznikiem powtorzen tego bajtu,2 bajt
bufora wyjsciowego to bedzie oryginalny bajt.Jesli ciag np.5 bajtow sie nie
powtarza,1 bajtem bufora wyjsciowego bedzie licznik(ale juz bez negacji),kolejne
bajty wyjscia to oryginalne 5 bajtow wejsciowych np.:

db 'AAAABBBBCCDCA'

zostanie skompresowany do postaci:

db -4,'A',-4,'B',-2,'C',3,'D','C','A'

zysk 3 bajty, realny zysk uzyskuje sie jesli wiekszosc bajtow powtarza sie w
szeregach wiecej niz 2 razy.Oto moja wersja nieco zmodyfikowanego algorytmu
ByteRun:

;	Zasada dzialania w obrazkach:
;	wej:
;
;	31h 31h 31h 31h 32h
;
;	wyj:
;
;	31h 43h 21h
;		
;	31h to pierwszy zrodlowy bajt
;	43h 4-ka oznacza ilosc powtorzen bajtu 31h
;	3-ka to high nibble oryginalnego kolejnego bajtu
;	21h 2-ka to low nibble oryginalnego bajtu
;	1-ka to ilosc powtorzen tego bajtu(30h or 02h=32h)
;
;	Najlepsza kompresje uzyskuje sie przy duzej ilosci powtorzen bajtow
;	bufora wejsciowego.
;
;	push	lBufferSize		; rozmiar bufora do kompresji (w bajtach)
;	push	offset lpBuffer		; dane do skompresowania
;	push	offset lpDest		; tu bedzie zapisany wynik
;	call	BRCompress
;
;	zwracane wartosci:
;	eax	rozmiar skompresowanego bufora
;
BRCompress	proc near
	pop	eax			; adres powrotu
	pop	edi			; lpDest
	pop	esi			; lpBuffer
	pop	ecx			; lBuferSize
	push	eax			; zapamietaj adres powrotu

	push	edi			; adres bufora wyjsciowego

compress_loop:
	or	edx,-1			; edx -1

	mov	al,byte ptr[esi]	; kolejne bajty z bufora wej
go_for_more_1:
	inc	edx			; licznik powtarzajacych sie bajtow
	cmp	dl,0Fh			; nie moze przekroczyc 0Fh
	je	no_more_1		; jesli 0Fh zwieksz wskaznik bufora wej
					; tak zeby wskazywal na nastepne bajty
	cmp	byte ptr[esi+edx],al	; jesli dwa kolejne bajty sa takie same
	je	go_for_more_1		; zwieksz edx o 1
no_more_1:
	add	esi,edx			; wskaznik na nastepne bajty

	mov	byte ptr[edi],al	; bajt ktory sie powtarza
	shl	dl,4			; XXXX0000b ilosc powtorzen bajtu na high
					; nibble bajtu
	inc	edi			; wskaznik bufora wyj + 1
	mov	byte ptr[edi],dl	; ilosc powtorzen bajtu shl 4

	shr	dl,4			; ilosc powtorzen

	sub	ecx,edx			; odejmij od globalnego licznika bajtow
					; bufora wej
	js	end_compress_loop	; jesli <= 0 zakoncz petle kompresujaca
	je	end_compress_loop

	or	edx,-1			; edx -1

	mov	al,byte ptr[esi]	; kolejne bajty z bufora
go_for_more_2:
	inc	edx			; licznik powtarzajacych bajtow
	cmp	dl,0Fh			; nie moze przekroczyc 0Fh
	je	no_more_2

	cmp	byte ptr[esi+edx],al
	je	go_for_more_2
no_more_2:
	mov	ah,al			; ah bajt ktory sie powtarza
	shr	al,4			; polowke tego bajtu zapisana w low nibble
	or	byte ptr[edi],al	; or-uj z iloscia powtorzen poprzedniego
					; bajtu(hi nibble)
	inc	edi			; wskaznik bufora wyj + 1
	shl	ah,4			; 2 polowke do high nibble bajtu, zapisz
	mov	byte ptr[edi],ah	; zapisz do bufora
	or	byte ptr[edi],dl	; or z iloscia powtorzen tego bajtu
	inc	edi			; wskaznik bufora wyj + 1

	add	esi,edx			; zwieksz wskaznik bufora wej o ilosc
					; powtorzen bajtu
	sub	ecx,edx			; minus globalny licznik bajtow
	jns	compress_loop
	jne	compress_loop

end_compress_loop:
	pop	eax			; adres bufora wyjsciowego
	sub	edi,eax			; minus aktualna pozycja
	xchg	eax,edi			; = rozmiar

	ret
BRCompress	endp

;
;	push	offset lpBuffer		; dane do dekompresji
;	push	offset lpDest		; gdzie zapisac zdekompresowany wynik
;	call	BRDecompress
;
;	zwracane wartosci:
;	eax	rozmiar zdekompresowanego bufora
;
BRDecompress	proc near
	pop	eax			; adres powrotu
	pop	edi			; lpDest
	pop	esi			; lpBuffer
	push	eax			; zapamietaj adres powrotu

	push	edi			; zapamietaj adres startowy bufora wyj

decompress_loop:
	mov	al,byte ptr[esi]	; bajt z bufora wej
	test	al,al			; jesli to 00h to zakoncz
	je	end_decompress		; dekompresje

	inc	esi			; zwieksz o 1 wskaznik bufora wej
	mov	ah,byte ptr[esi]	; kolejny bajt, w high nibble jest ilosc
	shr	ah,4			; powtorzen bajtu, w low nibble polowka
					; nastepnego bajtu
copy_byte_1:
	mov	byte ptr[edi],al	; zapisz oryginalny bajt
	inc	edi			; zwieksz wskaznik bufora wyj
	dec	ah			; ilosc powtorzen
	jne	copy_byte_1		; kopiuj powtarzajacy sie bajt

	mov	al,byte ptr[esi]	; kolejny bajt z bufora wej

	inc	esi			; wskazik bufora wej + 1

	shl	al,4			; polowka oryginalnego bajtu do high nibble
	mov	ah,byte ptr[esi]	; w ah,high nibble zapisana 2 polowka
					; bajtu, w low nibble ilosc powtorzen
	shr	ah,4			; powtorzen kolejnego bajtu
	or	al,ah			; or i w al mamy caly bajt
	je	end_decompress

	mov	ah,byte ptr[esi]
	and	ah,00001111b

copy_byte_2:
	mov	byte ptr[edi],al	; kopiuj powtarzajacy sie bajt
	inc	edi			; wskaznik bufora wyj + 1
	dec	ah
	jne	copy_byte_2

	inc	esi			; wskaznik bufora wej + 1
	jmp	decompress_loop

end_decompress:
	pop	eax			; adres bufora wyjsciowego
	sub	edi,eax			; minus aktualna pozycja
	xchg	eax,edi			; = rozmiar

	ret

BRDecompress	endp

Kod jest dobrze skomentowany wiec sadze, ze wprowadzenie poprawek, ewentualnie
takich bajerow jak dodatkowe szyfrowanie nie powinno stanowic problemu.

Patrzac w deep,deep przyszlosc nasuwa mi sie taka mysl, mamy np. plik 20kb,
przepuszczamy go przez jakis algorytm wyliczajacy 8 bajtowa, niepowtarzalana
sume kontrolna, suma stanowi skompresowany plik.Jesli chcemy zdekompresowac dane
majac tylko sume kontrolna, nalezaloby wybrobowac wszystkie kombinacje 20kb az
do momentu gdy suma kontrolna wyliczona z tych wygenerowanych bajtow, bylaby
taka sama jak ta zapisana w pliku.Stopien kompresji uzyskany w ten sposob bylby
zabojczy, ale moc dzisiejszych procesorow nawet nie pozwala zeby realnie o tym
myslec...

4.Slowo na niedziele

Pamietajcie, ze tworzenie zabezpieczen ma utrudnic zlamanie czy to programu czy
crackme, nie oczekujcie, ze wasze nowe zabezpieczenie jest nie do zlamania, zawsze
znajdzie sie ktos, kto po raz kolejny zburzy mur, ale dazenie do konstruowania
coraz to lepszych zabezpieczen swiadczy o tym, ze ciagle sie rozwijamy, rozwijamy
nasze umiejetnosci, swiat sie rozwija.

5.Pozdrowienia

Ptasiek      - moze zrobimy w koncu tego keygena do KatalogCD :))
Dulek        - gdzie sie ukrywasz?Czekam z utesknieniem az dokonczysz
               swojego pe-pakera(niech ci z zachodu zobacza kto umie
               robic prawdziwe pe-pakery!)
GustawKit    - moze CP przydalaby sie jakias komisja poborowa ;)
Smola        - irc for live X-)
ImagiNative  - czekam na nowego LW, moze tym razem w win32asm :)
NoiR         - AAOCG to prawdziwa armia, trzymajcie klase
CoxoC        - czekam na coxpak#2 bo jedynka to prawdziwy hit, ring0 roxx
               thnx za pomoc przy testowaniu i antydebugi
WiteG        - big thnx za anty
p10tr        - twoje tutoriale sa 100 razy lepsze niz te na fravii :)
devon        - thnx za pomoc przy kodowaniu gui
TepeX        - powodzenia z grupa Under, mam nadzieje, ze bedzie ona
               najaktywniejsza grupa na scenie :)


bart^CrackPl
cryogen@poland.com
cryogen@box43.pl
http://www.cryogen.prv.pl


begin 644 sha1.rar
M4F%R(1H'`,^0<P``#0````````!)0G1`@"8`20H``-$=```"ZHK6W5EX0BD4
M-08`(````'-H83$N8Q`!40R-#4``%9OLY53\/G7M;L!@VUY$!T;?>C/$CJMK
M9*;<[V53UR4A)-B&,1T2&,<W?^XDL-0DCMYB\"0C9NRI/HZW6"Q7XY?C+O,M
M_B^7R^&<Y:M8JH^_J6Q)X<JBHN3,Z5%\S'%?YOM^S2\?!I^K\+/":+4PK3KY
M\XNIE7R>/TJK*/,8</^U5/',K>XDU_$#,TPSUB_++>PK)BMZA2>N1#]:KRA?
M\5397R,"H=V30*[T?EU/(_/G$;6,??HO#WTKR>^,"%9KZ0^X>B)"J8C6-1LB
MY[+O.J58!:M:H9:.1M"KVK9ENM7MPFR@?Y71W:._1?T+:,&CPT8='CHQ:%]&
M/1_XJKK(5ZS000@%M'\]M]FX.K+OT[@NUZAIU>Y4%ID$8";IK@;'BMAULKXA
M9%<:K*S(?;6>OW"@$*C3P.*]4^!ZVA:0MNM]:ZB;<N<YVN6V^N^]JI]9&H\4
MS6I=>?P<9'->H!FMB5%[>CKZ_;<_:Y[K?19]PN/@%UP>/ML7U\N%05[5\1/]
MSL6?O9R95>(1$`!1&@0A0;IGO]7XW;HM%K7&LJ^"HDRD.NY>#*#UWL/)[Q9H
M.;XACB%$`I(^L,J:TBQ[EZ>0#.=J*85KT4DK.?&J`#$!3E39%^Y'%:3+B?K[
MX%H+\*^+O#UP#J]E3ML;GV)ZY,3/8<WM.^ED@[R]W<KD[$4">[XE:7]K77^@
M>`QY+K*%T>O(D]0&C\14-VMZ?SDK1L2D/T8EM8%"-1\`!F!A7S9F$?YXP>FK
MN"7:4?`5_<K46&(%%<4Q&71A+"*A-#BCK#9AV'&`-MT"]DFV1?#0\GP<JKR;
MT'3%%\JA#YNGDY'873=7P%)J\W,T#SA<,?KTD.^A[V'P,H:O2Q*$^P?7&KD*
M]%F^_,_4$]@"A__&!!)+UT"[UV6U`>D382XB2R*9<A/AF^02902O=\A,FS$1
M=__5U>UZ.<J(]2Q-W$W8#L:$H76!F'0NGX</-`)V+=HN5W@W4W4IG(/&]@C/
M.[=,,)_W;[X947/CX;E#BK-*%"C!'1^I7"PK/"LLW(2`W?=,)=L=`F#:A!_/
M#6T"'LBI/+/HQ&/ZBVO56C].Z:2<7'-#<(F('Z\C9)"*'292Z7=J+I.#_GZ$
M!)9%2Q)46)LVURLB7"1Y*&H!E8+.X6:J&*$3?/D4/*GFI^=//3]*:T4#?2`L
M(>4,P+BAYN5G;!?V*D5ZFI%B?8SSJ6:QU4+!0J4+F'R:E&P)ZW+#Y/K'F4;$
MI5FM?_N%5]4J3FN^8$[Y[E0HW+=>YSV3?K6,/:^`4GP:!(]YR[V",X[MM85K
MIO/SVK=IKWGJ]#^>R5JRB=:-MHM>>68R$4J7X),7Q)")9\POVA=7X0H^0S"\
M4-NA9@LKZ9+4R_<7[I8>]VA:I\8:-W]I7M?&"T_--0K1WM,GH4T*V)=M%XY*
M5_[[FTLF1&[JV;A[#48;5=J"OI>+[?Q]WWZA*EY>AP0M^77BJ`R[X.I",8LG
MM+;-KU\&,:2:LI?%0Y*B,Q/4=A7$3^*OBGCSQ48J<3E/EO1HV)*;_\*/A,+&
MRG,0LXR\&G/':=X>:&`?:)E<H`)8#\H.&"Q#(8)`F82,R1@)-(UKB;;DX"R@
MV>/BHW(XP`>A#9P+QF3C)=*E5)._Q"]J^H@>L$U&%6*V60Y"E:E3O4TZ:E,+
MIH3?$T73'IB-<2E-O@CFTG3'P1]&Q(C(T4$KR#BE)ZHUM'15',I1UFT.:[6C
M9P]-:J1&;%&;.8(UJIH]-:V:W42,-EJ,K!&RT\W1-%R:S:;FO(C#I2C.*P1P
MY.-AT6X..;0X.V0`.:@X&"'Y&6\HN;<<Q1MD>1EO!\X>GN.6HSYS!'N&6/3V
MMGN.8HYS!'N&8GIP]/<9XX0;)[D(#)XX0.AT1M#0>.$`8+:3PRWE%S;CF*-L
MCR,MXJ3A[4<<M1U)S!5'#+'M1K=1QS%'.8*HX9B>G#VHXT!PBV4'(1&4!PB,
MH#A$;0'"(,%M)X9;RBYMQS%&V1Y&6\59P]JN.6HZLY@JKAECVJUNJXYBCG,%
M5<,Q/3A[5<X%CVRHH7@_3+7EY)/P)UOUX[<_%';FCL>QVTB<31*Q+TS6KO-Y
M.<M,>3QVH/*`[5V!W_JV-4@262O859MU0ET4ES8ER<EQ@P#7XQ<8"3MM\(NB
M'KE+>CP\2N:-C]OBQ$)9_;AAXGL0*)[1+1HTMV%L@.'E[`SM<!<SM;/FC50J
M''F&V-T2&M*Y=M6[//6KM]#G%7K!A6;5VX/H>-&F'G5$*J(^B#BM'VSKAMTP
M>H<$Z4<;6F;VRWS%?1TZG%GU9DAPN[-\.$3WWP^5F(>#1_<X$<L#)FJL\7`7
MDH$8#`3CA3V%#``%[*:)\A2&)1H-^M+E(IB8$<#`,"9)X48^SA.<?2I2)2=Z
MR(\J#``A<QFKV"RD5D]6`&*,?-RV<K6A[)$?%SS,';3B<P';>?910X<%&/>,
M3\F="BA-CE3%M(W8#2?@&+Z@K$#(J0`T7(V2^)%-,#GTU1["",,`Y+#7QEJS
M!%*'%Z"?&SG%`YV+W?;]GT7&1JFF6&O"]FC,F9)37QI*%PS9,/)D59RY,1:=
M1,@NEWJQ8G>=-1_!BHQ?-Q4FXZLT4SDU#=Q$W#)#6[9&F+`"%N0<!R"K!\4J
M4Q_,HXAQ!TQ+T9TYX([F>*@+_3Z8O\ASR^W8BBXQ.@?P@1>PD[HN`F%;)LC.
M)?G+3LN!T\4L0;9%`R_(?K%#4QF1-E[U)-Y6T[;@H[_O.,,WR]$Z,XS7UL*N
MZW!0!5#`EQ_P`Y0K3&_^M5DBJ^F*MCTH>C(6O)#F@$T@P\@DMJ>NB5P>[AD<
M9/AG#)GNH7RH&T-K:&\_FYCNWZ$)-IG_GW*2"+!M[4];;-#:VI!V`8^XUN20
MH9*1G&-=HGE.56VB+V2WU1^_=8`8K%>*7@KM8/A?R@BS!A'0RO+/W!VZ<V(4
M6`OF=VS^=4$C[[47`X_H_A9&B6E2?R!:&ZM6X*>O]A5W`N(Z(484#2^?G%(J
M>IJ)]9`/)C$ZCHJ'S`-:I0^]5GK63\+1'PO]?J&82L\]EY=R[KCWN/C%Z`$=
M78"<+?+@$^N,!@(0G`#^5KZ7A8V,JB^%=A?-G?Y?1WM,6=QGE,L,\AH?JE2%
M1WMXB(=KS<GIG"JOX-W`4^23B'%:P#HIQ@:K!R(H?(<%$IM"2A*]`3B2(<@5
M`8;P,1@[>6@5@J(O6">AQM9[\Q1N\K"K@)F5TL#.<&VTW@^,_V[28UO4WIM`
MPA,Z02Y8FL8B8C,`7O0COQ?E@$%9ZK71T/8L!!][@05[J&_ZV/?BWZ;SJ-B$
MHQKORO!?1*Z=\]ZQ%TY3.0EHN&!<HN?\QK`]51<D)M?Z4\Y6Y0.PM2-_HSA8
M'YIQ#0">G\C7)XR,EE!UQ&&DY3L=P20^E129#?H[52;$\)8B;*>$AOLQ@*T,
MBN4OOB.X3(MOJA#+T%V&4L2RBZZ&".8?X\"!G;J!5(:@)9"XI"8?9@:I`/AR
DRQXD?T8=^@5L(ZE'`A2P%X')(@EB)&4!"&/*OEU]Z*^=%RC0
`
end
sum -r/size 57668/2691
