1. Podstawowe wiadomości o wirusach Rodzaje wirusów



Pobieranie 215,13 Kb.
Strona3/5
Data24.02.2019
Rozmiar215,13 Kb.
1   2   3   4   5

7.4 Sector level stealth

Oczywiście, technikę stealth można stosować także w przypadku odczytu sektorów; należy przejąć obsługę funkcji (02/13) oraz ewentualnie (0A/13). W momencie próby odczytu zarażonego MBR lub BOOT-sektora wirus podsuwa programowi odczytującemu ich oryginalna, nie zainfekowaną zawartość (podobnie jak w przypadku plików, sektory zainfekowane muszą być w jakiś sposób oznaczone, żeby wirus mógł je rozpoznać). Aby ustrzec się przed programami umożliwiającymi odświeżenie tablicy partycji lub BOOT-sektora, można dodatkowo zabezpieczyć sektory zawierające wirusa przed zapisem poprzez przejęcie funkcji (03/13) i ewentualnie (0B/13).



7.5 Hardware level stealth

W przypadku sektorów istnieje możliwość zastosowania tzw. techniki hardware level stealth, która jest rozszerzeniem metody opisanej w poprzednim punkcie. Technika ta polega na przechwyceniu odwołań do dysków już na etapie przerwania sprzętowego IRQ 14 (INT 76h) lub poprzez przejęcie wywoływanej przez to przerwanie funkcji (9100/15h), co umożliwia oszukiwanie systemu na najniższym poziomie programowym- Podczas obsługi tego przerwania lub funkcji wirus może odczytać z portów lFx dane o aktualnie wykonywanej operacji i następnie, w razie wykrycia odwołania np. do MBR, zmienić rozkaz tak, aby odczytywać inny sektor, zawierający prawdziwą zawartość MBR.

Sprawdzenie aktualnie wykonywanej operacji polega na odczycie bajtu z portu 1F7 (rejestr statusowy IDE) i przetestowaniu bitów jego młodszej części. Jeżeli któryś z nich jest ustawiony (tzn. ma wartość l), oznacza to, iż właśnie jest wykonywana operacja odczytu. Parametry odczytywanego sektora (cylinder, głowicę, sektor) można odczytać z portów 1F3, 1F4/1F5/1F6. Jeżeli odczytane dane są zgodne z oczekiwanymi, wirus musi wykonać do końca operację odczytu oraz ustawić dane w portach kontrolera na inny sektor (np. na taki, w którym znajduje się oryginalna zawartość odczytywanego sektora).

W efekcie program antywirusowy, który używa do odczytu sektorów bezpośredniego adresu przerwania int 13h, zawartego w BIOS-ie, i tak będzie oszukiwany przez wirusa. Aby technika hardware level stealth zadziałała, sterownik dysku musi generować IRQ14 (co można ustawić programowo) przy realizacji operacji dostępu do dysku.



7.6 Modyfikacja CMOS-a

Nowoczesne BIOS-y zawierają mechanizmy zabezpieczania niektórych newralgicznych sektorów przed zapisem (MBR, BOOT-sektory). W momencie wykrycia próby zapisu do któregoś z tych sektorów system najczęściej w jakiś sposób alarmuje użytkownika i czasem prosi go o potwierdzenie wykonywanej operacji lub też, aby kompletnie uniemożliwić tę operację, zawiesza komputer i czeka na naciśnięcie klawisza RESET.

To, czy BIOS będzie reagował na próby zapisu do newralgicznych sektorów, ustalane jest zwykle z poziomu programu SETUP, który dostępny jest po naciśnięciu jakiegoś klawisza podczas uruchamiania komputera. Ustalone w programie SETUP parametry są po jego opuszczeniu zapisywane do podtrzymywanej baterią pamięci CMOS.

Korzystając z tego, iż pamięć ta dostępna jest programowo, można zmodyfikować pewne dane, tak aby np. na chwilę odblokować zapis do MBR. Wymaga to jednak znajomości różnych systemów BIOS, gdyż znajdująca się w nich pamięć CMOS ma zazwyczaj zawartość odmienną od pamięci w innych komputerach, a jej zgodność ogranicza się do ustalonego znaczenia początkowych komórek tej pamięci (co jest konieczne ze względu na kompatybilność).

Drugim parametrem możliwym do zmodyfikowania w CMOS-ie jest wartość określająca, jakie napędy dyskietek zamontowane są w komputerze. Wirus może wyzerować tę wartość, tak iż przy starcie BIOS nie będzie widział żadnej dyskietki i będzie próbował załadować system z twardego dysku (z MBR), razem ze znajdującym się w nim wirusem. Po załadowaniu wirus przywraca parametry dyskietek w CMOS-ie i następnie sprawdza, czy napęd FDD zawiera dyskietkę i ewentualnie wczytuje z niej BOOT-sektor, po czym oddaje do niego sterowanie.

Zainstalowany w systemie wirus przy odwołaniach do dyskietek ustawia parametry w CMOS-ie, a po ich zakończeniu znowu je kasuje, tak więc po wyłączeniu komputera parametry w CMOS-ie będą najczęściej skasowane. Takie działanie dość mocno utrudni załadowanie systemu z czystej dyskietki, zwłaszcza jeśli wirus potrafi także zainstalować w pamięci CMOS hasło, uniemożliwiające przeciętnemu użytkownikowi dostanie się do programu SETUP.



7.7 VolumeID

Do ukrywania się w systemie niektóre wirusy wykorzystują fakt, iż programy przeglądające zawartości katalogu (typowe nakładki lub polecenie DIR) nie pokazują zwykle pozycji katalogu zawierających atrybut VolumeID (etykieta dysku). Wirusy dodają do dotychczasowych atrybutów pliku, zawierającego np. kod wirusa, także wyżej wymieniony atrybut .Tak ukryty plik będzie widoczny najczęściej tylko w tzw. edytorach binarnych, operujących na zawartości fizycznych sektorów (np. Disk Editor).



  1. Szyfrowanie kodu

8.1 Procedury szyfrujące kod

Do zaszyfrowania wirusa można zastosować dowolną z dostępnych w procesorze, odwracalnych operacji matematycznych lub logicznych, a więc:

> dodawanie - operacja ADD;

> odejmowanie - operacja SUB;

> suma modulo 2 - operacja XOR;

> negowanie arytmetyczne - operacja NEG;

> negowanie logiczne - operacja NOT;

> przesunięcie cykliczne w lewo - operacja ROL;

> przesunięcie cykliczne w prawo - operacja ROR.

Są to najczęściej wykorzystywane metody szyfrowania, choć nic nie stoi na przeszkodzie, aby wprowadzić inne, np. przestawianie bajtów miejscami, traktowanie licznika lub indeksu jako wartości używanej w wymienionych wyżej operacjach matematycznych i logicznych.

To, która operacja (lub operacje) będzie użyta oraz z jakimi parametrami, zależy wyłącznie od inwencji projektującego procedurę. Jeżeli składa się ona za każdym razem z tych samych instrukcji, to jest to stała procedura szyfrująca i będzie dawać za każdym razem taki sam obraz zakodowanego wirusa. Zupełnie inaczej sprawa ma się ze zmienną procedurą szyfrującą, która po wywołaniu wybiera przypadkowo ilość operacji szyfrujących, a następnie w pętli losuje:

> rodzaj operacji wykonywanej na argumencie;

> argumenty operacji;

> rodzaj argumentów (bajt, słowo, podwójne słowo, ewentualnie inne).

Wybierane operacje oraz argumenty należy gdzieś zapamiętać (zwykle w tablicy lub na stosie), gdyż w przyszłości będzie je wykorzystywać procedura generująca dekoder.

8.2 Procedury dekodujące

Działanie typowego dekodera ogranicza się do wykonania w odwrotnej kolejności operacji wykonywanych przez procedurę szyfrującą, z uwzględnieniem koniecznych zmian operacji, np. ADD-SUB, SUB-ADD. Na przykład, jeżeli procedura szyfrująca wygląda następująco:

MOV CX, Ile Bajtów

MOV BX, PoczątekDanychDoZakodowania

Pętla:

XOR byte ptr [BX],12h



ADD byte ptr [BX],34h

NOT byte ptr [BX]

INC BX

LOOP Pętla,



to procedura dekodująca powinna wyglądać mniej więcej tak:

MOV CX, IleBajtów

MOV BX,PoczątekDanychDoZakodowania

Pętla:


NOT byte ptr [BX]

SUB byte ptr [BX],34h

XOR byte ptr [BX],12h

INC BX


LOOP Pętla.

W przykładzie tym operacja ADD z procedury szyfrującej przeszła w operację SUB w dekoderze (oczywiście można zastosować także operację ADD z przeciwnym argumentem, tzn. -34h). Niestety, nawet jeżeli kod wirusa jest szyfrowany za każdym razem inaczej, to i tak wszystkie możliwe do wygenerowania procedury dekodera będą zawsze zgodne ze schematem (dla poprzedniego przykładu):

MOV CX,IleBajtów

MOV BX,PoczątekDanychDoZakodowania

Pętla:

DEKODUJ [BX] DEKODUJ [BX]



DEKODUJ [BX]

INC BX


LOOP Pętla,

co dla nowoczesnych skanerów nie stanowi żadnej przeszkody. Aby uzyskać za każdym razem inny, bardziej unikalny dekoder, można zastosować zmienne, polimorficzne procedury dekodujące.



8.3 Polimorficzne procedury dekodujące

Stworzenie własnego wirusa polimorficznego nie jest zadaniem łatwym, czego pośrednim dowodem jest dość mała ilość oryginalnych wirusów tego typu. Najtrudniejszym elementem jest oczywiście stworzenie samego generatora zmiennych procedur dekodujących.

Ze względu na specyfikę zadania, jakie musi on wykonywać (generowanie wykonywalnych sekwencji rozkazów), do jego zaprogramowania niezbędna jest znajomość rozkazów procesorów 80x86 oraz ich maszynowych odpowiedników.

Poniżej omówiono dwie metody tworzenia zmiennych procedur szyfrujących. W obu przypadkach założono, iż cały kod wirusa został już zakodowany w sposób omówiony w poprzednich punktach, zaś operacje i ich argumenty są zachowane w jakiejś tablicy.

Aby rozkodować kod wirusa najczęściej stosuje się pętlę podobną do poniższej sekwencji:

MOV licznik, IleDanych

MOV indeks, PoczątekDanychDoZakodowania

Pętla:


dekoduj_i [indeks]

DEKODUJ_2 [indeks]

DEKODUJ_N [indeks]

ADD indeks, przyrost

LOOP Pętla

Jest to procedura, którą bardzo łatwo wykryć, ponieważ w zasadzie jest ona stałą. Chcąc uczynić ją w jakiś sposób zmienną, można zdefiniować pewną ilość podobnych do siebie w działaniu procedur i spośród nich losować tę, która zostanie użyta przy kolejnej generacji wirusa. Niektóre wirusy zawierają od kilku do kilkudziesięciu takich stałych procedur dekodujących, które choć działają tak samo, zbudowane są z różnych rejestrów i instrukcji. Ze względu na ograniczoną ilość takich procedur, które mogą być zawarte w ciele wirusa (zwiększają one przecież długość kodu), ilość różnych możliwych wariantów wirusa jest tak naprawdę bardzo ograniczona.

Inny sposób uzyskania pewnej zmienności w procedurze dekodującej polega na stworzeniu bufora wypełnionego przypadkowymi wartościami, w którym umieszcza się kolejne instrukcje procedury dekodującej, a po każdej z nich - rozkaz skoku do następnej instrukcji. Zaprogramowanie generatora takich procedur nie stanowi dużego problemu. Wystarczy znać odpowiednie kody maszynowe kolejnych rozkazów procedury dekodującej i sekwencyjnie umieszczać je w buforze, a bezpośrednio za nimi generować rozkaz skoku o kilka bajtów do przodu, np. rozkazem JMP SHORT NEAR (kod maszynowy 0EB/??) lub JMP NEAR (kod maszynowy E9/??/??).

Jedynym problemem, na jaki natknąć się może twórca takiej procedury są offsety, pod które powinien skakać program przy wykonywaniu pętli, gdyż zapisując instrukcje sekwencyjnie napotykamy na konieczność umieszczenia wartości początkowej np. w rejestrze, choć jeszcze jej nie znamy. Aby ominąć tę przeszkodę, najprościej zapamiętać offsety do instrukcji, zarezerwować dla nich miejsce, a następnie w dalszej części kodu (kiedy już są znane), zmodyfikować je pod zapamiętanymi offsetami.

W zamieszczonym programie przykładowym za pomocą powyższej metody szyfrowany jest krótki programik, mający za zadanie wyświetlenie komunikatu po jego uruchomieniu. Wynik kilkakrotnego działania procedury zapisywany jest w plikach SEMI????.COM, gdzie ???? jest parametrem podawanym przy starcie programu (w wypadku braku parametru - domyślnie=10). Wygenerowane pliki zawierają tylko jeden stały bajt na początku programu (część rozkazu JMP SHORT o kodzie EB). Poprzez zastąpienie procedury Losowy-Skok (wstawić RET zaraz po etykiecie LosowySkok:) można łatwo zmodyfikować ten program, tak aby generował pliki szyfrowane w standardowy sposób (bez dodawania losowych skoków pomiędzy instrukcjami).

Program z prezentacji w katalogu Semipol

8.4 Pełny polimorfizm

Prawdziwie polimorficzne generatory zmiennych procedur szyfrujących powinny tworzyć przy każdym uruchomieniu całkowicie nową, unikalną procedurę dekodującą. Procedura taka najczęściej zachowuje tylko funkcje poniższej sekwencji:

LICZNIK:=IleDanych

INDEX:=PoczątekZaszyfrowanegoKodu

FOR I:=1 to Licznik do begin

DEKODUJ_1 DANA [INDEX]

DEKODUJ_2 DANA [INDEX]

DEKODUJ_N DANA [INDEX]

INDEX:=INDEX + PRZYROST ;

end;


Dla każdej procedury dekodującej generator losuje najczęściej odpowiednio:

> indeks;

> licznik;

> kierunek zwiększania licznika;

> kierunek dekodowania;

> rodzaj używanych instrukcji (8086, 80286 itd.).

Licznik oznacza najczęściej rejestry procesora wybierane z listy (E)AX, (E)BX, (E)CX, (E)DX, (E)BP, (E)SI, (E)DI. Rejestr (E)SP jest z wiadomych względów pomijany. Oczywiście, możliwe jest także użycie rejestrów 8-bitowych (najprościej jako licznika). Litera (E) oznacza, iż możliwe jest wybranie także rejestrów 32 bitowych, jednak należy pamiętać, że użycie ich znacząco komplikuje samą procedurę generującą.

Z kolei indeks wybierany jest z listy możliwych sposobów adresowania dla procesorów 80x86. Może to być więc [BX+????], [BP+????], [SI+????], [DI+????], [BP+SI+????], [BP+DI+????], [BX+SI+????] lub [BX+DI+????]. Są to wszystkie możliwości, jakie może zawierać pole MmRejMem w instrukcjach operujących na danych w pamięci.

Liczbę różnych indeksów można poszerzyć poprzez użycie sposobów adresowania, które pojawiły się w procesorach 386 i wyższych. Umożliwiają one (poprzez zastosowanie słowa rozkazowego SIB) zastosowanie do adresowania wszystkich użytkowych rejestrów procesora.

Kierunek zwiększania licznika określa, czy licznik rośnie aż do wartości maksymalnej, czy też maleje od tej wartości do zera.

Kierunek dekodowania określa, od której strony zacznie się dekodowanie wirusa po uruchomieniu procedury; czy od początku zaszyfrowanego kodu, czy też od jego końca. Konsekwencją obranych kierunków będą odpowiednie rozkazy zwiększające lub zmniejszające indeks i licznik. Od kierunków również zależeć będzie instrukcja sprawdzająca warunek zakończenia pętli dekodującej. Stopień skomplikowania samego dekodera wzrośnie, jeżeli pozwolimy na dynamiczne zmiany licznika i indeksu podczas działania programu. Na przykład, jeżeli początkowy licznik ustalimy jako CX, to po kilku wygenerowanych instrukcjach należy za pomocą instrukcji MOV REjl6, CX lub XCHG REJ16, CX wymusić zmianę licznika na inny. Jeśli chcemy skomplikować całą procedurę i uczynić ją polimorficzną, należy między instrukcje stanowiące integralną część dekodera wstawić rozkazy niepotrzebne z punktu widzenia działania programu, lecz niezbędne do zaciemnienia i ukrycia głównej procedury dekodującej (rozkazy te muszą być wybierane losowo).

O ile w procedurze semi-polimorficznej do zaciemnienia kodu dekodera służył tylko jeden rozkaz, JMP SHORT, o tyle w przypadku pełnej procedury polimorficznej należy uwzględnić jak największą ilość rozkazów procesora.

Instrukcje stanowiące taki wypełniacz muszą spełniać kilka warunków. Najważniejsze Jest to, aby rozkazy te:

> nie zamazywały dowolnie nie używanej przez siebie pamięci;

> nie zawieszały komputera;

> nie powodowały generowania wyjątków;

> nie niszczyły zawartości ważnych dla działania programu rejestrów (m.in. CS, SS, SP oraz rejestrów stanowiących indeks i licznik).

Część powyższych ograniczeń można ominąć, o ile wykonana operacja zostanie odwrócona, a więc możliwe jest np. zamazanie pamięci, ale tylko pod warunkiem, iż w zniszczone miejsce wpiszemy chwilę później oryginalną, zapamiętaną wcześniej zawartość. Podobnie ma się sprawa ze zmienianiem rejestrów. Jeżeli chcemy np. zmienić rejestr będący licznikiem, możemy jego wartość na chwilę zapisać do innego rejestru lub na stosie, a następnie zmienić go tak, aby po wykonaniu kilku kolejnych instrukcji przywrócić jego oryginalną zawartość.

Najprostszy sposób to wstawienie jako wypełniacza instrukcji 1-bajtowych, jednak jest ich tak niewiele w liście rozkazów, iż procedura zawierająca tylko takie instrukcje będzie łatwa do wykrycia. Ponadto zbyt duża ich ilość w programie też nie jest pożądana, gdyż z reguły programy używają instrukcji kilkubajtowych, w związku z czym nadmiar instrukcji 1-bajtowych może wydać się podejrzany (zwłaszcza programowi antywirusowemu).

Pamiętać trzeba, iż najlepiej byłoby, gdyby wygenerowana procedura przypominała fragment typowych programów komputerowych, stąd też wskazane jest używanie w generatorze wywoływań przerwań BIOS i funkcji systemu DOS (np. sprawdzanie wersji systemu DOS, sprawdzanie, czy jest jakiś klawisz w buforze klawiatury itp.), które spowodują, iż ewentualnemu programowi antywirusowemu program wyda się typową aplikacją.

Innymi, prostymi do wykorzystania instrukcjami są rozkazy operujące na akumulatorze (AX) i na jego młodszej części (AL), gdyż posiadają uproszczone w stosunku do innych rejestrów kody. Również operacje przypisywania rejestrom roboczym jakiejś wartości są bardzo proste do wstawienia (grupa rozkazów MOV REJ/???? o kodach B0/??-B7/?? dla rejestrów AL - BH lub B8/????-BF/???? dla rejestrów AX - DI).

Trochę trudniejsza jest symulacja operacji stosowych, rozkazów skoków i wywołań procedur, jednak, jak pokazują istniejące generatory, można je z powodzeniem stosować. Aby procedura dekodująca była jak najbardziej podobna do fragmentu typowego programu, warto stosować instrukcje modyfikujące jakieś komórki w pamięci. Używając ich należy pamiętać, iż zmodyfikowaną wartość należy później bezwzględnie przywrócić (chyba, że mamy pewność, iż jest to dana nieistotna np. w kodzie wirusa).

Korzystając z operacji działających w pamięci na argumentach typu word i dword nie wolno zapominać o tym, iż np. przy adresowaniu bazowym lub indeksowym wartości rejestrów (SI, DI, BX, BP) będą najczęściej nie ustalone, stąd może wystąpić sytuacja, w której instrukcja będzie pobierała lub modyfikowała daną z pogranicza dwóch segmentów. Innymi słowy, offset obliczany na podstawie indeksu będzie miał np. wartość 0FFFFh, co przy próbie dostępu do danej word lub dword z miejsca pamięci określonego przez ten offset spowoduje wystąpienie wyjątku (numer 13 - ogólne naruszenie mechanizmów ochrony).

Aby uwzględnić w generatorze jak najpełniejszą listę rozkazów, należy zaopatrzyć się w spis instrukcji procesorów 80x86 i ich kodów maszynowych, a następnie przeanalizować każdą z instrukcji pod kątem jej przydatności jako części wypełniacza oraz warunków, w których zadziała ona poprawnie. Dobrym przykładem może być analiza instrukcji LODSB, którą chcielibyśmy zastosować zamiast instrukcji INC SI. Oprócz tego, iż modyfikuje ona rejestr AX (dokładniej AL), pamiętać trzeba, iż jej poprawne działanie zależne jest także od stanu bitu kierunku DF, zawartego w rejestrze znaczników. Używając jej musimy mieć więc pewność, iż wcześniej wystąpiła już instrukcja CLD, która właściwie ustawiła bit DF.

Po wygenerowaniu procedura dekodująca powinna mieć postać podobną do poniższej sekwencji:

....... ; wypełniacz

inicjuj rejestr - licznik lub indeks

....... ; wypełniacz

inicjuj rejestr - indeks lub licznik

....... ; wypełniacz

pierwsza instrukcja dekodera

....... ; wypełniacz

druga instrukcja dekodera

....... ; wypełniacz

n-ta instrukcja dekodera

....... ; wypełniacz

sprawdzenie warunku zakończenia pętli

....... ; wypełniacz

skok, jeżeli warunek spełniony

....... ; wypełniacz

właściwy, zaszyfrowany

kod wirusa, do którego

zostanie przekazane

sterowanie po rozkodowaniu

....... ; wypełniacz

Przeprowadzając kilkakrotnie powyższe operacje można uzyskać kilkustopniową procedurę dekodera, która będzie bardzo trudna do wykrycia przez programy antywirusowe.

Aby przyspieszyć wykonywanie procedury dekodującej, często szyfruje się cały kod wirusa jakąś stałą procedurą, a dopiero ta jest rozkodowywana za pomocą zmiennej procedury deszyfrującej, której wystarcza na rozkodowanie kilkakrotne wykonanie pętli dekodera. Innym sposobem może być tu całkowite pominięcie pętli i stworzenie kodu, który wygeneruje właściwą, stałą procedurę dekodująca w locie, budując odpowiednie jej instrukcje z odpowiednich fragmentów rozrzuconych po kodzie.

Poniżej zebrano informacje o wszystkich znanych instrukcjach procesorów 80x86 (kierując się ich przydatnością do wykorzystania w generatorze).

Lista nie zawiera rozkazów koprocesora, rozkazów systemowych oraz instrukcji wykorzystywanych przez języki wysokiego poziomu(nie są one wykorzystywane przez generatory polimorficzne). Oprócz kodów instrukcji podano opis sposobu zapisywania adresów za pomocą bajtu MmRejMem (286+) i SIB (386+).Informacje podane poniżej dotyczą działania instrukcji w trybie rzeczywistym procesora, a więc adresowania segmentów o rozmiarze maksymalnym 64k. W przypadku trybu chronionego i segmentów większych niż 64k (najczęściej 4G, czyli model FLAT) należy wziąć pod uwagę, iż działanie przedrostków 66 i 67 ma w nich odmienne znaczenie. Na przykład, jeżeli chcemy wygenerować rozkaz zerowania rejestru AX (MOV AX/0), należy dla segmentu zadeklarowanego z dyrektywą USE16 umieścić w buforze ciąg B8, 00, 00, zaś dla segmentu zadeklarowanego z dyrektywą USE32 użyć sekwencji 66, B8, OO, 00. Chcąc natomiast zerować rejestr EAX (MOV EAX/0), należy dla segmentu USE16 użyć sekwencji 66, B8, 00, 00, 00, 00, a dla segmentu USE32 - B8, 00, 00, 00, 00. Jak widać, przedrostki 66 i 67 służą do wymuszania trybu interpretowania instrukcji, odmiennego od tego którego używa procesor w danym trybie. W trybie rzeczywistym wymusza on instrukcje 32-bitowe, w chronionym zaś 16-bitowe.

Ze względu na to, iż instrukcje często zawierają argumenty w postaci kilku bitów umieszczonych w różnych miejscach, wszystkie kody instrukcji zawarte poniżej występują jako liczby binarne.

Działanie procedury generującej wypełniacz może wyglądać mniej więcej tak:

N:=LiczbaPseudolosowa

For I:=1 to N do GenerujInstrukcję

Liczba N określa, ile instrukcji zostanie wygenerowanych podczas jednego wywołania procedury.Procedura GenerujInstrukcję powinna wybrać (np. z tablicy) pierwszy bajt instrukcji i zapisać ją do bufora, a następnie, o ile to konieczne, dodać wymagane operandy. Podczas wybierania argumentów instrukcji należy sprawdzać czy wylosowany, przypadkowy argument nie koliduje w jakiś sposób z używanymi przez dekoder rejestrami. Jeżeli tak jest, procedurę wybierania argumentu należy kontynuować, aż do znalezienia odpowiedniego argumentu. Procedura GenerujInstrukcję powinna wyglądać mniej więcej tak (zapisana w pseudojęzyku, trochę podobnym do Pascala):

procedura GenerujInstrukcję;

X:=LiczbaPseudolosowa wybierająca generowana instrukcję sprawdź atrybuty instrukcji wskazywanej przez X

if są jakieś operandy then

dla każdego operandu begin

repeat

arg:=Pseudolosowa



if arg nie koliduje z zasobami dekodera to argument znaleziony

until argument znaleziony lub furtka bezpieczeństwa

wstaw operand=arg end else wstaw instrukcję bez operandów;

end;


Załóżmy, iż dla dekodera zostały wybrane:

Indeks:= 000 ;{Rejestr: z przedziału 000.. 111}

Licznik:= 001 ;{trzeba zdekodować używane

; przez indeks dwa rejestry, w tym

; przypadku oczywiście ręcznie }

IndeksModyf1:= 110 ; [numer rejestru SI}

IndeksModyf2:= 011 ; (numer rejestru BX}

Niech procedura GenerujInstrukcje wybierze teraz do wstawienia np. rozkaz DEC, który w tabeli jest zapisany jako:

01001Rej DEC rejestr Rej.

Jak widać, instrukcja ta posiada parametr Rej, w tym przypadku rejestr 16 - lub 32 - bitowy. Załóżmy, iż rejestr jest 16-bitowy. Zapisana w asemblerze sekwencja szukająca operandu dla instrukcji powinna wyglądać mniej więcej tak:

SzukajArgumentu:

MOV AX,7

Call RandomAX ; weź liczbę z przedziału 000...111

; liczba w AL (bo AH-0)

cmp AL,IndeksModyfl ; czy rejestr zajęty przez

; pierwsza część indeksu ?

je SzukajArgumentu

cmp AL, IndeksModyf2 ; czy rejestr zajęty przez

; druga część indeksu ?

je SzukajArgumentu

cmp AL, Licznik ; czy rejestr zajęty przez licznik ?

je SzukajArgumentu

cmp AL,100b ; czy rejestr to SP ?

je SzukajArgumentu

; argument znaleziony i jest w AL

; AL=00000arg - argument na trzech

; młodszych bitach

or al, 01001000b ; zsumuj logicznie z 5 bitami kodu

; operacji i DEC

stosb ; zapisz cała instrukcję DEC arg do bufora

Postępując w podobny sposób można rozszerzyć generator o wszystkie możliwe do wykorzystania, opisane w dodatekA.txt instrukcje.


  1. Inne mechanizmy stosowane przez wirusy



1   2   3   4   5


©operacji.org 2019
wyślij wiadomość

    Strona główna